一个基于Thrift与Etcd实现的RPC服务注册与发现、服务集群的架构 (一)

前记

大概一年前,BuyGa.me的后端的第一期架构的弊端逐渐暴露。SteamBot无法进行横向拓展、机器人向Steam的账单轮询没有使用队列,导致多个请求同时访问时会造成CPU占用过高,Steam Ban IP等情况。于是在半年前,我开始对
BuyGa.me的后端进行重构。作为一个福 (shui) 利(wen),在这向大家分享一下我重构时的一些心得。

为什么要做Bot的横向拓展呢,因为以BuyGa.me过往的销售记录来看,每当Steam有促销活动的时候,订单数都会到达一个高峰。这时候就要准备很多的机器人账户应对。在此前的架构中,每次都要人工去增加账户,加上由于每个账户都需要向Steam轮询订单信息,服务器压力增加,服务变得极度不稳定。在这种场景下就需要Bot的横向拓展。

一、初步架构

Web-NodeJS后端

服务后端使用我比较熟悉的Node.JS进行开发,API网关(Web端)由于Node.JS相关组件不够成熟,所以依旧选用PHP来写。

于是自然而然的就想以NodeJS后端暴露RESTful API的形式提供给Web端进行远程调用,这样在开发过程中能省去不少精力。

NodeJS后端

因为后端要做到的一个需求就是Bot能进行方便的横向拓展,于是就想到了一个类似于集群的想法。

这个构想中包括一个服务中心和多个服务节点,服务中心同时充当负载均衡和网关,负责调度各个服务节点、API请求进入后,通过UUID寻找到BOT所在的服务节点。

二、通信组件与存储

1.RPC组件选型

在这个设想中,RPC仅限于Node.JS中相同语言的远程调用,所以不怎么需要考虑该RPC组件的多语言兼容性。笔者选型时比对过bPRC、gRPC、Thrift等。最后选用了我认为的最为简单的Thrift。

其实其他RPC组件一样能实现这个架构,大家不必纠结,这里只提供我的想法,大家选自己熟悉的就行。(鉴于我在Thrift上遇到的不少坑,我推荐gRPC :XD)

#遇到的坑

  1. Apache基金会管理下的Thrift项目,由于各个语言由不同开发人员(组)开发,导致各个语言的SDK比较混乱,有的语言的SDK支持设置某些设置,某些语言就不支持设置。举个例子,在thrift的JS SDK下是无法设置请求的超时时间的,但是PHP下的SDK就能设置。
  2. JS版的Thrift SDK的服务端在Client断开后,只唤起一个事件,但不包括该Client的具体信息,导致需要使用其他手段检测到底是哪个Client断开了。
  3. Thrift不支持双向相互调用

2.服务的注册和发现

因为服务节点需要能做到动态添加和负载均衡,所以每个服务实例都需要向服务中心定时报告自己节点的负载,并且需要能实时、自动地做到服务注册与发现。同时,在笔者构想的整个服务架构中,服务中心和各个子服务的IP、端口等配置会因为docker的原因不断变化。这时候,笔者就考虑引入一个服务发现配置存储组件来存储每个服务节点和服务中心的配置与信息。

这时候,我很自然就想到了k8s所使用的Etcd,并在得知ETCD能实现消息发布与订阅、监控某个值的变化和实现TTL超时,所以就选用了Etcd作为服务发现配置存储组件。当然,Etcd还支持负载均衡、分布式通知与协调、分布式锁、分布式队列、集群监控与Leader竞选等高级特性。但由于笔者的系统的负载并不会这么大、自服务数量不多,所以不需要使用到这些高级特性。

引入Etcd后,笔者将考虑到因为SteamBot是运行在随机分配的服务节点上,所以SteamBot的运行信息、负载等都需要存放在Etcd上以供其他服务实施调取。并且Etcd支持TLL超时、所以我们可以做成一个心跳包的形式、让运行正常的服务不断向Etcd发送更新请求,保持TLL不超时,如果某个节点失效后,其他服务节点就能检测到这个失效节点上的SteamBot超时,就能及时自动的转移这些失效节点上的SteamBot到其他正常节点上运行以恢复服务。

3.持久化存储

在笔者的架构中,每个SteamBot实例中都有需要持久存储的数据、例如SteamBot轮询的Steam订单等。因为轮询数据只用于轮询的状态记录而非重要数据,我一开始是想使用sqlite3并且DB文件放在每个服务节点的容器里,容器销毁后这些数据也会相应的被删除。后来想了下,这些订单数据应该做份保留,所以就重新使用MySQL作为持久化存储的数据库。

为了方便管理,MySQL连接信息也由服务中心统一管理,但由于考虑到安全问题,就不将这些信息放到Etcd上了,而是在服务中心的RPC服务上给一个函数来返回这些信息。

四、Dockerize

因为我们的服务主要是基于NodeJS构建的,所以dockerize(容器化)就很简单了。只需要使用Dockfile配合NodeJS的容器模板就能很简单地实现(下一Part可能会来讲一下)

自此,整个服务架构的大概构型就出来了。

五、写在文末

本文其实早在2月就打算开始撰写,但实际开始写于5月,但是因为暑假回国玩和其他原因又搁置了,一直到现在才写完发出。今年没写啥,感谢大家一直的支持。

0 条评论

昵称

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

与博主谈论人生经验?