日订单峰值破40万,每秒处理10万订单乐视集团支付架构

外置搜索引擎:ES/Solr/XXXX

四、数据库高可用

没有任何机器或服务能保证在线上稳定运行不出故障。比如某一时间,某一数据库主库宕机,这时我们将不能对该库进行读写操作,线上服务将受到影响。

所谓数据库高可用指的是:当数据库由于各种原因出现问题时,能实时或快速的恢复数据库服务并修补数据,从整个集群的角度看,就像没有出任何问题一样。需要注意的是,这里的恢复数据库服务并不一定是指修复原有数据库,也包括将服务切换到另外备用的数据库。

数 据库高可用的主要工作是数据库恢复与数据修补,一般我们以完成这两项工作的时间长短,作为衡量高可用好坏的标准。这里有一个恶性循环的问题,数据库恢复的 时间越长,不一致数据越多,数据修补的时间就会越长,整体修复的时间就会变得更长。所以数据库的快速恢复成了数据库高可用的重中之重,试想一下如果我们能 在数据库出故障的1秒之内完成数据库恢复,修复不一致的数据和成本也会大大降低。

下图是一个最经典的主从结构:

图片 1

上图中有1台web服务器和3台数据库,其中DB1是主库,DB2和DB3是从库。我们在这里假设web服务器由项目组维护,而数据库服务器由DBA维护。

当从库DB2出现问题时,DBA会通知项目组,项目组将DB2从web服务的配置列表中删除,重启web服务器,这样出错的节点DB2将不再被访问,整个数据库服务得到恢复,等DBA修复DB2时,再由项目组将DB2添加到web服务。

当 主库DB1出现问题时,DBA会将DB2切换为主库,并通知项目组,项目组使用DB2替换原有的主库DB1,重启web服务器,这样web服务将使用新的 主库DB2,而DB1将不再被访问,整个数据库服务得到恢复,等DBA修复DB1时,再将DB1作为DB2的从库即可。

上面的经典结构有很大的弊病:不管主库或从库出现问题,都需要DBA和项目组协同完成数据库服务恢复,这很难做到自动化,而且恢复工程也过于缓慢。

我们认为,数据库运维应该和项目组分开,当数据库出现问题时,应由DBA实现统一恢复,不需要项目组操作服务,这样便于做到自动化,缩短服务恢复时间。

先来看从库高可用结构图:

图片 2

如上图所示,web服务器将不再直接连接从库DB2和DB3,而是连接LVS负载均衡,由LVS连接从库。这样做的好处是LVS能自动感知从库是否 可用,从库DB2宕机后,LVS将不会把读数据请求再发向DB2。同时DBA需要增减从库节点时,只需独立操作LVS即可,不再需要项目组更新配置文件, 重启服务器来配合。

再来看主库高可用结构图:

图片 3

如上图所示,web服务器将不再直接连接主库DB1,而是连接KeepAlive虚拟出的一个虚拟ip,再将此虚拟ip映射到主库DB1上,同时添 加DB_bak从库,实时同步DB1中的数据。正常情况下web还是在DB1中读写数据,当DB1宕机后,脚本会自动将DB_bak设置成主库,并将虚拟 ip映射到DB_bak上,web服务将使用健康的DB_bak作为主库进行读写访问。这样只需几秒的时间,就能完成主数据库服务恢复。

组合上面的结构,得到主从高可用结构图:

图片 4

 

数据库高可用还包含数据修补,由于我们在操作核心数据时,都是先记录日志再执行更新,加上实现了近乎实时的快速恢复数据库服务,所以修补的数据量都不大,一个简单的恢复脚本就能快速完成数据修复。

七、总结

任何复杂难题的解决,都是一个化繁为简,逐步击破的过程。

对于像订单中心一样复杂的“多key”类业务,在数据量较大,需要对数据库进行水平切分时,对于后台需求,采用“前台与后台分离”的架构设计方法

  • 前台、后台系统web/service/db分离解耦,避免后台低效查询引发前台查询抖动

  • 采用前台与后台数据冗余的设计方式,分别满足两侧的需求

  • 采用“外置索引”(例如ES搜索系统)或者“大数据处理”(例如HIVE)来满足后台变态的查询需求

对于前台需求,化繁为简的设计思路,将“多key”类业务,分解为“1对多”类业务和“多对多”类业务分别解决:

  • 使用“基因法”,解决“1对多”分库需求:使用buyer_uid分库,在oid中加入分库基因,同时满足oid和buyer_uid上的查询需求

  • 使用“数据冗余法”,解决“多对多”分库需求:使用buyer_uid和seller_uid来分别分库,冗余数据,满足buyer_uid和seller_uid上的查询需求

  • 如果oid/buyer_uid/seller_uid同时存在,可以使用上述两种方案的综合方案,来解决“多key”业务的数据库水平切分难题。 

数据冗余会带来一致性问题,高吞吐互联网业务,要想完全保证事务一致性很难,常见的实践是最终一致性

任何脱离业务的架构设计都是耍流氓,共勉。

若有收获,帮转哟。

 

原文链接:

为什么要迁库?谁痛谁知道!不想受到其他业务小伙伴的影响,就要做到解耦。

六、粗细管道

黑客攻击,前端重试等一些原因会造成请求量的暴涨,如果我们的服务被激增的请求给一波打死,想要重新恢复,就是一件非常痛苦和繁琐的过程。

举 个简单的例子,我们目前订单的处理能力是平均10万下单每秒,峰值14万下单每秒,如果同一秒钟有100万个下单请求进入支付系统,毫无疑问我们的整个支 付系统就会崩溃,后续源源不断的请求会让我们的服务集群根本启动不起来,唯一的办法只能是切断所有流量,重启整个集群,再慢慢导入流量。

我们在对外的web服务器上加一层“粗细管道”,就能很好的解决上面的问题。

下面是粗细管道简单的结构图:

图片 5

 

请看上面的结构图,http请求在进入web集群前,会先经过一层粗细管道。入口端是粗口,我们设置最大能支持100万请求每秒,多余的请求会被直 接抛弃掉。出口端是细口,我们设置给web集群10万请求每秒。剩余的90万请求会在粗细管道中排队,等待web集群处理完老的请求后,才会有新的请求从 管道中出来,给web集群处理。这样web集群处理的请求数每秒永远不会超过10万,在这个负载下,集群中的各个服务都会高校运转,整个集群也不会因为暴 增的请求而停止服务。

如何实现粗细管道?nginx商业版中已经有了支持,相关资料请搜索

nginx max_conns,需要注意的是max_conns是活跃连接数,具体设置除了需要确定最大TPS外,还需确定平均响应时间。

nginx相关:

一、什么是“多key”类业务

所谓的“多key”,是指一条元数据中,有多个属性上存在前台在线查询需求。

补贴大战,大量无效补贴,运营成本高

五、数据分级

支付系统除了最核心的支付订单表与支付流水表外,还有一些配置信息表和一些用户相关信息表。如果所有的读操作都在数据库上完成,系统性能将大打折扣,所以我们引入了数据分级机制。

我们简单的将支付系统的数据划分成了3级:

第1级:订单数据和支付流水数据;这两块数据对实时性和精确性要求很高,所以不添加任何缓存,读写操作将直接操作数据库。

第2级:用户相关数据;这些数据和用户相关,具有读多写少的特征,所以我们使用redis进行缓存。

第3级:支付配置信息;这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以我们使用本地内存进行缓存。

使用本地内存缓存有一个数据同步问题,因为配置信息缓存在内存中,而本地内存无法感知到配置信息在数据库的修改,这样会造成数据库中数据和本地内存中数据不一致的问题。

为了解决此问题,我们开发了一个高可用的消息推送平台,当配置信息被修改时,我们可以使用推送平台,给支付系统所有的服务器推送配置文件更新消息,服务器收到消息会自动更新配置信息,并给出成功反馈。

二、订单中心属性查询需求分析

在进行架构讨论之前,先来对业务进行简要分析,看哪些属性上有查询需求。

A1:分库看业务不同的需要,有自研的,也有使用第三方的。看自身的需要,中间件不一定是最好的,但它能够给你很多借鉴。

二、订单ID

订单系统的ID必须具有全局唯一的特征,最简单的方式是利用数据库的序列,每操作一次就能获得一个全局唯 一的自增ID,如果要支持每秒处理10万订单,那每秒将至少需要生成10万个订单ID,通过数据库生成自增ID显然无法完成上述要求。所以我们只能通过内 存计算获得全局唯一的订单ID。

JAVA领域最著名的唯一ID应该算是UUID了,不过UUID太长而且包含字母,不适合作为订单ID。通过反复比较与筛选,我们借鉴了Twitter的Snowflake算法,实现了全局唯一ID。下面是订单ID的简化结构图:

图片 6

 

上图分为3个部分:

  1. 时间戳

这里时间戳的粒度是毫秒级,生成订单ID时,使用System.currentTimeMillis()作为时间戳。

  1. 机器号

每个订单服务器都将被分配一个唯一的编号,生成订单ID时,直接使用该唯一编号作为机器号即可。

  1. 自增序号

当在同一服务器的同一毫秒中有多个生成订单ID的请求时,会在当前毫秒下自增此序号,下一个毫秒此序号继续从0开始。比如在同一服务器同一毫秒有3个生成订单ID的请求,这3个订单ID的自增序号部分将分别是0,1,2。

上 面3个部分组合,我们就能快速生成全局唯一的订单ID。不过光全局唯一还不够,很多时候我们会只根据订单ID直接查询订单信息,这时由于没有uid,我们 不知道去哪个分库的分表中查询,遍历所有的库的所有表?这显然不行。所以我们需要将分库分表的信息添加到订单ID上,下面是带分库分表信息的订单ID简化 结构图:

图片 7

 

我们在生成的全局订单ID头部添加了分库与分表的信息,这样只根据订单ID,我们也能快速的查询到对应的订单信息。

分库分表信息具体包含哪些内容?第一部分有讨论到,我们将订单表按uid维度拆分成了8个数据库,每个数据库10张表,最简单的分库分表信息只需一个长度为2的字符串即可存储,第1位存数据库编号,取值范围1到8,第2位存表编号,取值范围0到9。

还是按照第一部分根据uid计算数据库编号和表编号的算法,当uid=9527时,分库信息=1,分表信息=7,将他们进行组合,两位的分库分表信息即为”17”。具体算法流程参见下图:

图片 8

上述使用表编号作为分表信息没有任何问题,但使用数据库编号作为分库信息却存在隐患,考虑未来的扩容需求,我们需要将8库扩容到16库,这时取值范围1到8的分库信息将无法支撑1到16的分库场景,分库路由将无法正确完成,我们将上诉问题简称为分库信息精度丢失。

为解决分库信息精度丢失问题,我们需要对分库信息精度进行冗余,即我们现在保存的分库信息要支持以后的扩容。这里我们假设最终我们会扩容到64台数据库,所以新的分库信息算法为:

分库信息 = (uid / 10) % 64 + 1

当uid=9527时,根据新的算法,分库信息=57,这里的57并不是真正数据库的编号,它冗余了最后扩展到64台数据库的分库信息精度。我们当前只有8台数据库,实际数据库编号还需根据下面的公式进行计算:

实际数据库编号 = (分库信息 - 1) % 8 + 1

当uid=9527时,分库信息=57,实际数据库编号=1,分库分表信息=”577”。

由于我们选择模64来保存精度冗余后的分库信息,保存分库信息的长度由1变为了2,最后的分库分表信息的长度为3。具体算法流程也可参见下图:

图片 9

如上图所示,在计算分库信息的时候采用了模64的方式冗余了分库信息精度,这样当我们的系统以后需要扩容到16库,32库,64库都不会再有问题。

上面的订单ID结构已经能很好的满足我们当前与之后的扩容需求,但考虑到业务的不确定性,我们在订单ID的最前方加了1位用于标识订单ID的版本,这个版本号属于冗余数据,目前并没有用到。下面是最终订单ID简化结构图:

图片 10

Snowflake算法:github.com/twitter/snowflake

订单中心业务分析

订单中心是一个非常常见的“多key”业务,主要提供订单的查询与修改的服务,其核心元数据为:

Order(oid, buyer_uid, seller_uid, time,money, detail…);

其中:

  • oid为订单ID,主键

  • buyer_uid为买家uid

  • seller_uid为卖家uid

  • time, money, detail, …等为订单属性

数据库设计上,一般来说在业务初期,单库单表就能够搞定这个需求,典型的架构设计为:

图片 11

  • order-center:订单中心服务,对调用者提供友好的RPC接口

  • order-db:对订单进行数据存储

随着订单量的越来越大,数据库需要进行水平切分,由于存在多个key上的查询需求,用哪个字段进行切分,成了需要解决的关键技术问题:

  • 如果用oid来切分,buyer_uid和seller_uid上的查询则需要遍历多库

  • 如果用buyer_uid或seller_uid来切分,其他属性上的查询则需要遍历多库

总之,很难有一个完全之策,在展开技术方案之前,先一起梳理梳理查询需求。

2、运营侧需求解决方案

五、假设没有oid

订单中心,假设没有oid上的查询需求,而只有buyer_uid和seller_uid上的查询需求,就蜕化为一个“多对多”的业务场景,对于“多对多”的业务,水平切分应该使用“数据冗余法”

图片 12

如上图所示:

  • 当有订单生成时,通过buyer_uid分库,oid中融入分库基因,写入DB-buyer库

  • 通过线下异步的方式,通过binlog+canal,将数据冗余到DB-seller库中

  • buyer库通过buyer_uid分库,seller库通过seller_uid分库,前者满足oid和buyer_uid的查询需求,后者满足seller_uid的查询需求

数据冗余的方法有很多种:

  • 服务同步双写

  • 服务异步双写

  • 线下异步双写(上图所示,是线下异步双写)

不管哪种方案,因为两步操作不能保证原子性,总有出现数据不一致的可能,高吞吐分布式事务是业内尚未解决的难题此时的架构优化方向,并不是完全保证数据的一致,而是尽早的发现不一致,并修复不一致

最终一致性,是高吞吐互联网业务一致性的常用实践。保证数据最终一致性的方案有三种:

  • 冗余数据全量定时扫描

  • 冗余数据增量日志扫描

  • 冗余数据线上消息实时检测

这些方案细节在“多对多”业务水平拆分的文章里详细展开分析过,便不再赘述。

本次分享内容主要分为4个部分:创业之初、高速发展、智能时代、总结。

一、库分表

在redis,memcached等缓存系统盛行的互联网时代,构建一个支撑每秒十万只读的系统并不复杂,无非是通过 一致性哈希扩展缓存节点,水平扩展web服务器等。支付系统要处理每秒十万笔订单,需要的是每秒数十万的数据库更新操作(insert加update), 这在任何一个独立数据库上都是不可能完成的任务,所以我们首先要做的是对订单表(简称order)进行分库与分表。

在进行数据库操作时,一般都会有用户ID(简称uid)字段,所以我们选择以uid进行分库分表。

分 库策略我们选择了“二叉树分库”,所谓“二叉树分库”指的是:我们在进行数据库扩容时,都是以2的倍数进行扩容。比如:1台扩容到2台,2台扩容到4 台,4台扩容到8台,以此类推。这种分库方式的好处是,我们在进行扩容时,只需DBA进行表级的数据同步,而不需要自己写脚本进行行级数据同步。

光是有分库是不够的,经过持续压力测试我们发现,在同一数据库中,对多个表进行并发更新的效率要远远大于对一个表进行并发更新,所以我们在每个分库中都将order表拆分成10份:order_0,order_1,….,order_9。

最后我们把order表放在了8个分库中(编号1到8,分别对应DB1到DB8),每个分库中10个分表(编号0到9,分别对应order_0到order_9),部署结构如下图所示:

图片 13

根据uid计算数据库编号:

数据库编号 = (uid / 10) % 8 + 1

根据uid计算表编号:

表编号 = uid % 10

当uid=9527时,根据上面的算法,其实是把uid分成了两部分952和7,其中952模8加1等于1为数据库编号,而7则为表编号。所以uid=9527的订单信息需要去DB1库中的order_7表查找。具体算法流程也可参见下图:

图片 14

 

有了分库分表的结构与算法最后就是寻找分库分表的实现工具,目前市面上约有两种类型的分库分表工具:

  1. 客户端分库分表,在客户端完成分库分表操作,直连数据库
  2. 使用分库分表中间件,客户端连分库分表中间件,由中间件完成分库分表操作

这两种类型的工具市面上都有,这里不一一列举,总的来看这两类工具各有利弊。客户端分库分表由于直连数据库,所以性能比使用分库分表中间 件高15%到20%。而使用分库分表中间件由于进行了统一的中间件管理,将分库分表操作和客户端隔离,模块划分更加清晰,便于DBA进行统一管理。

我们选择的是在客户端分库分表,因为我们自己开发并开源了一套数据层访问框架,它的代号叫“芒果”,芒果框架原生支持分库分表功能,并且配置起来非常简单。

  • 芒果主页:mango.jfaster.org
  • 芒果源码:github.com/jfaster/mango

三、前台与后台分离的架构设计

如果前台业务和后台业务公用一批服务和一个数据库,有可能导致,由于后台的“少数几个请求”的“批量查询”的“低效”访问,导致数据库的cpu偶尔瞬时100%,影响前台正常用户的访问(例如,订单查询超时)。

图片 15

前台与后台访问的查询需求不同,对系统的要求也不一样,故应该两者解耦,实施“前台与后台分离”的架构设计

前台业务架构不变,站点访问,服务分层,数据库水平切分。

后台业务需求则抽取独立的web/service/db来支持,解除系统之间的耦合,对于“业务复杂”“并发量低”“无需高可用”“能接受一定延时”的后台业务:

  • 可以去掉service层,在运营后台web层通过dao直接访问数据层

  • 可以不需要反向代理,不需要集群冗余

  • 可以通过MQ或者线下异步同步数据,牺牲一些数据的实时性

  • 可以使用更契合大量数据允许接受更高延时的“索引外置”或者“HIVE”的设计方案

解决了后台业务的访问需求,问题转化为,前台的oid,buyer_uid,seller_uid如何来进行数据库水平切分呢?

多个维度的查询较为复杂,对于复杂系统设计,可以逐步简化。

讲师介绍

编辑推荐:架构技术实践系列文章(部分):

  • 梁阳鹤:每秒处理10万订单乐视集团支付架构
  • 沈辉煌:亿级日PV的魅族云同步的核心协议与架构实践
  • 携程Docker最佳实践
  • 游戏研发与运营环境Docker化
  • 当当网高可用架构之道
  • 黄哲铿:应对电商大促峰值的九个方法
  • 1号店交易系统架构如何向「高并发高可用」演进
  • 从C10K到C10M高性能网络的探索与实践
  • 服务化架构的演进与实践
  • 1号店架构师王富平:一号店用户画像系统实践
  • 唯品会官华:实现电商平台从业务到架构的治理体系
  • 沈剑:58同城数据库架构最佳实践
  • 荔枝FM架构师刘耀华:异地多活IDC机房架构
  • UPYUN的云CDN技术架构演进之路
  • 初页CTO丁乐:分布式以后还能敏捷吗?
  • 陈科:河狸家运维系统监控系统的实现方案
  • 途牛谭俊青:多数据中心状态同步&两地三中心的理论
  • 云运维的启示与架构设计
  • 魅族多机房部署方案
  • 艺龙十万级服务器监控系统开发的架构和心得
  • 京东商品详情页应对“双11”大流量的技术实践
  • 架构师于小波:魅族实时消息推送架构

六、oid/buyer_uid/seller_uid同时存在

通过上述分析:

  • 如果没有seller_uid,“多key”业务会蜕化为“1对多”业务,此时应该使用“基因法”分库:使用buyer_uid分库,在oid中加入分库基因

  • 如果没有oid,“多key”业务会蜕化为“多对多”业务,此时应该使用“数据冗余法”分库:使用buyer_uid和seller_uid来分别分库,冗余数据,满足不同属性上的查询需求 

  • 如果oid/buyer_uid/seller_uid同时存在,可以使用上述两种方案的综合方案,来解决“多key”业务的数据库水平切分难题

运营小伙伴原来在单库的时候,因为复杂SQL跑的特别慢,导致无法统计特别,分完库以后,他连Join都用不了,更无法查询统计了。

三、最终一致性

到目前为止, 我们通过对order表uid维度的分库分表,实现了order表的超高并发写入与更新,并能通过uid和订单ID查询订单信息。但作为一个开放的集团支 付系统,我们还需要通过业务线ID(又称商户ID,简称bid)来查询订单信息,所以我们引入了bid维度的order表集群,将uid维度的order 表集群冗余一份到bid维度的order表集群中,要根据bid查询订单信息时,只需查bid维度的order表集群即可。

上面的方案虽然 简单,但保持两个order表集群的数据一致性是一件很麻烦的事情。两个表集群显然是在不同的数据库集群中,如果在写入与更新中引入强一致性的分布式事 务,这无疑会大大降低系统效率,增长服务响应时间,这是我们所不能接受的,所以我们引入了消息队列进行异步数据同步,来实现数据的最终一致性。当然消息队 列的各种异常也会造成数据不一致,所以我们又引入了实时监控服务,实时计算两个集群的数据差异,并进行一致性同步。

下面是简化的一致性同步图:

图片 16

数据库水平切分是一个很有意思的话题,不同业务类型,数据库水平切分的方法不同。

此外我们进行了推送的多通道化,从上图可以看到,我们针对每个司机选取了两种推送通道,同时我们也建议大家在做推送消息时采取这种方案。拿小米的手机来说,“小米”推送通道的到达率是最高的,但小米的通道在华为的手机上,到达率不如“个推”的推送到达率高。我们就会根据司机的机型来选取一个到达率最高的三方通道。同时在设计上不能有单点,假如说小米的通道出现了问题,那我们的服务就不可用了,司机接收不到订单,用户的需求就没法得到满足。所以我们还有一个自研渠道TCP通道,这个TCP通道除了和我们三方通道做一个双通道保活外,它还可以做一些数据的上传。

本篇将以“订单中心”为例,介绍“多key”类业务,随着数据量的逐步增大,数据库性能显著降低,数据库水平切分相关的架构实践。

第二次技术演进奔跑中的火车换轮子

前台访问,最典型的有三类需求:

  • 订单实体查询:通过oid查询订单实体,90%流量属于这类需求

  • 用户订单列表查询:通过buyer_uid分页查询用户历史订单列表,9%流量属于这类需求

  • 商家订单列表查询:通过seller_uid分页查询商家历史订单列表,1%流量属于这类需求

 

前台访问的特点:吞吐量大,服务要求高可用,用户对订单的访问一致性要求高,商家对订单的访问一致性要求相对较低,可以接受一定时间的延时。

问题分析-“任何脱离业务架构的设计都在耍流氓”

四、假设没有seller_uid

订单中心,假设没有seller_uid上的查询需求,而只有oid和buyer_uid上的查询需求,就蜕化为一个“1对多”的业务场景,对于“1对多”的业务,水平切分应该使用“基因法”。

再次回顾一下,什么是分库基因?

通过buyer_uid分库,假设分为16个库,采用buyer_uid%16的方式来进行数据库路由,所谓的模16,其本质是buyer_uid的最后4个bit决定这行数据落在哪个库上,这4个bit,就是分库基因。

也再次回顾一下,什么是基因法分库?

在订单数据oid生成时,oid末端加入分库基因,让同一个buyer_uid下的所有订单都含有相同基因,落在同一个分库上。 

图片 17

如上图所示,buyer_uid=666的用户下了一个订单:

  • 使用buyer_uid%16分库,决定这行数据要插入到哪个库中

  • 分库基因是buyer_uid的最后4个bit,即1010

  • 在生成订单标识oid时,先使用一种分布式ID生成算法生成前60bit(上图中绿色部分)

  • 将分库基因加入到oid的最后4个bit(上图中粉色部分),拼装成最终64bit的订单oid(上图中蓝色部分)

通过这种方法保证,同一个用户下的所有订单oid,都落在同一个库上,oid的最后4个bit都相同,于是:

  • 通过buyer_uid%16能够定位到库

  • 通过oid%16也能定位到库

立体化监控

后台访问,根据产品、运营需求,访问模式各异:

  • 按照时间,架构,商品,详情来进行查询

后台访问的特点:运营侧的查询基本上是批量分页的查询,由于是内部系统,访问量很低,对可用性的要求不高,对一致性的要求也没这么严格,允许秒级甚至十秒级别的查询延时。 

今天很荣幸给大家介绍58速运从艰苦创业到成为同城货运行业领头人的整个系统演进过程。简单来说我们的业务是做同城货运,比如您去买一个大型家具,自己的家用车肯定是装不下的,这时你可能需要找路边的小型面包车或者金杯车来帮你搬运。一般来讲,很容易遇到黑车,而且价格不标准,我们做的这个行业就是将这种传统的黑车行业进行线上化,在产品形态上可理解为滴滴打车的出租车版。

这两类不同的业务需求,应该使用什么样的架构方案来解决呢?

每个业务都有特殊的业务字段,单标数据量已经到达了千万级,每增加一个字段和修改一个字段,都需要耗费很长的时间,而且会造成锁库导致系统异常。

我们的水平拆分有两种方法:

订单-模型运用

我们拿数据库的Binlog日志看了一下,根据用户ID的访问大概是占99%,根据用户姓名、手机号、Email的这些属性的查询大概只有在1%的量。运营会根据年龄、性别、头像、登录时间、注册时间这些复杂的数据去做统计和分析。

这里举个非常普遍的例子,其他业务线小伙伴在上线时,不小心写了一个慢SQL,一个慢SQL就会把数据库的所有连接占满,导致所有的业务全部挂掉了,当时听到的最多的反馈是:什么情况,怎么你们又挂了。

上图为智能模型训练图,首先我们会将订单信息、用户信息、司机信息、客司关系信息、订单总体推送、司机接单等场景信息统一上传到大数据平台,通过这种归一化分桶、XGBoost、特征组合、独热编码等将这些数据分析为特征数据。

2015年我们进入了高速发展的阶段,市场上出现了蓝犀牛、1号货的、云鸟的等多个强劲的竞争对手。各方都是争分夺秒,一个系统、功能,我需要抓紧把它给迭代上来,谁也不能比谁落后。另外就是补贴大战,各大竞争对手投放大量的订单补贴,使得整体运营成本呈现水高船涨的趋势。

凌晨时间业务仍然有订单,会影响到用户访问。需要给用户发公告。停服迁移如果失败,无法向业务方解释,会丧失信任。

我们将所有的系统都按服务模块进行了拆分,比如说结算、充值、推送、司机任务等,现在大概已有20+个服务,每个服务都有独立的数据库,有独立的负责人。这样就可以做到我自己的代码我自己来写,别人都不允许去插手。

策略分流+监测

人是要不断地招聘,如果人手连业务需求都忙不过来,肯定是没有时间来做技术优化的。在业务需求实现的过程中穿插一些技术优化的项目,带领团队的小伙伴实现技术成长。建立技术图书馆,让大家自我学习。团队内部技术分享交流,参加技术大会等手段,提升团队的技术能力。和业务的小伙伴沟通,我们需要做一些技术优化,做这些技术优化可以给我们带来哪些收益,沟通和协调这些软实力也是架构师需要具备的重要技能。公司不同的阶段,需要采用不同的应对方案,创业初期,生存最重要,架构设计的再好,业务发展不起来也无法起到作用。

拿非Patition key和uid做一个索引表,这样我直接通过这个表和Patition key进来后先去找一下uid,这样就可以找到这个uid在哪个库,但是增加了一次数据库的查询。

业务高速发展,数据量急速增长

调用跟踪系统

基因法

58速运整体的订单增长非常迅速,在成立三个月以后,每天的单已达到了1万+,系统性能已成为瓶颈。

派单-智能时代

QA

索引表法:非patition key与uid建立索引表

智能系统需要有不同的算法在线上实验,当我们一些新算法研发完成以后,肯定不能用100%的流量在线上进行验证算法的可行性,如果有问题,会对线上业务产生影响。我们一般取5%或10%的流量在线上验证。一般根据用户手机号、设备码、用户属性等,以及取模、集合等方式。验证线上算法验证时,如何实时的监测算法的效果,避免错误算法对线上业务造成影响?

反思

针对分析出来特征数据,我们需要对它进行训练,如:订单价格、订单距离等特征在整个订单派单中起到的权重。因为特征很多,计算出来的权重可能并不是一个完美的解,只能说是近优、最优的一个解法,通过不断地迭代优化,最终训练出来最终的模型。

分库分表

智能模型训练

接入外键索引,如ES/Solr提供搜索服务。

另外运营需要对现在的市场和用户进行分析,整体的运营需求分析逐渐复杂。

Q2:刚才您说订单推送多元化,是按照城市的范围来推送的还是按照指定哪个区域来推送的?比如说你想要做山东省的业务,就指定地推这样?

特征计算

四、总结不同的阶段采用不同的架构,技术的重点跟随业务转变。订单的推送通道,建议使用双通道,保证推送的到达率。数据库的水平拆分,在资源允许的情况下,强烈建议分库。算法线上分流验证必须要有实时的监控和自动流量切换。监控很重要,第一时间发现问题,减少影响

利器-监控平台

再牛逼的算法,也需要稳定的系统来支撑;业务出现异常,我们肯定要第一时间知晓的;提高问题排查效率,就是在挽救损失。

冗余后台库:通过MQ/Canal实时同步到后台库

如上图所示,用户在APP的每个步骤、运用了哪个算法,我们都会将用户的ID、采用的算法ID通过日志上报的报到统计平台。业务监控平台会实时进行监控,对于出现异常的算法就自动关闭分流。

这时的系统架构是非常简单的,我们称之为“石器时代”,当时所有的订单调度的逻辑放在一个Jar包,然后通过MQTT服务将订单推送到司机的APP上。当时的订单调度是一个订单搜索附近的司机,然后由近到远的距离将订单推送出去,司机抢单后即中单。因为在创业阶段,我们需要吸引客户、司机,每单都会有补贴。

A2:不是,首先像这种车辆的调度,你肯定不能离得特别远,如果你那个订单和司机的距离超过了5公里,基本上这个订单成交的概率是非常地低的。我们是以订单的起始点,去搜索附近的司机,这个没有区域的限制。

最开始创业时团队只有几个人,工程都集中在几个集群中,后面扩大到30多个人时,大家都集中在这些集群上去开发,平均每天都要进行多次上线,遇到了个最核心、最痛点的问题,代码合并,合并代码就意味着出错的几率大大提升,当时BUG率很高。

策略服务的细化智能模型的接入智能的分流框架三、智能时代:效率、精准

拆分后的问题:

第一次技术演进迁库、集群解耦

业务初期我们一个库可以完成支撑所有的访问;随着数据量的增长,我们可以做了一些读写的分离,把一些读取SQL放在从库上,但这里给大家一个建议——订单状态的读取尽量不要在从库上读,网络一抖动,你的订单状态就很可能会出现不一致情况;加上从库,当表的数据量达到千万级,查询的性能依然会下降,这样我们就需要去做水平拆分和垂直拆分。水平拆分比较简单,大家也容易理解,而垂直拆分就是比如说我把一个用户10个最常用的属性放到一个组表里,把不常用的属性放到另外一张表里面去,这样可以减少I/O的操作,也可以提高整体的产品性能。数据库水平拆分以后,再给拆分后的库增加从库。

平台补贴是不是真的起到了作用,然后我们到底需要补多少钱才能帮助用户完成订单,如何去尽量满足用户的需求。每个新用户进入平台是有成本的,一个用户的成本在几十甚至到一百块左右,如何满足用户的需求,让用户持续的留在平台中。平台的司机良莠不齐,司机的收益应如何分配?第三次技术演进:战斧项目

二、高速发展:稳定高效

监控可以说是整个架构演进过程中非常重要的部分。

们采用的方案:将订单表单独地拆离出来,放在单独的数据库里,两个数据库之间使用双向同步。双向同步需要解决时问题:

1、前端解决方案

我们把Patition key与uid的映射关系放在缓存里面去,只会第一次比较慢,后面都会从缓存中取,而且这个缓存基本上不用淘汰。

缓存映射法:非patition key与uid映射关系放入缓存,缓存命中率高

痛点:

部分查询变慢了:非patition key查询,需要遍历全部库

下单阶段:在用户下单时,我们会采用这种用户订单定价的模型,观察这个订单所在的商圈的运力饱和度,如果司机少,而订单需求多,我们会进行一个订单的调价,推送阶段:系统推送的过程中,会根据司机的接单意愿来捞取。有的司机喜欢高价格订单,有的司机喜欢短程订单,有的司机喜欢去中关村等。我们会根据订单与司机意愿的匹配程度进行优先推送的排序。抢单阶段:先预估这个订单的接单人数,计算出来订单的价值,如果订单的价值高、那么这个订单不会发放补贴了,同时会扣取司机的一些积分或优先抢单次数等。如果订单价值比较低,会给这个订单适当地增加补贴,来确保订单的完成。指派阶段:当司机抢完单以后,我们会根据所有司机历史完成订单的数据,取司机的质量,来决定哪个司机中单,保证订单尽可能完成。订单完成阶段:订单完成了以后预测这个用户的流失概率,如果可能流失,会送一些券或者其他权益吸引用户留在平台。

这时我们进行了第二次技术演进,我们称之为“进行了奔跑中的火车换轮子”,我们进行了服务化解耦;缓存、分库分表,提升系统性能;接入大数据平台,进行复杂需求的分析。

非patition key生成uid

这时的订单调度,被称为探索阶段,初期的距离推送效果有限,谁抢到谁就中单,司机的服务质量我们没有办法去评判,补贴也是大众化的。所以我们自己研究了一个按象限推送的方法:1、首先我先推送一个很短的距离,比如说我先把一公里以内的所有司机都推送一遍,这时我是不给补贴的,当推完一公里以后没有人抢,或者是抢的人非常的少,我会按象限去推。2、在第一个象限,我给一块钱补贴,如果没人抢,第二个象限给两块钱补贴,第三个象限给三块钱,这样逐步地去增加。最后当司机抢了单,我们会根据司机的好评、完成率这些方面选择一个最优质的司机。

一个最简单的方案就是停服,把所有的服务停掉,然后把数据库抽离出来,相对来讲这是成本最简单的。但是停服会产生的影响:

针对以上痛点,我们做了第一次的技术引进——迁库、集群拆分。

一、创业之初-快速迭代试错

本文根据胡显波老师在〖Gdevops 2017全球敏捷运维峰会广州站〗现场演讲内容整理而成。

通过MQ或者是Canal读取MySQL的binlog,将几个前台的数据库实时地同步到后台库里去,后台库不对前台业务提供服务,仅供运营侧查询。注意这个后台库是千万不能用于现场生产的,因为运营会在上面做一些复杂的慢查询,数据库的响应会非常慢。

存在问题:

前面提到数据库性能已经成为瓶颈了,所以这里以一个用户服务给大家讲一下我们的分库分表是怎么做的。

业务化的指标监控,渠道转化率、渠道取消率、渠道推送数量、异常订单数量等等,如果出现异常,第一时间预警。

胡显波,58到家技术经理/58速运后端架构总负责人。14年7月加入58到家,先后负责58到家APP、58小时工、58美甲等,见证了58到家飞速发展。14年11月负责58速运整体业务,带领团队小伙伴支撑了速运业务日订单从0~50W的飞速增长。

我们在2015年时,订单增长了好几倍,同时每个订单大概会推送给50多个司机,这个数据量级,数据量告诉的增长。

上图在智能派单时代的系统架构图。用户在下完单以后,订单会进入到我们整体的策略系统,它包含推送系统、补贴系统、价格系统、任务系统等,然后通过特征匹配系统,计算出一个最优的订单调度解,将这个订单推送到司机的单队列引擎和订单的排序策略引擎,最终通过我们的推送服务将订单推送给司机。

经过多次的迁移,将原有的数据库按照业务划分成了订单库、结算库、配置库和轨迹库等,每个数据库会根据业务量容量的大小来配置数据库物理机的内核、内存,减少成本。

Q1:58速运在分库时用的是什么中间件?

如图所示,这是我们的计算链,里面包含多个Stage,包含准备阶段、转化阶段、取数阶段和计算阶段,每一个阶段都有自己独立的线程池,根据每个阶段的特征设置核心线程数,同时整个计算链做到了可插拔的形式,方便业务调整。

到了2016年,竞争对手基本上已经被消灭了,58速运已经成为行业的领头者了,如何使用更少的补贴获取最大化的收益?

范围法:用户ID在1K万以下的放到一个库,1K万~2KW以上的放到另外一个库,这样切分简单,扩容也方便,但是会存在数据库之间的负载不均匀。哈希法:根据用户ID进行哈希运算,切分简单,整体负载比较均衡的,平滑迁移可能需要我们去解决的难点。

快速迭代多人维护一套工程,效率差,BUG频发

运营分析需求越来越复杂

大数据平台

速运在2014年是作为58集团下20多个孵化业务中的其中之一,那个时期基本是平均三个星期一个业务孵化上线,当时有20多个业务孵化同时进行。这个时间我们不断的试错,不断去寻找58同城新的增长点。

目前已经做到的监控包含:关键字、接口、流量、端口,JVM、CPU、线程、缓存、DB所有的监控等等,同时还有服务治理,当服务节点发生异常,实时切换。

系统不稳定,一个慢SQL,全业务受影响

我们进行了第三次的技术引进,我们称之为是战斧项目,项目的定义:精准、高效。我们做了以下优化:

业务增长迅猛,数据库已成瓶颈

原文来自微信公众号:DBAplus社群

Q3:老师您好,我想问一下您提到了几次架构的演进,因为整个的产品迭代过程当中,整个业务需求一直持续地在演进,你的技术架构也在演进,那么整个的研发体系是专门有一个团队在做这一块的技术架构演进,还是跟着你的业务团队一起在做这个架构演进?

派单-石器时代

使用大数据平台,通过MySQL的binlog和日志上报,将数据读取到大数据平台进行实时地分析,供运营查询。

做完水平拆分以后,我们遇到了一个新的问题,实用patition key水平拆分,非patition key查询需要扫库,性能反而变慢了。

多业务并存,订单表索引多,性能下降

从上图中的大家可以看到,我们所有的服务都基于在一个数据库来运行,这个系统之间只需要通过一些简单的tag标记就可以区分开业务,系统迭代非常快。新孵化的业务,增加一些简单的业务逻辑就能实现这个产品的快速上线,我们在两周内实现了速运用户、商家的APP以及的后端的产品上线。

主键冲突:速运的订单会标记一个比较特殊的标记ID,与其它的业务线区分开发,这样就可以保证它在双向同步时不会出现主键冲突的问题。更新覆盖:update的操作在同步的过程中因为时间差的问题可能存在写覆盖的情况,我们采用订单日志的记录,迁库完成后做数据的校验。

特征数据中有40多万个特征,每个订单需要推送给很多个司机,需要进行进行上万次的运算,需要在几十毫秒内给出计算结果,如何保证计算的高性能呢?我们采用的是这种阶段性事件驱动的计算方式来最大化提高并行计算的能力。

订单模型的运用:

派单-铁器时代

当时有很多个业务在同时孵化,多业务并存,每一个业务都会根据它自己的业务需求去在订单表中建立索引,结果索引越来越多,整体的性能也越来越差。

调用跟踪系统,很多互联网公司都已经在使用,调用跟踪系统目的是需要看到的是APP发起的每个请求在整个Service后端走过的所有过程,效果下图所示,可以监控到每一步所调用的服务和耗时。

运营需求无法实现:各种维度统计,没办法联表查询

A2:肯定是随着业务的发展来进行架构演进,如何平衡业务需求的实现和架构的演进是架构师的职责所在。个人的几点建议:

根据Patition key生成一个uid,这个需要一定的生成技巧,同时这个可能有主键冲突的风险。

根据非Patition key的其中部分基因生成一个字段,如下图:

订单字段冗余,新增和修改字段非常痛苦

在这里水平拆分要重点提一下,就是如果资源允许,水平拆分还是建议分库。数据库的性能瓶颈也是会受到硬件设备和网络IO的影响,如果访问量的持续增加,数据库还是会成为瓶颈。

本文由澳门威斯尼人平台登录发布于服务器&运维,转载请注明出处:日订单峰值破40万,每秒处理10万订单乐视集团支付架构

相关阅读