一.发布订阅模式
一般来说,考虑到性能和持久化的因素,实际中不建议使用Redis的发布订阅功能来实现MQ。Redis的一些内部机制用到了发布订阅功能,这里做一个了解。
1. 流程
消息的生产者和消费者连接到同一个Redis的服务,通过channel(频道)进行关联
订阅者可以订阅一个或者多个channel,频道不用实现创建。
subscribe channel-1 channel-2 channel-3
3)消息的发布者给指定的 channel发布消息。(并不支持一次向多个频道发送消息)
publish channel-1 2673
4)只要有消息到达了channel,所有订阅了这个channel的订阅者都会收到这条消息
取消订阅(不能在订阅状态下使用)
unsubscribe channel-1
2. 按规则(Pattern)订阅
支持占位符,?代表一个字符,*代表0个或者多个字符
二.Redis事务
Redis的单个命令是原子性的(比如get set mget mset),要么成功要么失败,不存在并发干扰的问题。如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,就必须要依赖Redis的功能特性来实现了。Redis提供了事务的功能,可以把一组命令一起执行。
1.特点:
!) 按进入队列的顺序执行 2)不会受到其他客户端的请求的影响 3)事务不能嵌套,多个multi命令效果一样。
2.用法:
multi 命令开启事务。multi执行后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中。
2) exec 命令执行事务,被调用时,所有队列中的命令才会被执行。
3)如果中途不想执行事务了, discard 可以清空事务队列,放弃执行、
案例场景:A 和B 各有100元,A 向 B 转账10元
SET A 100
SET B 100
multi
decrby A 10
incrby B 10
exec
3. watch(监听)
为了防止事务过程中某个key的值被其他客户端请求修改,带来非预期的结果,在Redis 中还提供了一个watch命令。多个客户端更新变量的时候,会跟原值做比较,只有它没有被其他线程修改的情况下,才更新成新的值。为 Redis 事务提供CAS乐观锁行为。
watch监视一个或者多个key,如果开启事务之后,至少有一个被监视key键在exec执行之前被修改了,那么整个事务都会被取消(key提前过期除外)。可以用unwatch取消监听。
set A 100
watch A
mutil
incrby A 10
/*若在次之前另外一个线程修改了A,则incrby A 10 不会执行*/
exec
4.事务可能发生的问题
1.在执行exec之前发生错误
编译器错位)。事务会被拒绝执行,也就是队列中的所有命令都不会得到执行
mutil
set A 1OO
hset B 1000 //参数数量错误
exec
2.在执行exec之后发生错误
运行时错误. 最后 set A 10 执行成功。、
,只有错位的命令没有执行,但其他命令没有受到影响。这个显示不符合我们对原子性的定义,也就是我们没法用Redis的这种事务机制来实现原子性,保证数据一致性
flushall
mutil
set A 10 //A被定义成String型
hset A 100 200 //对A使用Hash命令
exec
三.Lua脚本
1.定义:
一种轻量级脚本语言,它是用C语言编写的,跟数据的存储过程有点类似。
2.作用:
1) 一次发送多个命令,减少网络开销
2) Redis会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性
3) 对于复杂的组合命令,我们可以放在文件中,可以实现命令复用
3.使用
1) 语法格式:eval lua-script key-num [keyl key2 key3.][valuel value2 value3.]
eval: 执行Lua语言的命令 ; lua-script :Lua语言脚本内容; key-num:表示参数中有多少个key,如果没有key的参数,那么写0。
[key1 key2 key3·] : 是 key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。
[value1 value2 value3·] : 这些参数传递给Lua语言,它们是可填可不填的
//返回一个字符串,0个参数
eval "return 'Hello World'" 0
2) Lua语言脚本内容格式:redis.call(command,key[paraml,param2...])
command是命令,包括set、get、del等。 key是被操作的键。 param1,param2···代表给key的参数。
//执行set A 1000
//固定方式
eval "return redis.call('set','A','1000')" 0
//传参方式
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 A 100
在redis-cli中直接写Lua脚本不够方便,也不能实现编辑和复用,通常我们会把Lua脚本放在文件里面,然后执行这个文件。
a. 创建Lua 脚本文件:例如在/usr/local 目录下创建文件 temp.lua
b. Lua脚本内容,先赋值,再取值
redis.call ('set','A','100')
return redis.call('get','A')
c.调用脚本(在temp.lua所在目录下运行)
redis-cli --eval temp.lua 0
4.使用案例
需求:对 ip进行限流,每个用户在X秒内只能访问Y次
实现思路:(1) 用String的key记录IP,用value记录访问次数。几秒钟和几次要用参数动态传进去
(2) 拿到IP以后,对IP+1。如果是第一次访问,对key设置过期时间(参数1)。否则判断次数,超过限定的次数(参数2),返回0。如果没有超过次数则返回1。
(3) 超过时间,key过期之后,可以再次访问。
实现:(1)编写l imit.lua 脚本
//KEYS[1]:IP键 ARGV[1]:过期时间 ARGV[2]访问次数
local num = redis.call('incr', KEYS[1])
//第一次访问
if tonumber(num)==1 then
redis.call('expire',KEYS[1],ARGV[1])
return 1
elseif tonumber(num) > tonumber(ARGV[2]) then
return 0
else
return 1
end
(2)对Ip 192.168.10.33 在 10 秒钟限制访问10次
redis-cli --eval limit.lua IP33 , 6 10 //Ip33是key值,后面是参数值,中间要加一个空格和一个逗号,再加上一个空格
5.缓存Lua脚本
背景: Lua脚本比较长时,如果每次调用脚本都需要把整个脚本传给Redis服务端,会产生比较大的网络开销. Redis可以缓存Lua脚本并生成SHA1摘要码,后面可以直接通过摘要码来执行Lua脚本。
流程:1.在服务端缓存lua脚本生成一个摘要码
evalsha "上一步生成的摘要码"
2.通过摘要码调用
script load "要运行的命令"
6.超时与中止
超时:Redis的指令执行本身是单线程的,如果Lua脚本执行时超时或陷入死循环,其他客户端的服务就会进入等待状态。所以Lua脚本执行有一个超时时间,默认时5秒。(lua-time-limit: 5000).超过5秒 ,其他客户端不会等待,而是返回“BUSY”错误.
中止:虽然超时后,客户端不会处于等待状态,但也不能一直拒绝其他客户端的命令执行。提供 script kill 中止脚本,,shutdown nosave 关闭Redis服务 的命令可以使用
补充 前执行的Lua脚本对Redis的数据进行了修改(SET、DEL等),那么通过script kill命令是不能终止脚本运行的。
shutdown nosave区别于shutdown,不会进行持久化操作,会丢掉发生在上一次快照后的数据库修改