总体架构
经过深入的讨论和分析, 最后我们认为, 根本原因还是系统设计方案有问题, 也就是说, 技术上是比较弱的.
得到这个根本原因之后, 解决策略就比较明显, 总结一句话就是 "把运维的锅让研发去背", 不要指望你的运气好或者烧香, 提高某某人的运维能力, 而是假设所有的这些问题都有可能发生, 发生之后你的系统是不是有应对的措施和快速的解决方案? 最终我们决定从技术方面来解决这个可靠性的问题.
高可用目标 - 传统方法
确定这个方向之后我们就需要定一个目标, 首先确定一个目标. 高可用其实都是指几个 9,5 个 9 的话可能就是电信级或者金融级的, 互联网大部分是 3 个 9 到 4 个 9.
刚开始也想用这个指标作为一个目标, 比如说想达到 4 个 9 或者 5 个 9, 但是这个指标的优点是业界通用, 搞技术的大家都接受了这个指标.
但是有一个缺点, 除了技术人员, 其他同学不是很好理解, 他们没有办法将 4 个 9 或者 5 个 9 转换成直观的理解. 所以, 我们当时在定项目目标的时候并没有这样去定.
高可用目标 - 面向业务
我们最终确定的目标跟几个 9 的目标有一个比较大的区别, 几个 9 的目标主要是从系统的角度去考虑的, 就是说这个系统的可靠性是几个 9.
而我们的目标是面向整个业务, 这个目标就是 3 分钟来定位问题, 5 分钟能恢复业务, 而且问题的发生频率不能太高, 2 个月发生一次.
这个目标的优点:
1, 聚焦业务.
2, 容易分解. 目标本身就是我们的工作方向, 首先要定位问题, 怎么定位问题? 我们就可以想一个办法, 其次是恢复业务, 第三是故障的频率不能太高;
3, 容易衡量. 我们后来再做方案的时候, 很多方案只要拿这个标准一套, 基本上就能够判断这个方案是否可行.
整个目标最后折算下来, 对应的差不多是 4 个 9 左右, 比 4 个 9 高一点点.
高可用整体架构
整体架构一共分为四层: 用户层, 网络层, 服务层, 运维层.
整个架构其实跟目标是一样的, 我们是面向整个业务的, 没有说哪个系统应该具备几个 9 的高可用, 而是站在整个业务的全流程来看假设要达到目标, 每一个应该怎么去做.
每一层都需要做一些应对的方案, 才能达到目标.
架构解耦
业务分离
下图是原来的架构, 这个系统把所有的功能都包含了, 比如说登录, 注册, 参数下发, 消息, 日志, 更新.
其实对于玩家来玩游戏来说, 真正强相关的只有登录注册和参数下发, 消息和日志, 更新其实并不是玩家玩游戏必须具备或者强相关的.
所以, 业务分离的做法就是把核心业务和非核心业务分拆到不同的系统中, 把两个系统之间通过接口调用, 互相访问.
这样做的好处, 假设非核心业务系统出现故障, 它并不影响核心业务系统, 因为它们之间是通过接口调用的, 并不共享相同的资源.
举一个最简单的例子.
现在因为运营的误操作, 下发了大量的消息, 最后把整个系统冲死了, 玩家登录不了, 游戏玩不了. 拆分之后, 这块系统自己能够完全挂起, 跟玩游戏相关的登录, 注册都不受影响, 他可以继续访问, 只要玩家只要能够登录游戏进行玩, 就不会有太大的问题.
当然, 这个架构对系统功能实现还是有一定要求的, 毕竟有接口访问, 核心系统和非核心系统还是有一定依赖的.
所以, 对于核心业务系统来说, 涉及到非核心业务系统的访问就需要做一定的容错处理. 如果接口调用失败后, 你自己的业务就失败了, 就起不到业务分离的作用.
服务中心
服务中心类似于 DNS, 是实现整个内部系统之间服务调用时候的调度功能, 服务中心是一个类似于服务的名字系统.
比如说有一个 A 业务想访问其他系统提供的业务. 它首先并不是直接访问到另外一个系统, 而是要到服务中心访问一下.
比如说我需要 X 服务, 服务中心告诉 A: 你去访问 Host1+port1 的 xxx 接口. 服务中心有一个配置和状态上报, 可以根据一些状态, 算法, 配置, 选择一个最优秀的服务器告诉 A 业务.
那么, A 业务收到之后就按照这个指示去访问真正提供业务的机器, 比如 B 系统里的 Host1+ port1 这个机器. 服务中心的作用跟 HTTP-DNS 的作用差不多, 就是希望在内部某个系统故障的时候可以快速处理或者切换.
假设 B 系统某台机器有问题了, 我们可以通过自动或者人工的方式在服务中心把这台机器直接下掉, A 业务请求的时候, 它就不会再请求到这个有问题的机器上, 这一台机器的故障就不影响 A 业务.
业务降级
整个系统拆分成核心业务系统和非核心业务系统, 在一些紧急情况下, 比如说非核心业务系统重启也没有办法, 甚至说某个数据库搞挂了, 它又影响业务核心系统.
这个时候, 接口是可以访问的, 但是响应时间特别慢, 核心系统就有点被拖慢.
那么, 在这种比较极端的情况下, 我们可以通过人工的方式下发降级指令, 把这个非核心业务系统的功能给停掉, 这个停掉并不是把程序停掉, 而是说把其中的一个接口或者 url 停掉, 核心系统去访问的时候就得到一个 500 或者 503 错误.
我们做了一个专门的降级系统, 降级系统可以去下发这些降级指令. 一般情况下由降级系统给非核心业务系统下发降级指令, 如果到了一些关键时刻, 其实核心业务系统中有的接口也是可以进行降级的.
也就是说, 我们降级的时候并不是对整个系统或者整个功能进行降级, 我们可以做到接口或者 url 这个级别的降级. 通过牺牲非核心业务系统的功能, 我们尽最大努力地去保证核心业务系统提供的业务.
这个业界有很多叫法, 比如有损服务, 可损服务, 其实我们这个也是一个可损服务, 功能上的可损, 不是流量上的可损.
异地多活
原来的老架构中分两个集群: A 集群, B 集群. 两个集群共用同一个数据库主库, 部署在 A 集群, 所有的写操作都放在 A 集群主库, 由 A 集群主库同步到 A 集群从库, B 集群从库, 各集群的读操作都直接读集群本地的从库.
它的缺点是存在全局单点的问题, 假如说 A 集群主库挂了, 两个集群的业务基本上全部不能提供了.
第二个问题是跨机房同步时延的问题, 从 A 集群同步到 B 集群, 我们是直接通过 MySQL 底层的复制机制去同步的, 在一些比较极端的情况下, 它的时延可能达到半小时甚至一个小时, 这么大的时延的情况下, 提供的业务可能就有问题.
为了解决全局单点, 跨机房同步时延的问题, 我们做了一套新架构, 这个新架构和老架构相比. 最大的特点:
1, 业务层数据同步. 现在有两个主库了, 每个集群读写自己的库. 那么, 两个集群的数据怎么同步呢? 我们其实是通过应用层进行数据同步的, 而没有采用数据库底层的机制读库.
这样做有一个好处, 我们能够尽量通过程序去保证同步的有效性和及时性, 而不依赖于底层, 用程序可以做多种动作.
2, 二次读取. 虽然程序进行同步能够尽最大可能缓解同步时延的问题, 但是在一定极端情况下其实还是有一定时延的问题. 所以, 我们又做了两个集群直接互相读取的功能, 叫二次读取.
也就是说, 有的数据在 A 集群还没有同步到 B 集群, B 集群此时是读取不到的, 这时 B 集群的系统会通过程序的接口再去访问 A 集群的系统, 意思是: A 集群, 你这边有没有这个数据? 如果有的话, A 集群就要返回数据给 B 集群, B 集群再把数据再写入本地.
3, 可重复生成全局唯一数据. 我们的游戏数据中有一个数据是要求全局唯一的, 不管在哪个集群, 不管什么时候生成, 对于同一款游戏, 同一个用户来说, 这一个值都是唯一的.
最开始的时候, 我们是通过 Redis 两个集群分段来实现的, 但是通过 Redis 分段就有一个问题, 假设其中一个集群宕机之后, 另一个集群并不能把原来集群的业务全部接过去, 只是能够保证自己集群的业务没有问题.
我们为了能够解决两个集群互相接管对方的业务, 就做了可重复生成全局唯一数据方案.
来源: http://www.bubuko.com/infodetail-3097873.html