基于Http协议订阅发布系统设计
基于Http协议订阅发布系统设计
--物联⽹系统架构设计
1,订阅发布(subscriber-publisher)
订阅发布模式最典型的应⽤场景就是消息系统的设计。在消息系统的架构中,消息的发送者称作(publisher),消息的接收者称作(subscriber),参见wikipedia: Publish–subscribe pattern。整个消息系统的架构可以⽤如下图1来描述:
图1
由图1可知消息系统主要包括3个组件: 发布者,订阅者和消息代理(Broker),⽽整个消息系统的核⼼即是Broker,⽽⽬前就业务能⼒⽽⾔Broker的实现难点主要在于它的吞吐量。拿⼿机消息推送举例,在当前的移动互联时代,就我们很常见的⼤多数app⽤户数基本都是百万级别以上(流⾏app基本是千万级别),这意味着Broker⾄少要能⽀持百万台设备的订阅,使⽤单台服务器做Broker显然不能解决问题。⽽在物联⽹时代,订阅者将不再只有⼿机,订阅者可以是任何电⼦设备,这种场景的级别将是⼿机数量的百倍。
2,Mqtt协议的发布订阅系统实现⽅案
2.1,Mqtt协议
根据官⽅的定义,mqtt协议即是machine-to-machine (M2M)的连接协议,该协议就是为发布订阅模式设计的⾮常轻量的消息传输协议。具体参见:
从mqtt协议定义可知,该mqtt就是为发布订阅系统⽽设计,并且⾮常轻量。
2.2,实现⽅案
实现⼀套完整的发布订阅系统,主要就是两个组件(client和broker)⼀个协议规范(mqtt)。
⽬前流⾏的开源mqtt client实现是paho(); 流⾏的开源mqtt broker实现包括 apache apollo 和
2.3,架构设计
发布订阅的服务系统架构⾮常简单,基本都遵照图1的基本架构模式。对于⼀个家庭的物联⽹应⽤,如果设备仅想要在局域⽹内访问,则broker只需要安装在(基于NanoPi或RasPi开发的)⼩型的设备中或者直接集成到路由器中。当然对于真正的物联⽹应⽤,我们还是希望设备可以通过互联⽹就可以管理和控制,所以很多broker实际应当在互联⽹服务器中。
2.4, Mqtt协议的订阅发布系统交互原理
⾸先引⽤⼀下开源项⽬paho提供的python版客户端执⾏订阅和发布动作的demo,代码⾮常简短
1#susbscriber
2import paho.mqtt.client as mqtt
3
4# The callback for when the client receives a CONNACK response from the server.
5def on_connect(client, userdata, rc):
6 client.subscribe("$SYS/#")
7
8# The callback for when a PUBLISH message is received from the server.
9def on_message(client, userdata, msg):
pic+""+str(msg.payload))
11
12 client = mqtt.Client()
_connect = on_connect
_message = on_message
t("", 1883, 60)
16
17# Blocking call that processes network traffic开源mqtt服务器
18 client.loop_forever()
View Code
Subscriber: 从订阅者客户端代码可知,订阅者只需做2个动作(连接broker和建⽴循环等待的长连接)和提供2个接⼝函数(订阅请求函数和处理broker响应结果的函数)。基本要素⽆⾮请求连接、订阅指定topic消息、和处理响应结果,但loop_forever()是⼀个⽆限循环,这意味着客户端和borker之间保持着⼀个socket长连接,所以从这⾥可以认识到broker的瓶颈之⼀便是能处理多少个这样的长连接。
1#publisher
2import paho.mqtt.client as mqtt
3
4 client = mqtt.Client()
t("")
6 client.loop_start()
7 res = mqttc.publish("$SYS/#", "HELLO")
8 client. loop_stop(force=False)
Publisher: 从发布者客户端代码可知,发布者操作⽐订阅者更加简单,基本要素⽆⾮是建⽴连接、向broker发布指定topic消息,忽略结果响应处理过程。
subscriber和publisher的交互逻辑本质是基于tcp协议的socket实现,对于server端的socket打开mqtt协议端⼝,并开启⼀个异步线程来持续监听端⼝,等待client端(subscriber和publisher )的socket发出mqtt请求,client端的subscriber的mqtt请求有些不⼀样,那就是subscriber的socket实际和server⼀直保持长连接,随时等待server那边推送过来的消息,直到连接关闭。所以抛开细节处理问题,完全可以使⽤netty框架,基于mqtt协议很快的开发出⼀套server和client端的应⽤。
3,http协议broker设计实现
图2 订阅发布系统Broker设计
http协议和mqtt协议⽐较:
优点:http在互联⽹时代得到最⼴泛的应⽤,充分检验了它的有效性和稳定性,充分的社区⽀持和成熟的开源资源可⽤
缺陷:相对mqtt协议太重,对⽹络要求更⾼,直接基于http1.x⽆法实现发布订阅(http1.x是单⼯协议,需要依赖websocket、servlet3.0等技术实现双⼯,也可以使⽤http2.0,但⽬前⽀持较少)
本⽂是使⽤servlet3.0的技术实现基于http协议的发布/订阅系统broker, 图2所⽰即为物联⽹broker系统设计架构。后台broker分成两⼤模块:发布中⼼(⽤户和设备)和订阅中⼼(⽤户和设备),以及事件总线。这样的设计或许会有疑惑,为什么不直接抽象成事件的发布和订阅中⼼,如此不久和mqtt broker⼀致了么?的确,既然是使⽤http协议实现,那为什么要完全仿照mqtt协议的模式呢,⽽且我们要设计的实际是⼀个“物联⽹的业务系统“⽽不是⼀个“中间件“,所以如果你换了⼀个业务场景,你⼜得重新设计系统,⽽恰巧基于http协议servlet应⽤正是为业务系统提供了丰富的开源资源。
下⾯详细解释⽤户发布中⼼和订阅中⼼的设计,因为在物联⽹的应⽤场景中,主要业务交互逻辑是围绕⽤户和设备之间做publish和subscribe.
⽤户发布中⼼(publisher):
在物联⽹场景中⽤户充当了核⼼业务的publisher,对于broker的发布中⼼,接收到所有的前端⽤户请
求过来的数据都将被封装成event在broker的内部系统中由发布中⼼⼴播到订阅中⼼。以摩拜单车为例,app是publisher的终端,摩拜单车的核⼼业务逻辑就是开锁指令和⼀系列的交易逻辑。就开锁动作⽽⾔,发布中⼼收到开锁event,在publish这个event之前,针对这个event不同业务场景或许有不同的业务需求,典型场景有:该事件是否需要发、该事件是否需要定时功能,该事件是否需要可靠发布。特别的,对于事件的可靠发布,在交易类系统中属于必备要求。拿摩拜单车来说,开锁指令发出后就会开始计时计费和扣钱,这时候就需要依赖broker在应⽤层⾯对数据做事务保证,⽽不能依赖基础系统服务的稳定。
订阅中⼼(subscriber):
对于订阅中⼼(⽆论是⽤户或设备)的设计,完全遵照table或key-value的数据结构来设计,也即是对于每⼀个请求,broker都将为其关联⼀个handler以及和其对应id标识。当事件被发布到订阅中⼼,订阅中⼼的processor便会⽤事件ID(或唯⼀标识的设备ID)去查询对应的handler,并作结果响应。由于是基于http协议,所以在具体实现时需要依赖servlet3.0或websoket技术。
4,领域建模
4.1 发布中⼼领域建模
发布中的核⼼功能是发布事件,因此Event是发布中⼼的核⼼领域对象。在图2中已经阐明,事件发布所需要实现的基本功能要素,Event 设计也就主要是达到第3部分所描述功能。
图3 发布中⼼领域模型抽象
在图3中可知,AbstractEvent即是Event的顶层的Entity抽象设计。因为发布中⼼可能会发布多种不同类型的Event,所以AbstractEvent必须有EventType属性来表述事件的类型。⽆论是那种类型的Event实际都是⼀个Entity,既然是Entity就意味着有⾃⼰的ID,EventId作为event的唯⼀标识符,需要有⼀个明确的说明的是EventId表⽰意义实际相当于topic,这就是说不是每发布⼀个Event就会⽣成⼀个新的EventId。例如在摩拜单车的应⽤中,就开锁这⼀类事件,对于每⼀辆单车,都有对应⼀个唯⼀的EventId。对于之前第3部分提到的关于事件需要实现周期、延时以及可靠发布,AbstractEvent定义了cronExpression和deliveryStatus属性,其中cron表达式可以⾮常简洁的描述和实现周期和延时的事件设定,⽽deliveryStatus则需要使⽤状态来保证分布式⽹络环境下事件动作的事务。此外,定义GroupEvent是为了解决第3部分中提到的发布⼀个事件,响应多台设备。
4.2 订阅中⼼领域建模
订阅中⼼领域核⼼抽象是Handler,每⼀个handler对应为⼀个订阅http request。每⼀个订阅请求handler都持有其希望响应的EventId,携带的业务数据以及结果响应的回调⽅法。订阅者期望的是当E
ventId标识的event发⽣时,可以⽴刻收到对应的事件响应,也即是说订阅http request是作为⼀个保持长时间等待的⽹络连接。因此所有的handler应当有⼀个holder将其缓存起来管理,这就是单例模式的HandlerHolder 存在的意义。对于HandlerHolder在对handler缓存策略可以有两种选择:1,以table形式缓存;2,以map形式缓存。相较两种缓存策略各有优缺点,table形式节约存储但查代价⾼(可有序存放提⾼速度,实际在使⽤Java HashMap或ConcurrentHashMap实现的保持10^5个连接时,并不会多消耗太多内存,相对于List也仅仅是多⼏⼗M的内存⽽已,因为map中的空桶实际存储量极⼩),map形式查快但耗存储,但⽆论那种形式缓存都可以通过分级缓存来提⾼缓存能⼒(例如⼀级缓存简要数据在系统内存,⼆级缓存主要数据在redis等缓存系统)。
在图4中的状态图描述了(⽤户和设备)订阅中⼼以event驱动的handler转移流程。初始时刻,设备发起订阅CommandEvent请求,等待发布中⼼收到⽤户发过来的CommandEvent请求,此时发布中⼼会去判断该事件是否需要记录事件交付状态,如需要得到设备响应的OKEvent,则会去订阅中⼼⽣成对应handler。此时,响应给设备的handler将携带deliveryStatus=Waiting 标识,等待设备返回确认结果。随后,设备返回的确认响应即可通过发布中⼼发布OKEvent响应⽤户处理结果。(实际处理流程应当更复杂,因为没有考虑异常情况,如设备没有收到响应结果、备响应结果丢失等,这些都需要做⼀些补偿策略)
图4 订阅中⼼领域建模抽象
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论