1关于Redis使用的一点想法
1.1进行缓存前,需考虑
(1)该数据属于短暂保留,例如只保留三天、七天或者一个月,此时建议采用缓存;
(2)该数据在某一个时间段请求量很大,此时建议采用缓存;
(3)随着用户使用,数据不断变化,更新操作比较频繁,此时建议采用缓存;
(4)如果数据量不大,且和应用性能提升不大,数据需要长久保留,此时不建议采用Redis进行缓存,直接使用MySQL等关系型数据库存储即可;
(5)如果数据量很大,但是过了一段时间后,该数据几乎没有什么价值,此时建议采用缓存,并设定过期时间和定时清理该数据的脚本,这样处理可以减轻存储空间,也便于优化系统的数据库层。
1.2进行缓存后,需考虑
(1)缓存该key在极端情况下,占用系统内存会有多大?或者说存储的记录大概会达到什么数量级?
(2)缓存该key,过期时间是否方便设置?如果不方便设置,是否可以隔段时间考虑转存到MySQL等关系型数据库中,从而清理缓存,释放内存空间。
(3)缓存该key,思考一下手动删除缓存数据的脚本如何编写,缓存的所有数据,如何区分出有价值的数据进行保留,无价值的便利用脚本进行自动化删除。
(4)使用缓存后,要思考选择恰当的数据结构来完成代码构建。因为一个适合的数据结构不仅使得代码变得更加优雅,后期维护也很方便。
1.3缓存使用一段时间后
如果发现某一个key占用内存很大,超出预料,提供的优化建议:
(1)分析该key的具体实际功能,和目前的需求,看能否在后续缓存数据时,添加过期时间设定;
(2)考虑在取出缓存数据的时候,能否转存到MySQL等关系型数据库,如果能够转存成功,则在此处可以进行立即执行删除该条缓存数据的方法;后续取数据时,可采用先查询Redis数据库,未查到再次查询一下MySQL等关系型数据库;
(3)依据已经缓存的数据,看能够依据数据中的字段或者相关属性对已经缓存的数据进行过滤查询,把那些不重要的数据通过脚本进行手动删除处理。
2编写Redis数据库层规范建议
2.1选择适合的redis客户端
例如,定义了以下两个客户端:
# -*- coding: utf-8 -*-"from django.conf import settingsimport redis redis_db_client = redis.StrictRedis( host=settings.REDIS['redis_db_host'], port=settings.REDIS['redis_db_port'], db=settings.REDIS['redis_db_db'], socket_connect_timeout=4, socket_timeout=2, decode_responses=True, ) redis_hot_client = redis.StrictRedis( host=settings.REDIS['redis_hot_host'], port=settings.REDIS['redis_hot_port'], db=settings.REDIS['redis_hot_db'], socket_connect_timeout=2, socket_timeout=2, decode_responses=False, )
此时,可以依据key的设计和作用,选择合适的客户端来操作。其中不同的客户端对应的端口和具体数据库不同,以上客户端定义仅作参考。(PS:以上定义是基于Django框架的配置文件来使用,其它Python框架也可以类似定义)
2.2规范化定义key的名称并初始化
在定义redis操作key的名称建议采用大写字母加下划线组成;在初始化key对象时,最好能够设定过期时间。
Key定义的类示例:
class RedisKey: """RedisKey类对象""" def __init__(self, prefix, ex=None): self.prefix = prefix self.ex = ex # 生成key值 def __call__(self, key): return self.prefix + str(key)
则定义一个key的示例:
USER_PULL_URL = RedisKey(prefix='user_pull_url:', ex=8 * 60 * 60)
其中参数prefix为key存储在Redis数据中具体的键名,可以通过调用__call___方法来为key添加后缀,例如user_pull_url:1808表示一个键名;ex为该key对象定义的过期时间设定,等到具体编写该key的添加操作时,调用参数ex来设定key的具体过期时间。
其中key存放的文件,要依据选择的客户端存放在指定的文件中,这样方便查看和管理。
2.3 选择合适的数据结构
Redis数据库包含String(字符串)、Hash(哈希表)、List(列表)、Set(集合)、SortedSet(有序集合)五种数据结构。下面简单介绍一下这五种数据结构的特性:
String(字符串):添加数据时,采用key-value格式进行存储。key是定义的键名,在上面(2)中已有说明。value是具体要存储的数据,该数据的类型是String类型。遇到的需求中,例如需要存储某用户的在线时长,可以采用key_user_id组成键名,具体时长存储在value中,此时可选择String数据结构来存储,比较方便。
Hash(哈希表):添加数据时,采用key-value格式来进行存储,不过这里的value表示一张哈希表。可以这样做比喻,key比作关系型数据库中的表名,value存储该关系型表中的所有行的数据记录。
List(列表):添加数据时,采用key-value格式来进行存储,不过这里的value表示一个具体的列表。该列表的功能可类似C++数据结构列表一样,有出表操作,计算列表长度操作,依据下标获取某个元素的功能,获取指定区间内的列表元素,从列表头部插入元素或者尾部插入元素等。
Set(集合):添加数据时,采用key-value格式来进行存储,这里的value存储和Hash(哈希表)存储类型,而Set(集合)区别在于在添加数据记录时,会自动过滤掉相同值得记录,如果插入多条记录相同得数据,在Set(集合)存储得value中只会找到一条记录。
SortedSet(有序集合):添加数据时,采用key-value格式来进行存储。存储的方式实现功能和Set(集合)基本相同,但是其唯一突出的特点就是在存数据的时候要存储元素值对应的score值,也就是依据score值的大小来对value中元素进行排序存储。后续有查询操作时,可以很方便的返回value中元素的排序序列。
2.4 规范化定义操作方法
每一个服务层,建议单独创建一个cache.py文件,专门用于存放操作Redis数据库层的方法,此类的功能可以类比models.py文件。
每一个key使用其键名创建符合代码规范的类名,然后在该key对应的类里面,定义操作的redis_client和redis_key,最后通过cliet和key定义相关数据的添加、修改、查询和删除的方法。
最后,最重要的一点建议:该cache.py中定义的操作方法建议只在该服务层中被其它类中方法体调用。这样的好处,可以让我们对于该key在以后的数据管理上有可控的预估操作,也使得代码调用变得更加规范。
此处给出一个示例:
定义一个key:
键名初始化:
USER_SESSION = RedisKey(prefix='user_session:', ex=4 * 60 * 60)
选择客户端:
redis_hot_client
在cache.py中定义的类和相关操作方法:
class CacheUserSession: # 类名和key的名称对应 """ 原始数据类型: dict 存储数据类型: bytes 数据说明:将user_dict序列化成二进制数据,存入Redis中 """ db = redis_hot_client key_prefix = rediskey.USER_SESSION @classmethod def get(cls, user_id: IdInt): key = cls.key_prefix(user_id) # key的具体初始化值 user_dict = cls.db.get(key) if user_dict: user_dict = pickle.loads(user_dict) return user_dict @classmethod def delete(cls, user_id: IdInt): # key的删除方法定义 key = cls.key_prefix(user_id) cls.db.delete(key) logger.info("Delete UserSession:{}".format(user_id)) @classmethod def set(cls, user_id: IdInt, user_dict: dict): key = cls.key_prefix(user_id) data = pickle.dumps(user_dict) cls.db.set(key, data, ex=cls.key_prefix.ex) # 注意设定过期时间 logger.info("Set UserSession:{}, {}".format(user_id, user_dict))
此处,给出一个很好用的存储数据的途径。通过以下途径,可以把一个字典格式的数据当作字符串存入Redis数据库,取出后可以重新解析出字典格式。通过该途径,可以较好的把Redis当作关系型数据库存储一样,一条数据记录可以存储多个属性的值。
例如,一个学生,包含学号、姓名、性别、年级、专业等属性。如果采用关系型数据库存,以学号作为主键,其它属性作为列名进行设计,通过学号即可查询出该学生的所有信息。但是,一个学生的多个属性信息如何存储到Redis数据库中,并且取出来能够很好的使用呢?
采用的策略:学号唯一,可作为key值,姓名、性别、年级、专业联合成一个字典,在存入Redis数据中之前先转换为指定结构的字符串格式,取出后再解析成字典格式。这样操作就能很好的解决这个需求。
为什么要转换为字符串格式存入Redis数据库呢?因为不管什么数据结构的数据存入到Redis数据库中后,它取出都是字符串格式。
下面请看具体代码示例(具体cache.py中CacheStudent类实现代码不给出噢):
import json value_data = json.dumps({ name: "xiaoming", gender: "male", grade: "2014", profession: "软件工程", }) # 通过json模块把字典转换为指定格式的字符串CacheStudent.set(student_id, value_data) # 通过定义好的写入方法,把指定的学生数据存入 student_data = json.loads(CacheStudent.get(student_id)) # 取出指定学号学生的数据后,使用json模块的loads方法解析该字符串变成字典格式print(studnet_data["name"]) # 打印该学生的姓名信息print(studnet_data["gender"])print(studnet_data["grade"])print(studnet_data["profession"])
看到上述的实现,是不是发现Redis存储可以当作关系型数据存储来用?
2.5 开始愉快的调用之旅
此处调用,可适当选择时机何时进行数据删除操作。比如取出后,可以选择把最终存储的数据转存到MySQL数据库中,然后调用cache.py中的删除方法,对该数据记录进行删除操作。