在the last post中,我们创建了一个由Chatgpt提供支持的simple app。
现在,我们将研究productionise应用程序的方法。
使应用程序正常工作并从计算机中部署它是一个不错的开始。您将很快按以这种方式运行限制。我们将设置一切,以便GitHub部署到生产的主要分支上的任何内容。
我们遇到的另一个问题是我们依靠chatgpt API,这很慢!我们将向系统介绍另一个组件以缓存chatgpt响应。我们的系统看起来像这样。
使您的想法部署和可用只是开始。我们需要工具和技术来观察和运行我们在生产中的应用。我们还需要保持高质量质量高的方法,因此我们有信心经常部署。
我们通常会尽早自动化测试并设计我们的系统,以易于通过快速反馈进行测试。不过,这是一个很大的话题。测试基于及时的应用程序很有趣。还有很多可观察的性能。我们将节省本系列下一篇文章的查看。
部署管道
我在启动新项目时要做的第一件事是设置部署管道。设置一个从来都不容易,而且等待时间越长,这只会变得越来越困难。
及早部署到生产非常重要。您可能不需要令人信服,但是如果您想了解有关原因的更多上下文,请查看这篇文章:HOW MUCH IS YOUR FEAR OF CONTINUOUS DEPLOYMENT COSTING YOU?还考虑阅读更多Charity Majors' blog。另一个极好的资源是Modern Software Engineering by Dave Farley。
我们可以使用github动作进行管道。这对于公共存储库免费。我们将遵循fly.io docs的建议。
UI和后端的步骤都相同。我们只是在这部分设置连续部署。我们将在下一篇文章中更新管道以运行一些测试。
在UI和API中执行这些步骤。您必须遵循第1部分中的步骤进行fly.io。
-
cd
进入存储库并运行fly tokens create deploy
。 - 复制输出。
- 转到GitHub中的存储库设置,然后选择“秘密和变量” - >“操作”。
- 单击“新存储库秘密”按钮
- 致电秘密
FLY_API_TOKEN
并将您从运行fly tokens create deploy
粘贴到秘密输入中获得的令牌。 - 回到本地计算机上的存储库中,创建一个工作流文件:
mkdir -p .github/workflows/ && touch .github/workflows/fly.yml
。
- 将以下内容放在
.github/workflows/fly.yml
文件中。
name: Fly Deploy
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
这个工作流程与我们在本地机器中使用flyctl
CLI所做的相同的事情。它使用的是我们配置的令牌,而不是在本地进行fly auth login
,但否则,它是相同的。
将更改推到git add . && git commit -m 'Add deployment workflow' && git push
。
给它几分钟,您应该希望看到管道运行。
现在,每当发生主要推动时,最新的代码都会部署。
缓存chatgpt响应
如果您遵循,逐步创建应用程序,您可能会注意到API非常慢。
chatgpt需要很长时间才能做出回应。优化和调整性能有很多方法。但是,在许多情况下,简单的缓存将起作用。
您可能已经听过“ two hard things”说:
计算机科学中只有两种困难:缓存无效和命名。
- 菲尔·卡尔顿(Phil Karlton)
缓存可能具有挑战性,但大多数互联网都使用它。如果您非常简单,则缓存是一个强大的工具。我很少有一个现实世界中的应用程序案例,它必须进行耗时的计算或在我不使用缓存且无济于事的网络边界上工作。
通常,内存缓存足以开始。我们不能在不使用时关闭应用程序会关闭应用程序。幸运的是,我们可以旋转一个免费的redis实例。我们现在将通过。
fly.io提供the last post
cd
进入您的本地API存储库。
创建一个redis实例:
flyctl redis create
创建后,您应该为您提供必须复制的连接URL。如果您需要再次查找它,则可以运行以下内容:
flyctl redis list
您应该看到您的实例和ID。复制ID并运行:
fly redis status <the ID you copied>
您会看到一些输出,类似于以下内容,其中包括“私人网址”。
Redis
ID = aaV829vaMVDGbi5
Name = late-waterfall-1133
Plan = Free
Primary Region = mad
Read Regions = ams
Private URL = redis://password@fly-magical-javelin-30042.upstash.io
在该示例中,您将复制redis://password@fly-magical-javelin-30042.upstash.io
我们需要将连接URL作为秘密存储在我们的应用程序中。
flyctl secrets set REDIS_URL="the URL you copied"
使用上一个示例演示:
flyctl secrets set REDIS_URL="redis://password@fly-magical-javelin-30042.upstash.io"
现在,我们需要更新我们的API,以将REDIS用于缓存。
我们将使用一个方便的python软件包,称为fastapi-cache2。
poetry add fastapi-cache2
回到我们的app/main.py
文件中,我们需要导入一些缓存模块。
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
我们正在使用FastAPICache
初始化缓存。我们将配置Redis后端(我们可以但不会使用内存或其他一些存储后端)。
cache
装饰器添加了我们可以添加到任何功能的注释。装饰器缓存该功能的输出,并键入输入。
在引入这样的新组件时,我们再次面临选择和权衡。如果我们的应用程序取决于Redis,现在是否有人想运行它的任何人在本地运行REDIS?我们应该在本地运行时使申请表现不同,并且仅在生产中连接到REDIS?
选择是您的,但我会提出建议。在本地运行时,我尝试尽可能地像生产。 Docker Compose适合此。
添加一个docker-compose.yml
文件。
touch docker-compose.yml
将以下内容添加到该文件:
services:
app:
build:
context: ./
dockerfile: ./Dockerfile
depends_on:
- redis
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY:-UNDEFINED}
- NYTIMES_API_KEY=${NYTIMES_API_KEY:-UNDEFINED}
volumes:
- ./:/usr/app
- /usr/app/.venv/ # This stops local .venv getting mounted
ports:
- "8080:8080"
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
redis:
image: redis
ports:
- '6379:6379'
volumes:
- redis:/data
volumes:
redis:
driver: local
这设置了我们现在可以连接到的redis实例。您在容器中运行的应用程序将使用redis
的Hostmane在网络上看到Redis服务器。
向app/main.py
添加一个启动钩:
@app.on_event("startup")
async def startup():
REDIS_URL = os.getenv("REDIS_URL", "redis://redis")
redis = aioredis.from_url(REDIS_URL)
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
我们现在可以使用以下注释缓存我们的API调用:
@cache(namespace="test", expire=21600)
namespace
可以是命名这些缓存条目的任何字符串。
expire
是我们要在此条目中缓存多长时间。 21600
是6小时。
完整的主文件:
import os
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
from redis import asyncio as aioredis
from .nytimes_client import get_top_stories
from .story_formatter import format_stories_to_string
from .summariser import summarise_news_stories
app = FastAPI()
origins = [
"http://localhost:8080",
"http://127.0.0.1:5500/", # Live server default
"https://summer-ui.fly.dev/",
]
app.add_middleware(
CORSMiddleware,
allow_origins=[origins],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def index():
return {"msg": "Welcome to the News App"}
@app.get("/news")
@cache(namespace="test", expire=21600)
def news():
summary = ""
images = []
try:
stories = get_top_stories()
for story in stories:
images.extend(story["multimedia"])
summary = summarise_news_stories(format_stories_to_string(stories))
print(summary)
images = list(
filter(lambda image: image["format"] == "Large Thumbnail", images)
)
except Exception as e:
print(e)
raise HTTPException(
status_code=500, detail="Apologies, something bad happened :("
)
return {"summary": summary, "images": images}
@app.on_event("startup")
async def startup():
REDIS_URL = os.getenv("REDIS_URL", "redis://redis")
redis = aioredis.from_url(REDIS_URL)
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
现在运行:
docker compose up
您应该能够在http://localhost:8080/news上加载该应用程序。
我们在端口6379上暴露了redis服务器,这意味着您可以在localhost:6379
上连接到它。
使用此缓存设置,我们的API最多只能每6小时才能达到ChatGpt API。该网站的速度将更快,我们将在ChatGpt电话上花费更少的钱。
您可能会想,在缓存到期后打开应用程序的不幸用户呢?这里有许多改善性能的选择。例如,我们可以运行一个计划的作业,为我们刷新缓存。如果您觉得这个问题有趣并想学习一些技术,请查看Designing Data-Intensive Applications的书。
结论
现在我们的应用程序正在自动部署。我们还通过缓存提高了性能和降低的成本。
我们如何知道应用程序部署后会发生什么?我们如何测试?我们将在即将发布的下一篇文章中介绍所有这些。
谢谢您的阅读!