在好大夫在线内部,S3系统负责各业务方操作日志的集中存储、查询和管理。目前,该系统日均查询量数千万次,插入量数十万次。随着日志量的不断累积,主表已经达到数十亿,单表占用磁盘空间400G+。S3是业务早期就存在的系统,当时为了简单快速落地,使用了Mysql来存储,随着业务的不断增长,同时也要兼顾性能和可扩展性,到了必须要重新选型的时候了。
新项目命名为:LogStore。
1、安全性
S3系统在设计之初,没有按业务系统考虑数据隔离,而是直接采用 key(系统 + 类名 + id) + 有限固定字段 + 序列化value 的方式进行存储,这种方式显然不便于后续集群拆分和管理。LogStore系统要在逻辑上进行数据区域划分,业务方在接入时要指定app进行必要的权限验证,以区分不同业务数据,进而再进行插入和查询操作。
2、通用性
S3主要提供一种3层结构,采用MySQL固定字段进行存储,这就不可避免地会造成字段空间的浪费。LogStore系统需要提供一种通用的日志存储格式,由业务方自行规定字段含义,并且保留一定程度的可查询维度。
3、高性能
S3系统的QPS在300+,单条数据最大1KB左右。LogStore系统要支持当前QPS 10倍以上的写入和读取速度。
4、可审计
要满足内部安全审计的要求,LogStore系统不提供对数据的更新,只允许数据的插入和查询。
5、易拓展
LogStore系统以及底层存储要满足可扩展特性,可以在线扩容,满足公司未来5年甚至更长时间的日志存储需求,并且要最大化节省磁盘空间。
为了达成改造目标,本次调研了四种存储改造方案,各种方案对比如下:

1、我们不合适—分库分表
分库分表主要分为应用层依赖类中间件和代理中间件,无论哪种均需要修改现有PHP和Java框架,同时对DBA管理数据也带来一定的操作困难。为了降低架构复杂度,架构团队否定了引入DB中间件的方案,还是要求运维简单、成本低的方案。
2、我们不合适—TiDB
TiDB也曾一度进入了我们重点调研对象,只是由于目前公司的DB生态主要还是在MGR、MongoDB、MySQL上,在可预见的需求中,也没有能充分发挥TiDB的场景,所以就暂时搁置了。
3、我们不合适—ElasticSearch
ELK-stack提供的套件确实让ES很有吸引力,公司用ES集群也有较长时间了。ES优势在于检索和数据分析领域,也正是因为其检索和分析的功能的强大,无论写入、查询和存储成本都比较高,在日志处理的这个场景下,性价比略低,所以也被pass了。
4、适合的选择—MongoDB
业务操作日志读多写少,很适合文档型数据库MongoDB的特点。同时,MongoDB在业界得到了广泛的使用,公司也有很多业务在使用,在MongoDB上积累了一定的运维经验,最终决定选择MongoDB作为新日志系统存储方案。
为了验证MongoDB的性能能否达到要求,我们搭建了MongoDB集群,机器配置、架构图和测试结果如下:
1、机器配置
MongoDB集群3台机器配置如下:

2、架构图

3、测试场景
本次MongoDB测试采用YCSB(https://github.com/brianfrankcooper/YCSB)性能测试工具,ycsb的workloads目录下保存了6种不同的workload类型,代表了不同的压测负载类型,本次我们只用到了其中5种,具体场景和测试结果如下。

1) 插入平均文档大小为5K,数据量为100万,并发100,数据量总共5.265G 左右****,****执行的时间以及磁盘压力
结论:插入100w数据,总耗时219s,平均 insert 耗时21.8ms,吞吐量 4568/s
2) 测试90%读,10%更新,并发100的场****景
结论:总耗时236s,read 平均耗时23.6ms, update 平均耗时23.56ms,吞吐量达到 4225/s
3) 测试读多写少,100%读 ,并发100的场景
结论:总耗时123s,平均read 耗时12.3ms,吞吐量达到8090/s
4) 测试读多写少,90%读,10%插入,并发100的场景
结论:总耗时220s,read 平均耗时 21.9ms,insert 平均耗时21.9ms,吞吐量达到4541/s
5) 测试混合读写,50%读,25%插入、25%更新,并发100的场景
结论:总耗时267s,read 平均耗时26.7ms,update 平均耗时26.7ms,insert 平均耗时26.6ms ,吞吐量 为3739/s
4、测试结果对比

可以看出mongodb适合读多写少的时候,性能最好,读写速率能满足生产需求。
为了保障业务的无缝迁移,也为了最大化降低业务研发同学的投入成本,我们决定采用分阶段切换的方案。
1、系统应用层改造+LogStore系统搭建
首先,在S3系统中内置读开关和写开关,可将读写流量分别引入到LogStore系统中,而新应用的接入可以直接调用LogStore系统,此时结构示意图如下:

2、增量数据同步
为了让S3系统和LogStore系统中新增数据达到一致,在底层数据库采用Maxwell订阅MySQL Binlog的方式同步到MongoDB中,示意图如下:
Maxwell(http://maxwells-daemon.io)实时读取MySQL二进制日志binlog,并生成 JSON 格式的消息,作为生产者发送给 Kafka,Logstore系统消费Kafka中的数据写入到mongodb数据库中。
至此,对于业务方现有日志类型,新增数据在底层达到双写目的,S3系统和LogStore系统存储两份数据;如果业务方新增日志类型,则直接调用LogStore系统接口即可。接下来,我们将对已有日志类型老数据进行迁移。

3、存量数据迁移
此次迁移S3老数据采用php定时任务脚本(多个)查询数据,将数据投递到RabbitMQ队列中,LogStore系统从RabbitMQ队列拉取消息进行消费存储到MongoDB中,示意图如下:

由于原mysql表中id为varchar类型并且非主键索引,只能利用ctime索引分批次进行查询,数据密集处进行chunk投递到mq队列中。
数据无法一天就迁移完,迁移过程中可能存在中断的情况。脚本采用定时任务每天执行20h, 在上线时间停止执行,同时将停止时间记录到Redis中。
由于需要迁移数据量较大,在mq和消费者能承受的情况下,尽可能多地增加脚本数量,缩短导数据的时间。
脚本执行期间,观察业务延时情况和MySQL监控情况,发现有影响立即进行调整,以保障不影响正常业务。
4、检验数据
老数据导入完成后,下面就要对老数据进行校验,校验从两个方面进行: 数据量和数据完整性。
1)数据量
基于S3系统老数据的id, 查询在MongoDB中是否存在,如果不存在则进行补偿重发。
2)数据完整性
对于S3和MongoDB中的数据按照相同规则进行md5校验,校验不通过则进行补偿重发。

5、数据双写
将应用层预制的写开关打开,将流量导入到LogStore中,此时mysql的流量并没有停掉,继续执行binlog同步。结构如下:

从图中可以看到,从S3调用点的写接口的流量都写入到MongoDB数据库backuplogs集合中,为什么不直接写入到logs表中呢?留个小悬念,在后文中有解释。
6、灰度切换S3读到LogStore系统
上文我们提到,对于S3系统应用层读写调用点均分别内置了切换开关,打开应用层读开关,所有的读操作全部走LogStore, 切换后示意图如下所示:

7、灰度切换写接口到LogStore系统
打开应用层写开关,所有写操作会通过mq异步写到MongoDB中,那如何证明应用层写调用点修改完全了呢?
上文中双写数据一份到logs表中,一份到backuplogs表中,通过Maxwell的Binlog同步的数据肯定是最全的,数据量上按理来说 count( logs) >= count(backuplogs), 如果两个集合一段时间内的数据增量相同,则证明写调用点修改完全,可以去掉双写,只保留LogStore这条线,反之需要检查修改再次验证。切换写完成后,示意图如下:

故障演练能够检测服务是否真正高可用,及时发现系统薄弱的环节,提前准备好预案减少故障恢复时间。为了验证MongoDB是否真正高可用,我们在线下搭建了MongoDB集群:

同时,我们编写脚本模拟用户MongoDB数据插入和读取,基于好大夫在线自研故障演练平台,对机器进行故障注入,查看各种故障对用户的影响。故障演练内容CPU、内存、磁盘、网络和进程Kill等操作,详情如下图所示:

实验结果:
CPU、磁盘填充和磁盘负载对MongoDB集群影响较小。
内存满载可能会发生系统OOM,导致MongoDB进程被操作系统Kill,由于MongoDB存在数据副本和自动主从切换,对用户影响较小。
网络抖动、延迟和丢包会导致mongos连接服务器时间变长,客户端卡顿的现象发生,可通过网络监控的手段监测。
分别主动Kill掉MongoDB的主节点、从节点、仲裁节点、mongos、config节点,对整个集群影响较小。
整体而言,MongoDB存在副本和自动主从切换,客户端存在自动检测重连机制,单个机器发生故障时对整体集群可用性影响较小。同时,可增加对单机器的资源进行监控,达到阈值进行报警,减小故障发现和恢复时间。
1、MongoDB的使用
MongoDB数据写入可能各个分片不均匀,此时可以开启块均衡策略;由于均衡器会增加系统负载,最好选择在业务量较小的时候进行;
合理选择分片键和建立索引,会使你的查询速度更快,这个要具体场景具体分析。
2、迁移数据
必须保留唯一标识数据的字段,最好是主键id,方便校验数据;
一定要考虑多进程,脚本要自动化,缩短迁移时间和减小人工介入;
迁移过程中,要时刻关注数据库、中间件及应用相关指标,防止导出导入数据影响正常业务;
要在同样配置的环境下充分演练,提前制定数据比对测试用例,以防止数据丢失;
每一步线上操作(如切换读写),都要有对应的回滚计划,最大限度降低对业务的影响。
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立