千万级并发分布式KV存储系统设计实现和运营

设计背景

整体架构


如上图所示,即为我们的业务整体系统架构,在我们的整体架构中,VDE(VIP Data Engine)分布式KV存储系统是作为最核心的底层基础存储服务存在的,在整体架构中为周边各类系统提供数据解耦合、数据共享的功能,大大简化了业务的整体架构,但同时,由于VDE分布式KV存储系统在架构中的基础和核心地位,系统的可用性、实时性、可靠性和稳定性就极为的重要了。

推荐业务对底层分布式KV存储系统的要求:

千万级的并发量,10ms响应超时,持久化存储。
支持动态扩容、缩容、自动容错恢复。
高性能,减少资源占用,节省成本。
架构优秀,维护简单方便。

redis-twemproxy

redis-twemproxy为Twitter开源、应用范围最广、稳定性最高、最久经考验的分布式中间件,但是在我们的业务场景中, 对系统高并发、实时性、可扩展性要求非常高,redis-twemproxy的应用就存在着一些制约业务发展的问题:

无法动态扩容、缩容,大促时变更复杂,且业务需要同步变更,变更成本太大。
存储路径长、涉及组件多,机器资源占用比较多,同时,也加大了出问题风险, 历史上已经发生了若干由于底层数据库异常而导致数百万损失的问题。
无法自动容错,需要较多人工介入恢复,运维不够友好。

tair

tair是淘宝开源的一套分布式kv缓存系统,支持动态扩容、缩容、动态容错恢复。

业务场景

  1. 数据主要是key-value结构,value的数据为pb结构或简单字符串,主要为put、get、remove操作。
  2. 数据记录会不断覆盖更新,不要求强一致性,能够容忍特殊场景下极少量数据的语义不一致。

    推荐业务的本质是从低价值的海量数据中进行各种分析计算,得到相对更有价值的数据,然后基于这些数据进行个性化的排序推荐,为用户提供差异化的服务体验,因此,极其少量的数据在系统极端异常场景下出现数据语义不一致并不会对整体效果产生影响,这对于我们在系统设计时,在一致性、高可用、可靠性及成本等方面合理取舍有重要的考量作用。

  3. 数据也会有并发修改更新的需求,需要确保并发修改更新场景下的数据一致性。

    我们会为数据带上版本号,业务先get数据,修改后再更新的场景下,系统只会处理第一个更新请求,后续的基于同一版本数据的修改操作均会失败。

  4. 数据读写需要非常高的实时性,最坏情况下只能接受小于0.5%的10ms超时率,同时,业务峰值并发为每秒数百万笔读写请求,未来还可能会持续增长,系统设计需要考虑支持千万级QPS并发能力。
  5. 数据需要支持持久化存储,避免集群异常情况下的大量数据丢失,从而导致集群不可用。

分布式KV存储系统设计实现

我们选择借鉴tair系统架构和和部分代码,同时,针对我们的业务场景,自主设计实现了一套VDE分布式KV存储系统,系统以高并发、低延迟、可扩展为主要设计目标,多个副本间采用异步复制策略,数据保证最终一致性。同时,我们在系统设计和实现的过程中,也会在收益与成本之间进行合理评估和取舍,如同二八原则所描述,有时候80%的成本,只能产生20%的收益。

系统架构及设计

系统服务模块组成

ConfigServer

负责集群所有DataServer节点信息、数据路由表等元数据的管理,在检测到下面的事件发生后,ConfigServer会重建路由表,同时会将变更后的路由表下发给DataServer跟Client,引起数据迁移跟数据读写重定位:
1. group配置文件更新。
2. data server状态发生变化(比如原来活着变为死掉,或者死掉变活)。

DataServer

负责集群实际的数据存储跟数据读写功能。
1. 与Config server进行心跳交互,上报自己的心跳和状态统计数据。
2. 数据更新时,先更新master dataserver节点,成功后立即返回客户端,然后在后台异步发送到所有的slave dataserver节点。
3. 集群状态变化引起路由表重建和数据迁移后,由master dataserver节点负责按桶迁移数据到所有的目标节点。

Client

内部封装了与集群的交互过程,包括路由表拉取、更新、数据定位、数据读写等功能,为上层业务提供一组完全透明跟友好的api接口,支持C/C++、Java、Python的api接口。
1. 初始化时访问config server拉取路由表,同时,会将路由表信息缓存到本地内存。
2. 接口内部会根据路由表自动计算,定位到对应的dataserver节点进行实际的数据读写操作。
3. 检测是否需要更新路由表(判断错误码是否为DataServer不work、不是在master dataserver节点读写),如果发现需要更新路由表,则去config server拉取最新路由表并进行更新。

集群路由表


稳定状态下,ConfigServer、DataServer均保存有3张路由表:快表、迁移表、目标表,
快表

顾名思义,快表即为用于快速恢复读写的路由表,当集群状态变化时,会快速得到一张路由表返回给客户端恢复读写操作。以如下路由表中A节点下线为例,ConfigServer会把对应bucket的节点重新组织,如果下线的节点为master节点,则会在该桶剩余其他的node中选择承载bucket最少的node提升为master接管数据读写操作。如果下线的为slave节点,则直接删除对应bucket中节点列表中该下线节点即可。

迁移表

迁移表在集群稳定状态下跟快表、目标路由表相同,在集群状态变化时,dataserver会根据快表和目标表决定自己要迁移哪些bucket到哪些节点,当dataserver完成某个bucket的迁移后,会上报到config server,config server会生成最新的迁移状态表,其实,迁移表就是快表到目的对照表的过度状态表,刚开始与快表相同,当dataserver迁移完所有bucket后,迁移表最终会与目的路由表一样。

目标路由表

在集群稳定状态下,快表、迁移表、目标路由表完全一样,当集群状态变化时,configserver首先会根据集群负载均衡策略构建最新的目标路由表,该目标路由表即为集群再次达到稳定后的状态。

数据读写


上图所示即为我们系统的数据读写流程,在刚开始或者集群状态有变更时,我们会去ConfigServer拉取最新的快表并缓存在自己的内存里,如果后续集群状态正常,客户端会直接根据内存里的快表进行hash计算,映射到一个bucket上,然后到这个bucket对应的master node上进行读写。

在我们实际的线上运营过程中,我们读写都是只针对master node,我们做了如下考虑和设计:

  1. 尽可能为业务保证数据的读写一致性,如果读取slave node,则对于实时并发更新的业务而言,容易拿到slave node的一些old的数据进行更新,但是基于读取的old数据更新后提交到master node会失败,进而增加了业务的失败率。
  2. 我们在构建路由表的时候,其实就已经考虑到了master node的负载均衡,master node的读写负载跟master node承载的bucket有关,我们构建路由表的策略之一就是使master node承载的bucket尽量均衡。

对于ConfigServer的可用性设计,我们考虑并采用了典型的master + slave模式,master支持元数据读写操作,slave只支持元数据读取操作,我们做了如下考虑和设计:

  1. 主备ConfigServer之间的元数据同步,在一些异常场景下无法保证主备ConfigServer元数据的强一致性,比如master更新完了元数据,在同步给slave之前挂了,如果直接进行主备切换,则有可能会出现集群状态异常,甚至无法恢复的严重问题,因为元数据是集群极为重要的核心数据。
  2. 我们的集群一旦达到稳定状态,集群的元数据就几乎不会变更,绝大部分情况下是元数据读操作,因此,可用性主要是要保证可读性,且客户端会缓存元数据,ConfigServer即使短暂的不可用也不影响服务。典型的master + slave机制可以以极低的代价保证我们ConfigServer的可用性,而实际在我们线上近2年的运营过程中,该方案完全满足了我们对ConfigServer可用性的要求。
  3. 当然,我们采用master + slave机制非常依赖于我们及时发现和恢复master ConfigServer节点,否则,一旦master节点挂掉我们没有及时恢复,期间如果有dataserver下线,那么集群将会无法达到稳定状态,并且会持续对业务产生影响,比如如果集群有400个节点,每个节点承载1024/400=2.56个bucket,挂掉一个节点,那么大概会有2.56/1024的概率会读写失败。如果存在master configserver,则会立马重建路由表(快表)恢复读写,然后启动数据迁移工作,如果master Configserver挂掉没有恢复,那么影响会持续。基于以上考虑,支持configserver的自动主备切还是有必要的,在我们的后续版本中已规划采用外部组件zookeeper来存储我们的元数据,以保证我们元数据的强一致性,而不用自己去重新设计实现一套raft或paxos的强一致性协议。

对于数据的持久化和恢复策略,我们做了如下考虑和设计:

  1. mdb存储引擎本身支持内存映射文件存储(文件存储在tmpfs内存文件系统),当节点挂掉、机器未挂掉的情况下,可以直接从内存映射文件中进行恢复。
  2. 在正常状态下,如果挂掉的节点数少于副本数,则完全可借助于数据迁移机制完成副本数据的恢复,但是,还存在一种极端的异常场景,bucket对应的副本节点所在的机器全部挂掉,则后面即使机器重启,内存映射文件会被删除,对于这种极端场景,考虑到对系统性能影响,我们会将数据操作在后台异步的记录到binlog文件,同时会定期的进行checkpoint,当机器挂掉重启后,启动副本节点时,可以通过checkpoint + binlog文件进行恢复,当然,可能由于异步写binlog的机制在节点异常时可能会丢失极少量数据,但是一方面我们讨论的这种一个bucket对应三个副本全部挂掉的这种极端异常场景极少发生,另一方面,如果发生了这种极端异常场景,这种极少量数据丢失的损失是我们优先考虑性能收益的设计所能够预期的。

数据迁移

数据迁移流程
1. dataserver(ds)定期向configserver(cs)上报心跳消息,cs判断是否有集群状态变更,比如是否有dataserver节点上线或下线。
2. cs如果检测到集群有状态变更,比如发现超时时间内节点c还没有给cs发送心跳消息,则认为节点c已下线,此时,cs会重建路由表,得到最新的快表、迁移表、目标表。同时更新版本号。
3. ds定期向cs上报心跳消息,心跳消息中会携带自己的版本号,cs收到心跳消息后,会判断ds的版本号跟自己的版本号是否一致,如果不一致,则下发cs中最新的路由表给ds。
4. ds接收到最新的路由表后,会计算得到自己要迁移的bucket和迁移的目标ds,然后,ds开始逐个bucket进行迁移,将ds上该bucket承载的数据迁移到目标ds,单个bucket迁移完成后,会向cs发送bucket迁移完成消息。
5. cs收到ds的bucket迁移完成消息后,会更新cs中的迁移表。同时,定期检查是否所有节点的待迁移bucket均已迁移完成,如果已迁移完成,则将快表、迁移表、目标表全置为相同,并且,更新版本号,ds下一次心跳消息过来后会判断并发送最新的路由表给ds,至此,迁移流程结束,集群最终达到稳定状态。

动态扩容

proxy机制
这里要注意的是ds是逐个bucket进行迁移的,期间cs收到单个bucket迁移完成消息,会修改迁移表,但不会修改快表,客户端依然还是用的原来的快表操作。当桶3的数据被从C迁移到D后,请求依然会落到节点C,而C迁移完成后已经不承载桶3的数据读写,如果不对这种情况做处理,则客户端会出现较高的失败率,针对此种场景,我们会让ds进行proxy,会将C上对桶3的操作proxy到节点D。

动态缩容

动态缩容过程中,proxy机制同样会生效,当master节点上的bucket迁移到其他节点时,迁移完成前对该bucket的后续请求依然会落到该master节点,master节点会proxy到其他节点。

负载均衡和数据安全的思考

集群的负载分布依赖于集群的路由表构建策略,集群的路由表是通过cs构建的,cs构建路由表的时机:
1. group文件发生变化,比如修改了group相关配置。
2. 集群状态发生变化,比如有节点上线或下线。

集群路由表与集群的数据分布和读写负载分布对应,因此,要让集群的数据分布和读写负载均衡,路由表构建的过程就非常重要了,如下为三副本下我们的系统典型的路由表结构:

以3副本、5节点、1023个bucket的集群配置为例,我们对于我们的系统的负载均衡,在路由表构建时的设计和实现过程中,主要考虑了如下的几个方面的问题:

  1. 每个作为master的节点平均需要承载1023/5=204个master bucket,同时,每个节点平均需要承载3*1023/5=613个bucket,每个master节点在平均需要承载的master bucket数量的基础上,额外最多可承载的bucket数为1023%5=3,同时,每个节点在平均承载的bucket数基础上,额外最多可承载的bucket数为3*1023%5=4,同时,会考虑将多余的bucket平均分布在不同的节点上。
  2. 重建路由表的过程中,还会最终做一次均衡bucket的操作,尽量把所有bucket平均分布到所有节点上,把所有承载master bucket数跟总bucket数多于平均值的节点上的多余的bucket数迁移到承载到master bucket跟总bucket数少于平均值的节点上。
  3. 如果系统在重建路由表的过程中,如果发现某个bucket对应的3个副本节点全部挂掉了,那么我们不会对该bucket重新选择副本节点,因为,如果我们重新选择了节点,那么这个bucket对应的数据就无法定位到了,数据就丢失了,我们不希望这种场景下我们的数据丢失,因此,我们会跳过这种场景,直到这个bucket对应的3个副本节点有一个节点成功启动且已经恢复数据后,我们才会对这个bucket重新选择缺失的几个副本,之后选择的几个副本会通过数据迁移机制同步到全量的数据。
  4. 关于数据安全性的考量,同一个bucket对应的3个副本,必须要分布在不同的机器上,否则,数据的安全性和可靠性无法保证,这里,我们暂时没有考虑跨机房的数据分布,我们的集群都是部署在同一个机房内的机器,因此,在这里我们只考虑了同机房的不同机器间的数据安全策略。

数据一致性的考虑和设计


关于数据的一致性,我们系统保证的是最终一致性,数据写入时采用异步复制的策略,对于数据写入的的异步复制策略,我们做了如下考虑和设计:

  1. 为了尽可能提高系统的并发性,我们的主备复制采用的是异步复制策略,数据一旦成功写入master的dataserver节点,立即返回客户端成功,同时,master dataserver会将同步请求扔到队列,由后台线程从对列获取同步请求并异步的发送给slave的dataserver,同时,如果异步发送过程中发现类似发送失败、响应超时的同步请求,由于kv操作时幂等的操作,我们可以对这种超时的同步请求进行重试,保证数据最终最终达成一致的。

    在上面超时重试的逻辑中,我们还需要解决数据的时序性问题,让我们考虑这样一种场景:
    key1第一次发送了同步请求给slave ds,还在超时时间内等待异步响应的时候,key1第二次又发送了同步请求给slave ds,如果第二次同步成功,第一次同步失败,则经过超时时间后,我们会将key1的第一次同步请求进行重试,则会将最新的第二次同步请求覆盖,从而导致两者数据不一致。
    对于上述的问题,我们在异步发送会在定时器中查找相同的key,取消比自己old的相同的key的请求的超时定时器。

  2. 另一方面,异步的主备复制策略会导致一些极端场景下数据语义不一致的问题,比如,master dataserver更新完本地后,返回了客户端成功,然后未来得及发送给slave的dataserver就挂掉了,那么后面客户端就拿不到这条数据了,对于系统而言,这条记录是操作失败的,但是对客户端而言,操作是成功的,这就产生了语义的不一致,对于这个问题,我们做了如下的考虑和设计:

    1. 我们系统的主要设计目标是高性能及高并发,我们采用最终一致性的异步复制策略,是综合考虑了我们的系统成本、复杂性和可靠性以及业务场景等多方面因素的结果,采用异步复制的策略,让我们大大简化了系统的设计实现,同时,尽可能提高了我们系统的并发能力,如果采用raft、paxos等强一致性协议,虽然保证了数据强一致性,但必然会带来系统并发能力,相应的增加了系统成本。
    2. 虽然如此,但是我们也需要考虑异步复制的场景所带来的问题,同时尽力去降低问题带来的影响,因此,为了尽可能保证业务数据的一致性,我们在读写策略上也做了一些限制,业务只会读写master节点的数据,同时,我们采用同机房部署,尽量减少master写入数据并返回成功情况下,slave未同步到数据的极端异常场景的影响,因为只有这种情况才有可能出现数据的语义不一致场景。在我们实际的近2年的线上运营过程中,我们完全保证了极端异常场景下我们数据的可靠性达到个9个9 ~10个9以上。

    1. 在上面的异常场景中,master节点写入成功,并将数据扔到后台队列中,此时,master挂掉,如果后台队列中的数据未发送到其他副本,我们认为此时会产生会将的不一致或者丢失,但是,由于异步发送延迟极低,我们预期这种极端场景下的记录数影响极少,在我们的测试盒验证过程中,我们发现即使在高并发的场景下,也只有偶尔会出现极少的若干条记录存在这种问题,数据几乎是一扔到后台队列,就立马通过异步的方式发送出去了,在我们线上运营的集群中,如果单个节点异常的情况下,我们的系统可靠性至少可以达到10个9以上,如果多个节点出现异常,数据可靠性也可达到9个9以上。
    2. 如CAP的理论所介绍,在分布式系统的设计中,没有一种设计可以同时满足一致性,可用性,分区容错性 3个特性,最多只能满足其中两个,设计时选择的关键点取决于业务场景。业界的redis cluter、mongodb等系统在设计上其实也是面对有类似的问题,redis cluster、mongodb在主备切换时采用了raft算法,raft算法选择主节点的关键在于选择数据更新最新的节点作为主节点,但是,由于redis cluster、mongodb均是采用的异步复制或拉取策略,主备切换时必然也存在新的主节点没有同步到原主节点的所有数据,因此,也会存在数据不一致的场景,即业务数据写入并得知成功了,但下次去读取的时候,有可能读取不到数据。当然,mongodb还提供了writeConcern机制,用以满足对数据一致性要求较高的业务,总计,还是一句话,选择的关键在于系统所面对的业务场景。

运营

性能数据

为了准确评估我们系统的性能和负载能力,同时为了验收和确认我们的系统是否满足业务的要求,我们对我们的系统进行了详细和完备的性能测试。为了支持我们的性能测试,我们开发了一个vde_benchmark的压力测试工具。

  1. 为了获得理论的稳定的性能数据,我们的vde_benchmark工具支持平滑速度的请求发送。
  2. 为了获得线上真实环境的性能数据,我们的性能测试环境的机器及集群部署方式与我们的线上环境机器配置、部署方式完全一致。

我们的性能测试环境的具体配置如下:
三机三副本、单机四节点配置、可运营cpu占用50% ~ 60%左右、10ms超时率低于0.1%,单机的操作系统版本及硬件配置如下:
CentOS release 6.6 (Final)、gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11)、24 * Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz、256G内存

在上面的环境下,我们的理论压测性能数据为:

压测类型 key+value长度(字节) ds1 cpu、网卡(KB/s) ds2 cpu、网卡(KB/s) ds3 cpu、网卡(KB/s) TPS(#/s)
set 500 59.9%、r(191763)、t(140874) 59.5%、r(174312)、t(127277) 59.4%、r(175298)、t(129682) 208891
set 1000 60.6%、r(292312)、t(207941) 61.1%、r(270940)、t(191009) 60.3%、r(271907)、t(194431) 198764
set 3000 54.9%、r(616387)、t(453439) 55%、r(561414)、t(404225) 57.4%、r(560129)、t(402664) 157274
get 500 47.7%、r(76228)、t(275857) 47.4%、r(69786)、t(256450) 46.5%、r(69122)、t(246354) 998325
get 1000 46.9%、r(67666)、t(412108) 43.9%、r(60778)、t(375198) 45.6%、r(59560)、t(361730) 881707
get 3000 34.2%、r(55013)、t(715608) 31.3%、r(49978)、t(648818) 31.8%、r(54318)、t(715514) 565959

大促场景下系统资源评估案例

以100台机器、单机部署4节点为例,总节点数为400,采用3副本部署,那么理论上在1k记录长度,10ms超时的要求下,可以支撑约662w/s的纯set并发或约2939w/s的纯get并发,按3:1 ~ 4:1的读写比,理论上可支撑的并发为2367w/s ~ 2481w/s,但是在线上运营时,我们的请求记录长度主要集中在0 ~ 5K,还有部分请求记录长度在5K ~ 数百K,同时,受请求波动影响严重,我们评估时不能仅仅从理论数据评估,一般都是综合考虑线上实际的cpu波动情况,以及我们要求的cpu安全运营范围、还有资源安全冗余等各类因素,再结合我们的经验,得到的一个相对保守的用以支撑大促的对外承诺的系统可支撑的最大并发量,然后,我们再根据这个对外承诺的最大并发量,与相关业务沟通确认大促场景下峰值并发请求成倍增长的情况下,推动和确认业务方的降级相关工作,从而确保我们的系统稳定支撑大促。

线上的实际运营环境远比理论的压测环境要复杂的多,我们在线上运营过程中实际评估系统资源时,我们会考虑系统可能面对的问题场景,根据线上实际运营情况综合评估, 主要考虑的问题场景包括了如下几种:

  1. 记录长度不确定,在线上的请求中,0 ~ 5K长度的请求占比97%以上,3%的请求分布在5K ~ 1024K之间。

    我们在线上评估时,我们可以主要以0 ~ 5K的记录长度作为主要的性能评估依据,但是过长的记录对系统性的影响也应当考虑,同时,需要思考如何在系统层面尽量降低过长的记录对系统性能的影响。

  2. 业务请求并发波动,我们的系统接入线上业务时,单机客户端连接数超过1w+,对于系统而言,容易出现并发波动的情况,无法保证所有请求均匀到达我们系统。

    针对上面的问题,我们一方面是尽力去推动业务对我们的系统请求做平滑改造,另一方面,我们也会在资源评估时兼顾瞬时峰值和平均值的问题,尽量以合理的base并发量评估资源,避免过多的资源浪费。

  3. 业务请求中set、get操作均有,且读写比有可能不定时变化,大促期间跟平常时候读写比也不一样。

    我们线上的业务请求读写比主要在3 : 1 ~ 4 : 1左右,线上的读写比也应当作为我们线上评估时需要考虑的因素之一。

线上运营

  1. 集群运营数据

    当前我们系统部署的节点数700+(全量集群460+,热备集群260+),总存储记录条数550亿+(全量集群380亿+,热备集群170亿+),单集群历史峰值并发600w/s左右,10ms超时率低于1/10w,99.99%的请求时耗为0~2ms。
    线上业务请求读写比为3:1 ~ 4:1,全量集群平常情况下承载的读写请求2000亿次+/天,热备集群1000亿次+/天,大促情况下每天的读写请求还会增长数百亿次甚至上千亿次。

  2. 常见问题
    在我们的线上运营过程中,我们在平时不断的去预防各类问题的发生,在平时就把一些可能出现的问题或者推动业务层改造,或者从系统层本身进行优化或限制,避免大促时小风险累计成大风险,甚至造成集群异常不可用。也正是由于我们对待底层系统的这种敬畏之心,保证了我们系统在近2年的线上运营过程中,虽然历经多次大促,期间也出现了一些波折,但是,我们的系统始终稳定、可靠的支撑和满足了我们的业务要求,没有出现过一次线上问题。

    请求热点

    在我们的线上运营过程中,我们经常会碰到热点数据的问题,热点数据指的是同一条请求在短时间内被大量集中访问,这里的集中访问又分为两种情况:
    1) 同一个进程短时间内对同一条记录的大量访问,这种场景一般可以通过在业务层设置缓存来保护底层系统。
    2) 分布式计算环境下的不同进程对同一条记录的大量并发访问,这种场景下我们一般会设法在业务层错开对同一条记录的并发访问,或者让业务读取多个副本。

    我们目前线上默认设置只读写master节点,由于我们在构建集群路由表的时候,会尽量使每个节点承载的master bucket个数基本均匀,因此,正常情况下,落到每个节点上的请求数也会是基本均匀的。

    线上请求的波动性和集中性

    线上的请求一个最大的特点就是请求的不确定性,你永远不会知道下一秒有多少请求会过来,也永远不会知道每一秒之间接收的请求量会有多大的差异。我们当前的并发量统计是按照1min的总量来得到的平均值,因此,实际的并发量级并发波动情况对我们准确评估资源干扰很大,如果按照波动峰值来评估,则会造成资源浪费。

    针对上面的问题,我们主要是从业务层去推动改造业务请求db的模式,由之前的同步、异步无限制方式统一改造为异步平滑请求模式,我们推动业务改造后,大大降低了业务对系统请求的波动,同时,由于业务统一改成异步请求方式,相同的并发请求下,大大降低了客户端的资源占用。

    过长的记录

    我们系统支持的最大记录长度为1M左右,但是在记录长度超过500k时,在高并发的场景下,就很容易导致一些请求时耗的抖动,由于mdb存储引擎在操作时会加锁,一个请求在mdb存储引擎中的时间过长,则会阻塞其他的请求,导致其他的请求处理时耗也变长。

    针对上面的问题,我们做了如下的处理:

    1. 一方面,我们内部的处理模型为单个队列、多个线程消费的模式,避免单个的请求阻塞影响后面请求的处理(这种情况主要是优化不是阻塞在存储引擎中的场景)。
    2. 另一方面,我们会从系统层面找出这些影响系统性能的请求对应的记录,然后推动业务去进行优化和改造。当然,这个由于涉及到数据生产和使用方业务处理逻辑的改造,代价会相对较大,因此,一般情况下,我们会对业务明确我们对请求长度的要求,让业务方在设计数据时就考虑到对存储系统的影响,避免后续的不必要的修改。

    无效请求

    在我们的线上运营过程中,发现了大量的无效请求,当然,我们从系统层面无法界定请求是否有效,我们判断请求是否有效是从业务层面来确定的,这里的一个主要场景是业务方在读取的时候,组装成了一些key是明显不存在的,我们完全可以推动业务方从业务层面去过滤这种key,以降低对系统的请求,避免资源被浪费,在我们线上的运营过程中,我们发现这种无效的请求有时会占用10%的请求以上,造成了很大的资源浪费。

    过载保护

    在我们线上运营的过程中,我们还碰到在大促时由于业务程序的异常,导致集群并发瞬时暴增,这种情况下,我们及时开启了我们系统的过载保护功能,限制了我们系统每秒最大的处理请求数,在业务请求并发超过我们的限制时,我们直接丢掉了部分请求,通过提供有损的服务来避免了在这种异常场景下的系统异常甚至雪崩的问题。

    权限控制

    在我们系统逐渐在部门内部广泛使用后,不断的有新的业务接入我们系统,这就带来了另一个问题,即新的业务方有可能在我们不知情的情况下接入我们系统,带来了诸如请求波动、过长记录等各种问题,还导致了大促期间的资源评估难以准确评估,因此,我们对业务方的接入做了权限限制,只允许白名单列表中的机器能够接入我们的系统,不在白名单列表的老业务暂时依然可以访问集群,但后续会慢慢出现异常直至不可用,不在白名单的新业务则直接无法接入系统和访问集群,所有新接入的业务必须与我们确认和沟通,当然这种限制不够彻底,但是能够很好的控制和评估新用户接入的问题,对于老用户和业务,一般情况下,我们会根据从系统层面获取的业务数据及时主动去沟通和确认,及时控制系统风险,从而保证了无论是在平时还是大促期间,我们的底层kv存储系统的稳定和可靠运营。

异地双集群热备


如上图所示,我们目前的跨机房备份采用的是双集群热备模式,分别在两个机房部署了独立的两套集群,其中一个为主集群,460节点,存储的是全量的业务数据,另一个为备集群,260节点,存储的是核心的业务数据,平常情况下,我们生产数据的业务方会在两个不同的机房部署完全相同的业务,主集群接入的是全量的数据生产业务,备集群只会接入核心的数据生产业务。
线上请求会经过流量分发,70%的线上流量落入主集群,30%的线上流量落入备集群(这里主要是核心的业务场景的的的流量),平常情况下两套集群均会正常对外提供服务,一旦有一个机房的集群出现问题,则会将其他流量全部切换到另一个机房的集群。

1) 如果主集群故障或网络中断,则马上切换到热备集群时,但是,服务将会是有损的,因为备集群只存储了核心业务场景的数据,因此,只能支持核心的业务场景。如果备集群故障或网络中断,则所有流量马上切换到主集群,此时,数据是全量的,不会对业务场景产生影响。
2) 我们这里的双集群热备模式也会存在比较明显的问题,对业务层不够友好,且业务端需要重复部署数据生产的程序,且业务越多,这类问题影响越明显,在这样的问题背景下,我们在后续的版本中会规划跨机房数据同步方案的设计和落地。

统计监控

为了及时和准确观察我们系统的健康状态,我们接入了一套实时的监控系统,我们vde系统会定期的上报我们系统的各类数据给监控系统,监控系统收到我们上报的数据后,会做一个全面的展示,这对于我们快速定位线上问题和分析历史数据提供了很好的方法和手段。

猜你喜欢

转载自blog.csdn.net/shangshengshi/article/details/80725040