本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
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五种基本数据类型相关知识点
-
String字符串
-
List列表
-
Hash字典
-
Set集合
-
Zset有序列表
SpringBoot和Redis进行整合
多路复用器,阻塞和非阻塞
同步,异步,阻塞,非阻塞的异同: I/O模型与多路复用 推荐看看 I/O模型与多路复用2 这篇博客介绍的很详细,也很专业,需要一定的io方面的知识
redis线程模型
首先,redis基于reactor模式开发了网络事件处理器(文件事件处理器),由于这个处理器是单线程的,所以,决定了redis也是单线程的。这整个文件事件处理器也可以称为redis的线程模型。
redis线程模型由5部分组成:
- socket
- IO多路复用器
- socket队列
- 文件事件分配器
- 事件处理器(连接应答处理器,命令请求处理器,命令响应处理器)
更详细一点的防止自己看不懂
redis一次请求的处理流程示意图
在 Redis 启动初始化的时候,Redis 会将连接应答处理器跟 AE_READABLE 事件关联起来,接着如果一个客户端跟Redis发起连接,此时会产生一个 AE_READABLE 事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的 Socket,同时将这个 Socket 的 AE_READABLE 事件跟命令请求处理器关联起来
- 当客户端向Redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在 Socket 产生一个 AE_READABLE 事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从Socket中读取请求相关数据,然后进行执行和处理
- 接着Redis这边准备好了给客户端的响应数据之后,就会将Socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在 Socket 上产生一个 AE_WRITABLE 事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入 Socket,供客户端来读取。
- 命令回复处理器写完之后,就会删除这个 Socket 的 AE_WRITABLE 事件和命令回复处理器的关联关系
推荐博客: redis线程模型-byene redis线程模型-以梦为码 redis线程模型-全菜工程师小辉
redis发布和订阅
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优势
- RDB其实是不大的单文件,该文件只记录某个时间点redis内存中的数据,很适合做数据备份。你可以24小时内,每小时备份一次,或者30天内,每天备份一次等等
- 这个备份文件是可以上传到备份服务器上,作为容灾的备份文件使用
- RDB极大程度上利用了redis的性能,因为redis的主进程会创建一个子进程去完成持久化的工作,同时在进行持久化期间,主进程不会做任何磁盘io操作及类似的操作,这就保证了备份数据的完整性。
- RDB恢复备份会比AOF快很多,因为RDB的备份文件要远小于AOF的备份文件。
RDB劣势
- 由于RDB备份不是实时数据全量备份,所以当对redis突然宕机时数据完整性要求比较高时,RDB不是很好。在某次备份时突然宕机,就会丢失本次备份的数据
- 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的优势
- AOF更加耐用,可以以秒级别为单位备份,如果发生问题,也只会丢失最后一秒的数据,增加了可靠性和数据完整性。所以AOF可以每秒备份一次,使用fsync操作。
- 以log日志形式追加,如果磁盘满了,会执行 redis-check-aof 工具
- 当数据太大的时候,redis可以在后台自动重写aof。当redis继续把日志追加到老的文件中去时,重写也是非常安全的,不会影响客户端的读写操作。
- AOF 日志包含的所有写操作,会更加便于redis的解析恢复。
AOF重写的一些设计细节: AOF在进行重写时,其实是执行aof_rewrite
命令,执行这个命令会长时间占据调用这个函数的线程,而redis是单线程的,所以如果执行这个函数就会导致主线程无法处理客户端的请求,所以redis会fork出一个子进程在后台默默的重写AOF,但是在重写期间,redis可能会接受处理客户端的请求,所以会导致数据的不一致,那么redis就使用了重写缓冲区,在处理客户端请求的同时,会将数据copy一份到重写缓冲区中,这样就避免了数据会不一致的问题 参考文章: Redis之AOF重写及其实现原理
AOF的劣势
- AOF的文件比RDB的文件要大的多
- 针对不同的同步机制,AOF会比RDB慢,因为AOF每秒都会备份做写操作,这样相对与RDB来说就略低。 每秒备份fsync没毛病,但是如果客户端的每次写入就做一次备份fsync的话,那么redis的性能就会下降。
- 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):
-
(主动)定期清理:redis会定期检查key,发现key过期了,就把它从内存中清理了,释放被占用的内存。(每秒检查次数在redis.conf中的hz配置,默认是每秒检查10次)
-
(非主动)惰性清理:当client读取了一个已经过期的key时,redis才会主动去释放这个key占用的内存
内存淘汰机制
redis可以设置一个memory最大值的阈值,当redis占用的内存超过这个阈值时,redis就会去优化释放内存,这就涉及到了redis内存淘汰机制。
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释放锁,这种锁实现是错误的
- 对于低版本redis应该使用lua脚本保证setnx和expire命令的原子性
- 高版本的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分布式锁需要满足的几个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。