前言
Github传送门
golang在工业中用途最多的方面就是编写中间件以及上游设施,因此切入golang的话,了解其网络库是很有必要的。因此,笔者上手了自带的net包,花了一天左右的时间写了个简单的负载均衡(Load Balancer),也算是能用啦= =
用net包写负载均衡
我们在浏览网页时,通常会遇到服务方故障的情况,提示我们nginx、apache之类的字眼。这些,便都是负载均衡的工作了。
一个负载均衡可能会包含以下的功能:
- 负载调节
- 上游连接分配(策略:轮流(roundrobin)、最少连接等)
- 权重管理
- 健康检查(业务是否OK)
- 协议支持
- tcp、udp
- http、https
- 路由代理
- 数据缓存与压缩
- 安全
- 限流,DDOS防护
- 备用负载均衡
为了实践一下(培养手感),笔者用net包弄了一个基于tcp的简易负载均衡,实现了上述最基础的负载调节与健康检查功能。
首先设计一个struct:
1 | type loadBalancer struct { |
除了用于listen的端口与listener实例之外,用一个map储存每一个服务的信息与状态。虽然粒度有点大,但凑合能用。
每一个服务包含了地址、权重、是否活跃等信息:
1 | type Server struct { |
通过一个配置文件,可以生成一个不accept但listen的负载均衡实例:
1 | // 初始化负载均衡实例 |
其中注意到,启动实例后,会直接开启健康检查任务,这样就能实现上游服务信息的初始化,让我们一开始就能够了解哪些上游服务可用(active)。
1 | // 健康检查——轮询每个服务 |
之后,用户需要调用Run()
,才能真正接受外界连接。
1 | // 启动负载均衡服务 |
在每一个连接的handler中,会新增一个转发器forwarder,采取最少连接数策略连接上游服务,在程序中是选中权重最低的,尽可能active的那一个。由于“连接”是一个阻塞操作,因此我们乐观地认为所有连接都趋向于成功,在连接之前增加权重:
1 | // 处理客户端连接 |
如果连接成功则保持,连接失败则减去权重。成功后,通过forward方法,把用户连接的数据转发给相应的服务,并且也会把服务器write的数据转发给用户:
1 | // 转发客户端数据 |
这样,一个简易的负载均衡就完成了。
总结
上述的负载均衡还存在许多问题,从语言上来讲,比如:
- 并行/并发锁粒度太大
- 可以用一些相对封装较好的实例,如net.Dialer
- 可以利用golang自带channel、context等机制
而从业务上来讲,模块耦合较多,扩展性会差一点。并且真正完备的负载均衡设施,还需要考虑很复杂的功能,这些都需要一个个模块好好设计来实现的。
不论是语言层面还是业务层面都有很多的提升空间。加油吧~