netcore redis 存储集合_redis实现多对多消息传递

8aab018892c264db61259d732a11c894.png

多对多消息发送与获取(即是群组)

通常有两种方法实现:

  第一种为消息推送。Redis内置有这种机制,publish往频道推送消息、subscribe订阅频道。这种方法有一个缺点就是必须保证接收者时刻在线(即是此时程序不能停下来,一直保持监控状态,假若断线后就会出现客户端丢失信息)

  第二种为消息拉取。所谓消息拉取,就是客户端自主去获取存储在服务器中的数据。Redis内部没有实现消息拉取这种机制。因此我们需要自己手动编写代码去实现这个功能。

模块要求:

  1、用户能够自行创建群组,并成为群主

  2、群主可以拉人进来作为群组成员、并且可以踢人

  3、用户可以直接退出群组

  4、可以发送消息,每一位成员都可以拉取消息

  5、群组的消息最大容纳量为5000条

  6、成员可以拉取新消息,并提示有多少新消息

  7、成员可以分页获取之前已读的旧消息

功能就写这几个吧,有需要或者想练习的同学可以增加其他功能,例如禁言、匿名消息发送、文件发送等等。

很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,希望能够去帮助到小伙伴们,可以加入一下我创建的圈子【PHP架构师圈子】,里面有我分享的小白进阶到架构师学习资料以及路线

PHP架构师圈子 - 知乎​www.zhihu.com
da31a80389dc7a309a7382affe31882c.png

  Redis实现思路:

   1、群组的消息以及群组的成员组成采用有序集合进行存储。群组消息有序集合的member存储用户发送的json数据消息,score存储唯一值,将采用原子操作incr获取string中的自增长值进行存储;群组成员有序集合的member存储user,score存储非零数字(在这里这个score意义不大,我的例子代码中使用数字1为群主的score,其他的存储为2。当然这使用这个数据还可以扩展别的功能,例如群组中成员等级)可参考下面数据存储结构简图。

  2、用户所加入的群组也是采用有序集合进行存储。其中,member存储群组ID,score存储用户已经获取该群组的最大消息分值(对应群组消息的score值)

  3、用户创建群组的时候,通过原子操作incr从而获取一个唯一ID

  4、用户在群中发送消息时,也是通过原子操作incr获取一个唯一自增长有序ID

  5、在执行incr时,为防止并发导致竞争关系,因此需要进行加锁操作

  6、创建群组方法简要思路,任何一个用户都可以创建群组聊天,在创建的同时,可以选择时是否添加群组成员(参数通过数组的形式)。创建过程将会为这个群组建立一个群组成员有序集合(群组信息有序集合暂时不创建),接着将群主添加进去,再将群ID添加用户所参加的群组有序集合中。

数据存储结构图:

b650888558654f1137944f8f84cdaf6e.png

8ad321b0f10c645d61c4b9711a581e14.png

PHP的代码实现:

#ManyPullMessage.class.php

1 <?php
  2 class ManyPullMessage
  3 {
  4     private $redis='';  #存储redis对象
  5     /**
  6     * @desc 构造函数
  7     * 
  8     * @param $host string | redis主机
  9     * @param $port int    | 端口
 10     */
 11     public function __construct($host,$port=6379)
 12     {
 13         $this->redis=new Redis();
 14         $this->redis->connect($host,$port);
 15     } 
 16 
 17     /**
 18     * @desc 用于创建群组的方法,在创建的同时还可以拉人进群组
 19     * 
 20     * @param $user   string   | 用户名,创建群组的主人
 21     * @param $addUser array   | 其他用户构成的数组
 22     *
 23     * @param $lockName string | 锁的名字,用于获取群组ID的时候用
 24     * @return int 返回群组ID
 25     */
 26     public function createGroupChat($user, $addUser=array(), $lockName='chatIdLock')
 27     {
 28         $identifier=$this->getLock($lockName);  #获取锁
 29         if($identifier)
 30         {
 31             $id=$this->redis->incr('groupChatID');       #获取群组ID
 32             $this->releaseLock($lockName,$identifier);   #释放锁
 33         }
 34         else
 35             return false;
 36         $messageCount=$this->redis->set('countMessage_'.$id, 0);  #初始化这个群组消息计数器
 37         #开启非事务型流水线,一次性将所有redis命令传给redis,减少与redis的连接
 38         $pipe=$this->redis->pipeline();   
 39         $this->redis->zadd('groupChat_'.$id, 1, $user);  #创建群组成员有序集合,并添加群主
 40         #将这个群组添加到user所参加的群组有序集合中
 41         $this->redis->zadd('hasGroupChat_'.$user, 0, $id);  
 42         foreach ($addUser as $v)    #创建群组的同时需要添加的用户成员
 43         {
 44             $this->redis->zadd('groupChat_'.$id, 2, $v);
 45             $this->redis->zadd('hasGroupChat_'.$v, 0, $id);
 46         }
 47         $pipe->exec();
 48         return $id;    #返回群组ID
 49     }
 50 
 51     /**
 52     * @desc 群主主动拉人进群
 53     *
 54     * @param $user       string | 群主名
 55     * @param $groupChatID   int | 群组ID
 56     * @param $addMembers array  | 需要拉进群的用户
 57     *
 58     * @return bool
 59     */
 60     public function addMembers($user, $groupChatID, $addMembers=array())
 61     {
 62         $groupMasterScore=$this->redis->zscore('groupChat_'.$groupChatID, $user);  #将groupChatName的群主取出来
 63         if($groupMasterScore==1)     #判断user是否是群主
 64         {
 65             $pipe=$this->redis->pipeline(); #开启非事务流水线
 66             foreach ($addMembers as $v) 
 67             {
 68                 $this->redis->zadd('groupChat_'.$groupChatID, 2, $v);                 #添加进群
 69                 $this->redis->zadd('hasGroupChat_'.$v, 0, $groupChatID); #添加群名到用户的有序集合中
 70             }
 71             $pipe->exec();
 72             return true;
 73         }
 74         return false;
 75     }
 76 
 77     /**
 78     * @desc 群主删除成员
 79     *
 80     * @param $user       string | 群主名
 81     * @param $groupChatID   int | 群组ID
 82     * @param $delMembers  array | 需要删除的成员名字
 83     *
 84     * @return bool
 85     */
 86     public function delMembers($user, $groupChatID, $delMembers=array())
 87     {
 88         $groupMasterScore=$this->redis->zscore('groupChat_'.$groupChatID, $user); 
 89         if($groupMasterScore==1)     #判断user是否是群主
 90         {
 91             $pipe=$this->redis->pipeline(); #开启非事务流水线
 92             foreach ($delMembers as $v) 
 93             {
 94                 $this->redis->zrem('groupChat_'.$groupChatID, $v);                 
 95                 $this->redis->zrem('hasGroupChat_'.$v, $groupChatID); 
 96             }
 97             $pipe->exec();
 98             return true;
 99         }
100         return false;
101     }
102 
103     /**
104     * @desc 退出群组
105     *
106     * @param $user string     | 用户名
107     * @param $groupChatID int | 群组名
108     */
109     public function quitGroupChat($user, $groupChatID)
110     {
111         $this->redis->zrem('groupChat_'.$groupChatID, $user);
112         $this->redis->zrem('hasGroupChat_'.$user, $groupChatID);
113         return true;
114     }
115 
116     /**
117     * @desc 发送消息
118     *
119     * @param $user string        | 用户名
120     * @param $groupChatID int    | 群组ID
121     * @param $messageArr array   | 包含发送消息的数组
122     * @param $preLockName string | 群消息锁前缀,群消息锁全名为countLock_群ID
123     *
124     * @return bool
125     */
126     public function sendMessage($user, $groupChatID, $messageArr, $preLockName='countLock_')
127     {
128         $memberScore=$this->redis->zscore('groupChat_'.$groupChatID, $user); #成员score
129         if($memberScore)
130         {
131             $identifier=$this->getLock($preLockName.$groupChatID);  #获取锁
132             if($identifier)     #判断获取锁是否成功
133             {
134                 $messageCount=$this->redis->incr('countMessage_'.$groupChatID);
135                 $this->releaseLock($preLockName.$groupChatID,$identifier);  #释放锁
136             }
137             else
138                 return false;
139             $json_message=json_encode($messageArr);
140             $this->redis->zadd('groupChatMessage_'.$groupChatID, $messageCount, $json_message);
141             $count=$this->redis->zcard('groupChatMessage_'.$groupChatID);   #查看信息量大小
142             if($count>5000) #判断数据量有没有达到5000条
143             {   #数据量超5000,则需要清除旧数据
144                 $start=5000-$count;
145                 $this->redis->zremrangebyrank('groupChatMessage_'.$groupChatID, $start, $count);
146             }
147             return true;
148         }
149         return false;
150     }
151 
152     /**
153     * @desc 获取新信息
154     *
155     * @param $user string | 用户名
156     *
157     * @return 成功则放回json数据数组,无新信息返回false
158     */
159     public function getNewMessage($user)
160     {
161         $arrID=$this->redis->zrange('hasGroupChat_'.$user, 0, -1, 'withscores');    #获取用户拥有的群组ID
162         $json_message=array();  #初始化
163         foreach ($arrID as $k => $v)    #遍历循环所有群组,查看是否有新消息
164         {
165             $messageCount=$this->redis->get('countMessage_'.$k);    #群组最大信息分值数
166             if($messageCount>$v)    #判断用户是否存在未读新消息
167             {
168                 $json_message[$k]['message']=$this->redis->zrangebyscore('groupChatMessage_'.$k, $v+1, $messageCount);
169                 $json_message[$k]['count']=count($json_message[$k]['message']);  #统计新消息数量
170                 $this->redis->zadd('hasGroupChat_'.$user, $messageCount, $k);    #更新已获取消息
171             }   
172         }
173         if($json_message)
174             return $json_message;
175         return false;
176     }
177 
178     /**
179     * @desc 分页获取群组信息
180     *
181     * @param $user    string  | 用户名 
182     * @param $groupChatID int | 群组ID
183     * @param $page        int | 第几页
184     * @param $size        int | 每页多少条数据
185     *
186     * @return 成功返回json数据,失败返回false
187     */
188     public function getPartMessage($user, $groupChatID, $page=1, $size=10)
189     {
190         $start=$page*$size-$size;   #开始截取数据位置
191         $stop=$page*$size-1;        #结束截取数据位置
192         $json_message=$this->redis->zrevrange('groupChatMessage_'.$groupChatID, $start, $stop);
193         if($json_message)
194             return $json_message;
195         return false;
196     }
197 
198 
199     /**
200     * @desc 加锁方法
201     *
202     * @param $lockName string | 锁的名字
203     * @param $timeout int | 锁的过期时间
204     *
205     * @return 成功返回identifier/失败返回false
206     */
207     public function getLock($lockName, $timeout=2)
208     {
209         $identifier=uniqid();       #获取唯一标识符
210         $timeout=ceil($timeout);    #确保是整数
211         $end=time()+$timeout;
212         while(time()<$end)          #循环获取锁
213         {
214             /*
215             #这里的set操作可以等同于下面那个if操作,并且可以减少一次与redis通讯
216             if($this->redis->set($lockName, $identifier array('nx', 'ex'=>$timeout)))
217                 return $identifier;
218             */
219             if($this->redis->setnx($lockName, $identifier))    #查看$lockName是否被上锁
220             {
221                 $this->redis->expire($lockName, $timeout);     #为$lockName设置过期时间
222                 return $identifier;                             #返回一维标识符
223             }
224             elseif ($this->redis->ttl($lockName)===-1) 
225             {
226                 $this->redis->expire($lockName, $timeout);     #检测是否有设置过期时间,没有则加上
227             }
228             usleep(0.001);         #停止0.001ms
229         }
230         return false;
231     }
232 
233     /**
234     * @desc 释放锁
235     *
236     * @param $lockName string   | 锁名
237     * @param $identifier string | 锁的唯一值
238     *
239     * @param bool
240     */
241     public function releaseLock($lockName,$identifier)
242     {
243         if($this->redis->get($lockName)==$identifier)   #判断是锁有没有被其他客户端修改
244         { 
245             $this->redis->multi();
246             $this->redis->del($lockName);   #释放锁
247             $this->redis->exec();
248             return true;
249         }
250         else
251         {
252             return false;   #其他客户端修改了锁,不能删除别人的锁
253         }
254     }
255 
256 
257 }
258 
259 ?>

测试:

  1、建立createGroupChat.php(测试创建群组功能)

  执行代码并创建568、569群组(群主为jack)

1 include './ManyPullMessage.class.php';
2 $object=new ManyPullMessage('192.168.95.11');
3 #创建群组
4 $user='jack';
5 $arr=array('jane1','jane2');
6 $a=$object->createGroupChat($user,$arr);
7 echo "<pre>";
8 print_r($a);
9 echo "</pre>";die;

c8a7ae1956c9f5ff88d7d062aa8e116c.png

db9c7fec94a7e5e416a07eecce943004.png

  2、建立addMembers.php(测试添加成员功能)

  执行代码并添加新成员

1 include './ManyPullMessage.class.php';
2 $object=new ManyPullMessage('192.168.95.11');
3 $b=$object->addMembers('jack','568',array('jane1','jane2','jane3','jane4'));
4 echo "<pre>";
5 print_r($b);
6 echo "</pre>";die;

054606e6cfcf9659d669ab30b166bcad.png

  3、建立delete.php(测试群主删除成员功能)

1 include './ManyPullMessage.class.php';
2 $object=new ManyPullMessage('192.168.95.11');
3 #群主删除成员
4 $c=$object->delMembers('jack', '568', array('jane1','jane4'));
5 echo "<pre>";
6 print_r($c);
7 echo "</pre>";die;

9e20beba5be497630e590aa5261c5929.png

4、建立sendMessage.php(测试发送消息功能)

  多执行几遍,568、569都发几条

1 include './ManyPullMessage.class.php';
 2 $object=new ManyPullMessage('192.168.95.11');
 3 #发送消息
 4 $user='jane2';
 5 $message='go go go';
 6 $groupChatID=568;
 7 $arr=array('sender'=>$user, 'message'=>$message, 'time'=>time());
 8 $d=$object->sendMessage($user,$groupChatID,$arr);
 9 echo "<pre>";
10 print_r($d);
11 echo "</pre>";die;

f46ea353020779fdcbf1394ef1a68ac7.png

6b801d847a081c784e2bfa8becc23f9b.png

  5、建立getNewMessage.php(测试用户获取新消息功能)

1 include './ManyPullMessage.class.php';
2 $object=new ManyPullMessage('192.168.95.11');
3 #用户获取新消息
4 $e=$object->getNewMessage('jane2');
5 echo "<pre>";
6 print_r($e);
7 echo "</pre>";die;

d563b1e84244d88a30a6c0a4fc0ed402.png

  6、建立getPartMessage.php(测试用户获取某个群组部分消息)

  (多发送几条消息,用于测试。568中共18条数据)

1 include './ManyPullMessage.class.php';
2 $object=new ManyPullMessage('192.168.95.11');
3 #用户获取某个群组部分消息
4 $f=$object->getPartMessage('jane2', 568, 1, 10); 
5 echo "<pre>";
6 print_r($f);
7 echo "</pre>";die;

page=1,size=10

a39ab8e63e7dc387f3af544dfadddc79.png

page=2,size=10

e3681d527f1e4c86808602c7cf82ccd4.png

测试完毕,还需要别的功能可以自己进行修改添加测试。

以上内容希望帮助到大家,需要更多文章可以关注公众号:PHP从入门到精通,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些PHP高级、架构视频资料和大厂PHP面试PDF免费获取,需要戳这里PHP进阶架构师>>>实战视频、大厂面试文档免费获取

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NetCore是一个开源的、跨平台的、用于构建现代化的互联网应用程序的框架。它具有高性能、高可扩展性和易用性的特点,被广泛应用于Web应用、移动应用和微服务等领域。 RabbitMQ是一个可靠的消息队列系统,它基于开放的AMQP协议,通过提供可靠的消息传递机制,实现了应用程序的解耦和面向服务的架构。RabbitMQ支持广泛的编程语言,并具有安全可靠、高性能的特点,使得它成为构建分布式系统和微服务架构的重要组件。 MongoDB是一个开源的、面向文档的NoSQL数据库。与传统的关系型数据库不同,MongoDB使用了类似JSON的BSON格式来存储数据,支持嵌套文档、动态模式和高度的可伸缩性,具有优异的读写性能和水平扩展能力。它广泛应用于大数据、实时分析和实时存储等场景,成为现代化应用开发中的重要选择。 Redis是一个高性能的键值存储数据库系统,它支持多种数据结构,包括字符串、哈希表、列表、集合、有序集合等。Redis实现了内存存储和快速持久化,具有极高的读写性能和响应速度,被广泛应用于缓存、消息队列、会话存储等场景。它还支持发布/订阅和事务等功能,为构建实时应用和并发系统提供了很好的支持。 综上所述,NetCore、RabbitMQ、MongoDB和Redis都是构建现代化应用程序的重要工具和组件。NetCore作为一个灵活、高性能的应用框架,可以与RabbitMQ、MongoDB和Redis等系统集成,实现分布式架构和高性能的数据存储和处理。通过使用这些组件,开发者可以构建高可扩展、可靠性强、性能优越的Web应用、分布式系统和大数据应用等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值