redis实现的发送订阅系统,即pub-sub,这部分的的代码比较少,也方便分析。在这只将会分析下普通的pub-sub(会忽略掉Pattern-matching subscriptions),以此来简述一个pubsub系统是如何实现的。

在redis主要有介绍redis的pub-sub,在开始之前, 需要知道redis的pubsub的几个命令:

SUBSCRIBE first second //订阅两个channel,分别是first和second

PUBLISH secondHello   //发送方向channel是second的发送"hello"消息

UNSUBSCRIBE      //取消之前订阅的所有channel

下面来看看在redis的Pubsub.c代码中对这些命令的实现。

首先看看subscribe的实现:

voidsubscribeCommand(redisClient *c) {
int j; for (j = 1; j < c->argc; j++)
//客户端订阅多个channel,对于需要订阅的每个channel都执行该函数
pubsubSubscribeChannel(c,c->argv[j]);
} /* Subscribe aclient to a channel. Returns 1 if the operation succeeded, or
* 0 if the client was already subscribed tothat channel. */
intpubsubSubscribeChannel(redisClient *c, robj *channel) {
struct dictEntry *de;
list *clients = NULL;
int retval = 0; //c->pubsub_channels是一个redis自己实现的hash表,这不是我们的重点,就不展开说了
//dictAdd向hash表中增加一个key为channel,value为null的键值对
/* Add the channel to the client ->channels hash table */
if(dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
//从服务端查找当前需要订阅的channel是否已经在服务端登记过
//没有登记过,则创建一个列表作为channel的值,并记录到服务端
//也就是意味着服务端是通过这个列表来得知这个channel是被哪些客户端订阅过了
//登记过,则取出在服务端,channel所对应的客户端列表
/* Add the client to the channel ->list of clients hash table */
de =dictFind(server.pubsub_channels,channel);
if (de == NULL) {
clients = listCreate();
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
clients = dictGetVal(de);
}
//向这个channel所对应的记录客户端的列表的尾部插入当前客户端,
//这样服务端就当前客户端订阅了这个channel
listAddNodeTail(clients,c);
}
/* Notify the client */
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.subscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
} //在看看pub的实现:
voidpublishCommand(redisClient *c) {
//发送则向某个channel发送消息,如 PUBLISH second Hello
//c->args[1]就是channel second,第二个参数就是要发送的消息Hello
int receivers =pubsubPublishMessage(c->argv[1],c->argv[2]);
addReplyLongLong(c,receivers);
} /*Publish a message */
intpubsubPublishMessage(robj *channel, robj *message) {
int receivers = 0;
struct dictEntry *de;
listNode *ln;
listIter li; //从服务端找到订阅了这个channel的客户端列表
//遍历这个列表,将消息发送给每个客户端
/* Send to clients listening for that channel */
de = dictFind(server.pubsub_channels,channel);
if (de) {
list *list = dictGetVal(de);
listNode *ln;
listIter li; listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
redisClient *c = ln->value; addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.messagebulk);
addReplyBulk(c,channel);
addReplyBulk(c,message);
receivers++;
}
}
/* Send to clients listening to matching channels */
发送到模式匹配的订阅方的处理... return receivers;
} //最后看看unsubscribe的处理:
void unsubscribeCommand(redisClient *c) {
if (c->argc == 1) {
//取消这个客户端的所有订阅
pubsubUnsubscribeAllChannels(c,1);
} else {
int j; for (j = 1; j < c->argc; j++)
//取消这个客户端的关于某个channel的订阅
pubsubUnsubscribeChannel(c,c->argv[j],1);
}
}
/* Unsubscribe from all the channels. Return the number of channels the
* client was subscribed from. */
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
//取得这个客户端的所有订阅的channel的迭代器
dictIterator *di =dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
int count = 0;
//通过遍历迭代器来获取在客户端记录的每个channel记录,并对每个记录取消订阅
while((de = dictNext(di)) != NULL) {
robj *channel = dictGetKey(de); count +=pubsubUnsubscribeChannel(c,channel,notify);
}
/* We were subscribed to nothing? Still reply to the client.*/
if (notify && count == 0) {
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.unsubscribebulk);
addReply(c,shared.nullbulk);
addReplyLongLong(c,dictSize(c->pubsub_channels)+
listLength(c->pubsub_patterns));
}
dictReleaseIterator(di);
return count;
} * Unsubscribe a client from a channel. Returns 1 if the operationsucceeded, or
* 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
struct dictEntry *de;
list *clients;
listNode *ln;
int retval = 0; //从客户端记录的hash表中删除这个channel记录,以此来取消客户对这个channel的订阅
//从服务端查找channel所对应客户端列表,从这个列表中删除这个客户端的记录
//这样就完成了取消订阅
/* Remove the channel from the client -> channels hashtable */
incrRefCount(channel); /* channel may be just a pointer tothe same object
we have in the hash tables. Protect it... */
if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
retval = 1;
/* Remove the client from the channel ->clients list hash table */
de = dictFind(server.pubsub_channels,channel);
redisAssertWithInfo(c,NULL,de != NULL);
clients = dictGetVal(de);
ln = listSearchKey(clients,c);
redisAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
if (listLength(clients) == 0) {
/* Free the list and associatedhash entry at all if this was
* the latest client, sothat it will be possible to abuse
* Redis PUBSUB creatingmillions of channels. */
dictDelete(server.pubsub_channels,channel);
}
}
/* Notify the client */
if (notify) {
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.unsubscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,dictSize(c->pubsub_channels)+
listLength(c->pubsub_patterns)); }
decrRefCount(channel); /* it is finally safe to release it*/
return retval;
}

redis-2.6.16源码分析之pub-sub系统的更多相关文章

  1. 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02

    百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

  2. Redis学习——ae事件处理源码分析

    0. 前言 Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理.Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的.即如果有一个事件阻塞过久的话会导致整 ...

  3. 【Redis】事件驱动框架源码分析

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  4. 【Redis】事件驱动框架源码分析(单线程)

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  5. Android源码分析(三)-----系统框架设计思想

    一 : 术在内而道在外 Android系统的精髓在源码之外,而不在源码之内,代码只是一种实现人类思想的工具,仅此而已...... 近来发现很多关于Android文章都是以源码的方向入手分析Androi ...

  6. [Abp vNext 源码分析] - 23. 二进制大对象系统(BLOB)

    一.简介 ABP vNext 在 v 2.9.x 版本当中添加了 BLOB 系统,主要用于存储大型二进制文件.ABP 抽象了一套通用的 BLOB 体系,开发人员在存储或读取二进制文件时,可以忽略具体实 ...

  7. JNI-从jvm源码分析Thread.interrupt的系统级别线程打断原理

    前言 在java编程中,我们经常会调用Thread.sleep()方法使得线程停止运行一段时间,而Thread类中也提供了interrupt方法供我们去主动打断一个线程.那么线程挂起和打断的本质究竟是 ...

  8. 【Redis】事件驱动框架源码分析(多线程)

    IO线程初始化 Redis在6.0版本中引入了多线程,提高IO请求处理效率. 在Redis Server启动函数main(server.c文件)中初始化服务之后,又调用了InitServerLast函 ...

  9. 认识 Redis client-output-buffer-limit 参数与源码分析

    概述 Redis 的 client-output-buffer-limit 可以用来强制断开无法足够快从 redis 服务器端读取数据的客户端.保护机制规则如下: [hard limit] 大小限制, ...

  10. Redis学习之SDS源码分析

    一.SDS的简单介绍 SDS:简单动态字符串(simple dynamic string) 1)SDS是Redis默认的字符表示,比如包含字符串值的键值对都是在底层由SDS实现的 2)SDS用来保存数 ...

随机推荐

  1. java学习第一阶段——面向对象

    你聪明有人会说你心机重, 你靠的是努力有人会说你运气好, 你说自己天生乐观有人会说你虚假, 有时候, 你明明就是一杯白水, 却被人硬生生逼成了满肚子憋屈的碳酸饮料. 人一生要遇见太多人, 即使有些话字 ...

  2. 虚拟器运行iOS8地图提示错误

    /SourceCache/ProtocolBuffer_Sim/ProtocolBuffer-225/Runtime/PBRequester.m:799 server (https://gsp13-c ...

  3. JavaScript 标识符

    JavaScript 标识符 和其他任何编程语言一样,JavaScript 保留了一些标识符为自己所用. JavaScript 同样保留了一些关键字,这些关键字在当前的语言版本中并没有使用,但在以后 ...

  4. MPICH2在两台Ubuntu上安装(用mpd做进程管理)

    本文在经过大量的实验终于不负众望成功的在两台Ubuntu 12.04上部署MPI的一个小型集群,MPICH2所用版本为mpich2-1.4.1,下载地址:http://www.mcs.anl.gov/ ...

  5. phpcms首页调用会员头像及金钱、积分等信息,并按照积分点数排列

    <div class="box"> <h5>最新会员</h5> <div class="col-auto">&l ...

  6. angularJS中如何写服务

    服务的用途 服务提供了一种能在应用的整个生命周期内保持数据的方法,它能够在控制器之间进行通信,并且能保证数据的一致性 服务提供了把特定功能相关联的方法集中在一起的接口 如何创建服务 angularJS ...

  7. sqlserver 进行MD5加密

    官方定义函数: HashBytes ( '<algorithm>', { @input | 'input' } )  <algorithm>::= MD2 | MD4 | MD ...

  8. 学生选课系统数据库SQL语句考试题

    一.            设有一数据库,包括四个表:学生表(Student).课程表(Course).成绩表(Score)以及教师信息表(Teacher).四个表的结构分别如表1-1的表(一)~表( ...

  9. 2014年基于Raspberry Pi的5大项目

    2014年基于Raspberry Pi的5大项目   Raspberry Pi(即树莓派)是一款基于Linux系统(Debian.ArchLinux)的单板机计算机,它只有一张信用卡大小,可用于电子表 ...

  10. LBS 与 GPS 定位之间的区别

    什么是LBS定位?   LBS英文全称为Location Based Services, 它包括两层含义:首先是确定移动设备或用户所在的地理位置:其次是提供与位置相关的各类信息服务.意指与定位相关的各 ...