🚄【Redis技术干货】推荐给大家的实战技术开发指南(提炼优化)

170 阅读11分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

Redis的命令行客户端

客户端相关命令

redis-cli -a password shutdown:关闭redis客户端 ./redis_init_script stop:关闭redis redis-cli:进入到redis客户端 auth pwd:输入密码 set key value:设置缓存get key:获得缓存del key:删除缓存 redis-cli -a password ping:查看是否存活

redis五种基本数据类型相关知识点

  1. String字符串

  2. List列表

  3. Hash字典

  4. Set集合

  5. Zset有序列表

SpringBoot和Redis进行整合

多路复用器,阻塞和非阻塞

同步,异步,阻塞,非阻塞的异同: I/O模型与多路复用 推荐看看 I/O模型与多路复用2 这篇博客介绍的很详细,也很专业,需要一定的io方面的知识

IO多路复用技术详解

redis线程模型

首先,redis基于reactor模式开发了网络事件处理器(文件事件处理器),由于这个处理器是单线程的,所以,决定了redis也是单线程的。这整个文件事件处理器也可以称为redis的线程模型。

redis线程模型由5部分组成

  1. socket
  2. IO多路复用器
  3. socket队列
  4. 文件事件分配器
  5. 事件处理器(连接应答处理器,命令请求处理器,命令响应处理器)

1977_1 更详细一点的防止自己看不懂

1981_1

redis一次请求的处理流程示意图

1979_1

在 Redis 启动初始化的时候,Redis 会将连接应答处理器跟 AE_READABLE 事件关联起来,接着如果一个客户端跟Redis发起连接,此时会产生一个 AE_READABLE 事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的 Socket,同时将这个 Socket 的 AE_READABLE 事件跟命令请求处理器关联起来

  1. 当客户端向Redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在 Socket 产生一个 AE_READABLE 事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从Socket中读取请求相关数据,然后进行执行和处理
  2. 接着Redis这边准备好了给客户端的响应数据之后,就会将Socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在 Socket 上产生一个 AE_WRITABLE 事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入 Socket,供客户端来读取。
  3. 命令回复处理器写完之后,就会删除这个 Socket 的 AE_WRITABLE 事件和命令回复处理器的关联关系

推荐博客: redis线程模型-byene redis线程模型-以梦为码 redis线程模型-全菜工程师小辉

redis发布和订阅

1648_1

redis订阅命令: subscribe 频道名1(food) 频道名2 redis批量订阅命令: psubscribe 频道名通配符(food*)

一般在公司,redis只做缓存的存储,不会使用redis做mq的工作

redis持久化机制-RDB & AOF

redis官方文档: redis提供了两种持久化数据的方式:RDB and AOF

  • RDB的持久化可以在一定的时间间隔备份一个redis数据的快照版本(比如每天晚上8点备份一个数据库版本)
  • AOF的持久化通过追加日志的方式记录redis服务收到的写操作,日志会在redis重新启动时重新生成redis中的数据集。AOF持久化命令使用和redis协议相同的格式以追加的方式记录日志。当这个日志文件过于庞大时,redis可以在后台重写这个日志。
  • 你可以关闭这个持久化功能,这样你的数据就只在redis运行时存在内存中,redis关闭就会丢失数据

RDB & AOF的优劣势

RDB优势

  1. RDB其实是不大的单文件,该文件只记录某个时间点redis内存中的数据,很适合做数据备份。你可以24小时内,每小时备份一次,或者30天内,每天备份一次等等
  2. 这个备份文件是可以上传到备份服务器上,作为容灾的备份文件使用
  3. RDB极大程度上利用了redis的性能,因为redis的主进程会创建一个子进程去完成持久化的工作,同时在进行持久化期间,主进程不会做任何磁盘io操作及类似的操作,这就保证了备份数据的完整性。
  4. RDB恢复备份会比AOF快很多,因为RDB的备份文件要远小于AOF的备份文件。

RDB劣势

  1. 由于RDB备份不是实时数据全量备份,所以当对redis突然宕机时数据完整性要求比较高时,RDB不是很好。在某次备份时突然宕机,就会丢失本次备份的数据
  2. RDB备份时会fork子进程,如果备份数据非常大,子进程就会一直占用CPU资源,CPU使用率会很高,一般而言这对云服务器不是问题。

配置文件修改RDB设置

RDB保存机制: 编辑redis.conf文件,找到SNAPSHOTTING部分,注释写的很清楚(save the db on disk),命令:save ,seconds 单位是秒,changes 修改次数,命令的意思:如果在多少秒之内修改了多少次就会触发RDB持久化策略

stop-writes-on-bgsave-error yes:如果save过程出错,则停止写操作 no:可能造成数据不一致

rdbcompression yes:开启rdb压缩模式 no:关闭,会节约cpu损耗,但是文件会大,道理同nginx

rdbchecksum yes:使用CRC64算法校验对rdb进行数据校验,有10%性能损耗 no:不校验

AOF的优势

  1. AOF更加耐用,可以以秒级别为单位备份,如果发生问题,也只会丢失最后一秒的数据,增加了可靠性和数据完整性。所以AOF可以每秒备份一次,使用fsync操作。
  2. 以log日志形式追加,如果磁盘满了,会执行 redis-check-aof 工具
  3. 当数据太大的时候,redis可以在后台自动重写aof。当redis继续把日志追加到老的文件中去时,重写也是非常安全的,不会影响客户端的读写操作。
  4. AOF 日志包含的所有写操作,会更加便于redis的解析恢复。

AOF重写的一些设计细节: AOF在进行重写时,其实是执行aof_rewrite命令,执行这个命令会长时间占据调用这个函数的线程,而redis是单线程的,所以如果执行这个函数就会导致主线程无法处理客户端的请求,所以redis会fork出一个子进程在后台默默的重写AOF,但是在重写期间,redis可能会接受处理客户端的请求,所以会导致数据的不一致,那么redis就使用了重写缓冲区,在处理客户端请求的同时,会将数据copy一份到重写缓冲区中,这样就避免了数据会不一致的问题 参考文章: Redis之AOF重写及其实现原理

AOF的劣势

  1. AOF的文件比RDB的文件要大的多
  2. 针对不同的同步机制,AOF会比RDB慢,因为AOF每秒都会备份做写操作,这样相对与RDB来说就略低。 每秒备份fsync没毛病,但是如果客户端的每次写入就做一次备份fsync的话,那么redis的性能就会下降。
  3. AOF发生过bug,就是数据恢复的时候数据不完整,这样显得AOF会比较脆弱,容易出现bug,因为AOF没有RDB那么简单,但是呢为了防止bug的产生,AOF就不会根据旧的指令去重构,而是根据当时缓存中存在的数据指令去做重构,这样就更加健壮和可靠了

AOF的配置

appendonly no AOF 默认关闭,yes可以开启

appendfilename "appendonly.aof" AOF 的文件名

appendfsync everysec 参数:no:不同步 everysec:每秒备份,推荐使用 always:每次操作都会备份,安全并且数据完整,但是慢性能差

no-appendfsync-on-rewrite no 重写的时候是否要同步,no可以保证数据安全

auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb 当前AOF文件的大小是上次AOF大小的100% 并且文件体积达到64m,满足两者则触发重写

到底采用RDB还是AOF呢? 如果你能接受一段时间的缓存丢失,那么可以使用RDB如果你对实时性的数据比较care,那么就用AOF使用RDB和AOF结合一起做持久化,RDB做冷备,可以在不同时期对不同版本做恢复,AOF做热备,保证数据仅仅只有1秒的损失。当AOF破损不可用了,那么再用RDB恢复,这样就做到了两者的相互结合,也就是说Redis恢复会先加载AOF,如果AOF有问题会再加载RDB,这样就达到冷热备份的目的了。

Redis 缓存过期处理与内存淘汰机制

缓存过期处理机制

前提补充: 我们都知道redis是基于内存的,通常我们会对一些key设置expire时间,当这些key到期后,就无法查询使用,但是这些过期key其实还是占用着我们的内存,除非redis清理了这些key。

redis提供的两种缓存处理机制(针对设置了expire时间的key)

  1. (主动)定期清理:redis会定期检查key,发现key过期了,就把它从内存中清理了,释放被占用的内存。(每秒检查次数在redis.conf中的hz配置,默认是每秒检查10次)

    1939_1

  2. (非主动)惰性清理:当client读取了一个已经过期的key时,redis才会主动去释放这个key占用的内存

内存淘汰机制

redis可以设置一个memory最大值的阈值,当redis占用的内存超过这个阈值时,redis就会去优化释放内存,这就涉及到了redis内存淘汰机制。 946da2af6d891b0d2c90c1fd4456c2df.png

maxmemory :设置进行缓存清理释放的阈值,默认不设置大小

redis提供了8中优化内存的策略,默认使用noeviction(当超过上面设置的阈值时,不优化内存,只是在进行写操作时返回一个error)

  • noeviction:旧缓存永不过期,新缓存设置不了,返回错误
  • allkeys-lru:清除最少用的旧缓存,然后保存新的缓存(推荐使用)
  • allkeys-random:在所有的缓存中随机删除(不推荐)
  • volatile-lru:在那些设置了expire过期时间的缓存中,清除最少用的旧缓存,然后保存新的缓存
  • volatile-random:在那些设置了expire过期时间的缓存中,随机删除缓存
  • volatile-ttl:在那些设置了expire过期时间的缓存中,删除即将过期的

分布式锁

分布式锁的目的:并发环境下保证串行化执行(保证操作的原子性)

redis分布式锁的核心就是setnx命令加锁+expire命令设置过期时间(2.8版本后扩展了set方法),通过del释放锁,这种锁实现是错误的

  1. 对于低版本redis应该使用lua脚本保证setnx和expire命令的原子性
  2. 高版本的redis加锁则可以直接使用set的扩展方法,这是setnx和expire命令的组合保证原子性

锁的释放:不能简单使用del命令进行删除,应该遵循“谁加锁,谁去释放”的原则,所以需要先进行匹配,是这个客户端加锁,才能进行del删除操作,但是匹配和del操作又不是原子性的,所以这就需要使用lua脚本去保证原子性

官网给出的脚本:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

redis分布式锁需要满足的几个条件

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。