1、背景:
在实际的开发中,很多的接口一放到生产环境,由于数据量大,请求量大等等原因,有些接口从请求到数据返回耗时很长,非常影响用户的体验(接口一般要保证在1s内返回) 。
2、解决方法思路:
(1)首先想到的当然是优化接口,提高接口性能。
(2)但是,当接口优化还解决不了问题时,那么就要考虑第二种方法了,立马想到了缓存,首先想到的是redis,又考虑到redis是内存缓存,如果redis出问题时,会丢失数据(没设置落盘),所有就想,再加一层mysql缓存。于是,思路如下:
a.编写定时任务,定时执行提前算好结果集,定义一个唯一的值来md5或者base64编码生成redis的key(可以用接口的入参、url、方法类型:post/get来唯一确定key),然后存储到redis,接着在存到mysql,如果有就更新,没有就添加.
b.当请求某接口时,先获取参数来生成key,用key在redis获取值,如果获取到,直接返回,这样的速度就非常快。
c.当连接redis失败或者从redis获取key对应的data失败时,就查询mysql读取缓存,如果有就返回,这样的返回速度也是很快的。
d.如果mysql还没获取到(理论上定时任务是提前进行的,基本不可能出现这个情况,如果出现了,就只能重新计算了),那就触发定时任务的计算函数,重复步骤a来添加缓存,并返回数据。
3、有了这个思路,就可以封装一个缓存工具类了。
redis_util.py
import config import base64 import json import hashlib redis_db = RedisDb(config.REDIS) mysql_cache_model = MysqlCache() class RedisUtil(object): # 添加或更新缓存 def add_cache(self, params, path, method, data): """ :param params: 函数入参 :param path: 方法请求的url :param method: 请求方法,POST/GET/DELETE/PUT等 :param data: 上面三个值生成的唯一缓存的key对应的值value :return: True/False """ # redis缓存 try: # 参数不对,返回false if not params or not path or not method or not data: logger.error("缺少必填参数,添加缓存失败") return False # 组装生成key的参数 key_args = {} key_args.update(params) key_args['path'] = path key_args['method'] = method logger.info("添加或更新缓存,入参为:") logger.info(key_args) # 获取编码后的redis缓存key,并添加或更新redis缓存 # redis_key = str(base64.b64encode(bytes(json.dumps(key_args), encoding='utf8')), 'utf8') redis_key = self.get_md5_key(key_args) # 添加或更新redis缓存 redis_db.str_set(redis_key, json.dumps(data, cls=CustomJSONEncoder)) # 添加或更新mysql缓存 mysql_cache_model.add_mysql_cache_info(redis_key, json.dumps(data, cls=CustomJSONEncoder)) return True except Exception as e: logger.exception(e) logger.error("添加或更新缓存失败") return False # 查询缓存 def get_cache(self, params, path, method): """ :param params: 函数入参 :param path: 方法请求的url :param method: 请求方法,POST/GET/DELETE/PUT等 :return: data 上面三个值生成的唯一缓存的key对应的值value """ # 参数不对,返回false if not params or not path or not method: logger.error("缺少必填参数,查询缓存失败") return {} # 组装生成key的参数 key_args = {} key_args.update(params) key_args['path'] = path key_args['method'] = method logger.info("查询缓存,入参为:") logger.info(key_args) try: # 获取编码后的redis缓存key # redis_key = str(base64.b64encode(bytes(json.dumps(key_args), encoding='utf8')), 'utf8') redis_key = self.get_md5_key(key_args) # 读取redis缓存 redis_data = redis_db.str_get(redis_key) # 如果读到,直接返回 if redis_data: return json.loads(redis_data) except Exception as e: logger.exception(e) logger.error("读取redis缓存失败") # 获取编码后的redis缓存key # redis_key = str(base64.b64encode(bytes(json.dumps(key_args), encoding='utf8')), 'utf8') redis_key = self.get_md5_key(key_args) # 如果redis异常,读取mysql缓存,返回数据 redis_data = mysql_cache_model.select_mysql_cache_info(redis_key) redis_data = redis_data[0].get('value', {}) if redis_data else {} return json.loads(redis_data) try: # 如果redis没读到,读取mysql缓存并添加redis缓存,返回数据 redis_data = mysql_cache_model.select_mysql_cache_info(redis_key) redis_data = redis_data[0].get('value', {}) if redis_data else {} redis_data = json.loads(redis_data) # 添加或更新redis缓存 redis_db.str_set(redis_key, json.dumps(redis_data, cls=CustomJSONEncoder)) return redis_data except Exception as e: logger.exception(e) logger.error("读取mysql缓存失败") return {} # 获取md5 key def get_md5_key(self, args): key = hashlib.md5(json.dumps(args).encode(encoding='UTF-8')).hexdigest() return key