1、问题出在好大富在线。S3系统负责对各业务方的操作日志进行集中存储、查询和管理。目前系统日均查询量几千万,插入几十万。随着日志量的不断积累,主表已经达到亿级,单表占用磁盘空间400G+。S3是业务早期就存在的系统。当时为了简单快速的实现,使用MySQL做存储。随着业务的持续增长,必须同时考虑性能和可扩展性。新项目命名为:LogStore。2.目标1.安全性S3系统在设计之初并没有根据业务系统考虑数据隔离,而是直接使用key(系统+类名+id)+有限的固定字段+序列化的value进行存储。这种方式显然不方便后续的集群拆分和管理。LogStore系统需要对数据区域进行逻辑划分,业务方需要指定app在访问时进行必要的权限校验,以区分不同的业务数据,然后进行插入和查询操作。2.通用性S3主要提供了3层结构,使用MySQL固定字段进行存储,必然会造成字段空间的浪费。LogStore系统需要提供一种通用的日志存储格式。业务方指定字段的含义,并保留一定程度的可查询维度。3、高性能S3系统QPS300+,单条数据最大1KB左右。LogStore系统必须支持当前QPS10倍以上的读写速度。4.可审计性为满足内部安全审计的要求,LogStore系统不提供数据更新,只允许数据插入和查询。5、易于扩展LogStore系统和底层存储必须满足可扩展的特点,可以在线扩展以满足公司未来5年甚至更长时间的日志存储需求,最大限度地节省磁盘空间。3.方案选择为了实现改造目标,本次调研了四种存储改造方案。各种方案对比如下:1.我们不适合-分库分表,分库分表主要分为应用层依赖中间件和Proxy中间件,不管是哪一种,都需要修改现有的PHP和Java框架,同时也给DBA管理数据带来一定的操作困难。为了降低架构的复杂度,架构组拒绝了引入DB中间件的方案,仍然需要一个运维简单、成本低的方案。2、我们不适合——TiDBTiDB曾经进入我们的重点研究对象,但是由于公司的DB生态主要是基于MGR、MongoDB、MySQL,在可预见的需求中没有可以充分利用TiDB的场景。所以暂时搁置。3、我们不适合——ElasticSearchELK-stack提供的套件,确实让ES变得很吸引人,而且公司也长期使用ES集群。ES的优势在于检索和数据分析领域。正是由于其强大的检索和分析功能,写入、查询和存储的成本都比较高。在日志处理的场景下,性价比略低,所以也pass了上去。4、合适的选择——MongoDB业务操作日志读多写少,非常适合文档型数据库MongoDB的特点。同时,MongoDB在业界得到了广泛的应用,公司也有很多业务在使用。在对MongoDB积累了一定的运维经验后,最终决定选择MongoDB作为新日志系统的存储方案。4.性能测试为了验证MongoDB的性能是否能够满足要求,我们搭建了一个MongoDB集群。机器配置、架构图及测试结果如下:一、机器配置MongoDB集群3台机器配置如下:CPU内存硬盘OSMongoversion8cores15GMongoDB内存分配单节点8G100GCentOSrelease6.6(Final)3.2.172.架构图架构图3.测试场景架构图本次MongoDB测试使用了YCSB(https://github.com/brianfrankcooper/YCSB)性能测试工具,ycsb的workloads目录下保存了6种不同的workload类型,分别代表不同的压力测试负载类型。这次我们只用了其中的5个。具体场景和测试结果如下。Workloada100%插入,用于加载测试数据。Workloadb读多写少,90%读10%更新。workloadc读多写少,100%读。工作负载读多写少,90%读取,10%插入。workloadf混合读写,50%读,25%插入,25%更新(1)insert平均文件大小5K,数据量100万,并发100,总数据量约5.265G,执行时间和磁盘压力:结论:插入100w条数据,总耗时219s,平均插入时间21.8ms,吞吐量4568/s。(2)测试90%读取,10%更新,100并发的场景:结论:总耗时236s,读取平均耗时23.6ms,更新平均耗时23.56ms,吞吐量达到4225/秒。(3)测试读多写少,100%读,100个并发场景:结论:总耗时123s,平均读耗时12.3ms,吞吐量达到8090/s。(4)测试读多写少,90%读,10%插入,100并发的场景:结论:总耗时220s,读平均耗时21.9ms,插入平均耗时21.9ms,吞吐量达到4541/s。(5)测试混合读写,50%读取,25%插入,25%更新,100个并发场景:结论:总耗时267s,读平均耗时26.7ms,更新平均耗时26.7ms,平均insert耗时26.7ms26.6ms,吞吐量3739/s。4、测试结果对比从架构图中可以看出,MongoDB适合读多写少,性能最好,读写速度可以满足生产需求。5.无缝迁移实践为了保证业务的无缝迁移,同时尽量减少业务研发同学的投入成本,我们决定采用阶段性切换的方案。第一步:系统应用层改造+LogStore系统搭建首先,S3系统内置读开关和写开关,可以将读写流量分别引入LogStore系统,新应用的接入可以直接调用LogStore系统。时序结构示意图如下。第二步:增量数据同步为了让S3系统和LogStore系统中的新增数据保持一致,底层数据库使用Maxwell订阅MySQLBinlog同步到MongoDB。原理图如下:Maxwell(http://maxwells-daemon.io)实时读取MySQL二进制日志binlog,生成JSON格式的消息,作为生产者发送给Kafka,Logstore系统消费kafka中的数据,写入mongodb数据库。至此,对于业务端已有的日志类型,新增数据在底层进行双写,S3系统和LogStore系统存储两份数据;如果业务方增加了新的日志类型,可以直接调用LogStore系统接口。接下来,我们将迁移现有日志类型的旧数据。Step3:股票数据迁移本次S3旧数据迁移使用php定时任务脚本(多个)查询数据,并将数据投递到RabbitMQ队列中。LogStore系统从RabbitMQ队列中拉取消息,用于在MongoDB中使用和存储。示意图如下:(1)由于原mysql表中的id是varchar类型,不是主键索引,只能使用ctime索引批量查询,chunk投递到mq队列在数据密集的地方。(2)数据无法在一天内完成迁移,迁移过程中可能会出现中断。脚本使用定时任务每天执行20小时,在线时间停止执行,同时记录停止时间到Redis。(3)由于需要迁移的数据量较大,在mq和消费者能够承受的范围内,尽量增加脚本数量,缩短导入数据的时间。(4)脚本执行过程中,观察业务延迟和MySQL监控,如有影响立即调整,确保不影响正常业务。第四步:验证数据旧数据导入后,接下来要对旧数据进行验证。验证从数据量和数据完整性两个方面进行。数据量:根据旧数据在S3系统中的id,查询MongoDB中是否存在,如果不存在,则进行补偿,重新发送;数据完整性:对于S3和MongoDB中的数据,按照相同的规则进行md5校验,校验不通过则进行补偿重传。第五步:数据双写打开预制的应用层写开关,将流量导入LogStore。此时MySQL的流量还没有停止,继续同步binlog。结构如下:从图中可以看出,来自S3调用点write接口的流量写入了MongoDB数据库的backuplogs集合。为什么不直接写入日志表呢?留一点悬念,后面会解释。第六步:灰度切换S3读取到LogStore系统上面我们提到,对于S3系统应用层读写调用点,内置了拨动开关,当应用层读取开关打开时,所有读操作到LogStore。切换后示意图如下:第七步:灰度切换写接口到LogStore系统,打开应用层写开关。所有写操作都会通过mq异步写入MongoDB。如何证明应用层write调用点已经被完全修改?上述双写数据在logs表中保存一份,在backuplogs表中保存一份。通过Maxwell的Binlog同步的数据一定是最全的。从数据量上来说,按理说count(logs)>=count(backuplogs),如果一段时间内两个集合的数据增量相同,则证明write调用点已经完全修改,可以去掉双写,只留下LogStore这一行。否则,需要重新检查验证。switch写好后原理图如下:六、MongoDB与故障演练故障演练可以检测服务是否真正高可用,及时发现系统中的薄弱环节,提前做好预案,减少故障恢复时间.为了验证MongoDB是否真的高可用,我们离线搭建了一个MongoDB集群:同时编写脚本模拟用户MongoDB数据的插入和读取,基于好大富在线自研故障演练平台,进行故障注入在机器上执行以查看各种故障对用户的影响。故障演练内容CPU、内存、磁盘、网络、进程Kill等操作,具体如下图所示:实验结果:CPU、磁盘填充、磁盘负载对MongoDB集群影响不大;内存满载时可能会出现系统OOM,导致MongoDB进程被操作系统Kill掉,因为MongoDB有数据副本和自动主从切换,对用户影响不大;网络抖动、延迟、丢包会导致mongos长时间连接服务器,客户端卡顿,可以通过网络Monitor通过监控的方式进行监控;分别主动killMongoDB的master节点、slave节点、仲裁节点、mongos、config节点,对整个集群影响不大。总体来说,MongoDB有副本和自动主从切换,客户端有自动检测和重连机制。当单台机器出现故障时,对整个集群的可用性影响很小。同时可以增加对单机资源的监控,达到阈值时发出告警,减少故障发现和恢复的时间。七、总结1、使用MongoDBMongoDB中的数据写入可能在各个分片中是不均匀的。这时候可以启用块平衡策略;由于balancer会增加系统负载,业务量小的时候最好选择;合理选择shardsShardkeys和indexing会让你的查询速度更快,这个需要具体场景具体分析。2、迁移数据必须保留唯一标识数据的字段,最好是主键id,方便数据校验;必须考虑多进程,脚本必须自动化,缩短迁移时间,减少人工干预;迁移过程中要时刻关注数据库、中间件、应用相关指标,防止数据的导出导入影响正常业务;在同一配置环境下充分演练,提前制定数据对比测试用例,防止数据丢失;线上操作的每一步(比如读写切换),都要有相应的回滚计划,尽量减少对业务的影响。
