发布与订阅
在Redis入门中我们了解到,可以通过List类型的lpop/rpush结合sleep或者blpop/rpush来实现消息队列。但是这两种做法存在以下弊端
1.当生产速度大于消费速度时,会导致数据堆积,占用存储空间。
2.使用sleep进行消息获取时,无法把握时间,消息实时性差
3.List的消息队列只能一对一发布,无法一对多。
因此,Redis提供了发布、订阅的相关用法
发布订阅的操作命令
#订阅频道,可以一次订阅一个或多个,在redis-cli输入这个命令后会进入订阅状态
subscribe channelN ...
#向指定的频道发布信息
publish channelN [massage]
#取消频道订阅(需要退出订阅状态)
unsubscribe channelN ...
#订阅一个或多个符合给定模式的频道。每个模式以 * 作为匹配符
psubscribe [pattern]
...
通过Jedis实现发布订阅
订阅
Jedis jedis = new Jedis("192.168.2.128", 6379);
JedisPubSub jedisPubSub=new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
System.out.println(channel+"频道收到消息:"+message);
}
};
//会阻塞
jedis.subscribe(jedisPubSub,"channel");
发布
Jedis jedis = new Jedis("192.168.2.128", 6379);
jedis.publish("channel","消息已发送");
运行结果
事务
Redis的事务具有以下特点:
1.一定会按照加入任务执行队列中的顺序执行
2.不会收到其他客户端的干扰
事务的操作命令
1.multi:开启事务
2.exec:执行事务
3.discard:取消事务
需要注意的是,与我们常见的oracle的事务不同的是,Redis不支持事务嵌套,没有子事务的概念。
4.watch:监控,对一个或多个KEY进行监控,起到一个乐观锁的作用。当多个客户端去操作同一个KEY时,watch会对比事务开启时的KEY的值,如果KEY没有改变,则执行成功,否则会取消执行。(如果KEY过期,则不起作用)
5.unwatch:取消监控
伪代码示例:
>watch KEY
>multi #开启事务
>...#redis 操作命令
>exec #执行
事务执行发生错误
1.执行exec之前:事务会拒绝执行。因为Redis只会对语法错误进行检查并警告。
2.执行exec之后:错误的命令不会被执行,但其他可以执行的命令会被执行,不会回滚。Redis认为程序上的缺陷应该在开发时就被解决。
因此Redis事务不具备原子性。
Lua脚本
Lua是一种轻量级的脚本语言,由C语言编写,作用类似于数据库的存储过程。
通过Lua脚本,我们可以批量执行Redis的命令,减少网络开销;并且,Redis会将Lua脚本中的命令当作一个整体来执行,不会被其他请求打断,保证了原子性。同时,一些复杂的操作命令也可以整合到Lua脚本中进行调用,实现了操作集合的复用。
Redis执行Lua脚本的操作命令
eval lua-script key-num [KEYN...] [VALUEN...]
eval:执行lua语言
lua-script:Lua脚本(可以是一个文件,也可以是一串命令)
key-num:表示参数中有多少个key,如果没有,则写0
[KEYN…]:key作为参数传给Lua,可以不填
[VALUEN]:value作为参数传个Lua,可以不填
Lua脚本中Redis命令的写法
redis.call(command,key,[param1,params2...])
command:redis操作命令类型,包括set、get、del等。
key:被操作的键。
param1,params2…:代表给key的参数。
其中,key和**[param1,params2…]**可以使用形参,在eval命令中给出参数具体内容。
示例:
eval "redis.call('set',KEYS[1],ARGV[1])" 1 KAYNAME VALUE
#注意Lua的数组下标是从1开始的
Lua脚本的缓存
当客户端通过Redis服务端执行一个Lua脚本时,如果脚本命令过长或者Lua脚本文件过大时,每次调用都要客户端发送文件给服务端,这样会产生很大的网络开销。因此,Redis提供了script load
命令,可以在服务端对脚本内容进行缓存,生成一个SHA1摘要串。客户端可以通过摘要进行脚本的执行
script load [Lua脚本文件或命令]
>返回一个SHA1摘要的字符串
执行命令:evalsha [摘要字符串]
Lua脚本执行时的超时处理
当Lua脚本执行时,其他redis操作命令会被拒绝执行,可以通过SCRIPT KILL
进行中断Lua脚本的执行。但当脚本中有设置值的操作时,SCRIPT KIILL 命令会被阻塞,因为Lua脚本要保证原子性,就需要命令一定会被执行,此时可以通过SHUTDOWN NOSAVE
进行中断,这个命令会在不保存执行结果的情况下,直接关闭服务器。
pipeline(管道机制)
pipeline可以通过一个队列,将客户端的命令缓存起来,然后一次性发送给Redis服务端,达到批量操作的目的。理论上,客户端每累计命令到8192个字节后,就会向服务端发送一次请求。
要注意的是,pipeline是将命令缓存到客户端的,所以要注意客户端内存的消耗情况。
Redis服务端在接收到请求后,如果客户端的请求还没有发生结束,会把响应的结果先放在客户端的接收缓存区,在该区域满了之后,会通知Redis服务器。服务端会将响应的结果放到服务端的输出缓冲区。
Jedis代码示例:
Pipeline pipelined = jedis.pipelined();
for (int i = 0; i < 100000; i++) {
pipelined.set("batch"+i, String.valueOf(i));
}
pipelined.syncAndReturnAll();
数据淘汰机制与内存回收
因为Redis是通过内存进行数据存储的,为了保证Redis运行的性能,需要按照一定规则进行数据淘汰与内存回收。Redis的内存回收主要有两种:过期策略
和淘汰策略
。
过期策略
删除过期时间的KEY值
1)定时过期:每个设置了过期时间(TTL)的KEY都会创建一个定时器,到了KEY过期的时间,会被立即删除。对内存友好,但由于会创建多个定时器,会占用大量CPU资源,并影响Redis的响应时间和吞吐量。
2)惰性过期:只有KEY被访问时才会去判断其是否过期,过期则删除。显而易见,这种方式与定时过期恰好相反,对CUP资源不会大量占用,但对内存来说十分不友好。
3)定期过期:每隔一段时间,扫描一定数量的KEY,并清除其中已经过期的KEY。可以通过调整扫描的时间间隔和每次扫描的限定耗时,使CPU和内存资源达到最优的平衡效果。
Redis中同时使用了惰性过期和定期过期两种过期策略
淘汰策略
KEY没有设置过期时间,导致内存使用达到MAXMEMORY极限时,会触发设置好的MAXMEMORY-POLICY,即内存淘汰算法,对数据进行清理,保证新数据的存入。
有以下8种算法可供选择:(绿色内容为redis提供的淘汰算法)
LRU:删除最近最少使用的KEY
LFU:删除最近最不常用的KEY
volatile:只对设置了过期时间的KEY进行淘汰
allkeys:对所有KEY进行淘汰
random:随机删除
noeviction:没有设置淘汰策略
volatile-ttl:对设置了过期时间的KEY,优先删除最近即将要过期的KEY。
需要注意的是,Redis的LRU算法实际上是一种近LRU算法,它并不会准确的删除所有键中最近最少使用的KEY,而是通过随机抽取maxmeory-samples设置的个数的KEY,删除这里面最近最少使用的KEY。
关于Redis的LRU算法的详情,可以查看这篇文件吃透Redis(八):缓存淘汰篇-LRU算法
(大佬🐂皮)
相关配置
1.最大内存配置:redis_conf中的maxmeory字段;当设置为0或者注释掉时,64位操作系统默认不限制大小,32位最多分配3G内存。可以直接改,也可以通过redis-cli执行set maxmeory [size]
2.淘汰策略:redis_conf对应属性:maxmeory-policy。
3.在使用LFU相关的算法时,通过设置lfu-log-factor
的数值来限制key被访问多少次时,相关计数器+1,lfu-decay-time
则是在多少秒内key没有被访问,相关计数器-1
数据持久化
Redis有两种持久化机制:RDB(Redis DateBase)
及AOF(Append Only File)
RDB
Redis默认开启RDB机制,其工作机制是在满足一定触发条件时,内存中的数据会写入rdb文件,并保存到磁盘上,重启服务后,Redis会读取rdb文件,将其重新读入内存中。
触发方式分为自动触发和手动触发;
自动触发
包括:配置规则触发/shutdown触发/flushall触发
其中,shutdown触发顾名思义是指在正常关闭服务时会触发rdb写入操作;
flushall触发则是在清空数据库命令后,触发rdb写入操作,清空rdb文件中的内容。
而配置规则触发,则需要在redis.conf文件中设置触发规则,其语法如下:
save [seconds] [changes]
指在[seconds] 秒内,至少有[changes]个KEY被修改时,触发写入操作。这个规则可以同时设置多条,且相互之前不冲突。
手动触发
包括两个命令:save
和bgsave
save
命令执行时,会直接阻塞主进程,在执行期间,redis无法执行其他操作命令。
bgsave
命令执行时,redis会在后台fork一个子进程,用来执行写入rdb文件的操作。其他操作命令依旧可以被执行。虽然不会发生阻塞,但是fork一个子进程也要消耗资源,在一些极端情况下,fork子进程的时间甚至超过了数据备份的时间。
rdf相关配置
1.生成路径:dir ./
(./指的是redis启动目录)
2.文件名:dbfilename dump.rdb
3.是否开启压缩:rdbcompression yes
(redis会通过LZF算法进行rdb文件压缩,这会占用一部分CPU资源,但是对内存来说,十分友好)。
4.文件完整性校验:rdbchecksum yes
(redis会在读入rdb文件时,通过CRC64校验文件完整性)
RDB的特点
rdb文件内容紧凑,会记录某个时刻,redis上的全部数据,适合作备份或灾难恢复工作;bgsave不会影响主进程;恢复大数据集时的速度很快。但同时,它的同步频率很低,无法进行实时的持久化。
AOF
AOF是使用日志的形式,每次处理完请求命令后都会将此命令追加到aof文件的末尾,在恢复数据时,会将文件中的命令从头到尾执行一次。需要注意的是,内存中的数据并不是实时写入aof文件,而是放在硬盘的缓冲区中,根据配置的fync规则,刷新到aof文件中。
aof相关配置
1.开启aof机制:appendonly yes
2.aof文件名:appendonlyfilename "appendonly.aof"
3.aof文件写入规则:appendfync everysec
[everysec]表示每1秒执行一次,[always]表示每次有新命令追加到 AOF 文件时就执行一次刷盘操作,[no]表示不同步,只在操作系统需要的时候进行刷新(指缓冲区满)
4.no-appendfsync-on-rewrite
表示在对aof文件进行压缩时,是否执行同步操作
Redis重写机制
由于aof机制的工作原理,会导致aof文件非常大。于是Redis提供了相应的重写机制,在aof文件的大小超过设置的阈值时,会触发重写机制,对内容进行压缩,根据当前数据库的内容,只保留用来恢复数据的最小指令集
AOF重写触发条件:
1.自动触发:
在redis.conf中有如下两个配置项来决定是否触发aof重写操作:
auto-aof-rewrite-percentage 100
指当前aof文件比上次重写的增长比例大小,达到这个大小就进行 aof 重写。
auto-aof-rewrite-min-size 64mb
最开始aof文件必须要达到64mb时才触发重写,这个只会在最初被使用一次。 之后的重写操作会通过倍数条件进行触发。
2.手动触发:
通过执行bgrewriteaof
命令来手动触发。其执行原理与bgsave
一样。
AOF的特点
1.同步频率高,数据丢失概率低。
2.文本内容高可读。
3.比RDB文件占用的磁盘空间更多,恢复速度相对要慢
持久化机制的实际应用
1.当redis只是作为缓存服务器时,不需要进行持久化
2.当两种持久化机制同时开启时,redis重启时会优先载入aof文件来恢复数据,因为通常AOF文件保存的数据集要比RDB文件完整。
3.因为RDB只做后备用途,所以建议只在save规则上持久化RDB文件,而且只需要15分钟备份一次就够了。
4.如果开启AOF,最差的情况下也只会丢失不超过2秒的数据。代价是会持续的IO,二是AOF重写的最后将重写过程中产生的新数据写入到新文件造成的阻塞几乎是不可避免的。在硬盘允许的条件下,应该尽量减少AOF重写的频率,可以将重写的基础大小设为5G以上。默认的倍数也可以适当提高。