redis 推送订阅信息 redis订阅和发布 消息推送php_lua

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

(学习视频分享:redis视频教程)

1.redis发布订阅模式

Redis除了提供像list的这种的消息队列模式,还提供了一组命令实现发布/订阅模式。例如微博,公众号等都是可以由此实现。

redis 推送订阅信息 redis订阅和发布 消息推送php_redis 推送订阅信息_02

1.2 订阅频道

发布者需要将消息发送到一个地方,让订阅者可以订阅消息,这个地方就是频道(channel)。订阅者可以订阅一个或者多个频道,所有订阅了这个频道的订阅者都会受到这条消息。

开启两个客户端进行测试客户端1 订阅channel1

127.0.0.1:6379> subscribe channel1

Reading messages... (press Ctrl-C to quit)

1) "subscribe"

2) "channel1"

3) (integer) 1

客户端2 发布一则消息

127.0.0.1:6379> publish channel1 test

(integer) 1

客户端1 订阅消息

127.0.0.1:6379> subscribe channel1

Reading messages... (press Ctrl-C to quit)

1) "subscribe"

2) "channel1"

3) (integer) 1

1) "message"

2) "channel1"

3) "test"

1.2 按规则订阅

支持?和*占位符。?代表一个字符,*代表 0 个或者多个字符。

启动四个redis-cli,一个作为消息的发布者,另外三个作为订阅者。

订阅者1:订阅体育相关psubscribe *sport

订阅者2:订阅新闻相关psubscribe news*

订阅者3:订阅天气相关psubscribe new weather*

发布者:publish news-sport Kobe

publish news-music jaychou

publish news-weather rain

此时订阅者1将会收到Kobe,订阅者2将会收到全部信息,订阅者3将会收到rain。

redis 推送订阅信息 redis订阅和发布 消息推送php_redis 推送订阅信息_03

2.redis事务

2.1 为什么要用事务

我们都是知道redis的单个命令是原子性的,但是如果需要用多个命令作为一个不可分割的操作序列,就需要用到事务。

例如使用setnx实现分布式锁,我们一般先set,然后对key设置expire,防止del发生异常时候锁不会释放,业务处理完之后在del,这三个操作我们就希望作为一组命令执行。

redis事务有两个特点:按照进入队列的顺序执行

不会受到其他客户端请求影响

redis的事务设计四个命令:multi(开启事务),exec(执行事务),dicard(取消事务),watch(监视)

2.2 事务的用法

转账场景A和B各有100元,A向B转账10元,A减10元,B加10元127.0.0.1:6379> set A 100

OK

127.0.0.1:6379> set B 100

OK

127.0.0.1:6379> multi

OK

127.0.0.1:6379> decrby A 10

QUEUED

127.0.0.1:6379> incrby B 10

QUEUED

127.0.0.1:6379> exec

1) (integer) 90

2) (integer) 110

127.0.0.1:6379> get A

"90"

127.0.0.1:6379> get B

"110"

通过multi命令开启事务。事务不能嵌套,多个multi命令效果一样的

使用multi开启事务之后,客户端向服务器发送多条命令,这些命令并不会立即被执行,而是会被放到一个队列中,当exec命令调用之后,队列中的命令才会被执行。

我们可以使用discard来清空事务队列。127.0.0.1:6379> multi

OK

127.0.0.1:6379> set k1 1

QUEUED

127.0.0.1:6379> set k2 2

QUEUED

127.0.0.1:6379> discard

OK

127.0.0.1:6379> get k1

(nil)

127.0.0.1:6379> get k2

(nil)

当我们执行事务的时候出现了问题会回滚吗?

exec之前发生错误 (如指令语法错误)127.0.0.1:6379> clear

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set name test

QUEUED

127.0.0.1:6379> hset user lisi

(error) ERR wrong number of arguments for 'hset' command

127.0.0.1:6379> exec

(error) EXECABORT Transaction discarded because of previous errors.

127.0.0.1:6379> get name

(nil)

exec之后发生错误(对同一个key使用不同数据类型的命令)127.0.0.1:6379> multi

OK

127.0.0.1:6379> set k1 1

QUEUED

127.0.0.1:6379> hset k1 a b

QUEUED

127.0.0.1:6379> exec

1) OK

2) (error) WRONGTYPE Operation against a key holding the wrong kind of value

127.0.0.1:6379> get k1

"1"

通过上面操作,我们可以知道当事务在exec前发生错误,将会回滚所有操作;如果在exec后发生错误,只有错误的命令不会被执行。

为什么redis在一个事务中存在错误不进行回滚呢?

我们从上面操作可以看出,redis只有在指令语法错误的时候进行回滚,而指令操作错误是有开发人员导致的bug,例如:你对一个int类型进行+1,然后不小心+2,或者对一个string类型进行+1,回滚是不适用的

2.3 watch指令

它可以为Redis事务提供CAS乐观锁操作,也就是多个线程更新某个变量的时候,会让旧值跟内存地址相比较,如果相等,则更新为新值。

我们可以用watch监视一个或者多个key,如果开启事务之后,至少有一个被监视的key在exec执行之前被修改,则会取消整个事务。

首先client 1执行watch监视money这个key,并开启事务对money进行增加100127.0.0.1:6379> set money 1000

OK

127.0.0.1:6379> watch money

OK

127.0.0.1:6379> multi

OK

127.0.0.1:6379> incrby money 100

QUEUED

在事务结束之前,在client 2对money进行减少100127.0.0.1:6379> decrby money 100

(integer) 900

此时client 1结束事务,money的值并没有被增加,反而减少,说明事务的修改失效127.0.0.1:6379> exec

(nil)

127.0.0.1:6379> get money

"900"

3. Lua脚本

Lua脚本是一种轻量级脚本语言,C语言编写的,跟存储过程有点类似。为啥要用lua脚本呢?

一次发送多个命令,减少网络开销Redis会将脚本作为一个整体执行,保证原子性(可用此方式替换事务)脚本复用,便于多个客户端完成相同的逻辑。

3.1 使用

我们可以使用以下命令进行调用lua脚本eval script numkeys [key1 key2 key3 ....] [arg1 arg2 arg3 ....]

eval 执行lua脚本

script 代表lua脚本的内容

numkeys key的数量

[key1 key2 key3 ....] 键名参数,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

[arg1 arg2 arg3 ....] 全局变量,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)

来个简单的例子127.0.0.1:6379> eval "return {KEYS[1],ARGV[1],KEYS[2],ARGV[2]}" 2 key1 key2 val1 val1

1) "key1"

2) "val1"

3) "key2"

4) "val1"

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 val1 val1

1) "key1"

2) "key2"

3) "val1"

4) "val1"

在lua脚本如何调用redis命令呢?

我们可以使用 redis.call(command, key [param1, param2…])进行操作

commond redis的命令,如set,get等key 被操作的键[param1, param2…]表示给key的参数127.0.0.1:6379> eval "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 name age lisi 18

(nil)

127.0.0.1:6379> mget name age

1) "lisi"

2) "18

以上命令等价于 mset name lisi age 18, key的数量为2,2 后面两个值为key,在之后就是args

直接在redis-cli中写lua脚本不够方便,通常我们会把脚本放在文件中,然后执行这个文件

我们在一个目录下新建一个test.lua的脚本,填写以下内容后执行。root@VM-0-5-centos src]# mkdir testlua

[root@VM-0-5-centos src]# cd testlua/

[root@VM-0-5-centos testlua]# ll

total 0

[root@VM-0-5-centos testlua]# touch test.lua

[root@VM-0-5-centos testlua]# vim test.lua

redis.call('set',KEYS[1],ARGV[1])

return redis.call('get',KEYS[1])

[root@VM-0-5-centos testlua]# redis-cli --eval test.lua 1 myname , Armin

"Armin"

值得注意的是key和arg之间需要加上空格逗号空格(myname , Armin)

3.2 缓存lua脚本

之所以需要缓存lua脚本,这是因为每次调用的时候都将整个脚本传给redis服务端,会产生较大的网络开销。为了解决这个问题,Redis提供了evalsha命令,让开发人员通过脚本内容的SHA1摘要执行脚本。

那么怎么将生成这个SHA1并将脚本内容加载到缓存呢,这就用到script load命令去计算脚本的SHA1摘要并记录脚本到缓存中,执行evalsha时,redis会根据提供的摘要去脚本缓存找到对应脚本内容,如果找到则执行,否则返回错误提示: “NOSCRIPT No matching script. Please use EVAL”127.0.0.1:6379> script load "return 'Hey boy'"

"3760855b303510c83f0be2e8acfb0be64113ae6e"

127.0.0.1:6379> evalsha 3760855b303510c83f0be2e8acfb0be64113ae6e 0

"Hey boy"

127.0.0.1:6379> script exists 3760855b303510c83f0be2e8acfb0be64113ae6e //判断是否存在

1) (integer)

Redis还给lua脚本的执行提供了超时时间,默认的超时时间为5s,超过5s之后redis会接受其他命令但是会返回一个"BUSY"的错误

可在redis.conf中修改指定参数lua-time-limit 5000

Redis提供了个script kill的命令来终止正在运行的脚本127.0.0.1:6379> set name lisi

(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

127.0.0.1:6379> script kill

OK

127.0.0.1:6379> set name lisi

OK

如果数据进行了修改操作,将无法使用script kill终止脚本,因为违反了原子性。此时只能通过shutdown nosave来强行终止redis。

shutdown nosave 和 shutdown 的区别在于 shutdown nosave 不会进行持久化

操作,意味着发生在上一次快照后的数据库修改都会丢失。