吴太银:华为消费者云服务Cassandra使用场景与最佳实践

大家好,我是华为消费者云的吴太银。

 

我今天分享的主要是华为消费者云服务使用Cassandra的应用场景和最佳实践。我这个可能跟其他嘉宾分享的不太一样,因为前几个嘉宾讲的实际上对Cassandra原生的代码有一定的修改,而我们当前使用的是纯粹的、原生的Cassandra,我们没有做任何的修改。所以这个分享可以给一些想大量使用原生的Cassandra的朋友有比较好的借鉴意义。


我今天会大概从这三个方面来给大家介绍一下:第一个就是我们用Cassandra的一些使用历程,经验和教训,以及我们当前的规模;第二个就是我们现网遇到的典型问题,我之前跟组织者交流,因为我们当前的规模比较大,他主要是想看我们在用到典型的ToC场景下,在用到大规模,大数据量的情况下,在现网有哪些典型的问题,这个对大家应该有一定的启示作用(哪些是Cassandra的雷区,是不能碰的,如果你把这些避免掉,就不会出大问题);第三个就是我们使用Cassandra总结出来的最佳实践,因为我们现在所有的终端业务,基本上都会用到Cassandra,我们的业务场景非常的复杂,必须有些设计上的,包括表结构的设计上的约束,不能随便用,因为随便用它一定会有问题。这个在前面的各位演讲嘉宾也经常提到:我们要顺着它来用。


我们现在先看一下为什么选Cassandra。大家可能都比较清楚了,我就简单的说一下:一个是去中心化的部署,不但简单,而且扩展性很好,可以轻松应对业务发展带来的数据容量和性能上的要求;第三个是它天然支持多DC的部署,我们当前是一主一备,再加容灾,三个数据中心,它天然的支持(这种部署),内部自动同步;第四个是它的监控指标和监控接口非常完善,通过nodetool和JMX可以非常容易监控到Cassandra原生的各个指标,这个在我们后面的幻灯片里会看到,这一块是非常重要的,在现网,特别是你在集群规模变大了之后,你需要快速恢复一些故障的时候,没有这些东西,你是做不到的;第五个是它的这个开源社区确实很活跃,包括稳定的版本演进,可以让我们不停的选择它。


这个(幻灯片)是我们的使用历程,给大家看一下。我们实际上是从2010年开始就使用了。Cassandra是差不多2008年开始在Apache孵化,我们差不多是跟孵化同时的时间开始接触,一开始是0.7版本。Cassandra在我们这边用大概分两个阶段:前一个阶段,可能是一个相对来说比较失败的一个经历,因为这个阶段我们还是主要用于ToB的场景。当时我们的华为手机还没完全(流行)起来。这个场景在这个阶段我们面对都是电信级的应用。

 

在这个时间段,其实NoSQL还没有完全流行起来。我们找的应用都是电信级的应用。但是电信级的应用大家都习惯用SQL的方式去做,第一个当时KV的方式大家也不太习惯,第二个当时Cassandra的接口不像现在这么好。当时是纯的Thrift接口,现在支持CQL,还有很多CQL的驱动。所以说当时是我们找业务,所以我们要按照它的使用方式,提供了一堆的定制化的东西,比如说我们在Thrfit的基础上,定制了一个类JDBC的接口,让它像SQL一样用Cassandra。这一块当时我们也是深入的修改,我们写了一整套SQL解析的模块(DDL,DML全部都重写了,然后转换成原生的mutation对象)。序列化和反序列化我们全部都改了。包括我们做得比较前沿的东西(因为当时0.7的版本还没有堆外内存),因为它的GC比较严重,我们把memtable, index summary, bloom filter, row cache, key cache这些常驻内存的一大部分全部都放到了堆外。

 

另外,我们还做了存储过程、二级索引、触发器等。其实当时我们就是对标的关系型数据库去做。但是实际上我们大家也知道本来Cassandra原生的是列数据库,我们强制按照行的方式来改造,实际上有很大的问题。再加上电信级的业务场景,这个对可靠性和数据的准确性的要求是非常高的。所以说我们当时虽然做到了SQL的形状,但是实际上没有SQL的实质。这个只是在小范围使用,也没有完全用起来。这个基本上算是一个失败的尝试。


然后这个过程对我们有什么好处呢?这个让我们深入的看到了一些Cassandra架构,以及它的处理方式,还有它的源码。因为在后续的发展过程中,Cassandra的代码虽然重写了好多版(不停的重构),但是它的整个框架,整个处理流程是没有变化的。这些知识对我们后面这个阶段是有很好的指导意义的。虽然我们把Cassandra应用在电信场景没有很成功,但是后来华为的手机慢慢流行起来了。2014年开始,终端开始起来了。

 

之后,我们面临互联网ToC的场景,其实是非常适合Cassandra的,我们就慢慢的找到了Cassandra存在的一些价值,并且不停的在往下走。这一阶段我们就没有修改任何源码了,完全用原生的。因为根据我们第一阶段的教训,改了源码之后,基本上就成为了孤版,很难向前演进。然后在终端情况下,我们不停的找,它最佳的使用场景。第二个因为ToC终端用户对实时性和可靠性要求都非常高,所以我们基于Cassandra的天生的多DC方式,实现故障切换。

 

这里仔细讲一下:我们当前的业务一般是1+1+1,一主一备一容灾,每个DC都是3副本。正常的情况下我们只会向一个DC写,如果出现故障,我们通过这个我们重写的驱动,把它切到另一个DC去,保证任何DC里的两个节点出现故障,对终端业务的请求来说是无损的,客户端会自动切换数据中心。另外,Cassandra原来是有OpsCenter来进行管理的,但是因为我们公司的安全规范,没有用它。我们现在是构建了一套华为自己的集群部署管理,包括监控系统。第三,我们不断跟进社区的新版本。第四,Cassandra在华为的使用场景非常多。可以这样说,凡是华为终端,包括手机,包括穿戴式,包括IoT的所有华为终端应用的背后,你看得见看不见的背后,都有我们Cassandra的身影。比如举个简单的例子,运动健康,大家跑步的时候,就是这些数据都基本上存在我们Cassandra里去。华为手机上的应用,只要你看得到的,基本上后面都有Cassandra的身影。所以Cassandra伴随了我们消费者云,伴随了我们华为终端,六年的快速发展。


然后我们可以看一下我们当前的规模。我们当前的规模还是比较庞大的,基本上我们这里存的全是用户数据。Cassandra我们全球的节点大概有三万多台,我们的数据规模大概有20PB。我们的集群数量可能有500多。我们最大的集群的节点数有600多节点。我们现在全网每秒有一千万每秒的访问吞吐量。我们的平均延迟是4毫秒。我们当前最大的一张表,单表达到三千亿条记录。像我们这个量,在原生的没有改动Cassandra源码的情况,能够达到这个规模,也是比较值得让人骄傲的一件事情。这些数据从另一个角度证明,Cassandra原生的稳定性,使得它足以在ToC的这种线上场景,可以有很好的一个应用。

我们下面再看一下,虽然我们规模有这么大,但是不代表Cassandra是万能的,也不代表Cassandra它什么问题都能解决。我们要避开这些问题。


我们当前面临的挑战,首先是华为终端,包括中国区和海外不停的业务发展带来的庞大数据量造成的稳定性的问题。现在华为终端卖得非常好,而且用的人是越来越多,这个对我们数据库的压力很大,也带来数据一致性的问题。当前我们有些数据是没有上云的。我们自建了机房,自建机房一块块的盘,是不稳定的,会遇到一些坏盘的问题,坏盘会带来一致性,包括僵尸的问题。第三个是基础设施的问题,比如JDK的问题,网络的问题,磁盘的问题,我们都全部遇到过。第四个是故障的快速定位、定界,以及恢复。因为我们现在面临的都是OLTP的场景,全是ToC的。ToC的场景,基本上就是华为终端用户的场景。我举个例子:假如你用到的华为手表,故障的时间一长,你的业务终端用户就不能用,人家是很着急的。所以说我们现在对于业务的体验,包括故障的恢复的要求也非常高。我们必须在半个小时之内把所有的故障必须恢复,你可以定位不出来问题,但是你必须把它恢复掉。


我这里有一个分类,把我们现网里遇到的典型问题列了一下。我们现网遇到的问题比这多得多,可能是这个的好几倍,但是我总结了一下这些典型的问题,希望对大家,或者是即将使用原生Cassandra来构建自己的核心业务的朋友做一个提醒,你一定要注意这些方面的问题。这里都是我们在业务发展过程中遇到的典型问题。我后面会针对每一个问题,包括它的现象,包括从监控里面的反应,包括堆栈,都会介绍一下,结合我们的业务场景,给大家讲讲。


大家可以看一下,这里是我们的监控系统,我把一些IP抹掉了,这是我们的业务成功率,这是我们现网节点的CPU、IO等系统指标。大家可以看到案例的描述:有一次,现网扩容,但是扩着扩着就发现,到一定程度的时候,所有节点的CPU和IO都全部非常高,这个对我们的业务影响大家可以在右边的图里看到,本来成功率百分之百,忽然一下降了这么多。对应的时间点内CPU、IO全部都飙升。为什么,这个就是集群规模过大造成的影响。我们可以先看一下为什么会这样。


根本原因是:第一是我们的集群非常大,几百个节点,第二个是我们的Token数有256个,这样算起来, 我们最多可以有十几万个Token范围。新节点加入集群过程中,Token信息需要更新。同时,Cassandra读写流程里面,也需要获取Token信息用于路由。两个流程使用读写锁获取一个对象。当集群规模达到一定的程度时,Token数量过大,会导致Token信息更新缓慢,如果此时刚好业务高峰,请求会因为拿不到锁而阻塞,从而导致业务请求大量超时失败。这里我们给出的解决方案是,控制单集群规模,主要是虚拟Token数量,尽量不要超过十万。集群过大的时候,需要考虑拆分,不要让一个集群无限膨胀。我们现在ToC的集群为了稳定性,我们的集群节点数不超过两百。超过两百个节点我们建议业务去拆分。


第二个是,单节点数据量过大的时候,会有什么问题。我们当时每个节点数据量达到了5TB,集群变得非常不稳定。表现在单节点数据量大时,bloomfilter、index summary等需要的常驻内存量会很大,导致频繁full GC甚至OOM;另外,我们默认使用的压实算法是Leveled Compaction Strategy,如果使用LCS而且数据量过大,磁盘空间可能不够,因为L0经常需要使用STCS来做压实操作。解决的方法是避免单节点数据量超过1.5TB,另外在扩容过程中临时增大磁盘空间或者设置disable_tscs_in_l0=true。注意,这个参数只能在紧急时候使用,扩容完成后,务必记得恢复成默认值。


第三个问题是节点压实操作(Compaction)堆积严重。大量的压实堆积说明压实跟不上,会产生大量小文件,影响读性能。后面这两张图里可以明显看到在LCS的小文件太多的时候,读延迟大大增高。我们找出的解决办法,一个是调整compaction的速度,一个是调整两个系统参数:sstable_preemptive_open_interval_in_mb,以及-Dcassandra.never_purge_tombstones。通过jstack查看线程的调用栈可以判断需要调整那个参数。另外注意,never_purge_tombstones也仅限紧急情况下使用,压实的堆积消除以后必须恢复原有的默认配置。


第四个问题是大Key的问题。前面的几位嘉宾也提到单个partition太大的时候对性能和稳定性的影响。这个在Cassandra日志里会出现告警信息。解决的办法是在业务里改变表结构和使用方法。比如一个文件删除记录表,对于个人文件来说,某个文件下面的删除记录不会很大,但是对于公共文件,比如华为手机上的锁屏图片,就会出现大Key问题,解决的办法就是在业务里增加判断,如果是热门文件,在删除次数达到某个阈值后就不再新增删除记录。再比如,如果记录一个热门电影的预约用户,使用电影的resourceID作为分区键,预约用户的UserID作为聚类键,当预约的用户数达到千万甚至上亿级别,就一定会出现大Key问题。解决办法就是使用额外的hash串将resourceID继续离散,避免单个resourceID下的分区太大。


第五个问题是热点Key问题。表现在短时间内对同一个Key频繁操作,会导致该节点的CPU和Load过高,影响其他的请求,导致业务成功率下降。这个从右边监控系统的截图可以看到,部分节点的CPU和负载都非常高。应急处理的方法,一般是通过toppartitions找到访问量最大的partition key,在业务侧加黑名单屏蔽这种热Key。最终的解决方案是利用缓存来减小热Key对数据库的冲击。


第六个是墓碑问题,这个我就不花太多时间说了。这方面一定要避免的就是短期内如果有频繁的删除并且还有频繁的读操作的话,可能Cassandra并不适合这种场景。另外,作为应急方案,可以临时减少gc_grace_seconds,以加速墓碑的物理清理回收时间。


第七个是坏盘导致的僵尸数据,这个大家可以直接看一下图示和源码,因为我的时间有限。我们的解决方案是如果你用的是自建的IDC机房,出现坏盘了,必须在gc_grace_seconds的周期内完成数据修复,或者直接replace掉出了坏盘的节点;当然,如果你的业务有条件上云的话,这种坏盘发生的可能性要低很多。


第八个是基础设施方面的网络丢包问题。我们现网当时出现的症状是突然时延大幅度增加,Cassandra驱动侧出现大量的慢日志信息。排查了集群的资源利用和线程池都没发现问题,但是我们用getendpoints把慢查询日志涉及的分区键对应的副本节点打出来,发现都涉及到一个*.*.23.20的节点。后来果然发现这个节点的网卡出现了丢包的故障。修复了丢包故障后,业务时延恢复正常。


第九个是集群节点规格不一致导致的节点负载不均。这个其实涉及Cassandra的一个优化,但是优化用得不好,也会带来问题。因为Cassandra的Gossip交换的信息里,会包含每个副本节点的负载,负载越小,收到的请求就越多。如果你的节点的物理配置不均,会导致请求集中在高配的几个节点上。这个对自建IDC的影响比较大。在云上,大家的节点配置比较一致,会较少遇到这个问题。


第十个是操作系统的网络调参。我们这里只列举了一个。网络参数不合理的症状是数据迁移不会出错,但是会卡死。在集群扩容时,200GB的数据不是卡死,就是长达一两天。在公有云上我们发现把net.ipv4.tcp_sack (Selective ACK) 开启,之后我们200GB的数据迁移20分钟就完成了。这个参数能够减小报文重传的概率,在网络拥塞或者乱序的情况下会有很好的效果。


最后一个是JDK的STW (Stop The World)的问题。这个问题我们到现在都还没有复现,我们是通过把业务切到备用的DC,然后重启故障DC的所有节点解决的。我们是怎么发现这个问题呢,当时业务的平均时延增高到3秒,但是系统CPU、IO、负载都正常,我们也排查了集群节点系统的各个核心参数,均没有发现问题,但是注意到Hint大量出现,这说明数据在写入过程中,出现了大量的业务节点被短暂识别为宕机状态,引起Hint被记录下来。通过查看Jstack和GC日志,发现线程卡顿和STW经常长达10秒。

最后,我们再来看看我们总结出来的Cassandra最佳实践。我这里总结了几点。


首先,需要管控业务使用场景,加强业务表结构的评审。不管你是用云上的,还是用自建IDC的,这个对大家都是有一定的借鉴意义的。我们现在整个业务大概有几百个利用Cassandra数据库。我们这个组现在负责在业务上线之前,对业务场景和表结构进行评审。我们基于这样一些方面的规范(我们也叫“军规”):主键设计合不合理,Schema约束,数据老化机制,单条数据频繁更新/删除,单条记录过大,大面积数据删除引发墓碑问题,集群规模。我们现在为什么会做到这么大的量,是因为我们管控了使用的场景,让它必须按照我们要求的来做。


然后,是构建完善的Cassandra集群监控系统。我们有很多方面的监控:第一个是主机级别的监控,包括CPU/IO/磁盘/内存;第二个是读写请求的监控,这个是从业务的角度来看请求量是多少;第三个是Cassandra内部核心线程监控,这个是Cassandra内部的一些显微镜级别的监控手段,必须可视化出来,否则的话现网几万台机器,出了问题的话,你是没有任何办法可以快速恢复的;第四个是集群规模的监控,包括节点数和集群统计量,告警,自动化部署相关的指标。


另一个最佳实践,就是使用Cassandra,一定要多看源码,多熟练掌握nodetool各种命令和使用场景。Nodetool命令其实非常好用,这个只是列了一些我们用得最多的,包括cleanup, compactionstats, getendpoints, netstats, rebuild, repair, toppartitions, tpstats, cfstats,我不在这里一一说明,大家可以看这个表。如果你在大规模应用中,需要快速的恢复,这些命令对于故障排查和恢复会非常有帮助。

 

posted @ 2020-08-03 16:54  DataStax  阅读(1887)  评论(0编辑  收藏  举报