redis主要用法:并发问题、慢查、淘汰策略、管道执行、雪崩
缓存并发问题
参考:https://blog.csdn.net/hijameschen/article/details/109382666
这里的并发指的是多个redis的client同时set key引起的并发问题。比较有效的解决方案就是把redis.set操作放在队列中使其串行化,必须的一个一个执行,具体的代码就不上了,当然加锁也是可以的
1,使用原子性的命令:incr,decr 2,使用锁:setnx设置锁,del释放锁
3,lua脚本
性能优化
参考:https://www.cnblogs.com/moonandstar08/p/7282108.html
1、尽量使用短的key 当然在精简的同时,不要为了key的“见名知意”。对于value有些也可精简,比如性别使用0、1。 2、避免使用keys * keys *, 这个命令是阻塞的,即操作执行期间,其它任何命令在你的实例中都无法执行。当redis中key数据量小时到无所谓,数据量大就很糟糕了。所以我们应该避免去使用这个命令。可以去使用SCAN,来代替。 3、在存到Redis之前先把你的数据压缩下 redis为每种数据类型都提供了两种内部编码方式,在不同的情况下redis会自动调整合适的编码方式。 4、设置key有效期 我们应该尽可能的利用key有效期。比如一些临时数据(短信校验码),过了有效期Redis就会自动为你清除! 5、选择回收策略(maxmemory-policy)
6、尽可能地使用hash哈希存储
7、想要一次添加多条数据的时候可以使用管道 8、限制redis的内存大小(64位系统不限制内存,32位系统默认最多使用3GB内存)
9、SLOWLOG [get/reset/len]
慢查询分析
参考: https://www.cnblogs.com/williamjie/p/9660427.html
延迟时间
redis-cli --latency -h 127.0.0.1 -p 6379 //Redis的响应延迟时间以毫秒为单位,小于秒的时间都是已1000倍计数
慢日志
#命令
slowlog get 1) 1) (integer) 12849 #日志的唯一标识符 2) (integer) 1495630160 #被记录命令的执行时间点,以 UNIX 时间戳格式表示 3) (integer) 61916 #查询执行时间,以微秒为单位 4) 1) "KEYS" 2) "20170524less*"
slowlog len
slowlog reset
#配置项
config set slowlog-log-slower-than 10000 #超过10毫秒就记录
slowlog-max-len #慢日志最大条数
Pipeline管道
设置数据
$redis = new Redis(); //开启管道模式,代表将操作命令暂时放在管道里 $pipe = $redis->multi(Redis::PIPELINE); //循环遍历数据,执行操作 foreach ($users as $user_id => $username) { //用户名修改次数+1 $pipe->incr('changes:' . $user_id); // 更新用户名 $pipe->set('user:' . $user_id . ':username', $username); } //开始执行管道里所有命令 $pipe->exec();
获取数据
$redis = new Redis(); //开启管道模式 $pipe = $redis->multi(Redis::PIPELINE); //循环遍历数据,执行操作 foreach ($users as $user_id => $username) { // 用户被访问的次数+1 $pipe->incr('accessed:' . $user_id); // 获取用户数据记录 $pipe->get('user:' . $user_id); } // 开始执行管道里所有命令 $users = $pipe->exec(); // 打印数据 print_r($users);
注意,由于管道里每一条命令都会返回数据,所以最终打印的数组,会含有incr操作带来的记录,还有从获取用户操作那里拉下来的redis key值作为了打印数组的索引值。
取消管道
$pipe->discard();
reids6种淘汰策略:
主要配置项:
maxmemory #最大内存,将maxmemory设置为0,则表示不进行内存限制
maxmemory-policy #内存淘汰策略
达到最大内存限制时(maxmemory
), Redis 根据 maxmemory-policy
配置的策略, 来决定具体的行为。
allkeys-lru:所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。 allkeys-random:所有key通用; 随机删除一部分 key。#最不好的配置 volatile-lru:只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。 volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外。
一般来说:
如果分为热数据与冷数据, 推荐使用 allkeys-lru 策略。 也就是, 其中一部分key经常被读写. 如果不确定具体的业务特征, 那么 allkeys-lru 是一个很好的选择。
如果需要循环读写所有的key, 或者各个key的访问频率差不多, 可以使用 allkeys-random 策略, 即读写所有元素的概率差不多。 假如要让 Redis 根据 TTL 来筛选需要删除的key, 请使用 volatile-ttl 策略。
volatile-lru 和 volatile-random 策略主要应用场景是: 既有缓存,又有持久key的实例中。 一般来说, 像这类场景, 应该使用两个单独的 Redis 实例。
值得一提的是, 设置 expire
会消耗额外的内存, 所以使用 allkeys-lru 策略, 可以更高效地利用内存, 因为这样就可以不再设置过期时间了。
淘汰机制获取,修改
获取:127.0.0.1:6379> config get maxmemory-policy 设置:maxmemory-policy allkeys-lru(通过redis.conf文件) 修改:127.0.0.1:6379> config set maxmemory-policy allkeys-lru
缓存雪崩,穿透,击穿
雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
解决方案:
缓存雪崩的事前事中事后的解决方案如下。
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
- 事中:限流&降级,避免 MySQL 被打死。
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解决方案:
每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN
。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
缓存击穿
就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方案:
1,可以将热点数据设置为永远不过期;
2,或基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。