Golang Go语言中实现的分布式WebSocket微服务 github开源

发布于 1周前 作者 yibo5220 来自 Go语言

Golang Go语言中实现的分布式WebSocket微服务 github开源

使用场景

在实现业务的时候,我们常常有些需求需要系统主动发送消息给客户端,方案有轮询和长连接,但轮询需要不断的创建销毁 http 连接,对客户端、对服务器来说都挺消耗资源的,消息推送也不够实时。这里我们选择了 WebSocket 长连接的方案。

有大量的项目需要服务端主动向客户端推送消息,为了减少重复开发,我们做成了微服务。

使用于服务器需要主动向客户端推送消息、客户端需要实时获取消息的请求。例如聊天、广播消息、多人游戏消息推送、任务执行结果推送等方面。

使用流程

用 Websocket 客户端连接本服务,服务端会返回客户端一个唯一的 client id,通过这个 client id 可以知道是哪个连接,客户端拿到这个 id 之后上报到服务端,服务端根据业务需求可以给这个长连接发送指定信息,或者绑定到分组。

分布式方案

维持大量的长连接对单台服务器的压力也挺大的,这里也就要求该服务需要可以扩容,也就是分布式地扩展。分布式对于可存储的公共资源有一套完整的解决方案,但对于 WebSocket 来说,操作对象就是每一个连接,它是维持在每一个程序中的。每一个连接不能存储起来共享、不能在不同的程序之间共享。所以我能想到的方案是不同程序之间进行通讯。

那么,怎样知道某个连接在哪个应用呢?答案是通过 client id 去判断。那么通过 client id 又是如何知道的呢?有以下几种方案:

  1. 一致性 hash 算法

    一致性 hash 算法是将整个哈希值空间组织成一个虚拟的圆环,在 redis 集群中哈希函数的值空间为 0-2^32-1 ( 32 位无符号整型)。把服务器的 IP 或主机名作为关键字,通过哈希函数计算出相应的值,对应到这个虚拟的圆环空间。我们再通过哈希函数计算 key 的值,得到一个在圆环空间的位置,按顺时针方向找到的第一个节点就是存放该 key 数据的服务器节点。

    在没有节点的增减的时候,可以满足我们的需求,但如果此时一个节点挂掉了或者新增一个机器怎么办?节点挂点之后,会在圆环上删除节点,增加节点则反之。这时候按顺时针方向找的数据就不准确,在某些业务上来说可以接受,但在 WebSocket 微服务上来说,影响范围内的连接会断掉,如果要求没那么高,客户端再进行重连也可以。

  2. hash slot (哈希槽)

    服务器的 IP 或者主机名作为 key,对每个 key 进行计算 CRC16 值,然后对 16384 进行取模,得出一个对应 key 的 hash slot。

    HASH_SLOT = CRC16(key) mod 16384
    

    我们根据节点的数量,给每个节点划分范围,这个范围是 0-16384。hash slot 的重点就在这个虚拟表,key 对应的 hash slot 是永不变的,增减节点就是维护这张虚拟表。

以上两种方案都可以实现需求,但一致性 hash 算法的方案会使部分 key 找到的节点不准确; hash slot 的方案需要维护一张虚拟表,在实现起来需要有一个功能去判断服务器是否挂了,然后修改这张虚拟表,新增节点也一样,在实现起来会遇到很多问题。

然后我采取的方案是,每个连接都保存在本应用,然后用 redis 的 key value 记录每个连接 client id 对应的服务器 IP 和端口。对指定 client id 进行操作时,去 redis 找出响应的 ip 和端口,判断是否为本机,不是本机的话进行 RPC 通讯告诉相应的程序。长连接的连接数据不可迁移,程序挂掉了相应的连接也就挂了,在该程序上的连接也就断开了,这时重连的话会找到另一个可用的程序。

Golang 实现的分布式 WebSocket 微服务

简介

本系统基于 Golang、Redis、RabbitMQ、RPC 实现分布式 WebSocket 微服务,也可以单机部署,单机部署不需要 Redis、RabbitMQ 和 RPC。分布式部署可以支持 nginx 负责均衡、水平扩容部署,程序之间使用 RabbitMQ 广播、RPC 通信。

基本流程为:用 ws 协议连接本服务,得到一个 clientId,由客户端上报这个 clinetId 给服务端,服务端拿到这个 clientId 之后,可以给这个客户端发送信息,绑定这个客户端都分组,给分组发送消息。

目前实现的功能有,给指定客户端发送消息、绑定客户端到分组、给分组里的客户端批量发送消息。适用于长连接的大部分场景,分组可以理解为聊天室,绑定客户端到分组相当于把客户端添加到聊天室,给分组发送信息相当于给聊天室的每个人发送消息。

架构图

单机服务 WebSocket 单机服务架构图

分布式

WebSocket 分布式服务架构图

时序图

单发消息

  1. 客户端发送连接请求,连接请求通过 nginx 负载均衡找到一台 ws 服务器;
  2. ws 服务器响应连接请求,返回 client id,记录到 redis,保持长连接;
  3. 客户端拿到 client id 之后,交给业务系统;
  4. 业务系统拿到 client id 之后,通过 http 发送相关消息,经过 nginx 负载分配到一台 ws 服务器;
  5. 这台 ws 服务器拿到 clinet id 和消息,去 redis 查询到 IP 地址和端口;
  6. 拿到 IP 地址和端口,通过 PRC 协议给指定 ws 程序发送信息;
  7. 该 ws 程序接收到 client id 和信息,给指定的连接发送信息;
  8. 客户端收到信息。

WebSocket 微服务单发时序图 WebSocket 微服务单发时序图

群发消息

  1. 前 3 个步骤跟单发的一样;
  2. 业务系统拿到 client id 之后,通过 http 给指定分组发送消息,经过 nginx 负载分配到一台 ws 服务器;
  3. 这台 ws 服务器拿到分组 ID 和消息,发布给 RabbitMQ ;
  4. 所有订阅到 RabbitMQ 的服务,会收到新信息推送,找到本机所有该分组的连接;
  5. 给所有这些连接发送消息;
  6. 客户端收到信息。

WebSocket 微服务群发消息时序图 WebSocket 微服务群发消息时序图 WebSocket 微服务群发消息时序图

源码

github:https://github.com/woodylan/go-websocket

交流

QQ 群:1028314856


更多关于Golang Go语言中实现的分布式WebSocket微服务 github开源的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

这时候就甚是怀念 erlang 和 elixir

更多关于Golang Go语言中实现的分布式WebSocket微服务 github开源的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


什么暗号?

有一起完善的吗?

在Golang中实现分布式WebSocket微服务是一个既实用又具挑战性的任务。GitHub上有许多开源项目可以作为学习和参考的典范。以下是一些关键点和建议,帮助你更好地理解和实现这一功能:

  1. 选择合适的WebSocket库: Go语言生态中有多个优质的WebSocket库,如gorilla/websocket,它提供了稳定且易用的API,适合构建高性能的WebSocket服务。

  2. 设计分布式架构: 分布式系统需要考虑服务发现、负载均衡、故障转移等机制。可以使用Kubernetes、Docker Swarm等容器编排工具来管理微服务,以及etcd、Consul等服务发现工具。

  3. 消息传递与同步: 为了实现分布式WebSocket服务之间的消息传递和同步,可以考虑使用Redis Pub/Sub、NATS等消息中间件。

  4. 安全性: WebSocket服务需要特别注意安全性,包括使用WSS(WebSocket Secure)进行加密通信,以及实施身份验证和授权机制。

  5. 监控与日志: 分布式系统需要完善的监控和日志系统,以便及时发现和解决问题。Prometheus、Grafana等监控工具,以及ELK Stack等日志系统都是不错的选择。

  6. 开源项目参考: 在GitHub上搜索“Golang WebSocket 微服务”可以找到许多开源项目,这些项目通常包含详细的文档和示例代码,是学习和实践的好资源。

总之,实现分布式WebSocket微服务需要综合考虑多个方面,包括技术选型、架构设计、安全性、监控等。希望这些建议能对你有所帮助。

回到顶部