随着乐视硬件抢购的不断升级,乐视集团支付面临的请求压力百倍乃至千倍的暴增。作为商品购买的最后一环,保证用户快速稳定的完成支付尤为重要。所以在15年11月,我们对整个支付系统进行了全面的架构升级,使之具备了每秒稳定处理10万订单的能力。为乐视生态各种形式的抢购秒杀活动提供了强有力的支撑。
在进行数据库操作时,一般都会有用户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),部署结构如下图所示:
根据uid计算数据库编号:数据库编号 = (uid / 10) % 8 + 1根据uid计算表编号:表编号 = uid % 10当uid=9527时,根据上面的算法,其实是把uid分成了两部分952和7,其中952模8加1等于1为数据库编号,而7则为表编号。所以uid=9527的订单信息需要去DB1库中的order_7表查找。具体算法流程也可参见下图:
有了分库分表的结构与算法最后就是寻找分库分表的实现工具,目前市面上约有两种类型的分库分表工具:客户端分库分表,在客户端完成分库分表操作,直连数据库使用分库分表中间件,客户端连分库分表中间件,由中间件完成分库分表操作这两种类型的工具市面上都有,这里不一一列举,总的来看这两类工具各有利弊。客户端分库分表由于直连数据库,所以性能比使用分库分表中间件高15%到20%。而使用分库分表中间件由于进行了统一的中间件管理,将分库分表操作和客户端隔离,模块划分更加清晰,便于DBA进行统一管理。我们选择的是在客户端分库分表,因为我们自己开发并开源了一套数据层访问框架,它的代号叫“芒果”,芒果框架原生支持分库分表功能,并且配置起来非常简单。芒果主页:mango.jfaster.org芒果源码:github.com/jfaster/mango
上图分为3个部分:时间戳这里时间戳的粒度是毫秒级,生成订单ID时,使用System.currentTimeMillis()
作为时间戳。机器号每个订单服务器都将被分配一个唯一的编号,生成订单ID时,直接使用该唯一编号作为机器号即可。自增序号当在同一服务器的同一毫秒中有多个生成订单ID的请求时,会在当前毫秒下自增此序号,下一个毫秒此序号继续从0开始。比如在同一服务器同一毫秒有3个生成订单ID的请求,这3个订单ID的自增序号部分将分别是0,1,2。上面3个部分组合,我们就能快速生成全局唯一的订单ID。不过光全局唯一还不够,很多时候我们会只根据订单ID直接查询订单信息,这时由于没有uid,我们不知道去哪个分库的分表中查询,遍历所有的库的所有表?这显然不行。所以我们需要将分库分表的信息添加到订单ID上,下面是带分库分表信息的订单ID简化结构图:
六、粗细管道
黑客攻击,前端重试等一些原因会造成请求量的暴涨,如果我们的服务被激增的请求给一波打死,想要重新恢复,就是一件非常痛苦和繁琐的过程。
举个简单的例子,我们目前订单的处理能力是平均10万下单每秒,峰值14万下单每秒,如果同一秒钟有100万个下单请求进入支付系统,毫无疑问我们的整个支付系统就会崩溃,后续源源不断的请求会让我们的服务集群根本启动不起来,唯一的办法只能是切断所有流量,重启整个集群,再慢慢导入流量。
我们在对外的web服务器上加一层“粗细管道”,就能很好的解决上面的问题。
下面是粗细管道简单的结构图:
请看上面的结构图,http请求在进入web集群前,会先经过一层粗细管道。入口端是粗口,我们设置最大能支持100万请求每秒,多余的请求会被直接抛弃掉。出口端是细口,我们设置给web集群10万请求每秒。剩余的90万请求会在粗细管道中排队,等待web集群处理完老的请求后,才会有新的请求从管道中出来,给web集群处理。这样web集群处理的请求数每秒永远不会超过10万,在这个负载下,集群中的各个服务都会高校运转,整个集群也不会因为暴增的请求而停止服务。
如何实现粗细管道?nginx商业版中已经有了支持,相关资料请搜索
nginx max_conns,需要注意的是max_conns是活跃连接数,具体设置除了需要确定最大TPS外,还需确定平均响应时间。