JDHBase在京东集团作为线上kv存储,承担了大量在线业务,11.11、6.18 均经历了每天万亿级读写访问请求,目前规模达到7000+节点,存储容量达到了90PB。场景涉及商品订单、评价、用户画像、个性推荐、金融风控、物流、监控等700+业务。
JDHBase上承载了大量核心业务,遍布全球多个Data Center。为了保障业务稳定不间断运行,我们构建了JDHBase集群的异地多活系统。主要介绍在我们在异地多活系统的实践。
HBase Replication原理
HBase是典型的LSM(Log-Structured Merge-Tree)结构数据库,服务端响应客户端写请求过程中,会写入Memstore(内存)中和顺序的写入WAL日志(HDFS文件)中,WAL日志确保数据真正写入磁盘,从而在节点故障时恢复未被持久化的内存数据。
HBase的Replication是基于WAL日志文件的。在主集群中的每个RegionServer上,由ReplicationSource线程来负责推送数据,在备集群的RegionServer上由ReplicationSink线程负责接收数据。ReplicationSource不断读取WAL日志文件的数据,根据Replication的配置做一些过滤,然后通过replicateWALEntry的rpc调用来发送给备集群的RegionServer,备集群的ReplicationSink线程则负责将收到的数据转换为put/delete操作,以batch的形式写入到备集群中。
因为是后台线程异步的读取WAL并复制到备集群,所以这种Replication方式叫做异步Replication,正常情况下备集群收到最新写入数据的延迟在秒级别。
JDHBase异地多活架构
JDHBase服务端与客户端交互主要包含三个组件:Client、JDHBase集群、Fox Manager。
Client启动时首先向Fox Manager端汇报用户信息,Fox Manager进行用户认证后,返回集群连接信息,Clinet收到集群连接信息后,创建集群连接HConnection,从而与Fox Manager指定的集群进行数据交互。
1
Fox Manager配置中心
负责维护用户及JDHBase集群信息,为用户提供配置服务,同时管理员做配置管理。
Policy Server:分布式无状态的服务节点,响应外部请求。数据持久化目前为可选的Mysql或Zookeeper。Policy Server中还包含了一个可选的Rule Engine插件,用于根据规则和集群的状态,自动修改用户配置,如集群连接地址信息、客户端参数等。
Service Center:Admin配置中心的UI界面,供管理员使用。
VIP Load Balance:对外将一组Policy Server提供统一访问地址并提供负载均衡能力。
2
JDHBase Cluster
JDHBase Cluster提供高吞吐的在线OLTP能力。我们对可靠性要求比较高的业务做了异地多活备份。
Active Cluster:正常情况下业务运行在此集群上。数据会异步备份到Standby Cluster,同时保证数据不丢失,但是会有延迟。
Standby Cluster:异常情况下,全部或部分业务会切换到此集群运行。在此集群上运行的业务数据也会异步备份到Active Cluster上。
两个集群间通过Replication备份数据,根据集群ID防止数据回环。主备集群间数据达到最终一致性。
实际生产中,我们根据两个建群间的Replication,构建了多集群间的Replication拓扑,使得集群互为主备。一个集群上会承载多个业务,不同的业务的备份也会散落在不同的集群上,形成多集群间的拓扑结构。
3
Client
Client负责拉取Fox Manager端配置信息,根据配置信息为用户提供接口,与主集群或者备集群进行数据交互,同时将客户端状态上报给Fox Manager端。
集群切换
HBase在读写数据时,需要先经过数据路由,找到数据所在(或应当所在)的节点位置,然后与节点进行数据交互。简单来说包含以下三步:
1、client端访问HBase集群的zookeeper地址,通过访问znode获取集群META表所在位置。
2、访问META表所在节点,查询META表获取数据分片(Region)信息。同时缓存META表数据。
3、根据数据分片信息访问数据所在节点,进行数据交互。
JDHBase在client端数据路由前,多加了一步访问Fox Manager的步骤,这一步骤主要有两个作用:一是进行用户认证;二是获取用户集群信息;三是获取客户端参数。
对集群切换来说,重要的是用户集群信息和客户端参数。Client端拿到具体的集群信息(zk地址),然后进行正常的数据路由,这样业务的client端不需要关心访问哪个集群,Fox Manager端只要保证为client提供的路由集群可用即可。
Fox Manager还会为Client提供一些特殊配置参数,例如重试、超时等,这些配置参数依据两个维度:集群特性和业务属性。这些参数的设置需要结合业务场景和要求长期观察,属于专家经验;也包括一些极端情况下的临时参数。
我们也在client sdk中添加了metrics,用于评估client端视角的服务可用性。基于metrics,我们为一些极度敏感的业务开启客户端切换,当客户端可用率降低生效。
在client sdk中添加的metrics,用于评估client端视角的服务可用性。Client启动后会与Fox Manager建立心跳,一方面通过心跳上报客户端状态以及部分metrics指标到Fox Manager,这些数据能够帮助我们分析服务运行状态;另一方面Client端能够获取Fox Manager端对Client的配置更新。这样,当管理员在Fox Manager为Client更新了集群配置,Client端能够及时感知并重建数据路由。
另外,我们也做了对Client的精准控制。一方面可以使业务的部分Client实例路由到不同集群,另一方面可以作为一些极端情况下单个Client实例强制更新集群信息并切换的备用手段。
自动切换
在有了主备集群切换之后,我们仍面临时效性的问题。故障情况下,我们从监控到异常到报警,到人工介入,最快仍需要分钟级恢复服务可用性。这对一些线上业务来说仍然不可接受。
为了提高服务SLA质量,我们开发了基于策略的主备集群自动切换。可以根据策略在服务异常时,触发切换,将故障恢复时间控制到秒级。
首先我们在HMaster上做了状态检测插件,用于收集一些影响服务可用性的指标信息,heartbeat的方式上报到Fox Manager的PolicyServer中。
PolicyServer 是对外提供查询和修改策略的服务,它所有策略数据会存储在MySQL中。可以通过加节点的方式动态扩展形成一个服务集群,避免单点问题。
PolicyServer中的Rule Engine负责根据HMaster上报的集群状态的指标信息推测执行切换策略。服务可用性对不同指标的敏感度不同,本质上Rule Engine在多个时间窗口上对不同的指标或多个指标的组合执行策略。
Rule Engine不需要高吞吐,重要的是保障可用性,因此基于Raft做了高可用。Active的Rule Engine节点挂掉后,立即会被另外一台节点接管。
动态参数&自动调速
Replication本身是通过RegionServer发送到备机群,而RegionServer本身有大量线程用于客户端请求,Replication Source的线程和负载很难与客户端请求相匹配,在大量写或者有热点的情况下,很容易出现Replication积压。
这个问题我们可以通过调节Replication 参数来缓解这种积压的情况。HBase本身基于观察者模式支持动态参数,更新RegionServer节点参数后,执行update config动作即可生效。我们扩展了动态参数,将Replication的一些参数做成了动态生效的。当Replication积压比较严重时,可以在集群上或者在响应的分组、节点调整参数,不需要重启节点。
虽然Replication动态参数不需要重启RegionServer,但是上线还是比较麻烦的,需要人工参与,并且写热点积压不可预测,依然很难做到Replication平稳顺滑。因此我们进一步在Replication Source端根据当前节点积压的情况(几个阈值),在一定范围内自动调节Replication参数,从而达到自动调速的功能。目前参数自动调节范围在基础参数值的1-2倍之间。
跨机房异地数据中心的之间的带宽是有限的,在业务流量高峰期不能将有限的网络资源用于同步数据。因此在Fox Manager端我们也做了对集群的相应控制,分时段调整Replication速度。
串行Repication
主备集群间的Replication本身是异步的,正常情况下两个集群可以达到最终一致性。但是某些情况下并不能完全保证。
在HBase的Replication中,通过读取每个RegionServer中的WAL将数据变化推到备集群。HBase在zookeeper中维护了一个对WAL文件的队列,因此可以按创建时间顺序读取这些WAL文件。但是当Region发生移动或者RegionServer故障转移,那么Region所在的新的RegionServer上的WAL日志可能会先于老的WAL日志推送到备集群,这种情况下备集群上的数据写入顺序与主集群是不一致的。更极端的情况,如果这种顺序不一致发生在同一条数据上,那么可能会导致数据永久不一致。
举个例子,首先在主集群中执行Put,然后执行Delete来删除它,但是Delete操作先replication到了备集群,而备集群如果在接收Put之前进行了major compact,major compact过程会删除掉delete marker,随后备集群接收到了这条put,那么这条put在备集群上将没有机会再delete,将会一直存在。
解决这个问题需要保证任何情况下,Replciation的顺序与主集群的mutation顺序是一致的,即串行Replication(Serial Replication, backport form v2.1)。例如当Region发生移动从RegionServer1移动到了RegionServer2,那么RegionServer2应当等待RegionServer1将此region的所有数据推送完,再进行推送。
串行Replciation使用Barrier和lastPushedSequenceId来解决这个问题。每当Region发生Open时,都会在meta表中记录一个新的Barrier,这个Barrier为当前Region的最大SequenceId + 1。lastPushedSequenceId为当前region推送到备集群的SequenceId,在Replciation的过程中,每个batch成功,会在Zookeeper中记录最大的SequenceId,即lastPushedSequenceId。
如图所示,一个Region从RegionServer1移动到RegionServer2,又到RegionServer3,发生多次Region Open,记录了多个Barrier,构成多个Range:[ Barrier(n) , Barrier(n+1) )。期间有多个mutation操作记录的SequenceId:s1、s2、s3、……
RegionServer在进行数据Replication前,首先检查lastPushedSequenceId 是否大于自己区间的起始Barrier。例如上图中RegionServer3会首先检查,当lastPushedSequenceId >= Barrier1 – 1时才会进行Replication,而此lastPushedSequenceId = s2,则说明lastPushedSequenceId所在Range的RegionServer2正在进行Replication,那么RegionServer3需要等待。这样就保证了数据抵达备集群的顺序与主集群的写入顺序是相同的。
总结与展望
JDHBase在不断吸收业界异地灾备经验的同时,也经过一系列的实践和演进,目前SLA已经能够达到99.98%,从毫无异地容灾措施到完善的监控、告警、切换、一致性保障机制,为业务提供稳定可靠的存储服务。
同时随着业务的增多和数据量的增大,集群规模也越来越大,仍面临一些挑战,未来我们在异地灾备方面将会着力在同步的Replication、去zookeeper依赖、客户端视角自动切换、降低数据冗余等方面继续提升可靠性及稳定性。