5.Redis - 管道,消息订阅,事务,布隆过滤器,缓存回收

管道

如果Client想对redis进程发送很多的命令,其实每发送一个命令都要走一次数据传输给redis执行并返回,返回之后才能线性执行第二个命令。
因此有了管道的概念,把多次数据传输压缩成一次数据传输,在redis中就是一次性发送多条命令,redis会返回多条结果。就像我们计算机编程我们更多的会使用buffer机制,其目的就是为了减少数据传输的次数。

[root@z8524210 ~]# yum install nc			//安装netcat,用于当做client发起和redis的连接

我们可以一条命令一条命令的发送:

[root@z8524210 ~]# nc localhost 6379			//和redis建立连接
keys *												//发送指令得到回应
*0
set k1 hello
+OK
get k1
$5
hello

我们也可以使用管道的方式发送把多条命令多条数据传输压缩成一次数据传输:

[root@z8524210 ~]# echo -e "set k2 99 \n incr k2 \n get k2" | nc localhost 6379
+OK
:100
$3
100

注:
需要注意的是,命令与命令之间是需要\n换行符隔开的。echo打印set k2 99 \n incr k2 \n get k2字符串,-e参数会识别字符串的\n为换行,就相当于发送了三条命令set k2 99incr k2get k2,然后把打印的内容交给nc localhost 6379也就是redis。
总结:管道的作用就是让通信的成本变低了一些,仅此而已。

Pub/Sub

应用场景以及命令

redis中list类型有三个命令能实现阻塞队列的功能,分别是blpopbrpopbrpoplpush命令。
但是list只能模拟单播队列,也就是一个ProviderClient push消息到list,同时又有多个ConsumerClient brpop消息,那么众多ConsumerClient只能有一个接收到消息并且解除阻塞状态。

但是如果我们想实现一个直播软件中直播间里实时聊天的功能,一个人发送消息所有能都能观看的到应该如何实现呢?
使用redis的Pub/Sub(发布订阅)就可以实现了,首先我们可以用help @pubsub命令查看发布订阅有哪些命令:

127.0.0.1:6379> help @pubsub

  PSUBSCRIBE pattern [pattern ...]
  summary: Listen for messages published to channels matching the given patterns
  since: 2.0.0

  PUBLISH channel message
  summary: Post a message to a channel
  since: 2.0.0

  PUBSUB subcommand [argument [argument ...]]
  summary: Inspect the state of the Pub/Sub subsystem
  since: 2.8.0

  PUNSUBSCRIBE [pattern [pattern ...]]
  summary: Stop listening for messages posted to channels matching the given patterns
  since: 2.0.0

  SUBSCRIBE channel [channel ...]
  summary: Listen for messages published to the given channels
  since: 2.0.0

  UNSUBSCRIBE [channel [channel ...]]
  summary: Stop listening for messages posted to the given channels
  since: 2.0.0

127.0.0.1:6379> 

演示:

comsumer1,comsumer2:

[root@z8524210 ~]# redis-cli 
127.0.0.1:6379> clear
127.0.0.1:6379> SUBSCRIBE channe1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channe1"
3) (integer) 1
1) "message"
2) "channe1"
3) "hello"
1) "message"						//收到消息
2) "channe1"
3) "how do you do!"			//接收到消息后依然是监听状态

provider:

127.0.0.1:6379> PUBLISH channe1 hello							//publish消息
(integer) 2
127.0.0.1:6379> PUBLISH channe1 "how do you do!"				//publish消息
(integer) 2
127.0.0.1:6379> 

需要注意的是,Consumer端要先subscribe,Provider端后publish,Consumer端才能收到消息。

聊天记录架构扩展:

举个例子:如果自己设计一个微信群聊天的功能,那么所有的聊天记录的数据放到哪里去存呢?redis还是mysql呢?

在这里插入图片描述
作为用户,对于聊天记录,一般都会关注两个点:

  • 实时聊天记录。
  • 历史聊天记录。历史聊天记录又分为近期(比如最近三天内)和 更老的数据。

首先,聊天记录的全量数据一定在数据库里面存了一份,但是如果所有用户的聊天记录都从数据库里面获取,对数据库的压力是非常大的。

所以我们选择使用redis来做缓存。redis缓存的使用大部分是为了解决数据库读的请求,那么我们如何在这个场景下使用redis呢?

read方面:

  • 对于 实时性 的聊天记录,我们就可以使用redis提供的Pub/Sub就能够解决。
  • 对于 更老的 聊天记录,因为这些数据访问量会很低,所以一定是从数据库里面获取。
  • 对于近期三天内的记录,我们可以把聊天记录存入redis的sorted set类型,把时间抽象成分数,让其排序,并且可以使用zremrangebyscore命令删除最近三天之外的聊天记录。同时还可以使用zremrangebyrank命令删除指定条数之外的记录。(sorted set物理内存左小右大,距离当前最近的时间数值一定大于三天前时间的数值,根据索引,删除条目之外的数据)

write方面有两个方案:

  1. 可以使用客户端代码先调用publish到redis,然后调用再线性单挑一次往sorted set里面存,最后掉用kafka慢慢往数据库里面存。

在这里插入图片描述
准备两个Redis,第一个Redis负责Pub/Sub,第二个redis负责sorted set相关功能。Client1给Pub/Sub redis发送消息时,Client2可以从Pub/Sub Redis服务器取走消息,同时sorted set Redis器从Pub/Sub Redis订阅并且存入sorted set类型,自己写的service也从Pub/Sub Redis订阅消息并交给kafka存入数据库。

redis的事务

常用命令

redis的特点就是速度快,redis在开发相关功能的时候也是按照相关思路去做的,redis的事务并没有回滚的动作。

先看帮助文档:

127.0.0.1:6379> help @transactions

  DISCARD -
  summary: Discard all commands issued after MULTI
  since: 2.0.0

  EXEC -
  summary: Execute all commands issued after MULTI		//提交事务
  since: 1.2.0

  MULTI -
  summary: Mark the start of a transaction block			//开启事务
  since: 1.2.0

  UNWATCH -
  summary: Forget about all watched keys
  since: 2.2.0

  WATCH key [key ...]								//监听,在事务之前发送一个
  summary: Watch the given keys to determine execution of the MULTI/EXEC block
  since: 2.2.0

127.0.0.1:6379> 

原理:

在这里插入图片描述
mutli命令 和 exec命令:
首先redis是单进程的,单进程就像一个管道,当我们先执行mutli命令之后,且在redis接收到exec命令之前(夹在mutliexec中间的命令)是不会得到执行的。

如果有多个Client同时发送mutli命令进入redis的单线程进行排队,在单线程串行的理论上,关注点不是在于哪个Client的mutli命令先到达,而是哪个Client的exec命令先到到达redis被处理,那么exec命令先到达的Client的命令先被执行(从一个Client的mutil命令到达开始,这个Client的命令会被存放在一个单独的缓冲区,哪个缓冲区的exec命令先到就哪个缓冲区的命令先执行)。

watch命令:
watch命令是一个乐观锁CAS的操作,就是在事务还没有执行的时候,通过一个watch命令 监控 将要被操作的元素,如果在这个元素在mutli开启之后没有等exec命令到达之前发生了改变,那么后续的事务会被直接撤销。 撤销之后如何处理,需要客户端自己捕获进行处理(重新提交等操作)。

演示:
127.0.0.1:6379> MULTI						//开启事务
OK
127.0.0.1:6379> set k1 99				//建立string元素 k1 99
QUEUED
127.0.0.1:6379> INCR k1						//k1自增
QUEUED
127.0.0.1:6379> EXEC						//提交事务
1) OK										//返回结果
2) (integer) 100							//返回结果
127.0.0.1:6379> 

watch演示:

Client1:

127.0.0.1:6379> watch k1						//先监控k1这个元素
OK
127.0.0.1:6379> MULTI							//开启事务
OK
127.0.0.1:6379> get k1						//获取k1,k1的值是100
QUEUED
127.0.0.1:6379> 							//Client1并没有提交事务

Client2:

127.0.0.1:6379> INCR k1						//Client2对k1进行了自增1
(integer) 101

Client1:

127.0.0.1:6379> exec							//Client1提交事务,因为k1被Client2更改了值,因此返回nil
(nil)

到底需不需要watch,需要根据具体的业务场景来判定。

redis的事务为什么不支持回滚?

摘自:http://redis.cn

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

布隆过滤器(bloom)

redis支持的布隆过滤器,用来解决缓存穿透的问题。

安装

首先需要redis对不拢过滤器的扩展库:

  1. 进入布隆过滤器在github的仓库,下载zip包上传到linux或者点击鼠标右键获得下载地址,然后在linux上使用wget命令直接下载。
  2. 下载完之后获得一个zip的压缩包,需要下载解压工具 yum install unzip
  3. 下载完unzip之后执行unzip RedisBloom-master.zip解压zip压缩包,得到RedisBloom-master目录。
  4. cdRedisBloom-master目录,执行make执行(执行之前确认当前linux里有安装过gcc编译器)。
  5. make之后目录里会出现redisbloom.so文件,一般情况下我们会把这个文件cp到redis可执行文件的目录里面去。执行命令cp redisbloom.so /opt/zxj/redis5/
  6. 查看当前启动的redis进程ps -fe | grep redis,关闭当前已经启动的redis进程service redis_6379 stop,如果没有此条可以忽略。
  7. 重新启动redis的时候从新挂载扩展库,可以通过修改配置文件启动挂载,也可以手工挂载扩展库启动redis进程redis-server /etc/redis/6379.conf --loadmodule /opt/zxj/redis5/redisbloom.so
  8. 进入redis-cli客户端,输入bf.,按Tab键,能出现BF.ADDBF.MEXISTSBF.INSERT等命令就说明挂载成功。
什么是缓存穿透?

在浏览器访问网站的时候,在请求一条数据的时候,往往会先访问redis中书否存在这个数据,如果存在就直接返回,如果不存在会从数据库查询这个数据是否存在,存在这个数据会返回给浏览器并且存入Redis。下次再有请求这个数据的时候,会直接从redis中查询快速返回,这样既能快速响应浏览器,又可以减轻数据库的压力。
但是如果有很多请求的数据redis中没有,数据库里也没有,甚至有人恶意 高并发请求不存在的数据,每次都使请求命中数据库,每次都和数据库建立socket,可能会造成网站的卡顿,严重的可能让数据库挂掉。

如何解决这个问题呢?我们可以把数据库已有的数据登记redis,浏览器请求数据的时候,先去redis中查找请求的数据有没有被登记,如果有登记并且redis中没有缓存该数据就放行允许命中数据库,如果没有登记,直接返回错误提示。
但是像淘宝这样的企业,数据库里面的数据量一定很惊人,redis中的内存不足以登记海量的数据怎么办?布隆过滤器很的解决了这些问题。

布隆过滤器的原理

在这里插入图片描述
布隆过滤器如何用小的空间解决大量数据的匹配呢?
redis是二进制安全,因此string类型可以操作bitmap位图。

某电商网站有10W种商品,每种商品的名称占4byte,所有的商品名称就是40W byte,如果每个商品可以使用几个二进制位代表的话,那么他们的体积会变得很小。
如何让每个商品使用几个二进制位代表呢? 每个商品都代表一个元素,假设这个商品为元素1,元素1会经历N个不同的映射函数,每个映射函数把元素1转换成一个对应的数字,以数字为索引标记bitmap对应位为1。

碰撞:
这个时候如果元素2也参与了计算,虽然两个元素是不一样的,但是经历N个函数的计算,其中某个函数得到的结果可能和另外一个元素1某个函数得到的结果相同,就发生了碰撞。


用户请求这个网站查找商品,如果网站存在这个商品,那么用户查找的商品经过函数计算,在bitmap对应的位上标记一定为1。redis中如果缓存了这个商品的数据就直接返回,没有缓存就放行到数据库进行查询。


但是如果用户搜索的商品网站没有,也会有一定的概率放行过去。因为这个商品得到的N个计算结果可能同时覆盖了元素1和元素2的 部分 计算结果。所以布隆过滤器是一个概率性的工具 ,这个概率取决于元素的数量和bitmap数组的宽度。

针对于碰撞放行:

如果发生了碰撞放行,数据库又不存在这个元素会给Client返回null。
Client增加redis中这个元素的key=元素名称, value=null。
下次Client再次请求这个元素的时候直接从redis中返回null。


如果有人直接对数据库的元素进行了修改,同时也要完成对bloom的添加。

架构模板

在这里插入图片描述

演示:
127.0.0.1:6379> BF.ADD k1 orange							//往k1布隆过滤器里面添加元素orange
(integer) 1
127.0.0.1:6379> BF.EXISTS k1 orange						//查询orange,返回1表示存在该元素
(integer) 1
127.0.0.1:6379> BF.EXISTS k1 dasdasd					//查询不存在的就直接返回0
(integer) 0

缓存回收

redis作为缓存和redis作为数据库的区别

在这里插入图片描述
redis作为缓存,它的数据完整性不是特别重要,因为缓存不是全量数据,它应该是随着访问而变化的热数据。毕竟内存大小是有限的,也是一个瓶颈。关注点是急速。

redis作为数据库的话,数据是不能丢失的,需要保持数据的完整性。关注点是 持久性 + 速度 。

那么redis是如何做到数据随着访问变化只保留热数据呢?
  1. 业务逻辑: key是可以设置有效期的,具体key的有效期 需要根据 用户 的关注时间窗而设定的。过了这个时间窗这个数据就不应该出现在redis里,因为这个数据被访问的次数会很低,不能称之为热数据。
  2. 业务运转: 内存是有限的,当redis内存满了,应该采取相应的策略剔除掉冷数据。

redis的key的有效期会随着get延长时间吗? 答:不会
发生写操作,会剔除过期时间。
倒计时操作,不能延时。expire命令
定时 expireat命令

Redis如何淘汰过期的keys?
Redis keys过期有两种方式:被动和主动方式。
被动:在redis中,即使key过期了也不会马上被删除,而是等待Client访问这个key才会检查是否过期,如果过期了就删除这个key。
主动:redis定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。具体就是Redis每秒10次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1.

这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

redis内存上限多少呢?
可以通过6379.config配置文件来设定:maxmemory <bytes> 单位为byte .

redis内存满了之后又如何处理?
在6379.config配置文件来设定maxmemory-policy noeviction

redis提供了6种回收策略:

noeviction: 直接返回错误,不允许Client继续使用了。(更适合redis作为数据库,能保持数据的完整性)

allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。(常用,结合业务场景,非期限key多)

volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在有期限元素集合内,使得新添加的数据有空间存放。(常用,结合业务场景,期限key多)

allkeys-random: 回收随机的键使得新添加的数据有空间存放。(比较随意)

volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在有期限元素集合内。(比较随机)

volatile-ttl: 回收在过期集合的键,并且优先回即将过期的键,使得新添加的数据有空间存放。(复杂度高)

注意:
LRU 的关注点是 该元素多久没有被使用。
LFU 的关注点是 该元素被使用了多少次。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值