航信机票售卖系统集群部署的演化之路

作者介绍

郭银枫:2013年加入去哪儿网,一直深耕GDS领域。在系统高并发、高可用、高性能积累了较多的实践经验。

一、前言

为了让大家更好的理解本文内容,我们先来介绍几个名词:

  1. agentId:中航信为每个代理人分配的编号。
  2. terminalNo:中航信为代理人分配的一个终端编号,每个代理人可以申请多个 terminalNo 终端,即一个 agentId 有多个 terminalNo;每个 terminalNo 终端和中航信系统保持着 tcp 长连接。工作人员通过 terminalNo 终端输入各种指令,完成机票售卖的全流程。

在国内,所有进行机票售卖的平台、代理商都离不开 terminalNo 系统,这个系统的稳定性十分的重要,支撑着机票最基础、也最核心的功能。下面让我们一起来看一下,qunar 在这个平台的探索之路。

二、单机版的诞生

为了将营业员的手工操作流程搬到线上,支撑业务的快速增长,我们需要对 terminalNo 终端进行服务化。有如下几个问题需要思考:

  1. 请求如何分发,业务上都是按照代理人维度进行配置的,为了和业务保持一致,我们也制定了请求按代理人(agentId)维度进行分发;但由于代理人都有多个 terminalNo,每个 terminalNo 都有一定的流量限制,所以还需要考虑流量平衡问题;否则会由于流量达到上限,terminalNo 就会被停用,影响代理人整体业务的处理能力。
  2. terminalNo 的指令执行都是串行的,指令间都有因果关系,所以要完成一组指令的操作,我们需要对 terminalNo 进行锁定,操作完成后,再进行释放。
  3. terminalNo 管理:如连接、登录、退出、添加、修改、删除等。
  4. terminalNo 的状态管理:如(已连接|连接失败)、(已登录|登录失败),已退出等状态;只有状态在已登录时才能够进行正常的指令操作。

经过综合考虑,为了项目快速上线,我们决定先实现单机版,减少一些开发周期,在后期不断的进行优化和完善。为了技术方案尽量的简单,我们把同一个 agentId 的所有 terminalNo 都部署在一台服务器上,这样请求分发问题、流量平衡问题,terminalNo 锁定问题等都在单个 JVM 内,程序的复杂性就降低了。

low 版本小集群:为了实现小规模的部署,我们实现了一个简单的 client,client 通过轮训方式,检测 agentId 在哪台服务器上,然后再将请求转发到该服务器。
图片
单机版在特定的历史条件下,为了实现业务快速线上自动化,贡献也就非常大的;我们把人工操作实现了机器自动化,大大的提升了机票售卖效率,但其自身的缺点也是十分的明显的,主要有如下两点:

  • 无法集群部署,这种靠轮训方式只能少量部署还可以,随着业务的增长,已经不能满足 qunar 的业务要求。
  • 容灾能力差,当一台服务器 down 掉之后,无法快速恢复,在这台服务器上的代理也就下线了。

三、注册中心版

针对单机版的问题,我们主要从两个方面入手,来进行方案的设计。

1.路由问题,也就是如何知道 agentId 在哪台服务器上,我们进行了如下的方案对比。

  • db 存储 agentId/server 对应关系:该方案实现简单,但由于 terminalNo 服务在机票的各系统中有大量的使用,服务器众多,直接暴露数据库,会造成有大量的连接直连数据库,大量连接的创建会对数据库稳定性有较大的影响,之前出现过这种问题,我们吸取这个经验教训,pass 掉了这个方案。
  • redis 保存 agentId/server 的对应关系

a.服务器启动后,将 agentId/server 的对应关系存储在 redis 上。

b.client 采用定时轮训方式进行数据同步。

c.当服务正常关闭时,删除 redis 对应的 agentId/server 数据。

d.当服务非正常关闭时,只能等 redis 的缓存时间过期了,会造成短时间内的业务失败。

  • zookeeper 作为注册中心:

a.服务器启动后,将 agentId/server 对应关系注册到zk上。

b.发布订阅机制,client 同步 agentId/server 数据,相比 redis 轮训方案,数据同步及时,同时也减少大量的无效查询。

c.当服务关闭或者异常 kill 掉后,zk 能够及时清理掉数据,但 redis 只能等待数据过期了。

通过如上方案的对比,我们最终选定 zookeeper 作为注册中心方案。

2.容灾问题:当服务器出现 down 机、网络故障时,如何快速的进行服务器切换,减少对业务的影响,是十分重要的。为此我们进行了两个方案的对比。

哨兵机制 vs zookeeper 选举:两者都是优秀的故障转移方案。当发生故障时,除了需要对服务器进行切换外,我们的服务还需要特有的处理流程,如 terminalNo 连接、登录等操作。这一点哨兵不能够定制。但 zk 的选举提供了良好的功能支持,当一台服务器被选举成为 leader 后,我们可以定制诸多功能,如数据准备、资源准备、服务器切换通知等。所以我们选择 zookeeper 选举作为我们的容灾方案。

3.系统架构:根据我们上面的调研结果,我们制定了如下的系统架构方案。

  • 引入 zookeeper 作为注册中心,各服务器启动时将 agentId/server 的对应关系在 zk 上注册;在变更时将zk上的信息及时修改。
  • 每组服务由一台主机和一台备机组成,部署在不同的机房,通过 leader 选举决定服务提供者和切换决策。
  • client 监听 agentId 和 server 的对应关系的变更通知,在本地进行缓存。当准备向集群发送请求时,查找本地缓存,获取 agentId 对应的服务器信息,然后直接将请求发送到对应的服务器。

图片

注册中心版解决了我们最关心的集群扩展、请求路由、容灾等关键问题,该版本为 qunar 服务了多年,支撑了 qunar 业务的快速发展,但还是有几个问题需要我们做进一步的提升。

  • 集群有一半的机器是备机,正常情况下是不提供服务的,对资源造成了浪费。
  • 同一个 agentId 必须部署在一台服务器上,当机器出现问题的时候,要切到备机上,这会造成在分钟级别该代理的业务全部失败。
  • 当单个 agentId 有大量的 terminalNo 时,在业务量高峰时,单机性能会出现瓶颈。

四、一致性 hash 版

对于有状态的服务,要想分散部署和处理,一般都会按某种条件进行 hash 运算,将服务分散到集群中的某个节点。为此我们对比了两种技术方案。简单的 hash%N 方案和一致性 hash 算法。

hash%N vs 一致性 hash

  • 服务器变更:hash 算法会引起集群全部的资源进行重新分配;一致性 hash 只会影响变动的节点。
  • 数据倾斜问题:简单的 hash 算法很容易产生数据的倾斜;但一致性hash 只要节点多,就不会出现这个问题。
  • 热点问题:和数据倾斜类似,一致性 hash 只要节点多,就不会出现这个问题。

经过简单的方案对比,一致性 hash 就完胜了。一致性 hash 在分布式系统中应用广泛,最典型的就是 redis 了,redis 利用该算法,防止缓存出现雪崩,数据倾斜都起到了关键的作用,完全符合我们的要求。

下面让我们来看下这次优化几个重点的技术点:

  • terminalNo 和服务器绑定,使用一致性 hash 部署,当服务器变更时,terminalNo 会自动从下一个服务器启动,继续提供服务。其它节点不受影响。虚拟节点数我们没有进行实验,直接使用 double 的的数值。部署示意图如下:

图片

  • 由于同一 agentId 的 terminalNo 被分散到了集群中的不同节点,就需要一个集中存储管理 terminalNo 状态流转的公共设施,我们使用 redis 多队列实现 terminalNo 的状态的流转,这样就能实现 terminalNo 状态的过滤,流量平衡,terminalNo 锁定等功能。示例图如下:

图片

一致 hash 版,在 qunar 已经稳定运行了二年多,在集群的扩展性、容灾能力提升、分散热点数据都有非常不错的表现,主要特点如下:

  • 扩展性:集群做到了可以根据业务情况,做到横向无限扩展。
  • 容灾能力:出现服务器 down 机后,terminalNo 会在几秒钟从下一节点启动,继续对外提供服务。
  • 热点访问:将热点访问均匀分散到集群中的节点,不会造成单机负载过高。

五、总结

本文详细介绍了 qunar 后台团队对于有状态服务部署的探索之路,涉及了热点数据处理、集群扩展性、容灾能力提升等,希望对大家有所帮助。qunar 后台团队会一直关注业界先进的技术方案和实践效果,来优化我们的系统,更好的服务于业务。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYevcMZn' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片