59store定位校园生活服务平台,构建了丰富的消费场景,满足了校园生活多方面的需求。
2014年年底,59创造性地推出了“夜猫店”的全新商业模式。2015年下半年,随着生活服务平台理念的深化,59先后推出了饮品店、零食盒、创客中心、外卖等项目,并且针对校园金融推出了白花花、白借借等金融业务。2016年开始,以信用钱包为核心,升级并扩充了若干新的金融类业务。随着上云印店、社区的先后推出,一个校园生活服务平台雏形初现。
在早期开发夜猫店、饮品店、零食盒的过程中,我们看重敏捷开发,快速迭代,小步快跑,不断试错,以至于没有充分地考虑系统抽象,做了很多重复业务特征的实现,缺少复用和整合。随着业务平台化趋势的凸显,抽象而可持续的业务架构迫在眉睫。经过半年的不断抽象、剥离、重构,现在59已经形成了一整套基础服务体系,以及构建在其之上的众多业务。
在备战今年59校园狂欢节的过程中,我们不仅要应对巨大的访问流量,而且还有新的项目和活动需要上线(代号为folivora的秒杀系统和代号为paramecium的品牌馆)。得益于成熟稳定的基础服务,我们可以非常快速地构建上层应用,而忽略底层数据结构和服务接口的创建,让开发、维护变得轻松而愉悦。在基础服务不断演进成熟的过程中,我们总结出了以下几点感受。
1. 明确标准
一个制定良好的数据结构和服务接口,会让往后的开发、维护省去很多的潜在成本,包括但不限于常用单词的全局统一含义、变量命名标准,接口风格统一等等。没有这些统一的规范就好比脚底有沙子,走得越远,脚会被磨得越破。
2. 接口细粒度
接口定义到怎么样的细粒度,会直接影响使用者的效率。仅仅提供增删改查的操作,或每一个功能都配备特定的接口,都会比较偏颇。在不断摸索的过程中,59逐步统一了“原子业务化”的接口设计思想,即接口需要恰如其分地描述一个独立的原子业务。比如create_order即生成一个订单,包括了订单金额,折扣,促销,订单状态,相关时间,用户信息,订单信息,商品明细等一系列逻辑。cancel_order取消一个订单,包括了状态异常的判断,不同支付方式退款,优惠券、积分的处理等。
3. 划清职责
基础服务层需要服务众多业务,而不同的业务各有特点,如果基础服务支持所有业务的个性特点,势必会导致服务层数据结构的冗余和潜在的不稳定因素。因此我们严格划分服务和应用的交付界限,服务仅仅提供所有业务场景通用的数据结构和接口,比如订单状态,用户头像,商品名称等。简单的个性化业务需求,可以使用服务层数据结构包含的一个业务自定义JSON字段来满足需求。更复杂的业务个性信息则需要业务系统自身来维护,比如某个打印订单文件空白页检测结果信息等。在明确职责的基础上提供一定程度的灵活性,不断沉淀出稳定的基础服务。
5月7号开始,59会发起一系列的秒杀活动来预热59校园狂欢节,然后在5月9号达到活动高潮。对此,我们做了一系列的技术优化方案来保障秒杀业务的服务可用。
秒杀是一个读多写少的场景,并且瞬时下单的系统负载大,对数据的一致性要求比较高。针对这样的业务特点,我们对获取数据接口和下单接口做了不同的优化方案。
获取数据接口方面,秒杀商品信息全部缓存在Redis集群中,以减缓高并发场景下的系统压力和数据库负载。
秒杀下单接口的瞬时访问量大,对数据的一致性要求高,因此我们做了特殊的设计。假设一个商品秒杀库存有5件,库存的信息会被存储在一个Redis集群中,并且留有一定冗余,比如记录秒杀商品库存为8件。多台秒杀计算节点分布式部署,使得其可以线性水平扩展,保障高可用,不会成为性能瓶颈。下单请求会被分发到任意一台服务器,然后检测当前秒杀商品是否已秒完,如果是,则直接返回已秒完,如果不是,则直接调用订单服务接口下单。由于我们放入了比实际秒杀库存更多的请求进来,所以在下单过程中如果发现秒杀商品无库存,同样会给用户报出已秒完。
针对秒杀应用,行业的惯用做法是使用队列来削峰,从而减缓系统负载。不过经过团队讨论,我们决定简化系统设计为如上叙述。主要有以下几个方面的考虑:1、系统可以在秒杀下单逻辑一开始便判断业务逻辑是否需要继续(即是否已秒完),如果已秒完则立即返回,不继续后续业务逻辑执行,这隐含地做了“削峰”的工作。并且系统结构比使用消息队列的方案更简单。消息队列方案至少包含一个消息队列存储秒杀请求,生产者、消费者的逻辑实现,以及商品被秒杀完后,消息队列中请求的处理和后续到来请求的处理逻辑等。2、调用下单模块没有使用队列来“削峰”,因为业务决定我们每场秒杀的商品库存不多,数据库可以承受得起这样的并发量。
在推进服务化架构之前,59所有的数据库表都集中在同一个数据库实例中。随着业务的飞速增长,数据库承受了非常大的压力,瓶颈效应凸显。于是在服务化一开始,我们便做了分库优化。
首先是拆:针对业务特点,将独立的业务模块逐步抽离,比如数据统计,APP支持,活动模块等。并且新上线的项目会严格把控存储位置和依赖关系,能独立的数据库表便不放在主库中。其次是合:基础订单服务,商品服务等使用的分布式数据库独立部署。
在构建分布式数据库的过程中,我们选用mycat作为数据库拆分中间件,它可以提供良好的针对分片规则的数据库拆分服务。对于商品服务而言,几乎所有的场景(不管是卖家管理自己商品,或者买家浏览店铺商品),都需要通过卖家ID获得商品列表。所以以商品owner_id作为分片规则水平拆分,即可以满足需求。关于订单服务,常见业务场景不仅包括买家通过买家ID获取自己的订单列表,也包括卖家通过卖家ID获取自己的订单列表。因此从任何一个维度划分都不能满足另一方的需求。针对这个问题,59的解决方案如下。
我们定义了买家库和卖家库,两者通过MySQL的binlog机制保持数据同步。买家库表定义了一个sharding字段作为分片规则,被划分成了32个分片。sharding是用户ID的后若干位,从而在买家库,通过用户ID的订单检索可以非常方便地使用到分片规则,提升检索性能。卖家库我们暂时没有做水平拆分,而是一个数据库实例。在卖家ID上建立索引,从而在卖家库通过卖家ID获取订单列表同样可以有不错的性能表现。买家库没有在卖家ID建立索引,同步机制仅仅同步数据,而不同步结构。卖家库是一个只读实例,当卖家需要修改一个订单信息的时候,需要在买家库进行操作。如何在已知订单ID的情况下,检索订单的请求可以使用到买家库的分片规则便成为最后一个问题。我们的解决方案是:订单ID中包含了sharding信息,从而在修改一个订单信息的时候可以轻松应用到分片规则。
当订单数据量非常庞大的时候,冷数据归档会带来很大的性能提升。结合校园市场特点,用户4年一个大周期,1学期一个小周期,以每一学期作为时间节点来归档冷数据会带来很好的用户体验。
在59store的技术成长历程中,任何的技术选型、技术方案、技术优化都是业务来驱动的。从最开始的一个单独的PHP应用,到现在60多个服务、应用,从最开始的一台VPS,到现在数百台云服务器,技术始终在59的发展历程中扮演着至关重要的作用。不同的阶段,技术的主要矛盾点是不同的,如何准确的拿捏和把握,值得每一个创业的技术人揣摩和体会。
关于:中科研拓
深圳市中科研拓科技有限公司专注提供软件外包、app开发、智能硬件开发、O2O电商平台、手机应用程序、大数据系统、物联网项目等开发外包服务,十年研发经验,上百成功案例,中科院软件外包合作企业。通过IT技术实现创造客户和社会的价值,致力于为用户提供很好的软件解决方案。联系电话400-0316-532,邮箱sales@zhongkerd.com,网址www.zhongkerd.com