You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制Python Flask函数仅单实例运行 避免缓存过期重复调用

解决方案

你遇到的是典型的缓存击穿问题,核心解决思路是加互斥锁,保证缓存失效时只有1个请求执行数据拉取和计算逻辑,其余请求等待缓存生成后直接返回。以下是两种适配不同部署场景的实现方案:

方案1:单机部署(单进程多线程模式)

直接用Python内置线程锁实现,代码改动最小:

from flask import Flask, request, abort, render_template
from flask_caching import Cache
import threading
import time

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
# 为慢接口单独创建锁实例
page_lock = threading.Lock()

@app.route('/veryslowpage', methods=['GET'])
def veryslowpage():
    # 先查缓存,存在直接返回
    cache_key = f"veryslowpage_{request.query_string.decode()}"
    cached_result = cache.get(cache_key)
    if cached_result:
        return cached_result
    
    # 缓存不存在,尝试抢锁
    have_lock = page_lock.acquire(blocking=False)
    if have_lock:
        try:
            # 抢到锁后二次检查缓存,避免前面的请求已经生成了缓存
            cached_result = cache.get(cache_key)
            if cached_result:
                return cached_result
            # 执行业务逻辑
            data = callexternalAPIs()
            result = heavydataprocessing(data)
            rendered = render_template("./index.html", content=result)
            # 写入缓存
            cache.set(cache_key, rendered, timeout=3600)
            return rendered
        finally:
            page_lock.release()
    else:
        # 没抢到锁,轮询等待缓存生成
        while not cached_result:
            time.sleep(2)
            cached_result = cache.get(cache_key)
        return cached_result

方案2:分布式部署(多进程/多机器部署)

用缓存实现分布式锁,适配所有部署场景,通用性最强:

from flask import Flask, request, abort, render_template
from flask_caching import Cache
import time
import uuid

app = Flask(__name__)
# 建议用Redis等共享缓存
cache = Cache(app, config={'CACHE_TYPE': 'RedisCache', 'CACHE_REDIS_URL': 'redis://localhost:6379/0'})

@app.route('/veryslowpage', methods=['GET'])
def veryslowpage():
    query_str = request.query_string.decode()
    cache_key = f"veryslowpage_{query_str}"
    lock_key = f"veryslowpage_lock_{query_str}"
    # 锁的唯一标识,防止误删其他请求加的锁
    lock_identity = str(uuid.uuid4())

    # 先查缓存
    cached_result = cache.get(cache_key)
    if cached_result:
        return cached_result
    
    # 尝试加分布式锁,锁过期时间设为2分钟(大于接口最大执行时间即可)
    lock_acquired = cache.set(lock_key, lock_identity, timeout=120, nx=True)
    if lock_acquired:
        try:
            # 二次检查缓存
            cached_result = cache.get(cache_key)
            if cached_result:
                return cached_result
            # 执行业务逻辑
            data = callexternalAPIs()
            result = heavydataprocessing(data)
            rendered = render_template("./index.html", content=result)
            cache.set(cache_key, rendered, timeout=3600)
            return rendered
        finally:
            # 仅释放自己加的锁
            current_lock = cache.get(lock_key)
            if current_lock == lock_identity:
                cache.delete(lock_key)
    else:
        # 没抢到锁,轮询等待缓存
        while not cached_result:
            time.sleep(2)
            cached_result = cache.get(cache_key)
        return cached_result

优化建议

如果允许用户在缓存更新时拿到旧数据,可以选择逻辑过期方案:把缓存的实际过期时间设为7200秒(2小时),同时在缓存值里额外存储1小时的逻辑过期时间。当请求发现逻辑过期时,后台异步启动更新逻辑,当前请求直接返回旧缓存,用户完全无感知,不需要等待。

内容的提问来源于stack exchange,提问作者limbenjamin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 07:36:08