redis

1 篇文章 0 订阅
1 篇文章 0 订阅

Redis

为什么要使用redis:

性能和并发。

当然,Redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件,不如ZooKeeper等代替,并不是非要使用Redis。因此,这个问题主要从性能和并发两个角度去答。

性能:

如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

并发:

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。

这个时候,就需要使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问数据库。

单线程的Redis为什么这么快:

  1. 纯内存操作

  2. 单线程操作,避免了频繁的上下文切换

  3. 采用了非阻塞I/O多路复用机制

慢指令监控

redis-cli 命令提供了–intrinsic-latency 选项,用来监测和统计测试期间内的最大延迟(以毫秒为单位),这个延迟可以作为 Redis 的基线性能。

redis -cli --latency -h host -p port

比如执行如下指令:

redis-cli --intrinsic-latency 100
Max latency so far: 4 microseconds.
Max latency so far: 18 microseconds.
Max latency so far: 41 microseconds.
Max latency so far: 57 microseconds.
Max latency so far: 78 microseconds.
Max latency so far: 170 microseconds.
Max latency so far: 342 microseconds.
Max latency so far: 3079 microseconds.

45026981 total runs (avg latency: 2.2209 microseconds / 2220.89 nanoseconds per run).
Worst run took 1386x longer than the average latency.

注意:参数 100 是测试将执行的秒数

如何判断是否是慢指令呢?

有两种方式可以排查到:

  1. 使用 Redis 慢日志功能查出慢命令;

  2. latency-monitor(延迟监控)工具。

慢日志功能

Redis 中的 slowlog 命令可以让我们快速定位到那些超出指定执行时间的慢命令,默认情况下命令若是执行时间超过 10ms 就会被记录到日志。

slowlog 只会记录其命令执行的时间,不包含 io 往返操作,也不记录单由网络延迟引起的响应慢。

可以在 redis-cli 中输入以下命令配置记录 6 毫秒以上的指令:

redis-cli CONFIG SET slowlog-log-slower-than 6000

也可以在 Redis.config 配置文件中设置,以微秒为单位。

想要查看所有执行时间比较慢的命令,可以通过使用 Redis-cli 工具,输入 slowlog get 命令查看,返回结果的第三个字段以微秒位单位显示命令的执行时间。

假如只需要查看最后 2 个慢命令,输入 slowlog get 2 即可。

示例:获取最近2个慢查询命令
127.0.0.1:6381> SLOWLOG get 2
1) 1) (integer) 6
   2) (integer) 1458734263
   3) (integer) 74372
   4) 1) "hgetall"
      2) "max.dsp.blacklist"
2) 1) (integer) 5
   2) (integer) 1458734258
   3) (integer) 5411075
   4) 1) "keys"
      2) "max.dsp.blacklist"

以第一个 HGET 命令为例分析,每个 slowlog 实体共 4 个字段:

  1. 字段 1:1 个整数,表示这个 slowlog 出现的序号,server 启动后递增,当前为 6。

  2. 字段 2:表示查询执行时的 Unix 时间戳。

  3. 字段 3:表示查询执行微秒数,当前是 74372 微秒,约 74ms。

  4. 字段 4: 表示查询的命令和参数,如果参数很多或很大,只会显示部分参数个数。当前命令是hgetall max.dsp.blacklist。

如何解决 Redis 变慢?

Redis 的数据读写由单线程执行,如果主线程执行的操作时间太长,就会导致主线程阻塞。

网络通信导致的延迟

客户端使用 TCP/IP 连接或 Unix 域连接连接到 Redis。1 Gbit/s 网络的典型延迟约为 200 us。

redis 客户端执行一条命令分 4 个过程:

发送命令-〉 命令排队 -〉 命令执行-〉 返回结果

这个过程称为 Round trip time(简称 RTT, 往返时间),mget mset 有效节约了 RTT,但大部分命令(如 hgetall,并没有 mhgetall)不支持批量操作,需要消耗 N 次 RTT ,这个时候需要 pipeline 来解决这个问题。(Lua脚本)

Redis pipeline 将多个命令连接在一起来减少网络响应往返次数。

慢指令导致的延迟解决方式:

  1. 使用高效的命令代替,避免一次查询大量数据。

  2. 禁止使用keys的操作命令,它会遍历所有键值对,操作延时高。

Fork 数据 导致的延迟

redis 备份数据方式 : RDB(redis-database)快照 ,AOF(append-only-file)命令缓存追加

生成RDB快照,Redis 必须 fork 后台进程。fork 操作(在主线程中运行)本身会导致延迟。

Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,减少内存占用。

Redis 通过定时执行 RDB 内存快照,这样就不必每次执行「写」指令都写磁盘,只需要在执行内存快照的时候写磁盘。既保证了效率,还实现了持久化,宕机快速恢复。

Redis 使用了 fork 生成 RDB 做持久化提供了数据可靠性保证。

  1. 当服务器断电时间,会自动的生成(覆盖)一个dump.rdb文件,在服务器再次启动的时候会加载。

  2. 触发条件,当达到在seconds时间内,改变了changes次数,会重新生成一份rdb文件,覆盖之前的dump.rdb文件。

  3. 可以在redis.confg中设置参数 save <seconds> <changes>

AOF 和磁盘 I/O 导致的延迟

可以使用 appendfsync 配置将 AOF 配置为以三种不同的方式在磁盘上执行 write 或者 fsync (可以在运行时使用 CONFIG SET命令修改此设置,比如:redis-cli CONFIG SET appendfsync no)。

  1. no:Redis 不执行 fsync,唯一的延迟来自于 write 调用,write 只需要把日志记录写到内核缓冲区就可以返回。

  2. everysec:Redis 每秒执行一次 fsync。使用后台子线程异步完成 fsync 操作。最多丢失 1s 的数据。

  3. always:每次写入操作都会执行 fsync,然后用 OK 代码回复客户端(实际上 Redis 会尝试将同时执行的许多命令聚集到单个 fsync 中),没有数据丢失。在这种模式下,性能通常非常低,强烈建议使用快速磁盘和可以在短时间内执行 fsync 的文件系统实现。

我们通常将 Redis 用于缓存,数据丢失完全恶意从数据获取,并不需要很高的数据可靠性,建议设置成 no 或者 everysec。

除此之外,避免 AOF 文件过大, Redis 会进行 AOF 重写,生成缩小的 AOF 文件。

RDB和AOF两种持久化的方式是可以同时存在的

会优先加载 AOF文件,找不到AOF文件会去加载RDB文件

expires 淘汰过期数据 Redis 有两种方式淘汰过期数据:

  1. 惰性删除:当接收请求的时候发现 key 已经过期,才执行删除;

  2. 定时删除:每 100 毫秒删除一些过期的 key。

定时删除的算法如下:

  1. 随机采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的 key,删除所有过期的 key;

  2. 如果发现还有超过 25% 的 key 已过期,则执行步骤一。

大量同时到期的 key 可能会导致性能波动。

解决方案

如果一批 key 的确是同时过期,可以在 EXPIREAT 和 EXPIRE 的过期时间参数上,加上一个一定大小范围内的随机数,这样,既保证了 key 在一个邻近时间范围内被删除,又避免了同时过期造成的压力。

Redis 的过期策略以及内存淘汰机制:

定期删除+惰性删除

定期删除,Redis默认每个100ms检查,是否有过期的Key,有过期Key则删除。

需要说明的是,Redis不是每个100ms将所有的Key检查一次,而是随机抽取进行检查(如果每隔100ms,全部Key进行检查,Redis岂不是卡死)。

因此,如果只采用定期删除策略,会导致很多Key到时间没有删除。于是,惰性删除派上用场。

也就是说在你获取某个Key的时候,Redis会检查一下,这个Key如果设置了过期时间,那么是否过期了?如果过期了此时就会删除。

定期删除+惰性删除 可以解决大部分问题,但是依旧存在问题:

如果定期删除没删除Key。然后你也没即时去请求Key,也就是说惰性删除也没生效。这样,Redis的内存会越来越高。那么就应该采用内存淘汰机制。

在redis.conf中有一行配置:

 maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的:

  1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

  2. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的Key。推荐使用,目前项目在用这种。

  3. allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个Key。应该也没人用吧,你不删最少使用Key,去随机删。

  4. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的Key。这种情况一般是把Redis既当缓存,又做持久化存储的时候才用。不推荐。

  5. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个Key。依然不推荐。

  6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key优先移除。不推荐。

Redis和数据库双写一致性问题:

一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。

答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。

另外,我们所做的方案从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

如何应对缓存穿透和缓存雪崩问题

缓存穿透,故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

缓存穿透解决方案:

  1. 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。

  2. 采用异步更新策略,无论Key是否取到值,都直接返回。Value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。

  3. 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的Key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

缓存雪崩解决方案:

  1. 给缓存的失效时间,加上一个随机值,避免集体失效。

  2. 使用互斥锁,但是该方案吞吐量明显下降了。

  3. 双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。

  4. 然后细分以下几个小点:从缓存A读数据库,有则直接返回;A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存A和缓存B。

如何解决Redis的并发竞争Key问题:

  1. 设置时间戳,比较时间戳

  2. 设置版本号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值