flask-web Redis缓存实际项目中的应用

Posted 胖虎是只mao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flask-web Redis缓存实际项目中的应用相关的知识,希望对你有一定的参考价值。

项目缓存数据的设计

一. 用户的基本信息数据

  1. 多个用户的数据库记录是保存在redis中的一条还是多条?——>多条
    多条数据缓存放到多个redis记录中

  2. 字符串 or 复合型 ?

  user_1  user_2 user_3

都保存到redis中一条 X(不采用)

users -> hash {
    1: user_1_cache_data,
    2: user_2_cache_data
}

users -> list [
    user_1_cache_data, user_2_cache_data
]

users -> zset {
    值                    分数 user_id
    user_1_cache_data      1
    user_2_cache_data      2
}
user_id =1
  • 从数据的存储角度考虑 可以使用hash 方便
  • 从有效期的角度考虑,如果放到一个redis记录中,只能有一个有效期,不是我们的需求
    综合来说,不使用多条缓存放到一个redis记录中

每个用户一条缓存记录

        user1 user2 user3
redis键				值
user:{user_id} -> hash
user:1  ->  {
    name: python
    photo: xx
    mobile: xxx
}

user:{user_id}  ->  string
user:1 -> json.dumps({})  pickle
  
二、 用户关注列表的缓存
user1 -> user2 user3 user4
redis key                    值
user:{user_id}:following   list [user_3_id,user_2_id']
                           zset {
                               value      score  时间戳 1557986157.1353633
                               user_3_id   update_timestamp 关注的时间 
                               user_2_id   update_timestamp  
                           }

不是字符串的原因,是要考虑到应用程序可能分页获取

redis 性能1s 内可以执行 10000+ 读操作
mysql 大概0.12s 查询一次

优先选择zset,冲分利用score分数的价值,可以排序+过滤

三. 持久保存中的 统计数据

  • 发布数量
  • 关注数量
  • 粉丝数量
  • 点赞数量
redis key               值
user:{user_id}:count    hash -> {
	                      'article_count': 12w
	                      'folloing_count': 51
	                      "fans_count": 629w
						}

MIS后台管理系统:

文章数量有多到少的用户 显示出来 top10

关注数量由多到少的用户显示 top100

redis key    					 redis值
count:user:articles    zset -> [
    						值			score
    						user_id_1     12w
    						user_id_2    11w
					  ]
        
count:user:following    zset -> [
    						值			score
    						user_id_1     12w
    						user_id_2    11w
					  ]

四、## 缓存雪崩的解决方案

  1. 加锁方式 setnx -> set if not exist

    def get_cache_data():
        # 尝试获取锁
        lock_flag = False
        while not lock_flag:
            lock_flag = redis.setnx('article:1:info:lock')  // 抢到锁,没抢到则等待,  并发变串行
        	if lock_flag:
        		ret = redis.get('article:1:info')
        		if not ret:
            		ret = db.query.all()
            		redis.set(ret)
                redis.delete('article:1:info:lock')		// 释放锁 ,别人才能抢到
        return ret   
        
    
  2. 使用队列方式
    可以使用celery 进行操作,——>生产者消费者

    queue = []
    
    def get_cache_data():
        ret = redis.get('article:1:info')
        if not ret:
           	ret = db.query.all()
           	redis.set(ret)
        return ret   
        
    queue.append(get_cache_data, get_cache_data, get_cache_data)
    
    # 队列的消费进程
    func = queue.pop()
    func()
    

五、缓存的编写

以用户资料缓存数据为例

class UserCache(object):

	redis_key = ''user:1:profile'
	        
	def get_user_cache_data()  查询
		redis.get(redis_key)
	    
	def clear_user_cache_data()  删除
		redis.delete(redis_key)
	    
	def exists_user_id() 判断是否存在
		redis.get(redis_key)
POST  /followings/<user_id>  user_id=1
类的构造
  1. 确定是否要保存数据,即数据选择类属性还是对象属性
  2. 选择方法
  • 如果要构造的方法中需要处理对象属性和类属性,选择对象方法

  • 如果仅需要处理类属性,选择类方法既可以通过类名也可以通过cls处理类属性

    @classmethod
    def func(cls, ...)
    
  • 如果在方法中不处理类属性与对象数据,仅从逻辑角度考虑应该封装到一起,则选择静态方法,通过类名可以处理类型属性 UserCache.key

      @staticmethod
      def func()
    
  • 类属性
    -> 所有对象共享类属性,也就是说所有对象中读取出的类属性 数据值都相同
  • 对象属性
    -> 每个对象单独所有,也就意味着每个对象的数据可以不同

python中构建对象属性的方法: __init__()

项目代码:

from flask import current_app
import json
from sqlalchemy.orm import load_only
import random
from redis.exceptions import RedisError
from sqlalchemy.exc import DatabaseError
from models.user import User
from . import constants

# user1  UserCache(1) -> key='user:1:profile' get
# user2  UserCache(2) -> key='user:2:profile' get

class UserCache(object):
    """
    用户基本信息缓存工具类
    """

    def __init__(self, user_id):
        # redis数据记录的key 键
        self.key = 'user:{}:profile'.format(user_id)
        self.user_id = user_id

    # 类属性
    #   -> 所有对象共享类属性,也就是说所有对象中读取出的类属性 数据值都相同
    # 对象属性
    #  -> 每个对象单独所有,也就意味着每个对象的数据可以不同

    # 实例方法 (对象方法)
    # 类方法
    # 静态方法
    # 选择的依据:
    # 不同方法可以处理的类中的数据是不一样的
    #  对象方法-> 可以处理对象属性、类属性
    #  类方法-> 既可以通过类名也可以通过cls处理类属性
    #     @classmethod
    #     def func(cls, ...)
    # 静态方法  -> 通过类名可以处理类型属性 UserCache.key
    #    @staticmethod
    #    def func()

    def save(self):
        """
        保存缓存记录
        :return:
        """
        r = current_app.redis_cluster
        try:
            user = User.query.options(load_only(
                User.mobile,
                User.name,
                User.profile_photo,
                User.introduction,
                User.certificate
            )).filter_by(id=self.user_id).first()
        except DatabaseError as e:
            current_app.logger.error(e)
            # 对于这个数据库异常,我们自己封装的get方法无法为调用者做决定,决定返回什么值,所以抛出异常给调用者,由调用者决定
            raise e

        # 在django中 查询单一对象,而数据库不存在,抛出异常  User.DoesNotExists
        # 在sqlalchemy中,查询单一对象,数据库不存爱,不抛出异常,只返回None
        if user is None:
            # 数据库不存在
            try:
                r.setex(self.key, constants.UserNotExistCacheTTL.get_value(), -1)
            except RedisError as e:
                current_app.logger.error(e)

            return None
        else:
            user_dict = {
                'mobile': user.mobile,
                'name': user.name,
                'photo': user.profile_photo,
                'intro': user.introduction,
                'certi': user.certificate
            }
            try:
                r.setex(self.key, constants.UserCacheDataTTL.get_value(), json.dumps(user_dict))
            except RedisError as e:
                current_app.logger.error(e)

            return user_dict


    def get(self):
        """
        获取缓存数据
        :return: user dict
        """
        # 先查询redis缓存记录
        # 如果有记录 直接返回
        # 如果没有记录,查询数据库
        #      数据库中如果有记录,设置redis记录  json string
        #      数据库中如果没有记录,设置redis保存不存在的记录 -1
        # 返回
        r = current_app.redis_cluster
        try:
            ret = r.get(self.key)
        except RedisError as e:
            # 记录日志
            current_app.logger.error(e)
            # 在redis出现异常的时候,为了保证我们封装的get方法还能有返回值,可以进入数据库查询的部分
            ret = None

        if ret is not None:
            # 表示redis中有记录值
            # 判断redis记录是表示数据库不存在的-1值还是有意义的缓存记录
            # 切记: python3 从redis中取出的字符串数据是python中的bytes
            if ret == b'-1':
                return None
            else:
                # json.loads方法可以接受bytes类型
                user_dict = json.loads(ret)
                return user_dict
        else:
            return self.save()

    def clear(self):
        """
        清除缓存记录
        :return:
        """
        try:
            r = current_app.redis_cluster
            r.delete(self.key)
        except RedisError as e:
            current_app.logger.error(e)

    def determine_user_exists(self):
        """
        通过缓存判断用户id是否存在
        :return: boolean True:存在  False:不存在
        """
        # 查询redis
        # 如果存在Redis记录
        #   如果redis记录为-1,表示不存在
        #   如果redis记录不为-1, 表示用户存在

        # 如果不存在redis记录
        #   去数据库查询,判断是否存在
        #   设置redis缓存记录
        r = current_app.redis_cluster
        try:
            ret = r.get(self.key)
        except RedisError as e:
            # 记录日志
            current_app.logger.error(e)
            # 在redis出现异常的时候,为了保证我们封装的get方法还能有返回值,可以进入数据库查询的部分
            ret = None

        if ret is not None:
            # 表示redis中有记录值
            # 判断redis记录是表示数据库不存在的-1值还是有意义的缓存记录
            # 切记: python3 从redis中取出的字符串数据是python中的bytes
            if ret == b'-1':
                return False
            else:
                return True
        else:
            ret = self.save()
            if ret is not None:
                return True
            else:
                return False



以上是关于flask-web Redis缓存实际项目中的应用的主要内容,如果未能解决你的问题,请参考以下文章

flask-web 搜索系统项目实际应用suggest查询实现联想提示自动补全的实现

flask-web ——RPC实际项目业务简析

flask-web APScheduler 定时任务以及实际应用

redis 实际应用中的缓存作用

Redis在复杂业务ERP产品中的技术应用

缓存架构中分布式一致性hash应用解析