一.发布订阅模式

    一般来说,考虑到性能和持久化的因素,实际中不建议使用Redis的发布订阅功能来实现MQ。Redis的一些内部机制用到了发布订阅功能,这里做一个了解。

1. 流程

            

redis的channel用法 redis channel创建_redis的channel用法

消息的生产者和消费者连接到同一个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,不会进行持久化操作,会丢掉发生在上一次快照后的数据库修改