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、12、避免使用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值作为了打印数组的索引值。

不过有个好处,管道里每个操作命令返回的数据是按照管道里顺序存储的,key值是0,1,2这种。我们想要啥数据,自己稍微处理一下就好啦。

取消管道

$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 访问数据。

 

posted @ 2020-12-30 15:19  小匡程序员  阅读(139)  评论(0编辑  收藏  举报