2015 年 12 月 19 日 ,又拍云架构运维大会深圳站在科兴科学园举办举行,荔枝 FM 架构师刘耀华在会上作题为《异地多活 IDC 机房架构》的分享,以下是演讲实录:
多机房架构存在的原因
单机房死机,断电、维护无法挽回整个数据,想离线读取等都不行。当一个机房不可用,所有的业务都不可用。我们要求业务离用户很近,南方的用户连南方的机房,北方的用户连北方的机房,国外的用户连国外的机房。大陆的网络和国外的网络有一定的隔离性,如果没有做多机房的连通性,数据的传输和实时性就会有问题。
跨机房的作用是为了备份,一个机房的数据放在另一个机房是异地多活。上面是数据容灾,下面的是业务容灾,第三个是让服务离用户更近。这是我们做跨机房的来源。
做过数据备份的同学知道 CAP 理论,2000 年加州伯克利大学认为分布式系统有一致性,所有节点在任何时间都可以访问到新的数据副本;每个请求都能收到一个响应,无论是成功还是失败,必须有服务器的响应,而不是 TCP 超时、TCP 断开等;其中一个区挂了,不影响其他分区。
这三个特性无法共存,只能取两点,牺牲另一点。
AC 模型,可用性+强一致性,牺牲了分区容忍性。比如 MySQL Cluster 集群,内部还是可以用的,MySQL 集群提供两阶段提交事务方式,保证各节点数据强一致性。MySQL 的集群无法忍受脱离集群独立工作,一旦和集群脱离了心跳,节点出问题,导致分布式事务操作到那个节点后,整个就会失败,这是 MySQL 的牺牲。
CP 模型,一致性+分区容忍,牺牲了可用性。Redis 客户端 Hash 和 Twemproxy 集群,各 Redis 节点无共享数据,所以不存在节点间的数据不一致问题。其中节点大了,都会影响整个 Redis 集群的工作。当 Redis 某节点失效后,这个节点里的所有数据都无法访问。如果使用 3.0 Redis Cluster,它有中心管理节点负责做数据路由。
AP 模型,可用性 + 分区容忍性,牺牲了强一致性。我们用 Cassandra 集群时,数据可以访问,数据能备份到各个节点之间,其中一个节点失效的话,数据还是可以出来的。而分布式事务的各个节点更新了提交了只是其中一部分节点,底层继续同步,这是 AP 模型。
AC 是高可用性和高强制性,所有的关系型数据库、PG 等都是强一致性,牺牲了分区容忍性。MongoDB 和 BerkeleyDB 的可用性比较差。AP 模型缺少了强一致性。
互联网行业模型,不同的业务类型要求不同的 CAP 模型,CA 适用于支付、交易、票务等强一致性的行业,宁愿业务不可用,也不能容忍脏数据。互联网业务对于强一致性不高,发个帖子要审核,没人看到无所谓。发一个音频要进行编码审核才能看到。
Base 模型是什么,eBay 工程师提出大规模分布式系统的实践总结,在 ACM 上发表文章提出 Bash 理论是基本可用、软状态和jiao终一致性。我不要求实时一致性,但一定要收到更后一点。
基本可用。分布式系统在故障时允许损失可用性,保证核心业务可用。音频直播或是做活动时,当业务量非常大的时候可以降级。做游戏也是,在战斗的时候更关心数值的增长,看了多少人都无所谓,缓解核心内容的压力。
软状态。允许系统中出现的中间状态,中间状态不会耽误可用性。在写代码、编程业务的设计上,必须容忍有一定的临时数据同步,考虑到全局锁和数据多版本的对比,把各个节点的相关数据都上锁,这是一个悲观锁,一旦写任务,其他人都能改我的数据,这是比较悲观的心态。
而数据多版本,类似于乐观锁,导致其他人和我数据冲突的机会并不是那么多,只要我提交的时候发现版本不一样,更新一下,汇总数据就可以了。做好业务上的隔离,多数情况都属于多版本,技术都能解决,不一定要把所有的东西都锁死,我允许有一定的临时数据。jiao终一致性,在临时上的数据不一样,数据同步也是要花时间的。随着时间的迁移,不同节点的数据总是向同一个方向有一个相同的变化,这是 Bash 模型。这种模型非常适合互联网业务的发展。
数据一致性模型,允许窗口期数据不一致,互相关联的数据要同步。序列一致性,全局按照序列顺序来做。线性一致性,每一个时间的时钟要同步,时间序列是严格的,按顺序的。然后是强一致性,一个时间只能实行一个任务。这比较适合于互联网,所以我们选择了执行。
系统业务调研
简单介绍荔枝 FM 的系统架构,客户端接入代理,往上到我们的服务器,服务器专门对手机进行业务处理,后面是数据中心,数据中心后面是存储,有 Redis、MySQL 和 Memcached,这是一个简要的结构。变成一个分层的结构,分层结构和大家的业务差不多,首先是接入层,接入层负责连接管理、Socket 代理、Nginx,Socket 代理专门对连接状态进行管理。业务层,比如手机服务、网站服务、后台以及各种各样的业务层。下面有服务总线,接下来是数据层,专门对数据逻辑进行管理。底层的是存储层,比如有数据库、Bash、Memcached、Redis。
业务数据分析,我们要跨机房同步,一是资源文件,向用户上传的音频、图象、头像、图片,数据量非常大。数据量一旦不同步,会对用户的感知比较强,比如我发表评论,发表完就没了,用户会很紧张,不停的刷,是软件问题还是网络问题,优先级非常高。我们用了机房专线,它的故障率比较低,响应时间也比较低。后备限制公网,用两种策略进行传输。
架构设计
我们有两大 IDC 机房,一是绿色的机房专线,这是高速通道,二是红色的公网,类似于国道,比较便宜,这里有两个机房,为了离用户更近,我们用了智能 DNS 看用户的机房。
系统架构,这两个业务数据之间怎么进行管理,我们设置了一个主机房,Master 和 Slave,所有的业务都是读自己机房内部的,都会往 Master 方向写,然后同步在同一个机房。我们知道读业务比写业务高几个数量级。
主机房在北京,北方的用户直接写在自己的本机,然后回应。南方会把请求发到北京机房,异步传输回来同步到南方的机房里。
机房内部怎么做?我们提供了数据访问层 API,封装了下面所有同步,程序员和开发人员不用担心,下面都有 Data Store 接管。代理服务器可以根据你的类型往本地的存储性能去读。它会区分你写、数据库写还是好友写,分发到不同的 DS 服务端,处理完告诉你 OK 了。另一步进行分发,到另一个机房同步,是读写分离的系统架构。
接入层、客户端,通过代理往 DS 服务端写数据,资源分发同步。失效怎么办呢?左边的 Master 是主的,由 DS 代理进行检测,如果没响应,马上切换到另一个机房,穿到另一个机房里,把数据写到 Slave,等到这个机房的数据恢复后,再手动切换回来,这是主失效。重失效,我们会缓存起来放在文件里,等恢复后,再推送数据缓存过去。
麻烦的是老链问题,两头是活的,中间链断了,南北之间 IDC 链路,不管是专线还是公网都有可能会抽风,心跳检测出问题后,两边会独立写。DS 代理认为这边服挂了,Slave 认为主挂了,直接把数据往这边写。当线路恢复时,两边同步数据,要尽量避免数据冲突,这时候系统监控到底是哪一个链路出问题,果断关闭,减少数据冲突的风险。
由于配置服务器把所有配置发放到服务器里,监控、报警、数据报告会通过邮件、微信的手段告诉运维人员和开发人员,运维数据中心把所有的日志导入到数据库,更方便我们进行分析。
实践
把坑总结出来,一是数据切分,当数据量到达一定的情况,一定会出现分片,分片有两种,一是用户库、电台库、数据库;二是 ID,尾数 1 是 1,尾数 2 是 2 。
我们认为可以先纵切,一来对开发来说比较简单,带来的改观比较大,对压力的改善也比较大,纵切横切。当业务分得不能再分,我们就要进行横切,把所有 ID 进行 Hash 拆分。
数据只改不删。尽管我们开了 MySQL 的 Lock,里面没有任何的办法手动回滚。用户投诉一个月内有效。异步,能提高系统的响应,改善系统的使用效率,避免现场挂死,但会增加一定的编程复杂度,我们有一套比较简单的异步接口,进行异步操作。
所有的外部 RO 操作都有可能,业务量大的时候连写日志都会导致业务受影响,任何 IO 操作都要考虑到慢的可能性。复杂的逻辑处理也是异步,比如登录,我们要访问第三方平台的数据,和第三方进行较验、检查。
提高程序的可测试性,要有测试驱动,出问题后马上跑测试驱动,发现业务问题还是数据受影响了。写好代码提交后,会不会对旧的有影响。运行日志统一收集,分布式系统的业务可能会分发到各个不同的节点,把所有的数据拿出来统一监控。可以马上知道响应,而不是人肉去看。
持续逻辑对一致性可容忍,如 insert 之后马上 select 刚插入的记录,由于读写分离,有可能是无法读取刚生成的记录。中间没有时间允许重复数据,这就没有数据出来,所以不能用这种方式去做。
幂等操作,分布式系统有三种状态,成功、失败和超时,可以就可以,不行就不行,超时非常难搞,可能是请求处理了,但没有给你响应,也有可能根本没处理,调用者根本不知道是哪种情况。如果操作不幂等,客户端会出现数据失败和冲突。每次同一个操作都不会影响数据,都会产生同一个结果。同一个 ID 记录,用 Replace 或是 insert 把新的技术代替原来的技术。这不能一概而论,得看场景,它的效率比较低。
内网里感觉不到,但在两个 IDC 之间有网络延迟,对比很明显。要先拉一个请求,发送端发一个回应。推,不管你要不要,我直接发给你。我们测试过北京和广东之间的正常值都有 40 毫秒,加一个请求的话是 80 毫秒,这种效率不能容忍。推的问题是我推给你,你不一定能接受,接收只是写日志,执行由另一个来做,即使接收了还要记录,下一次再连上来,告诉发送者再次接收。
监控很重要,由于链路、老链问题,CPU、内存、磁盘、网络、文件描述服务,读写 IO 位置,任何一个环节出问题都会导致系统卡,每发现一个卡的节点就要增加一个监控,每查处一个就加一个监控。
业务进程监控,所有的错误与警告日志、内存泄露、命令响应速度,慢的比例比较大的时候就要报警了。
存储系统监控,像数据库、缓存的操作量、并发量、慢查询。服务监控包括消息拨测、网络拨测。
实时报警,邮件、IM、短信,我就是让你收到。报告,日报、周报、月报、分时曲线、峰值,高水位和低水位,这影响什么时候扩容,什么时候增加业务量。运营和产品增加了某个活动,程序员不知道,当沟通出现问题的时候,业务上有什么峰值的活动,技术这边可以马上知道,而不是靠业务人员口头通知。
业务架构易于随时扩展和缩减,遇到瓶颈时,能迅速扩容,加一台机器,对上层业务透明,加机器或节点必须增加配置或是改配置,配置完了重启激活,这与程序员无关。强硬的中断和重启,对大量的业务有非常大的损坏。做游戏时,量不是用钱买回来的。老板跟我说失效一分钟,几十万量钱都没了。一件事操作预案,点一下就行了,让每一步胆战心惊,错一个字母多一个空格会出现不可估量的后果。
架构师是一个产品,用户是程序员,产品为用户服务。我们封装好的 API 一定要简单易用,而不是注册、初始化、挂监听,一个 API 简单明了。与业务解耦,业务和我们的框架剥离,我们会根据业务优化,但尽量不要让业务关心我们的架构变动,减少依赖。配置简单,约定优于配置,引入这个框架要配置一大堆东西,我不知道这个配置有什么用,我不敢配,我有一个默认配置给你,只要直接引用包就能用,至于你要个性化的属性或操作,可以看文档和接口。这样能更好的为业务灵活变动,让程序员更快的业务迭代去开发。