金币(积分)商城架构漫谈

开篇

金币(积分)商城(下称“商城”)是众多App内的一个产品,随着App使用的用户愈来愈多,商城对于用户留存的提高,扮演着重要的角色;作为提升用户黏性的核心产品,在拥有很好用户体验的同时,也必须存在着一个高效、稳定的系统。node

分库分表

商城,是一个基于虚拟货币(下称“金币”)进行运营的产品,也就意味着,咱们须要给用户发放金币,用于用户兑换各类奖品。咱们须要详细记录用户金币的收支状况,并提供给用户查询。在redis、memcache盛行的时代,构建一个几十万QPS的只读系统并不复杂,须要作到:无状态服务+多级缓存,而且可以进行水平扩展,应该就差很少了。而商城须要记录每秒十万的用户行为,须要的是每秒数十万(这里翻倍了)的数据读写(insert&update)操做,这种量级是没法在单实例数据上完成的,那么该如何分库分表。redis

分库分表原则

Tip 1 . 在作设计时,首先要明确3个事情算法

  1. 业务场景,不要空谈设计,场景是什么
  2. 目标,系统须要作到的目标是什么
  3. 分析上述两点,获得什么结论

那么,对于用户行为数据的场景是:1.用户A查询本身的全部金币记录(不能查别人的),2.查看商城内金币数量分布状况。对于第二个场景能够直接经过大数据进行统计分析(不进行详细解读)。对于第一个场景,系统须要作到的目标是:用户A只进行一次查询,就能够获得全部数据(不考虑分页场景)。分析上述两点,获得结论:按用户id进行分库分表。(分析过程有些磨叽了,哈哈,忍着)
原则明确后,可以开始进行分库分表吗?不能。须要进一步确认,如何分?分多少?扩容成本?对于数据库扩容,咱们选择以2的N次幂进行扩容,这种方式的好处是,进行扩容时,只须要将数据copy一份就能够,上层应用增长数据库节点,无需考虑数据迁移问题(可靠性高),很差的地方是,会产生脏数据,这个问题并没太多影响,按照扩容后规则,删除便可。对于分表,咱们将金币记录在每一个库中拆成5份。数据库

Tip 2 .为何要进行分库分表segmentfault

  1. 服务器资源(cpu、磁盘、内存、IO)遇到瓶颈
  2. 数据量变大,数据操做(crud)开销变大

部署图以下:
图片描述缓存

算法

数据编号=uid%4,表编号=uid%5服务器

算法流程图以下:
图片描述并发

目前业内对分库实现方案有两种异步

  1. 客户端分库分表,在客户端完成分库分表操做,直接连接数据库。
  2. 中间件(例如:cobar),客户端连接中间件,由中间件完成分库分表操做。

这两种方案各有利弊,客户端分库分表因为直连数据库,因此性能比使用中间件要高。而使用中间件,可以很好的将分库分表操做与客户端隔离,数据调整对上层透明,便于统一管理。分布式

订单id生成策略

为何要关注id生成策略?全局惟一,全局有序,业务隔离,不容易被猜到等等,这些都不是关键。重点讨论下,如何让看似无心义的id,对系统后续扩展带来意义。

Java领域著名的惟一id应该算是uuid了,不过uuid太长,并且包含字母,不适合作为订单id。经过调研,咱们借鉴了Twitter的Snowflake算法来实现,算法思想是在64位长整型数字中,存储node编号,而且有序,同时支持并发。

为了便于订单数据后期扩展,咱们有必要在订单id生成时,就将其作好分库分表准备(虽然目前订单量很少)

图片描述

其中serverid,占2位,最大支持2^2台服务器(0-3),dbid占6位,最大支持2^6个数据库,其余以此类推。

最终一致性

订单数据除了用户维度查询外,还有经过商品维度来查询的场景,例如:按照商品,进行订单发货。为了解决这个问题,咱们对应的策略是,将订单数据进行冗余,并按照商品维度进行存储。方案虽然简单,可是保持两个订单库数据的一致性是一件很麻烦的事情。

显然单机数据库事务是没法解决的(数据不在同一个数据库中),因此要保证数据一致性,须要引入强一致性的分布式事务,这个方案先不谈实现成本问题,就凭其超低的效率,这是咱们没法接收的。因此引入异步数据同步,来实现数据的最终一致性。固然,异步同步数据也会带来数据不一致(消息总线丢消息,嘿嘿),因此咱们又引入了实时监控服务,实时计算数据差别,并进行一致性同步。

流程图以下:

图片描述

Tip 3 . 相似这种存在多个纬度的数据存储问题,均可以采用这种方案来解决

数据库高可用

这是个经典的议题了,咱们在这个方案上,并没有创新,用几张图来简单说明下。

图片描述图片描述
图片描述

Hold住流量

如何让商城在大流量下存活?这是一个相似抢购或者秒杀场景如何应对的问题,对于这个问题在@沈剑 的《秒杀系统优化思路》中已经写的很清晰了,那么,我再补充一下。

中心思路路仍然是”逐层消耗流量“,应用层面对大流量状况下,颇有可能自身难保,还没来得及拦截流量,自身就已经OOM了。那么该如何优化这个方案?见下图:

图片描述

在ngx层进行优化,有两个方案:

  1. 达到应用层最大处理能力时,Hold住流量,让请求排队,逐步施放流量到应用层。
  2. 达到应用层最大处理能力时,抛弃多余流量。

咱们采用的第二个方案。视频课程

图片描述==【完】==