Redis使用注意点
2022-12-14 09:36:55 13 举报
AI智能生成
登录查看完整内容
redis学习脑图总结
作者其他创作
大纲/内容
以业务名为key的前缀,用冒号隔开以防止key冲突覆盖。如anti:blacklist:001
确保key的语义清晰的情况下,key的长度尽量小于30个字符
可读性,简洁性
例如空格,换行,单双引号以及其他转义符
禁止包含特殊字符
key的规范要点
如果大量存储bigKey会导致慢查询,内存增长过快等问题
如果是String类型,单个value大小控制10k以内
如果是hash,list,set,zset类型,元素个数一般不超过500
拒接bigkey
选择合适的数据类型
value的规范要点
尽量设置ttl,以保证不适用的key能被及时清理或淘汰
因为Redis的数据是存在内存中的,而内存资源是很宝贵的,一般我们把Redis当做缓存来用,而不是数据库,所以key的生命周期不宜太长久。
给key设置过期时间,同时避免过期时间过于集中
我们日常写SQL的时候,批量操作效率会更高,一次更新50条比循环50次效率更高,Redis操作命令也是这个道理。
Redis客户端执行一次命令可以分为4个过程:1.发送命令->2.命令排队->3.命令执行->4.返回结果。1和4称为RRT(命令执行往返时间)
Redis提供了批量操作命令可有效节约RRT
mget,mset
Redis大部分的命令是不支持批量操作的,比如hgetall,并没有mhgetall的存在。Pipeline则可以解决这个问题。
Pipeline能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端
pipeline
批量操作提高效率
使用规范
因为Redis是单线程执行命令的,复杂度为O(n)的命令,当n持续增加时,会导致Redis CPU持续飙高,阻塞其他命令的执行。
hgetall、smember,lrange等这些命令不是一定不能使用,需要综合评估数据量,明确n的值,再去决定。 比如hgetall,如果哈希元素n比较多的话,可以优先考虑使用hscan。
慎用O(n)复杂度命令,如hgetall,smember,lrange等
Monitor命令在于实时打印出Redis服务器接收到的命令,如果我们想知道客户端对redis服务端做了哪些操作,就可以用Monitor命令查看,但是它一般调试用而已,尽量不要在生产上用,因为monitor命令可能导致redis的内存持续飙升。
monitor的模型是酱紫的,它会将所有在Redis服务器执行的命令进行输出,一般来讲Redis服务器的QPS是很高的,也就是如果执行了monitor命令,Redis服务器在Monitor这个客户端的输出缓冲区又会有大量“存货”,也就占用了大量Redis内存。
慎用monitor命令
redis的keys是遍历匹配的,复杂度是O(n),数据库数据越多就越慢,Redis是单线程的,如果数据比较多的话,keys指令就会导致redis线程阻塞,线上服务也会停顿直到指令执行完,服务才会回复。
因此,一般在生产环境,不要使用keys指令。
Keys命令用于查找所有符合给定模式pattern的key。比如 keys key前缀*
复杂度也是O(n),但是它通过游标分步进行,不会阻塞redis线程,但是会有一定的重复概率,需要在客户端做一次去重。
scan支持增量式迭代命令,增量式迭代命令也是有缺点的:举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。
可以使用scan指令,通keys命令一样提供匹配模式功能
禁止使用keys指令
Flushall命令用于清空整个Redis服务器的数据(删除所有数据库的所有key)
Flushdb命令用于清空当前数据库中的所有key
这两个命令是原子性的,不会终止执行,一旦执行开始,不会执行失败的。
禁止使用flushall,flushdb
所以删除时元素越多就越慢,当n很大时,要尤其注意,会阻塞主线程的。
如果是List类型,可以执行lpop或者rpop知道所有元素删除完成
如果是Hash/Set/ZSet类型,可以先执行hscan/sscan/scan查询,再执行hdel/srem/zrem依次删除每个元素
如果删除一个List/Hash/Set/ZSet类型时,他的复杂度是O(n),n表示元素个数
注意使用del命令
执行复杂度较高的命令,会消耗更多的 CPU 资源,会阻塞主线程。所以你要避免执行如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE等聚合命令,一般建议把它放到客户端来执行。
避免复杂度过高的命令,如SORT,SINTER等
有坑的命令
如果执行完setnx加锁,正要执行expire设置过期时间,进程crash或者重要维护重启了,那么这个锁就长生不老了,别的线程永远获取不到。
两个命令SETNX+EXPIRE分开写(典型错误实现范例)
方案缺点:过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步。没有保存持有者的唯一标识,可能被别的客户端释放/解锁。锁过期的时候,并发多个客户端同时请求过来,都执行了jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。
SETNX + value值是过期时间 (有些小伙伴是这么实现,有坑)
可能存在问题:锁过期释放了,业务还没执行完。锁被别的线程误删。
SET的扩展命令(SET EX PX NX)(注意可能存在的问题)
finally里面判断是不是当前线程加的锁和释放锁不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁
一般也是用lua脚本代替
Redisson 使用了一个Watch dog解决了锁过期释放,业务没执行完问题,Redisson原理图如上:
如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。
以上的分布式锁,还存在单机问题:
Redisson框架 + Redlock算法 解决锁过期释放,业务没执行完问题+单机问题
分布式锁注意点
如果是读请求,先读缓存,后读数据库。如果写请求,先更新数据库,再写缓存。每次更新数据后,需要清除缓存,缓存一般都需要设置一定的过期失效。一致性要求高的话,可以使用biglog+MQ保证。
缓存一致性注意点
Redis的所有数据结构类型,都是可以设置过期时间的。假设一个字符串,已经设置了过期时间,你再去重新设置它的值,就会导致之前的过期时间无效。
实际业务开发中,同时我们要合理评估Redis的容量,避免频繁set覆盖,导致设置了过期时间的key失效
合理评估Redis容量,避免由于频繁set覆盖,导致之前设置的过期时间无效
频繁set覆盖问题
指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
业务不合理的设计,比如大多数用户都没开守护,但是你的每个请求都去缓存,查询某个userid查询有没有守护。
业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。
产生情况
如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)
布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。
使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。
避免办法
缓存穿透问题
指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。
缓存雪奔一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。
Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群
使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
“永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间
缓存雪崩问题
指缓存中某一个key过期时间到了,而查询量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机
redis某个key过期了,大量访问使用这个key(热点key)
预先设置热门数据,在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
实时调整,现场监控哪些数据热门,实时调整key的过期时长
就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,先试用缓存工具的某些自带成功操作返回值的操作(如Redis的SETNX)去set一个mutex key。当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key。当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
使用锁
缓存击穿问题
在Redis中,我们把访问频率高的key,称为热点key。如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。
用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
请求分片集中,超过单Redis服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
热点key如何产生
凭经验判断哪些是热Key
客户端统计上报
服务代理层上报
如何识别热点key
Redis集群扩容:增加分片副本,均衡读流量
如何解决热点key问题
缓存热key问题
项目实战闭坑操作
如果使用短连接,每次都需要过 TCP 三次握手、四次挥手,会增加耗时。然而长连接的话,它建立一次连接,redis的命令就能一直使用,酱紫可以减少建立redis连接时间
连接池可以实现在客户端建立多个连接并且不释放,需要使用连接的时候,不用每次都创建连接,节省了耗时。但是需要合理设置参数,长时间不操作 Redis时,也需及时释放连接资源
使用长链接操作Redis,合理配置连接池
一个连接,Redis执行命令select 0和select 1切换,会损耗新能
Redis Cluster 只支持 db0,要迁移的话,成本高
Redis-standalone架构禁止使用非db0.原因有两个
只是用db0
为了防止内存积压膨胀。比如有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,运维小哥哥也忘记加大内存了。难道redis直接这样挂掉?所以需要根据实际业务,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间
volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰
volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据
allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰
noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错
内存淘汰策略
设置maxmemory+恰当的淘汰策略,避免内存膨胀
Redis4.0+版本支持lazy-free机制,如果你的Redis还是有bigKey这种玩意存在,建议把lazy-free开启。当开启它后,Redis 如果删除一个 bigkey 时,释放内存的耗时操作,会放到后台线程去执行,减少对主线程的阻塞影响。
开始lazy-free机制
配置运维
Redis使用注意点
0 条评论
回复 删除
下一页