# Python 使用缓存功能进行接口性能调优

## 背景

&#x20;   之前提过我们给组内开发了一个需求进度看板，数据源一周更新一次。由于数据量是两年的需求总量，接口查询比较慢，耗时10s左右。然后开发提供了一个思路，就是获取第一次请求的数据，存到缓存，后续再从缓存取数据，大大提高接口请求耗时。



### python内置缓存模块

> functools 模块的 lur\_cache装饰器可以提供缓存功能，从而提高程序执行效率。

**参数：**

* maxsize: 如果为None，则无限制，设置为2的幂时，性能最佳
* typed: 如果typed为True,则不同参数类型的调用将分别缓存，例如f(3)和f(3.0）

**示例代码：**

```python
from functools import lru_cache

# 这种缓存适合服务端
@lru_cache(maxsize=20)
def fib(n):
    print("此函数执行了")
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)


# 执行了函数，返回了结果 2
print(fib(3))
# 没有执行 fib函数，读取了缓存中的结果
print(fib(3))
```

**运行结果：**

![](https://2134947750-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvILwbD2PrkBCkSitM3m4%2Fuploads%2FhR1wOaXeXv1GJm4hMSpY%2Fimage.png?alt=media\&token=500cbc01-823a-4f6a-a25f-4767805ac54a)

**释义：**

> &#x20;   使用lru\_cache装饰器后，每次递归若有相同的参数调用，直接从缓存中取值，则fib(1)的调用只有一次，效率明显提升

### 使用redis进行缓存

> 上述方法可以实现本地的数据缓存，但是当我把代码发到服务器上，发现有不同集群的机器，里面的缓存不互通，不能满足我的需求，故采取redis方法设置缓存。

&#x20; &#x20;

查了资料，使用 redis 自定义装饰器实现缓存比较适合我。下面是一个示例：

```python
import redis
import pickle
import functools

def cache(redis_host, redis_port, redis_db, cache_time=60):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            r = redis.Redis(host=redis_host, port=redis_port, db=redis_db)
            key = pickle.dumps((func.__name__, args, kwargs))
            result = r.get(key)
            if result:
                return pickle.loads(result)
            else:
                result = func(*args, **kwargs)
                r.setex(key, cache_time, pickle.dumps(result))
                return result
        return wrapper

    
@cache(redis_host='localhost', redis_port=6379, redis_db=0, cache_time=60)
def expensive_operation(x, y):
    # some expensive operation
    return result


```

&#x20;这个装饰器函数接受4个参数：

<mark style="color:orange;">redis\_host</mark>：Redis服务器的地址

<mark style="color:orange;">redis\_port</mark>：Redis服务器的端口号

<mark style="color:orange;">redis\_db</mark>：Redis数据库的编号

<mark style="color:orange;">cache\_time</mark>：缓存的有效期时间，默认为60秒



&#x20;   装饰器内部定义了一个嵌套函数<mark style="color:red;">wrapper</mark>，这个函数负责实现缓存的逻辑。首先，使用<mark style="color:red;">pickle</mark>模块将函数名、参数和关键字参数打包成一个键值，然后使用Redis的get方法获取缓存中的结果，如果结果存在，则直接返回缓存结果；否则，调用被装饰的函数获取结果，再将结果保存到Redis缓存中。



&#x20;   装饰器会将<mark style="color:red;">expensive\_operation</mark>的结果缓存起来，下次再调用<mark style="color:red;">expensive\_operation</mark>函数时，如果参数相同，则直接返回缓存的结果，不用再次执行函数。这样可以大大提高程序的性能
