• 数据对接方式

    |

    一、主动请求

    作为数据需求方,主动向对方请求数据,常见有接口方式、数据抽取方式。

    1.1 接口请求

    常见的一种对接方式,比如通过HTTP请求,向数据中台获取相关实时数据,用于界面数据展现。

    接口请求网络示意图

    这种方式的优点

    1. 易于编程,目前Java很多HTTP框架支持,通过HTTPS加密传输数据,数据安全性高;
    2. 通用性强,数据请求和响应方无论什么语言架构,只要遵循WebService规范,均可支持;

    这种方式的缺点

    1. 数据需求方和提供方服务需同时工作,依赖较大,若提供方服务挂起,则直接影响数据需求方;
    2. 数据传输量较大时,容易导致带宽阻塞,影响交互体验,服务会变得不可靠;
    3. 请求量大时,对双方服务器要求都很高;

    解决办法

    如图所示,通过数据服务器/数据缓存服务器对数据做一层缓存,数据请求只需在需要的时候请求一次。能一定程度上减少缺陷造成的影响,但是不可杜绝。可以通过监控、预警方式人工介入,及时解决出现的问题。

    适用场景

    • 接口频繁调用,且数据变化较小,对实时性要求不高(历史汇总、月度年度汇总等);
    • 对同步、并发处理要求较高,有一定的编程量(接口并发处理,发起请求时机等);
    • 有相关交互,但是对方无相关权限和界面时(云闪付-营销系统);
    • 必须实时交互的场景(支付请求等,当然这个不属于数据对接范畴;属于API对接);

    推荐指数:一颗星 *

    1.2 数据定时抽取

    通过Kettle等ETL工具或者在1.1基础上做定时任务的方式,均可达到相同目的。

    • 1.1 基础上通过定时任务,执行HTTP请求;
    • 通过Kettle工具,方式一,内部通过HTTP请求做相关解析处理(与1.1雷同);方式二,直接通过抽取数据库方式做相关处理(与章节四雷同,后详细介绍);

    定时抽取示意图

    这种方式的优点

    1. 一定程度减少了对数据提供方的依赖,提示数据需求方的服务稳定性
    2. 可以根据业务对响应数据,做适度转换、改造,数据适应性更强

    这种方式的缺点

    1. 实时性较差,数据有一定延迟;
    2. 定时器选型要求高,需要有异常预警处理机制,技术要求较高;

    解决办法

    1. 采用分布式任务调度框架,解决定时抽取并发问题;
    2. 通过监控、预警机制以及必要时人工介入,提高定时处理的稳定性;

    适用场景

    • 对账单(定时获取对账单)
    • 实时性要求不高的业务场景(汇总数据、历史数据)

    推荐指数:三颗星 ***

    二、被动推送接收

    作为数据需求方,数据提供方主动推送数据给需求方,需求方接收后处理。一般是第一章节的反向操作,也有采用Socket方式(长连接)保障稳定性。

    被动接收网络示意图

    这种方式的优点

    1. 数据提供方数据发生变化,可以及时推给数据需求方,保障数据及时性;
    2. 数据提供方所在服务器避免了来自需求方的大量请求,减轻其服务器压力;
    3. 与1.1节中的优势重叠;

    这种方式的缺点

    1. 需要服务需求方提供接收入口(HTTP接收或Socket方式接收),有一定风险(特别公网对外,有被攻击风险),需要技术解决;
    2. HTTP方式被动接收,无法及时检测数据提供方服务问题,如服务挂起。若数据提供方服务器挂起,则无法保障数据及时性、准确性;
    3. 为提高稳定性,需要很多机制共同保障,有很强技术要求(对数据双方要求都高);

    解决办法

    1. 增强鉴权机制、IP白名单(HTTP反向推送);

    2. 采用心跳机制或Socket直连方式,提高故障响应率;

    3. 通过监控、预警机制以及必要时人工介入,提高定时处理的稳定性;

    适用场景

    • 支付反馈/短信发送反馈;
    • 其他对系统响应较高的场景;

    推荐指数:三颗星 ***

    三、FTP/文件共享

    对于大数据量的交互,第一、二章节不太适合(影响网络带宽),一般采用文件共享的方式。做法为:数据需求方(A)和系统提供方(B)约定文件服务器地址、文件命名规则和文件内容格式等,通过上传到约定的文件服务器进行数据交互。

    ftp示意图

    这种方式的优点

    1. 在数据量大的情况下,通过文件传输方式,极小概率超时,不占用网络带宽;
    2. 简单方便操作文件即可;

    这种方式的缺点

    1. 不适合做实时类业务;
    2. 必须共有文件服务器,存在授权不一致、文件被篡改、删除和泄密的风险;
    3. 约定的文件命名规则、内容格式要求双方必须严格遵守,若需改变,需要双方均要同步修改。

    解决办法

    1. 账号和权限做到严格管理;
    2. 在初期做好规则和格式冗余设计,减少或避免修改;

    适用场景

    • 特别适合批处理任务场景,如批量上账、对账等

    推荐指数:三颗星 ***

    四、数据(库)共享

    简而言之是通过共享数据库数据的方式,实现跨系统间的数据交换。第一种是连接同一个数据库服务器对同一张表进行数据交换;第二种是通过数据实时或非实时同步的方式对不通数据库服务器的不通表进行抽取、转换汇集到数据仓库或统一到数据中台,再对外或自己内部做数据交换。

    db示意图

    这种方式的优点

    1. 相比文件方式传输,使用同一数据库交互更简单;
    2. 通过数据库事务机制,可以保证数据的可靠性;

    这种方式的缺点

    1. 数据库连接数有限,多系统连接不可控;
    2. 分布式数据库和异构数据库之间,虽然可以通过数据同步方式(实时和非实时),复杂度较高,影响稳定性;
    3. 跨企业间数据对接,一般不会做数据库共享,影响数据安全性;

    解决办法

    1. 单一数据库共享,通过数据库授权只读,可以保障数据提供方数据安全(无法单方保障数据泄漏)
    2. 多系统涉及多数据库、异构数据库情况下,若需数据处理和汇总,则需通过数据同步方式;

    适用场景

    1. 单一需求,单一数据库可支持场景,适合数据库共享;
    2. 复杂需求,多数据库场景,通过数据同步方式,技术虽复杂单上可行性高(Berime、Flink等方式);
    3. 适合大中型企业各产品线做数据仓库/数据中台;

    推荐指数:四颗星 ****

    五、消息中间件

    Java消息服务(JMS)是消息数据传输的典型的实现方式。消息提供方将数据发送到消息服务器,由消息接收方订阅该消息,消息通过拉取或推送至消息接收方,双方约定好消息格式即可,诸如Kafka、ActiveMQ等开源消息中间件均可支持。

    mq示意图

    这种方式的优点

    1. 通用性强,接入方式简单,很多开源中间件服务可供选择;
    2. 处理灵活,可以采用同步、异步、可靠性的消息处理,消息中间件独立部署。

    这种方式的缺点

    1. 有一定学习成本;
    2. 大数据量情况下,消息可能产生积压,导致消息延迟、丢失,严重或导致中间件崩溃。

    解决办法

    1. 消息持久化(有概率消息丢失)、Confirm机制(效率较差);
    2. 消息提前持久化 + 定时任务(需要保证消息幂等等一些机制保障处理唯一)

    适用场景

    1. 大型互联网项目(电商、支付);

    推荐指数:四颗星 ****

    六、复杂场景举例

    图待讨论后细化。

    复杂场景示意图

    场景说明

    1. 系统部分数据实时性较强,业务系统有部分数据能够满足;
    2. 系统中一些业务场景需要多个业务系统数据统一汇总计算后展示,这部分数据量少且系统较老,还不支持WebService协议;
    3. 数据量较大(10M以上),数据提供方为了安全不提供接口和数据库访问权限,但是可以定期推送数据给需求方;
    4. 数据量极大,企业内部需要实时掌握交易数据/硬件采集数据;

    解决方案:问答互动模式。

    其他场景漫谈:电网大屏类项目、智慧城市项目。

    最后的建议:根据实际情况,选择合适的单一或混合方式,再根据业务的影响,选择是否对缺陷做相关弥补措施。

  • vivo 亿级优惠券系统架构设计与实践

    作者:vivo互联网开发团队-Yan Chao

    一、业务背景

    优惠券是电商常见的营销手段,具有灵活的特点,既可以作为促销活动的载体,也是重要的引流入口。优惠券系统是vivo商城营销模块中一个重要组成部分,早在15年vivo商城还是单体应用时,优惠券就是其中核心模块之一。随着商城的发展及用户量的提升,优惠券做了服务拆分,成立了独立的优惠券系统,提供通用的优惠券服务。目前,优惠券系统覆盖了优惠券的4个核心要点:创、发、用、计。

    • “创”指优惠券的创建,包含各种券规则和使用门槛的配置。
    • “发”指优惠券的发放,优惠券系统提供了多种发放优惠券的方式,满足针对不同人群的主动发放和被动发放。
    • “用”指优惠券的使用,包括正向购买商品及反向退款后的优惠券回退。
    • “计”指优惠券的统计,包括优惠券的发放数量、使用数量、使用商品等数据汇总。

    vivo商城优惠券系统除了提供常见的优惠券促销玩法外,还以优惠券的形式作为其他一些活动或资产的载体,比如手机类商品的保值换新、内购福利、与外部广告商合作发放优惠券等。

    以下为vivo商城优惠券部分场景的展示:

    aa4846ba4536af2b1ebb08ce2e7f30c8

    二、系统架构及变迁

    优惠券最早和商城耦合在一个系统中。随着vivo商城的不断发展,营销活动力度加大,优惠券使用场景增多,优惠券系统逐渐开始“力不从心”,暴露了很多问题:

    • 海量优惠券的发放,达到优惠券单库、单表存储瓶颈。
    • 与商城系统的高耦合,直接影响了商城整站接口性能。
    • 优惠券的迭代更新受限于商城的版本安排。
    • 针对多品类优惠券,技术层面没有沉淀通用优惠券能力。

    为了解决以上问题,19年优惠券系统进行了系统独立,提供通用的优惠券服务,独立后的系统架构如下:

    52cc20b82b4cb5fb939460dde17a713c

    优惠券系统独立迁移方案

    如何将优惠券从商城系统迁移出来,并兼容已对接的业务方和历史数据,也是一大技术挑战。系统迁移有两种方案:停机迁移和不停机迁移。

    我们采用的是不停机迁移方案:

    • 迁移前,运营停止与优惠券相关的后台操作,避免产生优惠券静态数据。

    静态数据:优惠券后台生成的数据,与用户无关。

    动态数据:与用户有关的优惠券数据,含用户领取的券、券和订单的关系数据等。

    • 配置当前数据库开关为单写,即优惠券数据写入商城库(旧库)。
    • 优惠券系统上线,通过脚本迁移静态数据。迁完后,验证静态数据迁移准确性。
    • 配置当前数据库开关为双写,即线上数据同时写入商城库和优惠券新库。此时服务提供的数据源依旧是商城库。
    • 迁移动态数据。迁完后,验证动态数据迁移准确性。
    • 切换数据源,服务提供的数据源切换到新库。验证服务是否正确,出现问题时,切换回商城数据源。
    • 关闭双写,优惠券系统迁移完成。

    迁移后优惠券系统请求拓扑图如下:

    27e7e93c08361b842835908c7da41168

    三、系统设计

    3.1 优惠券分库分表

    随着优惠券发放量越来越大,单表已经达到瓶颈。为了支撑业务的发展,综合考虑,对用户优惠券数据进行分库分表。

    关键字:技术选型、分库分表因子

    分库分表有成熟的开源方案,这里不做过多介绍。参考之前项目经验,采用了公司中间件团队提供的自研框架。原理是引入自研的MyBatis的插件,根据自定义的路由策略计算不同的库表后缀,定位至相应的库表。

    用户优惠券与用户id关联,并且用户id是贯穿整个系统的重要字段,因此使用用户id作为分库分表的路由因子。这样可以保证同一个用户路由至相同的库表,既有利于数据的聚合,也方便用户数据的查询。

    假设共分N个库M个表,分库分表的路由策略为:

    库后缀databaseSuffix = hash(userId) / M %N

    表后缀tableSuffix = hash(userId) % M

    66c19364de8a004f1696f456a3ba5d2f

    3.2 优惠券发放方式设计

    为满足各种不同场景的发券需求,优惠券系统提供三种发券方式:统一领券接口后台定向发券券码兑换发放

    3.2.1 统一领券接口

    保证领券校验的准确性

    领券时,需要严格校验优惠券的各种属性是否满足:比如领取对象、各种限制条件等。其中,比较关键的是库存和领取数量的校验。因为在高并发的情况下,需保证数量校验的准确性,不然很容易造成用户超领。

    存在这样的场景:A用户连续发起两次领取券C的请求,券C限制每个用户领取一张。第一次请求通过了领券数量的校验,在用户优惠券未落库的情况下,如果不做限制,第二次请求也会通过领券数量的校验。这样A用户会成功领取两张券C,造成超领。

    为了解决这个问题,优惠券采用的是分布式锁方案,分布式锁的实现依赖于Redis。在校验用户领券数量前先尝试获取分布式锁,优惠券发放成功后释放锁,保证用户领取同一张券时不会出现超领。上面这种场景,用户第一次请求成功获取分布式锁后,直至第一次请求成功释放已获取的分布式锁或超时释放,不然用户第二次请求会获取分布式锁失败,这样保证A用户只会成功领取一张。

    库存扣减

    领券要进行库存扣减,常见库存扣减方案有两种:

    方案一:数据库扣减。 扣减库存时,直接更新数据库中库存字段。
    该方案的 优点是简单便捷,查验库存时直接查库即可获取到实时库存。且有数据库事务保证,不用考虑数据丢失和不一致的问题。
    缺点也很明显,主要有两点: 1)库存是数据库中的单个字段,在更新库存时,所有的请求需要等待行锁。一旦并发量大了,就会有很多请求阻塞在这里,导致请求超时,进而系统雪崩。 2)频繁请求数据库,比较耗时,且会大量占用数据库连接资源。

    方案二:基于redis实现库存扣减操作。 将库存放到缓存中,利用redis的incrby特性来扣减库存。
    该方案的 优点是突破数据库的瓶颈,速度快,性能高。
    缺点是系统流程会比较复杂,而且需要考虑缓存丢失或宕机数据恢复的问题,容易造成库存数据不一致。

    从优惠券系统当前及可预见未来的流量峰值、系统维护性、实用性上综合考虑,优惠券系统采用了方案一的改进方案。改进方案是将单库存字段分散成多库存字段,分散数据库的行锁,减少并发量大的情况数据库的行锁瓶颈。

    89a17e7eea878dae372d45973e2f1762

    库存数更新后,会将库存平均分配成M份,初始化更新到库存记录表中。用户领券,随机选取库存记录表中已分配的某一库存字段(共M个)进行更新,更新成功即为库存扣减成功。同时,定时任务会定期同步已领取的库存数。相比方案一,该方案突破了数据库单行锁的瓶颈限制,且实现简单,不用考虑数据丢失和不一致的问题。

    一键领取多张券

    在对接的业务方的领券场景中,存在用户一键领取多张券的情形。因此统一领券接口需要支持用户一键领券,除了领取同一券模板的多张,也支持领取不同券模板的多张。一般来说,一键领取多张券指领取不同券模板的多张。在实现过程中,需要注意以下几点:

    1)如何保证性能

    领取多张券,如果每张券分别进行校验、库存扣减、入库,那么接口性能的瓶颈卡在券的数量上,数量越多,性能直线下降。那么在券数量多的情况下,怎么保证高性能呢?主要采取两个措施:

    a. 批量操作。 从发券流程来看,瓶颈在于券的入库。领券是实时的(异步的话,不能实时将券发到用户账户下,影响到用户的体验还有券的转化率),券越多,入库时与数据库的IO次数越多,性能越差。批量入库可以保证与数据库的IO的次数只有一次,不受券的数量影响。如上所述,用户优惠券数据做了分库分表,同一用户的优惠券资产保存在同一库表中,因此同一用户可实现批量入库。

    b. 限制单次领券数量。 设置阀值,超出数量后,直接返回,保证系统在安全范围内。

    2)保证高并发情况下,用户不会超领

    假如用户在商城发起请求,一键领取A/B/C/D四张券,同时活动系统给用户发放券A,这两个领券请求是同时的。其中,券A限制了每个用户只能领取一张。按照前述采用分布式锁保证校验的准确性,两次请求的分布式锁的key分别为:

    用户id+A_id+B_id+C_id+D_id

    用户id+A_id

    这种情况下,两次请求的分布式锁并没有发挥作用,因为锁key是不同,数量校验依旧存在错误的可能性。为避免批量领券过程中用户超领现象的发生,在批量领券过程中,对分布锁的获取进行了改造。上例一键领取A/B/C/D四张券,需要批量获取4个分布式锁,锁key为:

    用户id+A_id

    用户id+B_id

    用户id+C_id

    用户id+D_id

    获取其中任何一个锁失败,即表明此时该用户正在领取其中某一张券,需要自旋等待(在超时时间内)。获取所有的分布式锁成功,才可以进行下一步。

    接口幂等性

    统一领券接口需保证幂等性(幂等性:用户对于同一操作发起的一次请求或者多次请求的结果是一致的)。在网络超时、异常情况下,领券结果没有及时返回,业务方会进行领券重试。如果接口不保证幂等性,会造成超发。幂等性的实现有多种方案,优惠券系统利用数据库的唯一索引来保证幂等。

    领券最早是不支持幂等性的,表设计没有考虑幂等性。

    那么第一个需要考虑的问题:在哪个表来添加唯一索引呢?

    无非两种方案:现有的表或者新建表。

    • 采用现有的表,不需要增加表的关联。但如上所述,因为做了分库分表,大量的表需要添加唯一字段,并且需要兼容历史数据,需要保证历史数据新增字段的唯一性。
    • 采用新建表这种方式,不需要兼容历史数据,但缺陷也很明显,增加了一层表的关联,对性能和现有逻辑都有很大影响。综合考虑,我们选取了在现有表添加唯一字段这种方式,这样更利于保证性能和后续的维护性。

    第二个考虑的问题:怎么兼容历史数据和业务方?历史数据增加了唯一字段,需要填入唯一值,不然无法添加唯一索引。我们采用脚本刷数据的方式,构造唯一值并刷新到每一行历史数据中。优惠券已对接的业务方没有传入唯一编码,针对这种情况,优惠券侧生成唯一编码作为替代,保证兼容性。

    3.2.2 定向发券

    定向发券用于运营在后台针对特定人群进行发券。定向发券可以弥补用户主动领券,人群覆盖不精准、覆盖面不广的问题。通过定向发券,可以精准覆盖特定人群,提高下单转化率。在大促期间,大范围人群的定向发券还可以承载活动push和降价促销双重任务。

    定向发券主要在于人群的圈选和发券流程的设计,整体流程如下:

    a146b1b1ad0aa7327ac22d0f0265a608

    定向发券不同于用户主动领券,定向发券的量通常会很大(亿级)。为了支撑大批量的定向发券,定向发券做了一些优化:

    1)去除事务。事务逻辑过重,对于定向发券来说没必要。发券失败,记录失败的券,保证失败可以重试。

    2)轻量化校验。定向发券限制了券类型,通过限制配置的方式规避需严格校验属性的配置。不同于用户主动领券校验逻辑的冗长,定向发券的校验非常轻量,大大提升发券性能。

    3)批量插入。批量券插入减少数据库IO次数,消除数据库瓶颈,提升发券速度。定向发券是针对不同的用户,用户优惠券做了分库分表,为了实现批量插入,需要在内存中先计算出不同用户对应的库表后缀,数据归集后再批量插入,最多插入M次,M为库表总个数。

    4)核心参数可动态配置。比如单次发券数量,单次读库数量,发给消息中心的消息体包含的用户数量等,可以控制定向发券的峰值速度和平均速度。

    3.2.3 券码兑换

    站外营销券的发放方式与其他券不同,通过券码进行兑换。券码由后台导出,通过短信或者活动的方式发放到用户,用户根据券码兑换后获取相应的券。券码的组成有一定的规则,在规则的基础上要保证安全性,这种安全性主要是券码校验的准确性,防止已兑换券码的再次兑换和无效券码的恶意兑换。

    3.3 精细化营销能力设计

    通过标签组合配置的方式,优惠券提供精细化营销的能力,以实现优惠券的千人千面。标签可分为准实时和实时,值得注意的是,一些实时的标签的处理需要前提条件,比如地区属性需要用户授权。

    优惠券的精准触达:

    6d9d7deac94fff58ec765124870fd5fd

    3.4 券和商品之间的关系

    优惠券的使用需要和商品关联,可关联所有商品,也可以关联部分商品。为了灵活性地满足运营对于券关联商品的配置,优惠券系统有两种关联方式:

    a. 黑名单。

    可用商品 = 全部商品 - 黑名单商品。

    黑名单适用于券的可使用商品范围比较广这种情况,全部商品排除掉黑名单商品就是券的可使用范围。

    b. 白名单。

    可用商品 = 白名单商品。

    白名单适用于券的可使用商品范围比较小这种情况,直接配置券的可使用商品。

    除此以外,还有超级黑名单的配置,黑名单和白名单只对单个券有效,超级黑名单对所有券有效。当前优惠券系统提供商品级的关联,后续优惠券会支持商品分类维度的关联,分类维度 + 商品维度可以更灵活地关联优惠券和商品。

    3.5 高性能保证

    优惠券对接系统多,存在高流量场景,优惠券对外提供接口需保证高性能和高稳定性。

    多级缓存

    为了提升查询速度,减轻数据库的压力,同时为了应对瞬时高流量带来热点key的场景(比如发布会直播结束切换流量至特定商品商详页、热点活动商品商详页都会给优惠券系统带来瞬时高流量),优惠券采用了多级缓存的方式。

    33efb97643d4b79cad2818f9153a0be5

    数据库读写分离

    优惠券除了上述所说的分库分表外,在此基础上还做了读写分离操作。主库负责执行数据更新请求,然后将数据变更实时同步到所有从库,用从库来分担查询请求,解决数据库写入影响查询的问题。主从同步存在延迟,正常情况下延迟不超过1ms,优惠券的领取或状态变更存在一个耗时的过程,主从延迟对于用户来说无感知。

    13a8154349b934ac03b0b05c97c483fb

    依赖外部接口隔离熔断

    优惠券内部依赖了第三方的系统,为了防止因为依赖方服务不可用,产生连锁效应,最终导致优惠券服务雪崩的事情发生,优惠券对依赖外部接口做了隔离和熔断。

    用户维度优惠券字段冗余

    查询用户相关的优惠券数据是优惠券最频繁的查询操作之一,用户优惠券数据做了分库分表,在查询时无法关联券规则表进行查询,为了减少IO次数,用户优惠券表中冗余了部分券规则的字段。优惠券规则表字段较多,冗余的字段不能很多,要在性能和字段数之间做好平衡。

    四、总结及展望

    最后对优惠券系统进行一个总结:

    • 不停机迁移,平稳过渡。自独立后已稳定运行2年,性能足以支撑vivo商城未来3-5年的高速发展。
    • 系统解耦,迭代效率大幅提升。
    • 针对业务问

    • 原文作者: 架构师社区
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • Hexo 文章数量过大 out of memory 解决

    问题

    当hexo生成博文的时候,你的文章数量超过1000、2000或3000时,就是文章数量很多时,会出现生成不了的情况,具体的错误是out of memory,具体的错误如下:

    1
    ATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    原因

    于hexo项目本身的问题,太耗资源了,虽说hexo一直在改进这方面的问题,但是,还是很慢,而且耗费资源很大,导致出现内存溢出。

    解决办法

    解决办法其实很简单,我查看hexo项目的时候发现,hexo在5.0之后就这个问题做了一些改进,在生成的时候采用限制并行执行数量的方法,避免出现内存溢出。

    命令:在生成博客的时候,使用参数-c,代表生成博客时线程的数量吧,例如:

    1
    hexo -g -c 8

    执行这个命令后,跟以往的输出有所不同,会实时的显示正在生成的静态文件,同时,不会出现内存溢出的问题。

    注意,因gitee限制,以下方式不允许第三方访问,故采用 Gitee Pages服务。

    这个问题解决了很久,希望对大家有帮助!


  • 如何设计一套支付系统-对账模块

    编辑导语:很多人都有记账的习惯,但是记到后面却发现自己的帐算不清楚,记账不能只靠着单方面的账单,还要进行对账才能确保无误;本文将会从产品设计的业务知识点出发,详细介绍对账业务流程,并列举会出现的常见问题和解决方法。

    如何设计一套支付系统-对账模块

    业务背景:

    对账模块是支付系统的核心能力之一,是信息流和资金流关联的重要依据,平台如果只使用渠道的单边账单或者平台流水订单,出现差错或渠道恶意扣单的风险极高。

    为提高资金账务的正确性和保障平台的利益,需要通过平台系统对账能力与上游渠道对账单逐笔勾兑确认,如有差异能及时解决或归档。

    用户画像:

    1)清结算专员:负责发起清分的操作者,首先确保信息流对平,然后确认资金流应收款和信息流平账账单金额一致。希望能及时发现长短款问题,并解决,保障资金清算给商户(平台可收款用户)的时效性。

    2)对账异常订单处理专员:负责核算异常订单原因,并在平台操作将异常订单执行修正、平账。

    一、必须知道的业务知识点

    1. 对账

    在会计上概念:指为了保证账簿记录的正确性而进行的有关账项的核对工作;做到账证相符、账账相符、账实相符。

    在支付系统上的体现:

    1)账证核对:是将账簿记录与记账凭证进行核对。这里是记账凭证是指第三方上游提供的渠道对账单,第三方渠道会根据对账单金额实际结算资金,也就是常说的信息流对账。(有的支付公司或银行只要收到对账单了且平账,即使资金未实际到账,业务也允许发起清分以提高清算时效性)

    2)账账核对:是把有相互关系的多个账簿记录进行核对,有相互关系的账簿记录,包括总分类账簿间核对,明细账簿间核对等多种类型。整个支付系统可以被拆分成了多个子系统,如交易系统、账户系统、会计系统、账户系统,每个子系统在处理各自的业务并记录,其实就相当于会记理论中的账簿。系统间的对账,主要用于修正内部系统的数据不一致。

    3)账实核对:是各项资产物资的记录数值与实际真实数额间的核对。确认第三方汇款到银行账户资金和平账对账单结算金额是否匹配,也就是常说的资金流对账。

    2. 轧帐

    对账系统主要做的是信息流的对账,若对账中发现有差异的订单归类记入对账异常订单表,可称为轧帐。

    3. 平帐

    对账异常订单进入差错流程,可以通过人工或者自动的方式,按照事先设计好的规则处理这些异常差错,可称为平帐。

    4、渠道对账单

    上游渠道会按照平台在其申请的渠道账户维度推送对账单,渠道账户也就是常说的支付通道。

    如果是第三方支付公司或银行,上游渠道是微信、支付宝、银联二维码(云闪付)等等。例如:支付平台申请有微信2通道和微信6通道,则微信侧会生成2份对账单文件。

    每份对账单会包括支付成功订单和退款成功订单。第三方会以对账单中的结算金额(支付订单金额-支付订单手续费)-(退款订单金额-退款订单手续费)结算汇款给到平台资金账户。

    5、银联二维码(难点)

    银联二维码是银联平台自主推出的支付产品,C端使用云闪付、各手机银行APP支付,订单底层走的都是银联二维码通道。

    为什么银联二维码需要重点说呢?

    因为它不同于微信、支付宝通道统一费率的原则,银联会根据C端用户支付时使用的银行卡借贷性质和交易金额是否大于1000作为费率规则,并且还会收取额外的品牌服务费,详情参见下图。

    如何设计一套支付系统-对账模块

    所以设计银联二维码通道对账时,还需考虑到多费率及品牌服务费的场景。

    二、对账流程

    1. 业务流程

    对账业务可以插解为5个业务环节,本文主要说明每个环节的能力职责、常见问题和通用解决方法,具体的产品方案还需要结合读者平台自身的业务特点和系统架构设计。

    如何设计一套支付系统-对账模块

    三、对账任务

    对账是一个日常操作,正常情况下上游渠道都会以D+1的周期生成渠道对账单。

    每天系统可以默认生成定时对账任务,每个上游渠道生成时间不一样,可以事前和上游确认,并结合平台对账的处理时效和商户到账需求,设计一个合理的时间执行。

    对账任务设计前需确认,渠道对账单推送方式、解析方法、匹配字段,并提前做好联调适配工作;例如渠道有可能会需要申请白名单权限或提供SFTP地址信息,要谨防上线后才发现系统无法正常获取对账单的情况。

    1. 创建任务批次

    创建批次一方面是为了防止重复对账,另一方面需要在对账结束的时候将对账的结果信息存储到批次中。

    2. 记录任务信息

    对账任务信息,例如:通道名称、通道编号、渠道商户号、对账任务批次、对账任务状态、交易时间、任务创建时间、下载开始时间、下载结束时间、下载状态、对账开始时间、对账结束时间、对账结果、对账方式;

    对账方式为对账处理时的对账规则,可以根据业务实际情况分为:无需对账、以渠道为准、以平台为准。

    • 以渠道为准,则若对账订单平台交易状态为支付中或支付失败,但渠道为支付成功,则平台状态改为支付成功。
    • 以平台为准,则是若出现上述情况,对账订单记录为异常订单。

    3. 重置任务机制

    考虑到对账过程中可能会遇到的来自上游渠道的问题或平台系统自身问题,需要设计重置机制。

    上游渠道对账单错误,需求二次或多次推送,所以需要设计重新下载渠道对账单或重新上传渠道对账单;有可能平台自身数据错误导致出现大量的差异订单,修复后需要重新对账。

    4. 对账任务详情示例

    1. 对账信息:记录对账任务基本情况;
    2. 对账结果信息:显示关键对账字段值;
    3. 挂销账信息:显示是否有挂销账订单及金额;
    4. 对账异常信息:显示是否有对账异常订单及金额;
    5. 备注:将系统自动处理的过程记录,也可以手动修改。

    如何设计一套支付系统-对账模块

    四、对账单下载

    1. 获取文件

    渠道对账单获取方式,一般提前作为任务规则写死。

    大多数银行都要求接入方提供ftp服务,银行定时将对账单推送到接入方提供的ftp服务器上面;

    还有一部分银行会提供对账单的下载服务,通过ftp/http的都有,ftp方式居多;

    另外网银的对账单比较特殊,一般都需要结算登录网银的后台管理系统中,手动下载,结算下载完对账单后在导入到对账系统。

    2. 判断文件是否存在

    任务自动获取文件的情况下需要判断任务是否存在:

    自动获取渠道对账单:不存在需要设置轮询,每间隔一段时间重新获取。重试次数和间隔的设置需要小心,重试太频繁,容易把服务器打死.;时间间隔太大,又会阻塞后续处理步骤。5~10分钟是一个合适的重试间隔区间。

    手动导入渠道对账单:要设计导入入口,导入成功后任务状态也要做相应的变更。

    3. 下载文件

    技术实现上可以做成工厂模式,不同的支付渠道有不同的下载类,如果是http接口将文件写入到对账单,如果是ftp服务器,将服务器中的对账单下载到本地带解析的目录中。主要涉及的代码ftp工具类、http(s)工具类,相关IO读写。

    4. 判断来源渠道

    获取到上游对账单文件后,很有可能多个渠道的对账单在同一个SFTP地址,根据文件名匹配到对应的对账任务,文件名一般会包含账单时间、渠道商户号,然后再执行下一步。

    五、文件解析

    1. 解析文件

    解析文件主要是将下载的对账文件解析成我们可以对账的数据类型并且入库。

    解析的文件不同渠道有不同的类型,因此也可以设计成不同的解析模板,使用工厂模式将不同格式的文件解析成可以对账的统一数据类型。

    解析的文件类型一般包括:json、text、cvs、excle等,另外部分银行会对账单做加密或者提供zip打包的格式,这里就需要额外开发zip工具类和加解密工具类进行处理。

    对账文件中包含的主要信息有:商户订单号、交易流水号、交易时间、支付时间、付款方、交易金额、交易类型、交易状态这些字段。

    2. 转换入库

    每个渠道的账单格式都不尽相同, 在得到账单后,下一步是对账单做标准化处理,这样轧帐以及后续工作就可以统一处理了。

    标准化后的账单数据可以放在文件系统或者数据库中,这取决于交易数据量;每天百万以上的量,还是使用文件系统,比较合适,数据库操作相对比较慢,也浪费资源。

    基于文件系统的标准化涉及如下内容:

    • 文件格式标准化统一使用csv或者json或者xml格式,如果是使用hadoop或者spark来对账,也可以使用csv。
    • 文件存储统一化文件目录,文件名都需要遵循统一命名规范。

    六、对账单处理

    1. 获取对方对账单

    将事前准备的上游对账单标准文件放入缓冲对账池。

    2. 获取我方对账单

    本地交易记录的准备,总的来说有如下方法:

    啥都不做,直接用订单表的原始数据:鉴于大部分系统使用的是MySQL,这也意味着在MySQL上做对账。对账时需要大量的数据查找工作,必然会影响线上业务。在数据规模较大,比如超过100万时,就不太合适了。

    使用备库来执行对账:这样既简单,也不影响线上业务,这是典型的空间换时间的做法。

    采用分表分库对账:如果业务大到需要分表分库才能处理,那对账数据准备也不一样。

    3. 逐一匹配

    前文有提到对账方式有三种,不对账、以渠道为准、以平台为准,大部分的情况下的对账方式都以渠道为准,信息流的传递方向,支付成功结果是由上游渠道通知平台的,平台很有可能会因网络或系统问题而没有收到通知。

    一般按照交易金额、交易状态、手续费金额逐一匹配,对账方式选择以渠道为准的处理逻辑为例:

    1)交易金额不匹配:记入异常订单。

    2)交易状态不匹配:若上游为支付成功,我方为未支付或支付失败,则以上游为准。

    • 若我方订单为支付成功,上游只会推送支付成功的订单为对账单,上游对账单不存在的情况,将我方订单记入为挂账订单;
    • 若上游对账单存在,我方不存在的情况,记入异常订单。

    3)手续费金额不匹配:记入异常订单。

    4. 挂销账处理

    常会存在因日切时间点不一致或网络延时等情况,导致我方平台订单时间与上游渠道时间不一致,同一个订单在渠道交易时间是1月1号,但在平台是1月2号。

    1. 挂账,对账时若上游无我方有,订单放入挂账订单中。
    2. 销账,若匹配中挂账订单匹配上了,记录为当日平台账单,挂销账状态改为已销账。

    七、差错处理

    关于差错流程,每个系统的业务特性、运营团队流程、公司财务管理办法不一样,不能生搬硬套,但原则是一样的,所有的异常订单的报销账必须有理有据,多重审核。

    1. 异常原因

    (1)若出现订单金额不一致的情况,一般是平台调用上游交易接口时,双方字段的定义不匹配导致的。

    (2)若出现手续费金额不一致的情况,一般是平台手续费计算规则和上游不匹配导致的,差额不会太大,可以设计阀值若在可以接受的范围类不计为异常。

    (3)若出现上游对账单存在,平台订单不存在的情况,通常是有第三方绕过平台系统与上游系统发生支付或退款交易。需要及时核查是否存在交易密钥泄露或被绕过商户去上游系统平台操作。

    2. 修正

    选择以平台为准或以上游为准,修改订单金额、订单状态、手续费金额。此时写入修正原因很重要,便于后期业务追踪考求。

    3. 平账

    将平台异常订单状态改为平账,并将平账时的时间作为账单时间,合入至平台账单。平台会根据平台账单计算渠道分润金额和商户结算金额。

    八、业务规则系统化

    最后,每个平台产品细节都会有其特定的业务规则,就不具体说明平台页面和和平台操作功能,笔者不做详情说明。以上业务规则系统化,系统流程图如下,读者可以结合自己平台业务情况提取细化。

    如何设计一套支付系统-对账模块

    本文由 @Jamie Gao 原创发布于人人都是产品经理,未经作者许可,禁止转载。

    题图来自Unsplash,基于CC0协议


    • 原文作者:人人都是产品经理
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • 如何写好业务代码?

    写好代码,阿里专家沉淀了一套“如何写复杂业务代码”的方法论,在此分享给大家,相信同样的方法论可以复制到大部分复杂业务场景。

    一文教会你如何写复杂业务代码

    了解我的人都知道,我一直在致力于应用架构和代码复杂度的治理。

    这两天在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。针对该命题,我进行了比较细致的思考和研究。结合实际的业务场景,我沉淀了一套“如何写复杂业务代码”的方法论,在此分享给大家。

    我相信,同样的方法论可以复制到大部分复杂业务场景。

    一个复杂业务的处理过程

    业务背景

    简单的介绍下业务背景,零售通是给线下小店供货的B2B模式,我们希望通过数字化重构传统供应链渠道,提升供应链效率,为新零售助力。阿里在中间是一个平台角色,提供的是Bsbc中的service的功能。

    fdb66aa5fad975e16cfad86077922b9f.pngfdb66aa5fad975e16cfad86077922b9f.png

    在商品域,运营会操作一个“上架”动作,上架之后,商品就能在零售通上面对小店进行销售了。是零售通业务非常关键的业务操作之一,因此涉及很多的数据校验和关联操作

    针对上架,一个简化的业务流程如下所示:

    19331818d037870dca759ef01804e464.png19331818d037870dca759ef01804e464.png

    过程分解

    像这么复杂的业务,我想应该没有人会写在一个service方法中吧。一个类解决不了,那就分治吧。

    说实话,能想到分而治之的工程师,已经做的不错了,至少比没有分治思维要好很多。我也见过复杂程度相当的业务,连分解都没有,就是一堆方法和类的堆砌。

    不过,这里存在一个问题:即很多同学过度的依赖工具或是辅助手段来实现分解。比如在我们的商品域中,类似的分解手段至少有3套以上,有自制的流程引擎,有依赖于数据库配置的流程处理:

    2b2b39d33888147bfa43ed137a7a1c67.png2b2b39d33888147bfa43ed137a7a1c67.png

    本质上来讲,这些辅助手段做的都是一个pipeline的处理流程,没有其它。因此,我建议此处最好保持KISS(Keep It Simple and Stupid),即最好是什么工具都不要用,次之是用一个极简的Pipeline模式,最差是使用像流程引擎这样的重方法

    除非你的应用有极强的流程可视化和编排的诉求,否则我非常不推荐使用流程引擎等工具。第一,它会引入额外的复杂度,特别是那些需要持久化状态的流程引擎;第二,它会割裂代码,导致阅读代码的不顺畅。大胆断言一下,全天下估计80%对流程引擎的使用都是得不偿失的

    回到商品上架的问题,这里问题核心是工具吗?是设计模式带来的代码灵活性吗?显然不是,问题的核心应该是如何分解问题和抽象问题,知道金字塔原理的应该知道,此处,我们可以使用结构化分解将问题解构成一个有层级的金字塔结构:

    b66cdb29b0094cf5c077b4fc991177f8.pngb66cdb29b0094cf5c077b4fc991177f8.png

    按照这种分解写的代码,就像一本书,目录和内容清晰明了。

    以商品上架为例,程序的入口是一个上架命令(OnSaleCommand), 它由三个阶段(Phase)组成。

    @Command
    public class OnSaleNormalItemCmdExe {
    
        @Resource
        private OnSaleContextInitPhase onSaleContextInitPhase;
        @Resource
        private OnSaleDataCheckPhase onSaleDataCheckPhase;
        @Resource
        private OnSaleProcessPhase onSaleProcessPhase;
    
        @Override
        public Response execute(OnSaleNormalItemCmd cmd) {
            
            OnSaleContext onSaleContext = init(cmd);
            
            checkData(onSaleContext);
    
            process(onSaleContext);
    
            return Response.buildSuccess();
        }
    
        private OnSaleContext init(OnSaleNormalItemCmd cmd) {
            return onSaleContextInitPhase.init(cmd);
        }
    
        private void checkData(OnSaleContext onSaleContext) {
            onSaleDataCheckPhase.check(onSaleContext);
        }
    
        private void process(OnSaleContext onSaleContext) {
            onSaleProcessPhase.process(onSaleContext);
        }
    }
    

    每个Phase又可以拆解成多个步骤(Step),以OnSaleProcessPhase为例,它是由一系列Step组成的:

    @Phase
    public class OnSaleProcessPhase {
    
        @Resource
        private PublishOfferStep publishOfferStep;
        @Resource
        private BackOfferBindStep backOfferBindStep;
        //省略其它step
    
        public void process(OnSaleContext onSaleContext){
            SupplierItem supplierItem = onSaleContext.getSupplierItem();
    
            // 生成OfferGroupNo
            generateOfferGroupNo(supplierItem);
           
           // 发布商品
            publishOffer(supplierItem);
    
            // 前后端库存绑定 backoffer域
            bindBackOfferStock(supplierItem);
    
            // 同步库存路由 backoffer域
            syncStockRoute(supplierItem);
    
            // 设置虚拟商品拓展字段
            setVirtualProductExtension(supplierItem);
    
            // 发货保障打标 offer域
            markSendProtection(supplierItem);
    
            // 记录变更内容ChangeDetail
            recordChangeDetail(supplierItem);
    
            // 同步供货价到BackOffer
            syncSupplyPriceToBackOffer(supplierItem);
    
            // 如果是组合商品打标,写扩展信息
            setCombineProductExtension(supplierItem);
    
            // 去售罄标
            removeSellOutTag(offerId);
    
            // 发送领域事件
            fireDomainEvent(supplierItem);
            
            // 关闭关联的待办事项
            closeIssues(supplierItem);
        }
    }
    

    看到了吗,这就是商品上架这个复杂业务的业务流程。需要流程引擎吗?不需要,需要设计模式支撑吗?也不需要。对于这种业务流程的表达,简单朴素的组合方法模式(Composed Method)是再合适不过的了。

    因此,在做过程分解的时候,我建议工程师不要把太多精力放在工具上,放在设计模式带来的灵活性上。而是应该多花时间在对问题分析,结构化分解,最后通过合理的抽象,形成合适的阶段(Phase)和步骤(Step)上。

    36ae78000fccfeeea6a7205fcd1873d7.png36ae78000fccfeeea6a7205fcd1873d7.png

    过程分解后的两个问题

    的确,使用过程分解之后的代码,已经比以前的代码更清晰、更容易维护了。不过,还有两个问题值得我们去关注一下:

    1、领域知识被割裂肢解

    什么叫被肢解?因为我们到目前为止做的都是过程化拆解,导致没有一个聚合领域知识的地方。每个Use Case的代码只关心自己的处理流程,知识没有沉淀。

    相同的业务逻辑会在多个Use Case中被重复实现,导致代码重复度高,即使有复用,最多也就是抽取一个util,代码对业务语义的表达能力很弱,从而影响代码的可读性和可理解性。

    2、代码的业务表达能力缺失

    试想下,在过程式的代码中,所做的事情无外乎就是取数据–做计算–存数据,在这种情况下,要如何通过代码显性化的表达我们的业务呢? 说实话,很难做到,因为我们缺失了模型,以及模型之间的关系。脱离模型的业务表达,是缺少韵律和灵魂的。

    举个例子,在上架过程中,有一个校验是检查库存的,其中对于组合品(CombineBackOffer)其库存的处理会和普通品不一样。原来的代码是这么写的:

    boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();
    
    // supplier.usc warehouse needn't check
    if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {
    // quote warehosue check
    if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) {
        throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
    }
    // inventory amount check
    Long sellableAmount = 0L;
    if (!isCombineProduct) {
        sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());
    } else {
        //组套商品
        OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());
        if (backOffer != null) {
            sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();
        }
    }
    if (sellableAmount < 1) {
        throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + supplierItem.getId() + "]");
    }
    }
    

    然而,如果我们在系统中引入领域模型之后,其代码会简化为如下:

    if(backOffer.isCloudWarehouse()){
        return;
    }
    
    if (backOffer.isNonInWarehouse()){
        throw new BizException("亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
    }
    
    if (backOffer.getStockAmount() < 1){
        throw new BizException("亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]");
    }
    

    有没有发现,使用模型的表达要清晰易懂很多,而且也不需要做关于组合品的判断了,因为我们在系统中引入了更加贴近现实的对象模型(CombineBackOffer继承BackOffer),通过对象的多态可以消除我们代码中的大部分的if-else。

    adb9d12329cb1ad47435f571521cc87a.pngadb9d12329cb1ad47435f571521cc87a.png

    过程分解+对象模型

    通过上面的案例,我们可以看到有过程分解要好于没有分解过程分解+对象模型要好于仅仅是过程分解。对于商品上架这个case,如果采用过程分解+对象模型的方式,最终我们会得到一个如下的系统结构:

    3a3d46b54d829b2cdf0da6eafa88bf79.png3a3d46b54d829b2cdf0da6eafa88bf79.png

    写复杂业务的方法论

    通过上面案例的讲解,我想说,我已经交代了复杂业务代码要怎么写:即自上而下的结构化分解+自下而上的面向对象分析

    接下来,让我们把上面的案例进行进一步的提炼,形成一个可落地的方法论,从而可以泛化到更多的复杂业务场景。

    上下结合

    所谓上下结合,是指我们要结合自上而下的过程分解和自下而上的对象建模,螺旋式的构建我们的应用系统。这是一个动态的过程,两个步骤可以交替进行、也可以同时进行。

    这两个步骤是相辅相成的,上面的分析可以帮助我们更好的理清模型之间的关系,而下面的模型表达可以提升我们代码的复用度和业务语义表达能力

    其过程如下图所示:

    110888e4d1d911dc69c4d7ae1589d3f6.png110888e4d1d911dc69c4d7ae1589d3f6.png

    使用这种上下结合的方式,我们就有可能在面对任何复杂的业务场景,都能写出干净整洁、易维护的代码。

    能力下沉

    一般来说实践DDD有两个过程:

    1. 套概念阶段

    了解了一些DDD的概念,然后在代码中“使用”Aggregation Root,Bonded Context,Repository等等这些概念。更进一步,也会使用一定的分层策略。然而这种做法一般对复杂度的治理并没有多大作用。

    2. 融会贯通阶段

    术语已经不再重要,理解DDD的本质是统一语言、边界划分和面向对象分析的方法。

    大体上而言,我大概是在1.7的阶段,因为有一个问题一直在困扰我,就是哪些能力应该放在Domain层,是不是按照传统的做法,将所有的业务都收拢到Domain上,这样做合理吗?说实话,这个问题我一直没有想清楚。

    因为在现实业务中,很多的功能都是用例特有的(Use case specific)的,如果“盲目”的使用Domain收拢业务并不见得能带来多大的益处。相反,这种收拢会导致Domain层的膨胀过厚,不够纯粹,反而会影响复用性和表达能力。

    鉴于此,我最近的思考是我们应该采用能力下沉的策略。

    所谓的能力下沉,是指我们不强求一次就能设计出Domain的能力,也不需要强制要求把所有的业务功能都放到Domain层,而是采用实用主义的态度,即只对那些需要在多个场景中需要被复用的能力进行抽象下沉,而不需要复用的,就暂时放在App层的Use Case里就好了。

    注:Use Case是《架构整洁之道》里面的术语,简单理解就是响应一个Request的处理过程

    通过实践,我发现这种循序渐进的能力下沉策略,应该是一种更符合实际、更敏捷的方法。因为我们承认模型不是一次性设计出来的,而是迭代演化出来的。

    下沉的过程如下图所示,假设两个use case中,我们发现uc1的step3和uc2的step1有类似的功能,我们就可以考虑让其下沉到Domain层,从而增加代码的复用性。

    e156d5dde682bed3cbd2cafc3c0ae11c.pnge156d5dde682bed3cbd2cafc3c0ae11c.png

    指导下沉有两个关键指标:代码的复用性和内聚性

    复用性是告诉我们When(什么时候该下沉了),即有重复代码的时候。内聚性是告诉我们How(要下沉到哪里),功能有没有内聚到恰当的实体上,有没有放到合适的层次上(因为Domain层的能力也是有两个层次的,一个是Domain Service这是相对比较粗的粒度,另一个是Domain的Model这个是最细粒度的复用)。

    比如,在我们的商品域,经常需要判断一个商品是不是最小单位,是不是中包商品。像这种能力就非常有必要直接挂载在Model上。

    public class CSPU {
        private String code;
        private String baseCode;
        //省略其它属性
    
        /**
         * 单品是否为最小单位。
         *
         */
        public boolean isMinimumUnit(){
            return StringUtils.equals(code, baseCode);
        }
    
        /**
         * 针对中包的特殊处理
         *
         */
        public boolean isMidPackage(){
            return StringUtils.equals(code, midPackageCode);
        }
    }
    

    之前,因为老系统中没有领域模型,没有CSPU这个实体。你会发现像判断单品是否为最小单位的逻辑是以StringUtils.equals(code, baseCode)的形式散落在代码的各个角落。这种代码的可理解性是可想而知的,至少我在第一眼看到这个代码的时候,是完全不知道什么意思。

    业务技术要怎么做

    写到这里,我想顺便回答一下很多业务技术同学的困惑,也是我之前的困惑:即业务技术到底是在做业务,还是做技术?业务技术的技术性体现在哪里?

    通过上面的案例,我们可以看到业务所面临的复杂性并不亚于底层技术,要想写好业务代码也不是一件容易的事情。业务技术和底层技术人员唯一的区别是他们所面临的问题域不一样。

    业务技术面对的问题域变化更多、面对的人更加庞杂。而底层技术面对的问题域更加稳定、但对技术的要求更加深。比如,如果你需要去开发Pandora,你就要对Classloader有更加深入的了解才行。

    但是,不管是业务技术还是底层技术人员,有一些思维和能力都是共通的。比如,分解问题的能力,抽象思维,结构化思维等等。

    e85910c398d3b5bec55790e24dbbdd6b.pnge85910c398d3b5bec55790e24dbbdd6b.png

    用我的话说就是:“做不好业务开发的,也做不好技术底层开发,反之亦然。业务开发一点都不简单,只是我们很多人把它做“简单”了

    因此,如果从变化的角度来看,业务技术的难度一点不逊色于底层技术,其面临的挑战甚至更大。因此,我想对广大的从事业务技术开发的同学说:沉下心来,夯实自己的基础技术能力、OO能力、建模能力… 不断提升抽象思维、结构化思维、思辨思维… 持续学习精进,写好代码。我们可以在业务技术岗做的很”技术“!

    后记

    这篇文章是我最近思考的一些总结,可能需要一些DDD和应用架构的知识做铺垫。否则的话,有些地方可能会显得有些唐突,或是没有那么有体感。

    有时间的话,可以去看看《领域驱动设计》和《架构整洁之道》去了解一些前序知识。如果没有那么多时间,也可以快速浏览下我之前的文章领域建模去知晓一下我之前的思想脉络。

    更多云计算干货敬请关注阿里云官网知乎机构号:阿里云官网 - 知乎


    • 原文作者:阿里云网站
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • Java微服务下的分布式事务介绍及其解决方案

    1.前言

    1.由于最近在学习springcloud的项目,现在互联网下,分布式,微服务横行,难免会遇到分布式下的事务问题,这是一个难点,也是面试经常问的,别面试官一看你简历,都是微服务项目,问你了解啥是分布式事务不,你来句没有,这就很尴尬了,当然微服务下可能没有分布式事务,但是很多场景是需要分布式事务的,下面我就来介绍下什么是分布式事务,和分布式事务的解决方案

    2 问题描述

    在介绍分布式事务下,下面我们先来了解一个常见应用场景,这个场景(类似慕课网购买付费课程)也是我后面要讲的分布式事务的解决方案的案例

    2 用户支付完成会将支付状态及订单状态保存在订单数据库中,由订单服务去维护订单数据库。而学生选课信息在学习中心数据库,由学习服务去维护学习中心数据库的信息。下图是系统结构图:
    在这里插入图片描述
    ​ 如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里的关键是如何保证两个分布式服务的事务的一致性。

    ​ 尝试解决上边的需求,在订单服务中远程调用选课接口,伪代码如下:

    在这里插入图片描述
    下面我们分析下这种解决方案的问题

    1.更新支付表状态为本地数据库操作。
    2.远程调用选课接口为网络远程调用请求
    3.为保存事务上边两步操作由spring控制事务,当遇到Exception异常则回滚本地数据库操作。
    问题如下:
    1、如果更新支付表失败则抛出异常,不再执行远程调用,此设想没有问题。
    2、如果更新支付表成功,网络远程调用超时会拉长本地数据库事务时间,影响数据库性能。(远程调用非常耗时的哦)
    3、如果更新支付表成功,远程调用添加选课成功(选课数据库commit成功),最后更新支付表commit失败,此时出现操作不一致。
    上面的问题就涉及到了分布式事务的控制

    3.什么是分布式事务

    什么是分布式系统

    部署在不同结点上的系统通过网络交互来完成协同工作的系统
    比如:充值加积分的业务,用户在充值系统向自己的账户充钱,在积分系统中自己积分相应的增加。充值系统和积分系统是两个不同的系统,一次充值加积分的业务就需要这两个系统协同工作来完成。

    什么是事务

    事务是指由一组操作组成的一个工作单元,这个工作单元具有原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
    原子性:执行单元中的操作要么全部执行成功,要么全部失败。如果有一部分成功一部分失败那么成功的操作要全部回滚到执行前的状态。
    一致性:执行一次事务会使用数据从一个正确的状态转换到另一个正确的状态,执行前后数据都是完整的。 隔离性:在该事务执行的过程中,任何数据的改变只存在于该事务之中,对外界没有影响,事务与事务之间是完全的隔离的。只有事务提交后其它事务才可以查询到最新的数据。
    持久性:事务完成后对数据的改变会永久性的存储起来,即使发生断电宕机数据依然在。

    什么是本地事务

    本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。

    什么是分布式事务

    在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,如下图:在这里插入图片描述
    ​ 另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况在这里插入图片描述
    上面两种分布式事务表现形式第一种用的最多

    4.分布式事务的应用场景

    在这里插入图片描述

    CAP理论

    如何进行分布式事务控制?CAP理论是分布式事务处理的理论基础,了解了CAP理论有助于我们研究分布式事务的处理方案。
    CAP理论是:分布式系统在设计时只能在一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)中满足两种,无法兼顾三种。
    通过下图来理解CAP理论
    在这里插入图片描述
    **一致性(Consistency)**:服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
    **可用性(Availability)**:服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
    **分区容忍性(Partition Tolerance)**:分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。
    分布式系统不可避免的出现了多个系统通过网络协同工作的场景,结点之间难免会出现网络中断、网延延迟等现象,这种现象一旦出现就导致数据被分散在不同的结点上,这就是网络分区

    5.分布式系统能否兼顾C、A、P?

    在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足“一致性”、“可用性”和“分区容忍性”三者是几乎不可能的

    CAP有哪些组合方式?

    1、CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计。
    2、AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP进行设计。
    说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可
    3、CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
    ​ 说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞
    总结:​ 在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。

    6.分布式事务的解决方案(介绍其中三种)

    两阶段提交协议(2PC)

    为解决分布式系统的数据一致性问题出现了两阶段提交协议(2 Phase Commitment Protocol),两阶段提交由协调者和参与者组成,共经过两个阶段和三个操作,部分关系数据库如Oracle、MySQL支持两阶段提交协议,本节讲解关系数据库两阶段提交协议。
    参考:2PC:https://en.wikipedia.org/wiki/Two-phase\_commit\_protocol

    在这里插入图片描述

    1)第一阶段:准备阶段(prepare)
    协调者通知参与者准备提交订单,参与者开始投票。
    参与者完成准备工作向协调者回应Yes。
    2)第二阶段:提交(commit)/回滚(rollback)阶段
    协调者根据参与者的投票结果发起最终的提交指令。
    如果有参与者没有准备好则发起回滚指令。
    一个下单减库存的例子:
    在这里插入图片描述1、应用程序连接两个数据源。
    2、应用程序通过事务协调器向两个库发起prepare,两个数据库收到消息分别执行本地事务(记录日志),但不提交,如果执行成功则回复yes,否则回复no。
    3、事务协调器收到回复,只要有一方回复no则分别向参与者发起回滚事务,参与者开始回滚事务。
    4、事务协调器收到回复,全部回复yes,此时向参与者发起提交事务。如果参与者有一方提交事务失败则由事务协调器发起回滚事务。
    2PC的优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。
    缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。
    解决方案有:springboot+Atomikos or Bitronix

    事务补偿(TCC)

    TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下:
    1、Try 检查及预留业务资源完成提交事务前的检查,并预留好资源。
    2、Confirm 确定执行业务操作
    对try阶段预留的资源正式执行。
    3、Cancel 取消执行业务操作
    对try阶段预留的资源释放。
    下边用一个下单减库存的业务为例来说明
    在这里插入图片描述
    1、Try
    下单业务由订单服务和库存服务协同完成,在try阶段订单服务和库存服务完成检查和预留资源。
    订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。
    库存服务检查当前是否有充足的库存,并锁定资源。
    2、Confirm
    订单服务和库存服务成功完成Try后开始正式执行资源操作。
    订单服务向订单写一条订单信息。
    库存服务减去库存。
    3、Cancel
    如果订单服务和库存服务有一方出现失败则全部取消操作。
    订单服务需要删除新增的订单信息。
    库存服务将减去的库存再还原。
    优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。
    缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。
    注意:TCC的try/confirm/cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。

    什么是幂等性?

    幂等性是指同一个操作无论请求多少次,其结果都相同。
    幂等操作实现方式有:
    1、操作之前在业务方法进行判断如果执行过了就不再执行。
    2、缓存所有请求和处理的结果,已经处理的请求则直接返回结果。
    3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理。

    消息队列实现最终一致(本文打算介绍这种方案解决)

    本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成,如下图:
    下边以下单减少库存为例来说明:
    在这里插入图片描述1订单服务和库存服务完成检查和预留资源。
    2、订单服务在本地事务中完成添加订单表记录和添加“减少库存任务消息”。
    3、由定时任务根据消息表的记录发送给MQ通知库存服务执行减库存操作。
    4、库存服务执行减少库存,并且记录执行消息状态(为避免重复执行消息,在执行减库存之前查询是否执行过此消息)。
    5、库存服务向MQ发送完成减少库存的消息。
    6、订单服务接收到完成库存减少的消息后删除原来添加的“减少库存任务消息”。
    实现最终事务一致要求:预留资源成功理论上要求正式执行成功,如果执行失败会进行重试,要求业务执行方法实现幂等。
    优点 :
    由MQ按异步的方式协调完成事务,性能较高。
    不用实现try/confirm/cancel接口,开发成本比TCC低。
    缺点:
    此方式基于关系数据库本地事务来实现,会出现频繁读写数据库记录,浪费数据库资源,另外对于高并发操作不是最佳方案。

    总结:本文只是介绍了分布式事务的一些特性和解决方案,将会在另一篇文章上详细介绍消息队列实现最终一致性的分布式解决方案,需要了解:rabbitmq,SpringTask,springcloud

    分布式解决方案的第二篇文章地址如下,这篇文章就来介绍具体的解决方案,案例驱动


    • 原文作者:Carry-wws
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • 应用系统之间数据传输的几种方案

    应用系统之间数据传输的几种方式

    第一种方案:socket方式

       Socket方式是最简单的交互方式。是典型才C/S交互模式。一台客户机,一台服务器。
    服务器提供服务,通过IP地址和端口进行服务访问。而客户机通过连接服务器指定的端口进行消息交互。
    其中传输协议可以是TCP/UDP 协议。而服务器和约定了请求报文格式和响应报文格式。
    如图一所示

    3ba27193f9a9c335147c3342ef61a0c2.png

    目前我们常用的http调用,java远程调用,webservices 都是采用的这种方式,只不过不同的就是传输协议以及报文格式。
    这种方式的优点:
    1 易于编程,目前java提供了多种框架,屏蔽了底层通信细节以及数据传输转换细节。
    2 容易控制权限。通过传输层协议https,加密传输的数据,使得安全性提高 
    3 通用性比较强,无论客户端是.net架构,java,python 都是可以的。尤其是webservice规范,
     使得服务变得通用
    这种方式的缺点:
    1 服务器和客户端必须同时工作,当服务器端不可用的时候,整个数据交互是不可进行。
    2 当传输数据量比较大的时候,严重占用网络带宽,可能导致连接超时。使得在数据量交互的时候,服务变的很不可靠。

    第二种方案:ftp/文件共享服务器方式

    对于大数据量的交互,采用这种文件的交互方式最适合不过了。系统A和系统B约定文件服务器地址,文件命名规则,文件内容格式等内容,通过上传文件到文件服务器进行数据交互。
    文件命名规则,文件内容格式等内容,通过上传文件到文件服务器进行数据交互。

    5e669bd69575d73be3fe2dfd4f00230f.png

    最典型的应用场景是批量处理数据:例如系统A把今天12点之前把要处理的数据生成到一个文件,
    系统B第二天凌晨1点进行处理,处理完成之后,把处理结果生成到一个文件,系统A 12点在进行
    结果处理。这种状况经常发生在A是事物处理型系统,对响应要求比较高,不适合做数据分析型
    的工作,而系统B是后台系统,对处理能力要求比较高,适合做批量任务系统。

    这种方式的优点:
    1 在数据量大的情况下,可以通过文件传输,不会超时,不占用网络带宽。 
    2 方案简单,避免了网络传输,网络协议相关的概念。
    这种方案的缺点:
    1 不太适合做实时类的业务  
    2 必须有共同的文件服务器,文件服务器这里面存在风险。因为文件可能被篡改,删除,或者存在泄密等。  
    3 必须约定文件数据的格式,当改变文件格式的时候,需要各个系统都同步做修改。

    第三种方案:数据库共享数据方式

    系统A和系统B通过连接同一个数据库服务器的同一张表进行数据交换。当系统A请求系统B
    处理数据的时候,系统A Insert一条数据,系统B select 系统A插入的数据进行处理。

    9a6477b7e22d90189de5821fa7fca849.png

    这种方式的优点:
    1 相比文件方式传输来说,因为使用的同一个数据库,交互更加简单。  
    2由于数据库提供相当做的操作,比如更新,回滚等。交互方式比较灵活,  而且通过数据库的事务机制,可以做成可靠性的数据交换。
    这种方式的缺点:
    1 当连接B的系统越来越多的时候,由于数据库的连接池是有限的,
    导致每个系统分配到的连接不会很多,当系统越来越多的时候,可能导致无可用的数据库连接

    2 一般情况,来自两个不同公司的系统,不太会开放自己的数据库给对方连接,因为这样会有安全性影响

    第四种方案:message方式

    Java消息服务(Java Message Service)是message数据传输的典型的实现方式。
    系统A和系统B通过一个消息服务器进行数据交换。系统A发送消息到消息服务器,
    如果系统B订阅系统A发送过来的消息,消息服务器会消息推送给B。双方约定消
    息格式即可。目前市场上有很多开源的jms消息中间件,比如  ActiveMQ, OpenJMS 。

    9a8b62546a86d0ef21e2f4cf144249a3.png
    这种方式的优点:
    1 由于jms定义了规范,有很多的开源的消息中间件可以选择,而且比较通用。接入起来相对也比较简单  
    2 通过消息方式比较灵活,可以采取同步,异步,可靠性的消息处理,消息中间件也可以独立出来部署。
    这种方式的缺点:
    1 学习jms相关的基础知识,消息中间件的具体配置,以及实现的细节对于开发人员来说还是有一点学习成本的  
    2 在大数据量的情况下,消息可能会产生积压,导致消息延迟,消息丢失,甚至消息中间件崩溃。

    下面具体来分析一个场景,来看看系统之间数据传输的应用  场景 目前业务人员需要导入一个   大文件到系统A,

    系统A保存文件信息,而文件里面的明细信息需要导入到系统B进行分析,当系统B分析完成之后,

    需要把分析结果通知系统A。

    efccabf8b9ec3f20f140eac49faf5ac1.png
    A 系统A先保存文件到文件服务器。
    B 系统A 通过webservice 调用系统B提供的服务器,把需要处理的文件名发送到系统B。
    由于文件很大,需要处理很长时间,所以B不及时处理文件,而是保存需要处理的文件
    名到数据库,通过后台定时调度机制去处理。所以B接收请求成功,立刻返回系统A成功。
    C 系统B定时查询数据库记录,通过记录查找文件路径,找到文件进行处理。这个过程很长。 
    D 系统B处理完成之后发送消息给系统A,告知系统A文件处理完成。 
    E 系统A 接收到系统B请求来的消息,进行展示任务结果


    • 原文作者:闫 先生
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • 分布式架构设计之电商平台

    分布式架构设计之电商平台

     

    何为软件架构?不同人的答案会有所不同,而我认为一个好的软件架构除了要具备业务功能外,还应该具备一定的高性能、高可用、高伸缩性及可拓展等非功能需求。而软件架构是由业务架构和技术架构两部分组成,因为有了业务结构才会催生出软件架构,进而来满足业务上的需求,所以,在做软件架构设计时,需要分为业务架构设计和技术软件架构设计,二者不可分离哦!那么,接下来就以本人实际工作中的电商平台为例,进行说明电商平台架构设计,因为不同行业产品系统不同业务不同,而催生的系统软件的实现要求及架构设计就不同了!

     

    l   架构设计的必要

    l   电商平台的需求

    l   平台的业务架构

    l   平台的技术架构

    l   平台架构的总结

     

    一、架构设计的必要

    1.架构师,我想很多人都知道,其实该职位头衔在最早的IT领域是没有的,它是近些年来由互联网的发展所引发的需求,因为现阶段的数据量及高并发的活跃好动,引起了不少传统的技术人员的力不从心,企业愈发关注到了系统架构的重要性,所以不同行业开始招募架构技术人员,架构师就诞生了。

    2、架构设计的优势

    A、更好的梳理业务的结构体系;

    B、更好的拓展、维护及性能优化;

    C、更好的适应企业业务灵活的推进;

    D、更好的适应大数据的冲洗和应对;

    E、更好的稳定性、低成本及快速迭代;

     

    3、架构设计的注意

    架构设计需要注意的地方,不是怎么把架构搭建起来,而是必须根据业务需求,严格分析,实现该需求需要什么技术会更好及更长远发展的考虑;另外,构建好的架构虽然可以运行,但是性能需要跟起来,否则架构设计会适得其反,增加不必要的工作量,那么下面就详细介绍下架构设计的策略。

     

    二、电商平台的需求

    1、客户需求

    A、在线购物、在线支付或货到付款;

    B、购买商品后,客户可以与客服沟通;

    C、购买商品过程,物流的管理及跟踪;

    D、收取到商品后,商品、物流评价打分;

     

    客户的需求为最高,也代表了企业的核心需求,当然,企业需求还包括其它很多非功能性需求,具体请查看需求梳理部分。

     

    2、需求梳理

    客户需求

    功能需求

    非功能需求

    在线购买商品

    购物车、结算及会员管理

    用户体验(性能、可用性)

    在线与客服沟通

    在线客服功能

    即时通信能力

    在线支付或货到付款

    多种支付方式,含在线支付或货到付款

    安全、加密、多种支付方式灵活切换

    在线商品、物流评论打分

    商品、物流评价打分

    物流体系对接

     

    上面只是对电商平台需求的简单列举,还有很多需求未列出,这里只是为了分析和设计电商平台架构做准备,具体的其它需求,可以参看京东、淘宝等商城。

     

    三、平台的业务架构

    根据业务的需求进行子系统模块划分,可以划分为商品子系统、购物子系统、支付子系统、物流子系统、客服子系统、评论子系统;而非核心需求可拆分出客服子系统、评论子系统及接口子系统。另外,根据各个子系统的核心等级,可拆分出核心子系统和非核心子系统,前者包括商品子系统、购物子系统、支付子系统及物流子系统;后者,则包括评论子系统、客服子系统及接口子系统。需要注意的是一般大型电商平台的物流系统是单独分离出来的系统(入库、出库、库存管理、配送管理及货品管理),而这里划分为子系统的主要目的是为演示核心架构,本架构中物流子系统一般作为对接和管理独立子系统的对接模块哦。

     

    1、业务拆分目的

    A、为了解决各个模块子系统间的耦合、维护及拓展性;

    B、方便单独部署子系统,避免集中部署导致一个出问题,全部不能用;

    C、分配专门的团队,负责具体的子系统,最大化工作效率安排;

    D、应对大数据,高压力时,保护核心子系统正常使用;

     

    2、业务的架构图

    be931c03a030bac893524fcdced7548a.png

    在上面的业务架构图中,将核心和非核心业务进行拆分,同时每个系统都要独立部署实现,做到大数据量压下,各个系统独立运作,提高可用性,必要时可以暂停掉非核心系统的资源开销,保证核心业务正常为用户服务。

     

    四、平台的技术架构

    在上面业务架构图基础上,我们需要一个技术架构的演变过程,一切只为满足用户的体验和支撑为前提,所以技术架构的搭建不是一蹴而就的,而是随着业务的不断衍变,系统的架构会逐渐完善更新,以实现应对业务数据量的冲击。

     

    1、基本的架构设计

    记得很早的时候,很多中小企业所采用的架构设计十分简单,基本使用一台服务器来满足一切需求部署,比如:一台服务器同时用作应用部署、数据库存储以及图片存储等,不料的是待用户数据达到50万以上,系统出现很多性能问题,尽管对数据库和程序做个各种性能优化,结果仍无明显改善,架构如下:

    d0056a2e8e20657221192e8ed83513c3.png

     

    后来,IT程序猿发现图片的读写严重影响了系统性能,并将图片单独存放在独立服务器中,并且在架构中引入了Cache中间件,比如:Memcache,这种做法是可取的,而且比原来性能提高了1-2个性能级别,架构设计如下:

    8c69b8bb51da9578a324fd7322f37cab.png

     

     

    2、初级的架构设计

    前几年,一般的电商网站的做法是选用三台服务器,一台部署应用,一台部署数据库,一台部署NFS文件系统,做到将各个规模庞大并耗用性能的部分剥离到不同服务器设备,再配备必要的缓存中间件,基本可以满足近1000万的数据量,具体的架构图如下:

    36970cee9eb7617ab3fa122e3158ef78.png

     

    但是,目前主流使用的网站架构已经不同,大多采用集群的方式来实现负载均衡和高可用性,架构可以是下面的样子:

    9a77e0fa510ff1073fa03fcbd601f7e9.png

     

    注意:

    如果涉及到多台网站服务器的话,就会存在Session如何同步的问题,一般也是最为常用的做法,就是使用Cache中间件来存储和管理Session信息。

     

    3、优化的架构设计

    这里为解决高并发,高可用的大型电商网站的架构设计方案,主要采用了分布式、集群、负载均衡、反向代理、消息队列及多级缓存技术。该架构设计方案,是现今比较流程的大型电商网站采用的架构模式,比如:淘宝、京东等,也许会有细微不同的地方,但大同小异哦!具体的架构图方案如下:

    c6dc191e762db6d362361aab535448ed.png

    3.1、应用集群部署

     

    3.2、分布式

    分布式,即为借助互联网环境连接不同服务器,并各个连接的服务器之间通信交互,提供服务异步调用和返回的通信机制。在这里,主要就是实现商品评论、购物客服、支付接口及物流打分系统各自所在服务器间的通信化,我们可以通过RPC协议直接在他们之间交互通信即可,而上面优化的架构即为分布式架构。

     

    3.3、集群

    集群,分为服务器集群、数据库集群及缓存中间件集群等,但这里主要指的是数据库的集群设计。数据库集群,可以实现主备数据库,做到读写分离以及高可用的实现。大型网站需要存储大规模的数据量,需要实现高可用、高并发、高性能的系统设计,一般采用冗余的方式进行系统设计,具体如下架构:

    c06f08aa08b3b6082e3f411570d04137.png

     

    冗余方式设计数据库集群,最为常用的方式为:读写分离和分库分表了。主数据库服务器只负责写入数据,而备用服务器数据库只负责读取数据,可以做到降低数据库的IO压力;另外,如果业务系统比较庞大,可以进一步根据业务的关系度及增长频率分库,若库中的但表数据量比较大,可进一步分表,具体的分库分表可查看我的博客文章数据库的分库分表。

     

    3.4、消息队列

    消息队列,是分布式系统的常用组合,其可以解决子系统或模块间的异步通信,实现高可用,高性能的通信系统,比如:可以用在购物和配送环节,如下:

    A、用户下单后,写入消息到队列,并立即返回结果给客户端;

    B、库存子系统,读取消息队列,完成消减库存;

    C、配送子系统,读取消息队列,并进行配送货品;

     

    目前常使用的MQ技术有:Rabbit MQ、Active MQ、Zero MQ及MS MQ,需要根据具体的使用场景进行选择。具体的架构如下:

    e54ad56eacbe5f9947337c0537410dbe.png

    3.5、缓存策略

    缓存,是一种缓解系统压力的存储技术,主要使用在缓存数据库IO压力而设计。按照位置的不同,可以分为本地缓存和分布式缓存两种,本篇架构采用两级缓存,一级缓存为本地缓存,二级缓存为分布式缓存。而一级缓存一般用来缓存基本不变或规律变化的数据,二级缓存用来缓存所有需要的数据信息,应用程序首先访问一级缓存;如果一级缓存没有需要的信息,那么取访问分布式缓存,如果分布式缓存也没找到需要的信息,最后去访问数据库获得数据。另外,根据业务需要,缓存分为自动过期和触发过期,具体的架构图如下:

    a349f258d3c40fd4f271fc4f5aa844aa.png

     

    3.6、服务抽象化

    抽象化概念,可以很好的实现低耦合,高拓展作用,我们可以将各个子系统公用的功能或模块抽取出来,封装为共有的服务组件或接口,供各个现有子系统或是新增系统调用,这也是SOA架构的基础思想,具体的架构如下:

    dee3dd27aa0afb74da946edeca6aa472.png

     

    五、平台架构的总结

    这里主要总结的是优化架构,架构按层次结构罗列组织,共分为四层,分别为负载均衡代理层、应用集群系统层、分布式服务层及数据资源层,层次分工明确,高拓展,低耦合,负载均衡、集群、分布式及缓存等技术的使用,架构如下:

    f282dff01098542e200d6cc03c28f845.png

     

     

    作者介绍:半路学IT,做开发3年,先就职在一家共享单车公司,做后台开发!

     

     我开了一个公众号,欢迎各位有志同道合朋友,关注!不定期分享干活,和我得故事!

     

    eaf397b7b5db88797efec49990ea8546.png

     

     

    好了,电商平台的架构设计就介绍到这里,本篇主要是介绍架构设计的思路及应用的核心技术,供在架构设计的同学参考借鉴哦!由于作者水平有限,如有不对或是误导的地方,请不吝指出讨论(QQ群:497552060(新))。


    • 原文作者:gdsblog
    • 原文链接:点击查看原文
    • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。
  • Hexo + Gitee 自建博客

    整体步骤

    • Nodejs 环境依赖
    • 通过 NPM 下载 Hexo
    • 使用 Hexo 初始化博客目录
    • 使用Git下载更多的主题(theme文件夹下)
    • 新建Gitee同名项目,master分支用于存储资源, page-doc分支用于生成页面并开启 Gitee Pages
    • 为了防止单个Git资源过大,这里将图片、视频等资源单独存放,并开启 Pages 访问资源

    安装

    1
    $ npm install -g hexo

    初始化并启动

    1
    2
    3
    4
    $ hexo new 'blog folder'
    $ cd blog folder
    $ hexo init
    $ hexo server

    变更主题

    1
    2
    $ cd /博客目录
    $ git clone 主题所在GIT地址 themes/新的主题名称

    修改_config.ymltheme: landscape 改为 新的主题名称,再重启&重新生产即可

    部署到Git

    配置Deployment

    1
    2
    3
    4
    deploy:
    type: git
    repo: 仓库地址 # https
    branch: pages # pages 用于单推生成文件

    安装 hexo-deployer-git 依赖

    1
    $ npm install hexo-deployer-git --save

    提交

    • 原方式:
    1
    2
    $ hexo clean
    $ hexo deploy
    • 优化后:
    1
    2
    3
    // package.json
    "dev": "hexo s",
    "build": "hexo clean & hexo deploy"

    执行命令

    1
    $ npm run build