数据结构选择
- 合理选择基础数据结构
- 字符串(String):如果缓存简单的键值对数据,例如用户ID和对应的用户名,使用字符串结构是高效的。例如,将用户ID
1
作为键,用户名 John
作为值存储:SET user:1 John
。这种简单的结构读写速度快,占用内存少。
- 哈希(Hash):当缓存的数据是一个对象,包含多个字段时,哈希结构更合适。比如存储用户的详细信息,包括姓名、年龄、邮箱等。可以这样存储:
HSET user:1 name John age 30 email john@example.com
。相比每个字段单独使用字符串结构,哈希结构在存储和读取整个对象时更高效,减少了键的数量,降低了内存碎片化。
- 列表(List):适用于需要按顺序存储数据的场景,如消息队列。假设要实现一个简单的任务队列,将任务ID依次添加到列表中:
LPUSH task:queue 1001 1002 1003
。通过 RPOP
命令从列表右侧弹出任务进行处理,保证了任务处理的顺序性。
- 集合(Set):用于存储不重复的数据集合,且不需要保持顺序。比如统计网站的独立访客,每次用户访问时将其IP地址添加到集合中:
SADD unique:visitors 192.168.1.1
。可以通过 SCARD
命令快速获取独立访客数量,而且集合结构能自动去重,避免重复记录。
- 有序集合(Sorted Set):当需要对数据进行排序时,有序集合是理想选择。例如排行榜应用,以用户的分数作为排序依据。假设要记录用户的游戏得分:
ZADD leaderboard 100 user1 200 user2 150 user3
。可以通过 ZRANGEBYSCORE
等命令获取按分数排序的用户列表。
- 数据结构嵌套使用:根据业务需求,有时可以嵌套使用数据结构。比如在一个电商应用中,要缓存每个商品的评论。可以使用哈希结构存储商品的基本信息,而评论列表可以用列表结构嵌套在哈希中。例如:
HSET product:1 name "iPhone" price 999 comments:list:product:1
,然后通过操作 comments:list:product:1
这个列表来管理评论。
缓存策略设定
- 过期策略
- 定期删除:Redis会定期随机抽取一些设置了过期时间的键,检查是否过期并删除。可以通过配置
redis.conf
中的 hz
参数来调整检查频率,hz
默认为10,表示每秒检查10次。增加 hz
值可以提高过期键检查的频率,但会增加CPU开销。例如,对于一些临时验证码的缓存,可以设置较短的过期时间,如5分钟,利用Redis的定期删除机制自动清理。
- 惰性删除:当访问一个键时,Redis会检查该键是否过期,如果过期则删除并返回
nil
。这种方式减少了CPU开销,但可能会导致过期键长时间占用内存。对于一些不常用但偶尔会访问的数据,如用户的历史订单数据(很少查看但需要保留一段时间),可以采用这种策略。
- 淘汰策略
- noeviction:当内存达到最大限制时,不淘汰任何数据,此时如果执行写操作且内存不足,会返回错误。这适用于不允许丢失数据的场景,如缓存数据库配置信息等关键数据。
- allkeys - lru:从所有键中,淘汰最近最少使用(Least Recently Used)的键。适用于大部分缓存场景,例如缓存网页内容,长时间未访问的网页缓存可以被淘汰,以释放内存给新的缓存数据。
- volatile - lru:从设置了过期时间的键中,淘汰最近最少使用的键。如果应用中有部分数据是长期有效的,而部分是有过期时间的,且希望优先淘汰有过期时间的不常用数据,可以使用此策略。
- allkeys - random:从所有键中随机淘汰数据。这种策略不太常用,因为它没有考虑数据的访问频率和重要性,但在某些特殊场景下,如测试环境中模拟随机数据淘汰情况时可能会用到。
- volatile - random:从设置了过期时间的键中随机淘汰数据。
- volatile - ttl:从设置了过期时间的键中,淘汰即将过期的数据。适用于希望优先淘汰快过期数据的场景,例如缓存限时优惠活动信息,优先淘汰快要过期的活动缓存。
命令使用规范
- 批量操作
- 使用管道(Pipeline):通过管道可以一次性发送多个命令到Redis服务器,减少网络开销。例如,要批量设置多个用户的缓存数据,可以使用管道。在Python中,使用
redis - py
库实现如下:
import redis
r = redis.Redis(host='localhost', port=6379, db = 0)
pipe = r.pipeline()
user_list = [('user1', 'John'), ('user2', 'Jane')]
for user, name in user_list:
pipe.set(user, name)
pipe.execute()
- MSET和MGET命令:用于批量设置和获取键值对。例如,要设置多个商品的价格:
MSET product:1:price 99 product:2:price 199 product:3:price 299
。然后可以通过 MGET product:1:price product:2:price product:3:price
一次性获取这些商品的价格。
- 避免大键操作
- 大键(如非常大的列表、集合等)会占用大量内存,并且在操作时会阻塞Redis服务器。如果需要处理大量数据,可以考虑将数据分块存储。例如,对于一个包含100万个元素的列表,可以拆分成1000个包含1000个元素的小列表,每个小列表使用不同的键存储。比如
list:1
到 list:1000
,这样在操作单个列表时,不会对服务器造成长时间的阻塞。
- 合理使用只读命令
- 在集群环境下,对于一些只读操作,如
GET
、HGET
等,可以使用从节点来分担主节点的负载。例如,应用中大量的读请求可以配置为从从节点读取数据,通过Redis客户端的配置可以指定从节点读取。在 redis - py
库中,可以通过设置 read_from_replicas=True
来实现从从节点读取数据,从而提高整个集群的读性能。