Web 缓存简介,包含 Python 示例
Django 怎么样?
最困难的部分
什么是缓存?
缓存存储数据,以便将来的请求能够更快地接收。在动态 Web 应用程序中生成组件或视图可能成本高昂——尤其是在涉及数据库调用时。将计算结果保存到缓存中意味着为相同数据的下一个请求提供服务将更加高效。
Web 应用程序可以使用运行在同一 Web 服务器上的缓存、远程缓存或分布式系统。云服务公司提供用于高度可扩展的分布式缓存的 API。在简单的单线程应用程序中,您甚至可以使用对象作为缓存!我见过一些小型 Node.js 服务使用这种方法。
好的,举个例子
您可能听说过 Memcached 或 Redis。它们都支持键/值存储,以及将数据存储在其他数据结构中。以下是一个缓存使用的高阶示例。假设我们的 Web 应用程序收到一个请求,其中/books/312
312 是一个图书实体的 ID。我们希望返回一个动态页面,其中包含这本书的详细信息,以及一些读者对这本书的评论。
在伪代码中,将缓存应用于这种情况如下所示:
do we have key '/books/312' in our cache?
if so:
return that value as a response
else:
generate a page
store page in cache at '/books/321/'
return page as a response
在这个例子中,获取 321 号书的详细信息需要调用数据库。获取最近的评论可能也需要单独的数据库调用。然后,我们可以通过服务器端模板生成页面。如果用户在原始请求后不久访问同一页面,则可以避免所有这些计算。
当我们可以通过其他方式找到正确信息时,应避免数据库调用。本地缓存的响应时间可达亚毫秒级。缓存在填满时也会删除最后使用的数据项。总的来说,它们相当耐用,但设置合理的超时值至关重要。
Memcached 可以部署在彼此互不通信的分布式服务器中。只有客户端(我们的 Web 应用)知道缓存服务器的存在。其工作原理与哈希表类似,先对密钥进行哈希运算,然后选择一个服务器来获取/设置密钥。
这是一个高级示例:
call a hash function on 'books/321'
turn the hashed value into an index
check the corresponding cache server
get or set 'books/321/' from this server
Web 应用所支持的缓存服务器数量不受限制。它可以水平扩展,这意味着可以添加更多服务器来应对更高的负载。
以下是使用缓存系统时可能遇到的一些问题:
- 用户接收旧数据
- 缓存填满太快
- 请求太独特,无法缓存
- 竞争条件
竞争条件?
以下是一个涉及缓存的竞争条件示例。我们想在网站上显示一个老式的点击计数器。当用户请求页面时,我们会检查当前的点击计数,将其加一,然后覆盖相应的键值。
理想情况下,它的工作原理如下:
get 'hit_count' from cache
increase by one
save new 'hit_count' to cache
如果我们的 Web 应用是多线程的或以某种方式分布式的,则缓存可能会同时被命中两次。两个服务器实例都会增加从缓存收到的命中次数。然后,两个服务器实例都会保存该值。从缓存的角度来看,一个键被请求两次,然后保存两次。Web 应用的两个实例都会请求当前的命中值,10
并将该值加一,这样它们都得到了命中11
值,然后将它们保存到缓存中。真实的命中次数是,12
并且一些数据丢失了!
Memcached 提供了增量和减量函数,解决了这个问题。对于需要原子操作的更复杂情况,Memcached 提供了比较并设置功能。您可以使用重试循环尝试执行操作,而不会在此过程中丢失任何数据。
这是一篇很棒的博客文章,更详细地介绍了 CAS。
给我代码中的例子
让我们看一些实际代码中的例子。我们将使用最流行的 Python Web 框架 Flask 和 Django。这两个框架都提供了强大的开箱即用解决方案。
Flask 基于 Werkzeug 构建,这意味着我们werkzeug.contrib.cache
无需添加任何额外的模块即可使用其 API。Flask 有一个关于缓存模式的页面,就在这里。
这是我最近编写的原型应用程序中的一些代码,并添加了注释
# SimpleCache is a dev tool and is not threadsafe
# but it's very easy to swap in MemcachedCache or GAEMemcachedCache
from werkzeug.contrib.cache import SimpleCache
def create_app():
app = Flask(__name__)
# this line could be `cache = MemcachedCache(['127.0.0.1:11211'])`
cache = SimpleCache()
@app.route('/nearby/<postcode>/<radius>')
def nearby_stores(postcode, radius):
# have we performed this calculation for these arguments?
if cache.get('{}{}'.format(postcode, radius)) is not None:
return cache.get('{}{}'.format(postcode, radius))
# otherwise do some calculations
# ..
# store for next time we run with these arguments
cache.set('{}{}'.format(postcode, radius), jsonified)
return jsonified
在这个应用中,检查缓存可以节省处理时间、一次数据库调用和一个第三方请求!然而,使用 Werkzeug 的缓存模块确实会增加一些不必要的代码行,而像 这样的扩展Flask-Cache
可以帮我们省去这些代码。与其手动检查缓存并设置缓存,不如使用一个装饰器request.path
(它是可配置的)。
因此该函数的开头看起来是这样的:
@app.route('/nearby/<postcode>/<radius>')
@cache.cached(timeout=120)
def nearby_stores(postcode, radius):
Django 怎么样?
与 Flask 类似,Django 也有一个很棒的页面,教你如何为应用程序设置缓存。它的缓存框架与我们刚才看到的非常相似。最大的区别在于初始配置。Django 还可以将缓存数据保存到你使用的数据库中。配置也比 Flask 更成熟。
以下是他们文档中的一个例子:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request):
最困难的部分
构建应用程序并确定哪些计算可以保存并在以后使用是最难的部分!对于简单的 Web 应用程序来说,缓存 Web 请求是一个相当容易解决的问题。如何运用缓存之剑取决于你自己。
我每周都会在我的新闻通讯📧上发布独特的内容!
并发布有关科技的推文@healeycodes。
参考文献:https://dev.to/healeycodes/an-introduction-to-caching-on-the-web-with-examples-in-python-4ann