Redis队列功能介绍
List
常用命令:
Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用
Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用
Brpoplpush
Lindex获取一个元素,通过其索引列表
Linsert在列表中的另一个元素之前或之后插入一个元素
Llen获得队列(List)的长度
Lpop从队列的左边出队一个元素
Lpush从队列的左边入队一个或多个元素
Lpushx当队列存在时,从队到左边入队一个元素
Lrange从列表中获取指定返回的元素
Lrem从列表中删除元素
Lset设置队列里面一个元素的值
Ltrim修剪到指定范围内的清单
Rpop从队列的右边出队一个元素
Rpoplpush删除列表中的最后一个元素,将其追加到另一个列表
Rpush从队列的右边入队一个元素
Rpushx从队列的右边入队一个元素,仅队列存在时有效
Redis支持php、python、c等接口
应用场景:
Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。
Lists的另一个应用就是消息队列,
可以利用Lists的PUSH *** 作,将任务存在Lists中,然后工作线程再用POP *** 作将任务取出进行执行。Redis还提供了 *** 作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
如果需要还可以用redis的Sorted-Sets数据结构来做优先队列可以给每条消息加上一个唯一的序号。这里就不详细介绍了。
实现方式:
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便 *** 作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
示意图:
1)入队
2)出队(非阻塞模式)
lpopd出列表首元素(即最后入队的元素)
Rpopd出列表尾元素 (即入队的最开始的一个元素)
注意:如果要当作队列功能,应该是用这个出队
这里的出队都是非阻塞模式,就是你用pop出队的时候,如果队列是空的话,你得到的是一个NULL的值
3)出队(阻塞模式)
假如现在queue队列为空 我们用brpop命令
BRPOP 是一个阻塞的列表d出原语。 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法d出任何元素的时候阻塞连接。 该命令会按照给出的 key 顺序查看 list,并在找到的第一个非空 list 的尾部d出一个元素。
A)
我们执行brpop命令
可以看到队列queue没有元素的时候 是阻塞的 即不返回值
其中0是超时时间 为0表示一直等待
B)
这个时候我们用lpush往队列里 入队一个数据“bbb”
C)
阻塞的队列立马会d出出队元素 显示队列名字 和 出队元素 已经等待了多少时间
D)
Brpop还能同时阻塞多个队列比如这样
用redis的list当作队列可能存在的问题
1)redis崩溃的时候队列功能失效
2)如果入队端一直在塞数据,而出队端没有消费数据,或者是入队的频率大而多,出队端的消费频率慢会导致内存暴涨
3)Redis的队列也可以像rabbitmq那样 即可以做消息的持久化,也可以不做消息的持久化。
当做持久话的时候,需要启动redis的dump数据的功能暂时不建议开启持久化。
Redis其实只适合作为缓存,而不是数据库或是存储。它的持久化方式适用于救救急啥的,不太适合当作一个普通功能来用。应为dump时候,会影响性能,数据量小的时候还看不出来,当数据量达到百万级别,内存10g左右的时候,非常影响性能。
4)假如有多个消费者同时监听一个队列,其中一个出队了一个元素,另一个则获取不到该元素
5)Redis的队列应用场景是一对多或者一对一的关系,即有多个入队端,但是只有一个消费端(出队)
PHP的redis 简单 *** 作示例
插入redis队列有重复值首先插入redis队列:$redis = new \Redis();
$redis->connect('127001',6379);
$redis->rPush('email','test@testcom');
再在其他方法中用lSize函数获取队列长度:
$redis = new \Redis();
$redis->connect('127001',6379);
$lenth = $redis->lSize('email');
var_dump($lenth);
消息队列要能支持组件通信消息的快速读写,而Redis本身支持数据的高速访问,正好可以满足消息队列的读写性能需求。另外,消息队列在存取消息时,必须要满足三个需求:
针对消息队列的需求,本节就来分析下Redis实现消息队列的方案
BLPOP :队列为空时阻塞
LPUSH :队列满时阻塞
BRPOPLPUSH :取出消费同时保存到另外一个备份list
从消息有序性、唯一性、可靠性三个方面分析是否可行
为了解决可靠性问题可以使用BRPOPLPUSH
当consumer故障恢复后可以从备份队列中取出消息进行处理
Streams是Redis专门为消息队列设计的数据类型,它提供了丰富的消息队列 *** 作命令;
XADD :插入消息,消息的格式是键-值对形式,保证有序,可以自动生成全局唯一ID;
XREAD :用于读取消息,可以按ID读取数据;
XREADGROUP :按消费组形式读取消息;
XPENDING :用来查询每个消费组内所有消费者已读取但尚未确认的消息
XACK :用于向消息队列确认消息处理已完成
-------- over ---------
1、redis中的每一个数据库,都由一个redisDb的结构存储。其中,redisDbid存储着redis数据库以整数表示的号码。redisDbdict存储着该库所有的键值对数据。redisDbexpires保存着每一个键的过期时间。
2、当redis服务器初始化时,会预先分配16个数据库(该数量可以通过配置文件配置),所有数据库保存到结构redisServer的一个成员redisServerdb数组中。当我们选择数据库selectnumber时,程序直接通过redisServerdb[number]来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取redisDbid即可。
3、既然我们知道一个数据库的所有键值都存储在redisDbdict中,那么我们要知道如果找到key的位置,就有必要了解一下dict的结构了:
typedefstructdict{
//特定于类型的处理函数
dictTypetype;
//类型处理函数的私有数据
voidprivdata;
//哈希表(2个)
dicththt[2];
//记录rehash进度的标志,值为-1表示rehash未进行
intrehashidx;
//当前正在运作的安全迭代器数量
intiterators;
}dict;
由上述的结构可以看出,redis的字典使用哈希表作为其底层实现。dict类型使用的两个指向哈希表的指针,其中0号哈希表(ht[0])主要用于存储数据库的所有键值,而1号哈希表主要用于程序对0号哈希表进行rehash时使用,rehash一般是在添加新值时会触发,这里不做过多的赘述。所以redis中查找一个key,其实就是对进行该dict结构中的ht[0]进行查找 *** 作。
4、既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据key的哈希值找到该列表后,如果列表的长度大于1,那么我们需要遍历该链表来找到我们所查找的key。当然,一般情况下链表长度都为是1,所以时间复杂度可看作o(1)。
二、当redis拿到一个key时,如果找到该key的位置。
了解了上述知识之后,我们就可以来分析redis如果在内存找到一个key了。
1、当拿到一个key后,redis先判断当前库的0号哈希表是否为空,即:if(dict-
2、判断该0号哈希表是否需要rehash,因为如果在进行rehash,那么两个表中者有可能存储该key。如果正在进行rehash,将调用一次_方法,_用于对数据库字典、以及哈希键的字典进行被动rehash,这里不作赘述。
3、计算哈希表,根据当前字典与key进行哈希值的计算。
4、根据哈希值与当前字典计算哈希表的索引值。
5、根据索引值在哈希表中取出链表,遍历该链表找到key的位置。一般情况,该链表长度为1。
6、当ht[0]查找完了之后,再进行了次rehash判断,如果未在rehashing,则直接结束,否则对ht[1]重复345步骤。
到此我们就找到了key在内存中的位置了。
大致为两种措施:
一、脚本同步:
1、自己写脚本将数据库数据写入到redis/memcached。
2、这就涉及到实时数据变更的问题(mysqlrowbinlog的实时分析),binlog增量订阅Alibaba的canal,以及缓存层数据丢失/失效后的数据同步恢复问题。
二、业务层实现:
1、先读取nosql缓存层,没有数据再读取mysql层,并写入数据到nosql。
2、nosql层做好多节点分布式(一致性hash),以及节点失效后替代方案(多层hash寻找相邻替代节点),和数据震荡恢复了。
redis实现数据库缓存的分析:
对于变化频率非常快的数据来说,如果还选择传统的静态缓存方式(Memocached、FileSystem等)展示数据,可能在缓存的存取上会有很大的开销,并不能很好的满足需要,而Redis这样基于内存的NoSQL数据库,就非常适合担任实时数据的容器。
但是往往又有数据可靠性的需求,采用MySQL作为数据存储,不会因为内存问题而引起数据丢失,同时也可以利用关系数据库的特性实现很多功能。所以就会很自然的想到是否可以采用MySQL作为数据存储引擎,Redis则作为Cache。
MySQL到Redis数据复制方案,无论MySQL还是Redis,自身都带有数据同步的机制,比较常用的MySQL的Master/Slave模式,就是由Slave端分析Master的binlog来实现的,这样的数据复制其实还是一个异步过程,只不过当服务器都在同一内网时,异步的延迟几乎可以忽略。那么理论上也可用同样方式,分析MySQL的binlog文件并将数据插入Redis。
因此这里选择了一种开发成本更加低廉的方式,借用已经比较成熟的MySQLUDF,将MySQL数据首先放入Gearman中,然后通过一个自己编写的PHPGearmanWorker,将数据同步到Redis。比分析binlog的方式增加了不少流程,但是实现成本更低,更容易 *** 作。
打开浏览器,输入地址,按下回车,打开了页面。于是一个 >redis是什么东西就不多说了,网上文章一搜一大堆。首先来说一下我要实现的功能:
类似一个消息中转站吧,如果有人要发送消息,先将消息发到我这里来,然后我这边进行转发,为的就是有一个统一的管理和修改时方便,
而且所有的消息有优先级,也会有定时发送(如果同一时间消息过多,则会有延迟)
思路:
首先一个是将这两个分为两个队列来实现, 一个用来实现消息优先级,一个来实现定时发送
用的是redis的有序集合,用zadd添加时,将score比做是优先级,也可以用时间戳来当做score,用来表示时间
PHP 版本简易实现
将消息加入优先级的队列,将1,2替换为时间就是定时发送的队列了
1 $redis = new Redis();
2 $redis->connect('127001', 6379);
3 $redis->zAdd('zset1', 1, 'message');
4 $redis->zAdd('zset1', 2, 'message2');
从队列中取出数据
1 $redis->zRevRangeByScore('zset1, '+inf', '-inf', array('withscores'=>false, 'limit'=>array(0,20)));
这条语句表示从zset1这个队列里按照score从最大(+inf)到最小(-inf)的排序中取出20条,不带score,如果想要从小到大可以用 zRangeByScore
如果你想让这些都运行在命令行下,可以参考下面来,当然这些是经过删减的
1 <php
2 while (true) {
3 $pid = pcntl_fork();
4 if ($pid == -1) {
5 echo date('Y-m-d H:i:s') "fork失败!\n";
6 } else if ($pid == 0) {
7 $redis = new Redis();
8 $redis->connect('127001', 6379);
9 $redis->zRevRangeByScore('zset1', '+inf', '-inf', array('withscores'=>false, 'limit'=>array(0,20)));
10 exit;
11 } else {
12 pcntl_wait($status);
13 }
14 }
pcntl_fork是PHP中的生成子进程,当调用该函数时,会返回一个进程pid,当pid为0时表明是在子进程中,所以把要执行的东西全放这里
线上的一个项目,运行几个月了,用子进程方式还没有出过问题,也没挂过,相当不错在集群Redis队列中,分流指的是将任务或消息均匀地分配到不同的Redis节点中处理。这可以提高系统的性能和可伸缩性,确保集群中每个节点都能够被充分利用。
下面是一些常见的分流方法:
使用Hash槽和一致性哈希算法。每个Redis节点对应一个或多个Hash槽,并使用一致性哈希算法将任务或消息分配到相应的Redis节点中。这种方法能够保证在节点数发生变化时,只有极少数的任务或消息需要迁移,减少了数据迁移的工作量。同时,它也能够确保相同的任务或消息总是被发送到相同的节点上处理。
使用代理模式。在代理模式下,客户端直接与代理服务器通信,代理服务器负责将任务或消息分配到相应的Redis节点中。这种方法的好处是可以集中管理并控制队列的状态,并且容易实现动态节点负载均衡和故障转移。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)