<返回更多

微服务架构中的数据一致性:解决方案与实践

2023-06-14    OSC开源社区
加入收藏

1

为什么要做服务之间的数据一致性

作为互联网公司的研发工程师,微服务的架构思想对于各位读者朋友来说,已经不是陌生东西。我们当中的大多数人,或多或少经历过从单体应用到微服务化的系统拆分和演进过程。我们按照庞大系统的业务功能和特征,将其从一个单体的大应用,逐渐地拆分成很多的子系统的协同配合完成业务功能,甚至拆分后的某些子系统服务,还可能再拆分出来更多的更细颗粒度的子系统服务。拆分后的服务之间,采用PRC调用方式的通信,也就越来越多。随之而来的,跨系统服务之间的数据一致性的问题就会越来越突出了。比如电商系统中营销活动系统的积分和优惠券的发放和扣减,比如电商系统的核心下单核心链路上,首页瀑布流,商详页,下单页等等商品价格全链路一致性等等,支撑这些业务功能的实现,往往可能需要依赖来自N个不同的业务系统服务提供的数据读写服务能力来完成。

2

如何实现服务之间的数据一致性

说到数据一致性这个话题,我们可以想到的最常用最熟悉的解决问题的方式就是事务处理了。它存在的意义是为了保证系统中所有的数据都是符合预期的,并且存在关联关系的数据之间不会产生矛盾,即数据状态 一致性。事务的概念,起源于数据库,发展到今天,几乎在每一个业务系统中都会涉及到。尤其在一些大型复杂的分布式系统中,事务的概念已经不仅局限于数据库,还可以延伸为一切需要保证数据一致性的应用场景,包括但不限于数据库、事务内存、缓存、消息队列、分布式存储等等,这些都有可能会用到事务处理。

今天我们探讨的主题是服务之间数据一致性问题。当然,实际生产落地中,在不同业务背景下,具备可行性的方案也是非常多的,各有优劣和适用场景。我们从中选择一两个具体实现,聊一些相关的设计和实践。

3

几个核心名词

为了方便正在阅读本篇文章的同学对后续内容的阅读和理解,我们先对文章中使用到的几个名词的语义做一些解释和约定:

业务侧系统:指的是发起执行业务操作的一方系统服务,可以简单理解为消费方。

平台侧系统:指的是为发起执行业务操作而提供基础能力的一方系统服务,可以简单理解为服务方。

执行业务操作:指的是对数据的读写操作需要依赖多个系统服务的协同完成,业务侧系统发起,平台侧系统执行最终的数据读写操作。这样场景中就普遍存在着服务之间的数据一致性问题(备注 :业务侧系统和平台侧系统是一个逻辑概念,并非一定要存在具体的应用服务与之对应)。

业务操作标识:指的是业务操作应该归属的业务类型或者业务场景的标识,它是平台侧系统创建并且统一管理的,然后发放给业务侧系统,在业务侧系统的服务调用平台侧系统提供服务能力的身份信息。业务标识可以设计为单层结构,也可以设计为多层结构,符合当前系统和业务的需求即可。

业务操作唯一ID:指的是某个具体业务操作在某一次或者多次重复执行的唯一性标识。生产实践中,它一般是由业务侧系统的服务自定义实现和管理的,也可以是基于平台侧系统提供有约束性质和方便管理的规则限制下,再由业务侧系统的服务自定义实现,后者是比较推荐的方式。

业务操作记录表:指的是记录业务操作的日志流水表。生产实践中,一般是由业务侧系统的服务创建并且管理。

4

方案一:业务侧系统保证最终一致性

4.3 流程图4.3.1 数据一致性的校验核对同步执行流程

4.3.2 数据一致性的校验核对异步核对链路

5

方案二:平台侧系统保证最终一致性

5.2 设计的基本原则:

平台侧系统,提供支持业务操作执行的基础服务能力的接口,需要根据业务操作标识和唯一ID做幂等设计。它和方案一的一致性原则类似,省略不再赘述。

平台侧系统,提供业务操作的执行结果确认的回调SPI,可以方便业务侧系统来实现,根据业务操作标识和业务操作的唯一ID。

业务侧系统,提供根据业务操作标识和业务操作的唯一ID,来判断两边的数据是否具备一致性的回调实现。

5.3 流程图5.3.1 数据一致性的校验核对同步核对链路

5.3.2 数据一致性的校验核对异步核对链路

6

实践过程中一些经验分享

这一部分,我将会对平台侧系统和业务侧系统的接口设计的部分细节,做一些简单的扩展阐述。希望为大家后续的研发工作提供一些思路。后续的文章中,将会针对其中一些具体的解决方案,做更详细的阐述。

首先,接口幂等性设计,将从如下角度进行阐述: 数据结构,状态存储,异常处理,返回结果唯一等等角度做一些总结分享。

6.1 数据结构设计

接口幂等性设计,是基于业务操作的标识(这里是称之为Tag)和业务操作的唯一ID来实现的。业务操作标识的设计,可以是单层的设计,也可以是多层的设计。其中,多层的设计是为了满足业务侧系统的存在复杂并且多业务场景的诉求。业务操作的唯一ID的生成方式,可以是没有任何业务含义的自增趋势的不可重复的ID,比如MySQL的自增主键ID,分布式ID生成器等等方式,也可以是业务侧系统的某些特定的业务字段 ,比如用户的userId,订单的orderId,商品的spuId,skuId等等。在实际实践中,后者是我们比较推荐的常用方式,可以实现在不增加系统复杂度和额外依赖资源的同时,又可以和业务侧系统达到高度的契合。

6.2 状态存储设计

在一般情况下,建议把MySQL存储当做我们首选的存储,MySQL提供非常完善的数据一致性保证能力,最简单的方式是基于数据库的联合唯一索引设计,多次层Tag + 唯一ID的业务唯一键。但是也是有缺陷的,比如MySQL自身的性能瓶颈和昂贵的存储成本。性能上的瓶颈,可以通过访问MySQL的幂等校验之前,增加访问redis的幂等校验,校验不通过抛出异常,在MySQL幂等校验通过以后,异步刷数据到Redis中,这样保证Redis校验通过的同时MySQL校验一定是通过的。我们可以接受Redis的幂等校验的不准确性,仅仅是期望它成为流量漏斗的上层,为MySQL承担起流量过滤作用,当然你可以有其他的更多的方案来做这件事,甚至组合起来使用。也可以增加分库分表的策略,来解决MySQL的性能瓶颈。在MySQL的存储成本是相对比较高的,我们可以对历史的数据做归档处理,只保留一部分的热数据,原则上保持单表的数据行数在500w~1000w之间,同时也可以有能支持一定量的历史数量查询。同时这个过程也需要考虑无锁处理问题和MySQL空间碎片的问题等等。

6.3 异常处理设计

第一步,明确导致发生异常的原因有哪些?一般可以归为几个分类,网路异常,数据格式错误,业务逻辑异常。第二步,针对特定类型的问题,我们做出相应处理方案。比如我们重试机制,控制重试频次,重试周期的衰减时间执行控制,处理数据处理的终态的异常数据的兜底处理机制等等方式。

*文/ kof wang

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>