金融网站设计欣赏,在线教育网站建设公司,关于企业网站建设的市场比质比价调查报告,网站建设 中企动力 东莞在上一篇技术短文#xff08;单体架构 IM 系统之核心业务功能实现#xff09;中#xff0c;我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用#xff0c;“信箱模型” 见下图。 客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中#xff0c;然后客户端 B 去自己的 …在上一篇技术短文单体架构 IM 系统之核心业务功能实现中我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用“信箱模型” 见下图。 客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中然后客户端 B 去自己的 “信箱” 中取回自己的 “信件” “信箱模型” 故此得名。
“信箱模型” 中所有动作由客户端触发作为 “信箱” 的服务端被动响应即可 所以 “信箱模型” 实现逻辑非常简单但是对于 “信件” 的实时性不高 客户端 B 想要尽快获取到自己的 “信件”只能周期性地高频请求。我们知道在 IM 系统中所有客户端都在高频请求时无效请求即没有获取到自己的消息是非常高的这对服务器资源和网络资源来说是一种浪费。 在当前单体架构 IM 系统背景下单体架构 IM 系统之架构设计如何进行优化呢
我们知道http 是短连接协议即一次客户端请求和服务端回复后连接就断开了在不更换协议更换协议代价很大的前提下我们对其进行优化服务端接收请求到回复客户端的时间完全是可以控制的。 描述到这里很多同学应该已经非常清楚了即将短连接的 http 访问优化为 http 长轮询方式通过 http 长轮询方式模拟出 “长连接” 的效果。 http 长轮询见下图。 http-client 向 http-server 发出请求http-server 拿到请求后会立刻 hold 住该请求不返回 http-server 返回响应需要满足下面任何一个条件 超过了一定时间比如15秒即超时了该条件减少了无效的 http 请求 在超时之前产生了该 http-client 的数据。该条件提高了消息的实时性
http-client 收到响应后会立刻再次向 http-server 发出请求重复上述过程。
在单体架构 IM 系统的服务端只需改造 http 部分增加一个【http 长轮询】的插件即可大大提高消息的实时性改造成本低见效快 那么 【http 长轮询】插件应该如何实现呢我们分别介绍 “定时器” 和 “时间轮” 两种实现方案。 方案一、 定时器方案
定时器方案非常容易理解即针对每一个 http 客户端当服务端接收到请求后就开启 15秒假设超时时间是 15秒的定时器15秒内若产生了消息则立刻返回否则就等15秒后超时返回。我们基于 Go 语言代码进行描述如下。
chTimeout : make(chan struct{}, 1) //定义 “超时管道”
go func() {time.Sleep(time.Second * POLLING_TIMEOUT) //定时器15秒超时fmt.Printf(INFO | [heartHandler] timeout)chTimeout - struct{}{} //超时后向“超时管道” 中写入元素
}()select {case -chTimeout: //从 “超时管道” 中读数据fmt.Fprint(w, nonthing)case msg : -chPushMsg: //从 “消息管道” 中读数据bs, _ : json.Marshal(msg)fmt.Fprint(w, string(bs))
}
通过 Go 语言代码实现定时器方案非常简单
在 Go 语言中有一个类似于 “IO 多路复用” 的用法即通过 select-case 语句实现对多个管道的监听哪个管道先有了数据就执行哪个 case 语句。
基于此我们定义了两个管道一个是用于 15 秒定时器超时的 “超时管道”一个是传输消息的 “消息管道” “超时管道” 在一个独立的协程中由 “定时器” 控制超时后向 “超时管道” 写入一个元素数据 即 struct{}内容不重要有数据即可表示超时。select-case 语句非常巧妙地帮助我们实现了 要么15秒后超时返回要么15秒内有消息即可返回的选择情况。
我们分析一下这个定时器实现方案服务端需要针对每一次的 http 请求分别启动一个 “定时器””定时器“ 在本质上是一个计算脉冲的计数器达到设定值之后通过软中断方式向 CPU 发起中断请求当 http 客户端并发请求增大之后服务端同时运行的 “定时器” 也会增多于是软中断也增多CPU 会经常性的停下手头工作去处理中断请求CPU的工作效率会大大降低。那么在 http 客户端数量不断增多的时候如何进行优化呢 下面的时间轮方案可以非常优雅地解决这个问题。 方案二、时间轮方案
在时间轮实现方案只需要一个每秒走一格的定时器即可其核心思想是将同一秒内超时的所有客户端进行批量处理见下图。 在该时间轮实现方案中需要准备三个数据结构 一个作为 “时间轮” 的循环队列该时间轮的指针每秒钟走一格走一圈是一个完整的超时周期图中超时时间是 13秒 一个用户维度的 mapuid, 时间轮时间该 map 的 key 是用户 uidvalue 是时间轮指针所指向的时间刻度 一个时间轮时间维度的 map时间轮时间 uid列表该 map 的 key 是时间轮的刻度value 是这个时间刻度时所有发起 http 请求的 uid 列表。
当客户端发出 http 请求到服务端时服务端将用户和当前时间刻度信息分别写入到上述的两个 map 中在 13 秒超时之前如果产生了用户的消息则从上述两个 map 中删除用户和时间刻度信息时间轮当前指针每走一格所指向的时间刻度该时间刻度对应的用户列表就是 13 秒前发出 http 请求的用户列表这些用户就是超时的客户端需要超时返回即返回空的 http 响应。
这样描述可能比较抽象我们举一个例子 假设当前时间轮指针指向了当前时间刻度 2此时 有三个客户端分别是 101、102、103 发出 http 请求到服务端服务端需要在 第一个 map 中分别写入 101, 2 102, 2103, 3在第二个 map 中写入 2, [101, 102, 103] 三秒后时间轮指针指向了当前时间刻度 5此时产生了用户 102 的消息服务端需要先从第一个 map 中删除元素 102, 2同时记录下时间刻度 2方便后续操作再从第二个 map 中删除 102 的记录删除后的map为 2, [101, 103] 九秒后在时间轮指针指向当前刻度 2 时此时第二个 map 中key 是 2 的所有的uid列表即 [101, 103]就是所有超时的客户端列表需要超时返回。
时间轮实现方案通过一个定时器实现了对同一秒内超时的所有客户端的批量处理。 最后对文中关键进行总结
1、基于 http 周期轮询方式的 “信箱模型”消息的实时性不高可优化为 http 长轮询方式通过 http 长轮询模拟出 “长连接” 的效果
2、http 长轮询有两种实现方案定时器方案和时间轮方案
3、Go 语言实现的定时器方案通过 select-case 语句实现了对多个管道的多路复用监听达到了随时产生消息随时返回或超时返回的目的 定时器方案适用于客户端数量较少的情况
4、时间轮方案实现了对同一秒内所有超时客户端的批量处理该方案需要三个数据结构循环队列mapuid时间 map时间 uid列表。 大家思考一下
在该单体架构的 IM 系统中http 短轮询方式优化成 http 长轮询方式后点对点消息发送逻辑需要调整吗
http 短轮询方式的消息发送逻辑参见技术短文单体架构 IM 系统之核心业务功能实现