03. Redis 高级特性

05 Redis 高级特性

Pt1 发布订阅

介绍LIST的时候我们说过,可以使用LIST实现生产者消费者队列,但是如果要实现一对多的发布订阅模式,则无法实现。为此Redis提供了基于channel的发布订阅功能。

消息的发布者可以向指定的channel发布消息,订阅者可以订阅一个或者多个channel,只要消息到达了channel,所有订阅者都会受到这条消息。

 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) "hahah"

Redis还支持正则匹配的方式订阅channel,可以使用?和*模糊匹配。

  • ?代表一个字符

  • *代表0个或多个字符

 # 订阅者使用psubscribe模糊匹配
 127.0.0.1:6379> psubscribe chn*
 Reading messages... (press Ctrl-C to quit)
 1) "psubscribe"
 2) "chn*"
 3) (integer) 1
 ​
 # 以下是发布消息后的处理
 1) "pmessage"
 2) "chn*"
 3) "chnn"
 4) "hahah"
 ​
 # 发布消息
 127.0.0.1:6379> publish chnn hahah
 (integer) 1
 

Pt2 事务

Redis请求命令是单线程的,单个命令是原子的,要么成功要么失败,不存在并发的数据一致性问题。但是,多个命令或者多个客户端并发操作命令时,就需要通过处理来解决并发产生的问题。Redis提供了事务的功能,可以把一组命令一起执行,事务有3个特点:

  • 按进入队列的顺序执行命令

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

  • 事务不能嵌套,多个multi命令效果一样

Redis的事务涉及到四个命令:

  • multi 开启事务:通过multi命令开启事务,客户端可以继续向服务端发送任意多条命令,这些命令不会立即被执行,而是放到一个队列中;

  • exec 执行任务:队列中的命令被有序执行,不执行exec,所有命令都不会被执行;

  • discard 取消事务:取消队列中命令执行;

  • watch 监视事务执行:监视key的修改,后面会详说;

但是Redis的事务没有那么完美,和关系型数据库事务并不相同。我们知道,事务是保证一组命令执行的原子性,要么全部成功,要么全部失败,部分成功的命令必须回滚以保证原子性,但是Redis没有这么做,它没有回滚。

  • 当exec命令执行前发生异常时,将取消事务执行,队列中所有命令都不会得到执行;

  • 当exec命令执行后发生异常时,已经执行的命令不会被回滚,错误的命令将不会被执行;

 # 场景1:exec命令执行前发生异常
 # 开启事务
 127.0.0.1:6379> multi
 OK
 ​
 # 执行命令
 127.0.0.1:6379> set name lucas
 QUEUED
 127.0.0.1:6379> set age 30
 QUEUED
 ​
 # hset少一个value参数,所以命令是错误的
 127.0.0.1:6379> hset job huifu
 (error) ERR wrong number of arguments for 'hset' command
 ​
 # 执行事务显示因为命令错误事务已经被取消
 127.0.0.1:6379> exec
 (error) EXECABORT Transaction discarded because of previous errors.
 # 场景2:exec命令执行后发生异常
 127.0.0.1:6379> multi
 OK
 127.0.0.1:6379> set name lucas
 QUEUED
 ​
 # 命令和上一个key值冲突了
 127.0.0.1:6379> hset name chinese chen
 QUEUED
 127.0.0.1:6379> set age 30
 QUEUED
 ​
 # 执行结果
 127.0.0.1:6379> exec
 1) OK
 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
 3) OK

所以Redis的事务和通常理解的不太一致,我们很难使用Redis的事务机制来实现原子性,保证数据一致性。Redis官方也对此做出了解释:

However there are good opinions for this behavior:

  • Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.

  • Redis is internally simplified and faster because it does not need the ability to roll back.

主要意思就是说,在上面两种场景下,都是代码产生的问题,可以在生产上线前解决,Redis不愿意为这种错误做出牺牲,希望保持内部命令执行的简单和快速。

Watch命令

当多个客户端更新变量的时候,数据可能会被别的客户端修改,带来非预期的结果,所以Redis提供了Watch命令。

Watch命令和CAS乐观锁行为一致,当使用watch时,会记录key对应的值,在更新key值时,会和watch时的数据进行比较,只有相同的时候才能更新成功。

如果开启事务之后,至少有一个被监视的key在exec执行之前被修改,整个事务都会被取消。


Pt3 Lua脚本

Lua是一种轻量级脚本语言,和存储过程有点类似。使用Lua脚本执行Redis有以下好处:

  • 一次发送多个命令,减少网络开销;

  • Redis将整个脚本作为整体执行,不会被其他请求打断,具备原子性;

  • 复杂的组合命令,可以放在文件中,后续还可以复用;

Pt3.1 在Redis中调用Lua脚本

使用eval语法可以在Redis客户端执行Lua命令,命令格式如下:

eval luaScript keyNum [key1 key2 key3 ...][value1 value2 value3]

  • eval代表执行Lua语言的命令;

  • luaScript代表Lua语言脚本的内容;

  • key-num表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0。

  • [key1 key2 key3…]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。

  • [value1 value2 value3]这些参数传递给Lua语言,他们是可填可不填的。

 127.0.0.1:6379> eval "return 'Hello world'" 0
 "Hello world"

Pt3.2 在Lua脚本中执行Redis命令

使用redis.call在Lua脚本中执行Redis命令,语法如下:

redis.call(command, key, [param1, param2...])

  • command是命令,包括set,get..

  • key是被操作的键

  • param1,param2 代表给key的参数

 127.0.0.1:6379> eval "return redis.call('set', 'name','lucas')" 0
 OK
 127.0.0.1:6379> get name
 "lucas"

当然直接执行脚本看起来没有什么优势,而且很复杂,我们可以将多个命令放到文件中执行:

 # 查看Lua文件内容
 root@fa0050d94745:/data# ls
 appendonly.aof  appendonly.aof.aaa  backup.db  dump.rdb  redis.lua  root  zzh
 root@fa0050d94745:/data# cat redis.lua
 redis.call('set', 'age', '45')
 return redis.call('get', 'age')
 ​
 # 执行Redis Lua文件
 root@fa0050d94745:/data# redis-cli --eval redis.lua 0
 "45"

Pt3.3 Redis+Lua案例

用Lua脚本实现一个简单的限流操作,限制每个用户在X秒内只能访问Y次

脚本如下:

 root@fa0050d94745:/data# cat redisIpLimit.lua
 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

测试结果(6秒钟不超过5次):

 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 1
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 1
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 1
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 1
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 1
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 0
 root@fa0050d94745:/data# redis-cli --eval redisIpLimit.lua app:ip:limit:127.0.0.1 , 6 5
 (integer) 0

Pt3.4 Lua脚本缓存

如果Lua脚本过大,每次调用脚本都需要把整个脚本传给Redis服务端,网络开销会非常大。为了解决这个为题,Redis可以缓存Lua脚本并声称SHA1摘要码,以后可以直接通过摘要码来执行Lua脚本。

  • 通过script load命令生成摘要

  • evalsha 执行缓存的摘要脚本

 127.0.0.1:6379> script load "return 'Hello World'"
 "470877a599ac74fbfda41caa908de682c5fc7d4b"
 127.0.0.1:6379> evalsha "470877a599ac74fbfda41caa908de682c5fc7d4b" 0
 "Hello World"

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
内容简介: Redis的的是完全开源免费的,遵守BSD协议,是一个高性能的键值数据库。是当前最热门的的的NoSql数据库之一,也被人们称为数据结构服务器。本课程从Redis基本数据类型开始,了解不同数据类型的用法和底层实现 。进一步学习Redis的一些高级特性与工作原理。了解Redis在分布式环境中的工作方式,和实际项目的使用及问题解决。 为什么学Redis? 原因很简单,快!这个问题在大并发,高负载的网站中必须考虑.redis数据库中的所有数据都存储在内存中。由于内存的读写速度远快于硬盘,因此Redis在性能上对比其他基于硬盘存储的数据库有非常明显的优势。项目中使用Redis,主要是从两个角度去考虑:性能状语从句:并发。当然,Redis的的的还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件代替,并不是非要使用Redis的的的。因此,这个问题主要从性能和并发两个角度去答。性能:我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,请求使得能够迅速响应。 并发: 在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用的的Redis的做一个缓冲操作,让请求先访问到的Redis的的,而不是直接访问数据库。redis优势:1.运行在内存,速度快官方号称支持并发11瓦特读操作,并发8瓦特写操作,可以说是相当彪悍了。2.数据虽在内存,但是提供了持久化的支持,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务3.支持数据结构丰富(string(字符串),list(链表),set(集合),zset(sorted set - 有序集合))和Hash(哈希类型,md5加密出来的那个串)课程大纲: 为了让大家快速系统了解Redis核心知识全貌,我为你总结了「Redis核心框架图」,帮你梳理学习重点,建议收藏!!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值