当前位置: 首页 > article >正文

架构思维:构建高并发扣减服务_分布式无主架构

文章目录

  • Pre
  • 无主架构的任务
    • 简单实现
    • 分布式无主架构
  • 设计和实现扣减中的返还
    • 什么是扣减的返还
    • 返还实现原则
      • 原则一:扣减完成才能返还
      • 原则二:一次扣减可以多次返还
      • 原则三:返还的总数量要小于等于原始扣减的数量
      • 原则四:返还要保证幂等
  • 总结

在这里插入图片描述

Pre

架构思维:构建随时可写的高可用写服务_利用缓存+数据库构建高可靠的扣减方案

架构思维:构建随时可写的高可用写服务_从分库分表到无状态存储架构

上面两篇博文中均使用了异步任务(Worker)来做无状态存储到正式业务库的数据同步。只涉及扣减的正向流程。但关于具体如何设计异步任务来保证高可用,以及如何设计任务的执行来保障执行的速度,避免产生任务积压,其实并没有过多介绍


无主架构的任务

简单实现

对于无状态存储集群的数据同步任务,最简单的做法是为每个分库启动一个自循环 Worker ,通过不断轮询 SQL 拉取未执行任务并标记完成。

在这里插入图片描述

select task_id,task_body,... from t_task where id>lastId and status='未执行' limit 一批数量 order by task_id

上述的流程虽然在功能上能够满足需求,但在高可用及性能上还是有一些不足:

  • 扩展性差——任务量越大,单 Worker 无法水平扩容 . 如果任务库中任务特别多,上述单 Worker 单库的方式不具备扩展性,随着任务不断变多,会出现任务积压的瓶颈且无法通过扩容解决;

  • 单点风险——Worker 宕机导致任务持续积压。单库单 Worker 的方式存在单点问题,在执行过程中,当 Worker 发生故障导致宕机,如果没有监控等机制发现故障,Worker 得不到执行,任务就会一直积压。


分布式无主架构

为了解决上述瓶颈,可采用“分布式无主架构”, 对单库多 Worker 并发执行并自动协调分区:

  • 多实例并发:对每个分库启动 M 个 Worker 进程/容器,提升吞吐;

  • 自助协调分区:每个 Worker 计算唯一哈希值并上报 etcd/ZooKeeper;

  • Watch 机制感知拓扑变化:Worker 订阅同库其他实例的哈希列表;

  • 一致性 Hash 环分区:各实例根据哈希环负责自己的 ID 区间(见图3);

  • 动态扩缩容:节点上下线迅速触发 Watch,重分区后新旧 Worker 自动调整执行范围。


对于上述两个问题,这里介绍一种可以提升任务执行速度,既具备扩展性、又能保障高可用的任务架构模式,如下图所示:

在这里插入图片描述

在上述的整体架构里,每个分库对应的 Worker 的执行流程都类似,因此在讲解时,只对一个分库的 Worker 进行分析,其余的可以以此类推。

首先为了提升性能和高可用,单个分库的执行 Worker 配置的是多个并发进行执行。

单个分库配置的多个任务在执行时使用自助协调,协调流程如下。

  • (1)每个 Worker 在启动时,会根据机器的 IP、随机数、当前时间戳等进行组合拼接计算一个唯一串,再在此基础上使用各种哈希工具计算一个无符号整形哈希值。

  • (2)所有的 Worker 会将自己的无符号整形哈希值上报到强一致的 etcd 或 ZooKeeper 存储集群里。

  • (3)etcd 等集群具备通知功能(Watch)。借助通知功能,所有的 Worker 都去订阅某一个分库下的其他 Worker 的哈希值,比如一个新的 Worker 启动了或者扩容新增了一个新的 Worker。

  • (4)每一个 Worker 都会获取到当前分库的所有其他 Worker 的哈希值。假设一个分库配置了四个 Worker,其中一个 Worker 会获取到自己及其他三个 Worker 的哈希值,假设为{200,300,500,800}。这四个 Worker 的 Hash 值便组成了一个环形区间,如下图

任务一致性 Hash 环

在这里插入图片描述
这个环形区间其实就类似一致性 Hash,每一个结点都代表一个 Worker,这个 Worker 负责任务编号在它区间范围内的任务的执行。

  • (5)有了上述的哈希值列表后,就可以做任务分配了。如果当前 Worker 的哈希值为 300,那么当前 Worker 就处理任务 ID 在区间[200,300)里的值。比如哈希值为 200 的 Worker 则执行区间为[800,无穷大)和[0,200)的任务(即任务编号大于等于 800 和处在[0,200)区间内的任务),其他以此类推。区间处在[200,300)的 Worker 获取任务的 SQL 大致如下:
select * from task where id>=200 and id<300 and status='待执行' order by id limit 100;

通过上述方式,无论是某一台 Worker 发生故障还是新扩容一台 Worker,通过 etcd 和 ZK 的通知机制,所有的其他 Worker 都可以立马感知,并更新自己所负责的任务区间。

比如上述介绍的案例里,四个 Worker 代表 300 的那一个发生故障,整个哈希值列表就从{200,300,500,800}变成了{200,500,800},此时负责 500 的 Worker 就会执行[200,500)这个区间里的所有任务了,扩容 Worker 的流程和上述类似。

最后,在 Worker 扩缩容的间隙里,可能存在临界的并发情况,即两个 Worker 可能获取到同一条任务。对于此问题,可以从两点着手解决:

  • 首先,任务执行需要保持幂等,即任务可重复执行,这个可以从业务上着手实现;

  • 其次,可以给任务增加状态,如上述 SQL 里的 status 字段。当某一个 Worker 处理到该任务时,可以去修改该任务为处理中。其他 Worker 在获取任务时,显式指定状态,只处理为待执行的任务即可。


设计和实现扣减中的返还

什么是扣减的返还

扣减的返还指的是在扣减完成之后,业务上发生了一些逆向行为,导致原先已扣减的数据需要恢复以便供后续的扣减请求使用的场景。以在购买商品时的扣减库存举例,其中常见的逆向行为有:

当客户下单之后,发现某个商品买错了(商品品类买错或是数量填错),客户便会取消订单,此时该订单对应的所有商品的库存数量都需要返还;

其次,假设客户在收到订单后,发现其中某一个商品质量有问题或者商品的功能和预期有差异,便会发起订单售后流程,比如退、换货。此时该订单下被退货的商品,也需要单独进行库存返还。


返还实现原则

从上述的业务场景里可以看出,相比扣减而言, 返还的并发量比较低,因为下单完成后发生整单取消或者个别商品售后的情况概率较低。比如,对于热点商品或者爆品的抢购带来的扣减并发量是非常大的,但抢到爆品后再取消订单的概率是非常低的。此种场景里,扣减和返还的并发量的差距可能会达到上万倍。

因此,返还在实现上,可以参考商家对已有商品补货的实现,直接基于数据库进行落地。但返还自身也具备一些需要注意的实现原则,可以总结为以下几点。

原则一:扣减完成才能返还

返还接口在设计时,必须要有扣减号这个字段。因为所有的返还都是依托于扣减的,如果某一个商品的返还没有带上当时的扣减号,后续你很难对当时的情况做出准确判断。

  • 当前商品是否能够返还。 因为没有扣减号,无法找到当时的扣减明细,无法判断此商品当时是否做了扣减,没有做扣减的商品是无法进行返还的。

  • 当前返还的商品数量是否超过扣减值。假设外部系统因为异常,传入了一个超过当时扣减值的数量,如果不通过扣减号获取当时的扣减明细,你无法判断此类异常。


原则二:一次扣减可以多次返还

假设你购买的一个订单里包含了 A、B 两件商品,且这两个商品你各买了 5 件,在产生购买订单时即对应一次扣减。后续使用过程中可能会对某件不满的商品发起售后退货申请。极端情况下,可能会发生四次退货的行为,如:第一次,先退 2 个 A;第二次,再退 3 个 B;最后一次退货,一起将剩余的 3 个 A 和 2 个 B 退回。

由上述案例可以看出,一次扣减(即一个订单)在业务上可以对应多次返还。因此,在实现时需要考虑多次返还的场景。返还主要基于数据库实现,下面介绍下支持多次返还的数据库表的设计。

create table t_return{id bigint not null comment '自增主健',occupy_uuid bigint not null comment '扣减的ID',return_uuid bigint not null comment '返还的唯一ID',unique idx_return_uuid (occupy_uuid,return_uuid) comment '返还标识唯一索引'
}comment '返还记录表';
create table t_return_detail{id bigint not null comment '自增主健',return_uuid bigint not null comment '返还标识',sku_id bigint not null comment '返还的商品ID',num bigint not null comment '返还的商品数量',unique idx_return_sku  (return_uuid,sku_id) comment '返还商品唯一标识'
}comment '返还商品记录表';

上述返还记录表实现了一次扣减多次返还的数据记录,返还商品记录表实现了一次返还里有多个商品的场景,也就是上述案例里的最后一次一起退了 A 和 B 两个商品的场景。


原则三:返还的总数量要小于等于原始扣减的数量

从业务上来看,这是一个必要条件。但在真正实现时,很容易出现处理遗漏。依然以“原则二”里的案例来讲解,在并发返还时,即同一时刻有两个返还请求,一个请求返还 2 个 A,另一个请求返还 4 个 A。如果技术上没有并发的时序控制,在处理两个请求时,有可能都判断为可返还并实际进行返还,最终就会出现返还 6 个 A(实际当时只扣减了 5 个)的超返还的场景。具体如下图 所示:

超返还的场景

在这里插入图片描述
对于上述潜在的风险,可以在返还前,对返还所属的扣减 ID 进行加锁来保证串行化操作,规避超卖的风险,架构如下图 所示:

加锁串行的架构

在这里插入图片描述

在扣减 ID 上加锁,会导致该扣减 ID 下的所有返还都串行执行,有一定的性能损耗。但从业务上看,同一个扣减 ID 并发产生返还的场景极低且返还的调用次数也相对较少,从“架构是技术与业务场景的取舍”这个角度来看,暂不需要花费太大的人力去构建一个更加复杂的加锁架构。


原则四:返还要保证幂等

最后,设计的返还接口需要支持幂等性。比如外部系统调用返还接口超时后,因为外部系统不知道是否调用成功,就会再一次重试。如果返还接口不满足幂等性要求,且上次超时实际是执行成功的,则会导致同一个返还号产生两次数据的返还。处理这个问题最简单的做法是:在返还接口增加返还编号(上述表结构中的 return_uuid)字段并由外部系统传入,通过数据库唯一索引来防重,进而实现幂等性,大致的架构如下图所示

幂等的返还架构图

在这里插入图片描述

总结

  1. 分布式无主架构 —— 如何通过一致性 Hash 与 Watch 机制实现高可用、可水平扩缩容的异步任务执行;
  2. 扣减返还设计 —— 四大原则保障数据正确性与幂等性

在这里插入图片描述

相关文章:

架构思维:构建高并发扣减服务_分布式无主架构

文章目录 Pre无主架构的任务简单实现分布式无主架构 设计和实现扣减中的返还什么是扣减的返还返还实现原则原则一&#xff1a;扣减完成才能返还原则二&#xff1a;一次扣减可以多次返还原则三&#xff1a;返还的总数量要小于等于原始扣减的数量原则四&#xff1a;返还要保证幂等…...

Vue 3 官方 Hooks 的用法与实现原理

Vue 3 引入了 Composition API&#xff0c;使得生命周期钩子&#xff08;hooks&#xff09;在函数式风格中更清晰地表达。本篇文章将从官方 hooks 的使用、实现原理以及自定义 hooks 的结构化思路出发&#xff0c;全面理解 Vue 3 的 hooks 系统。 &#x1f4d8; 1. Vue 3 官方生…...

Vue3 打印表格、Element Plus 打印、前端打印、表格导出打印、打印插件封装、JavaScript 打印、打印预览

🚀 Vue3 高级表格打印工具封装(支持预览、分页、样式美化) 现已更新至npm # npm npm install vue-table-print# yarn yarn add vue-table-print# pnpm pnpm add vue-table-printgithunb地址: https://github.com/zhoulongshao/vue-table-print/blob/main/README.MD关键词…...

湖北理元理律师事务所:专业债务优化如何助力负债者重获生活掌控权

在当前经济环境下&#xff0c;个人债务问题日益凸显。湖北理元理律师事务所通过其专业的债务优化服务&#xff0c;为负债群体提供了一条合法合规的解决路径。本文将客观分析专业债务规划的实际价值&#xff0c;不涉及任何营销内容。 一、债务优化的核心价值 科学评估&#xf…...

RAGFlow知识检索原理解析:混合检索架构与工程实践

一、核心架构设计 RAGFlow构建了四阶段处理流水线,其检索系统采用双路召回+重排序的混合架构: S c o r e f i n a l = α ⋅ B M...

5月22总结

P1024 [NOIP 2001 提高组] 一元三次方程求解 题目描述 有形如&#xff1a;$ a x^3 b x^2 c x d 0 $ 这样的一个一元三次方程。给出该方程中各项的系数&#xff08;$ a,b,c,d $ 均为实数&#xff09;&#xff0c;并约定该方程存在三个不同实根&#xff08;根的范围在 $ -1…...

Java设计模式之桥接模式:从入门到精通

1. 桥接模式概述 1.1 定义与核心思想 桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。这种模式通过提供桥梁结构(Bridge)将抽象和实现解耦。 专业定义:桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化…...

uni-app学习笔记九-vue3 v-for指令

v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法&#xff0c;其中 items 是源数据的数组&#xff0c;而 item 是迭代项的别名&#xff1a; <template><view v-for"(item,index) in 10" :key"index"…...

MAC电脑中右键后复制和拷贝的区别

在Mac电脑中&#xff0c;右键菜单中的“复制”和“拷贝”操作在功能上有所不同&#xff1a; 复制 功能&#xff1a;在选定的位置创建一个与原始文件相同的副本。快捷键&#xff1a;CommandD用于在当前位置快速复制文件&#xff0c;CommandC用于将内容复制到剪贴板。效果&…...

Regmap子系统之六轴传感器驱动-编写icm20607.c驱动

&#xff08;一&#xff09;在驱动中要操作很多芯片相关的寄存器&#xff0c;所以需要先新建一个icm20607.h的头文件&#xff0c;用来定义相关寄存器值。 #ifndef ICM20607_H #define ICM20607_H /*************************************************************** 文件名 : i…...

常见高危端口解析:网络安全中的“危险入口”

目录 1. 经典高危端口列表 2. 典型漏洞案例&#xff1a;445端口与永恒之蓝 攻击原理 防御方案 Linux命令 2. 防护策略建议 三、扩展思考&#xff1a;从端口到攻防体系 结语 1. 经典高危端口列表 端口号 协议/服务 风险场景 21 FTP 明文传输凭据、弱密码爆破、匿名…...

华为2025年校招笔试手撕真题教程(二)

一、题目 大湾区某城市地铁线路非常密集&#xff0c;乘客很难一眼看出选择哪条线路乘坐比较合适&#xff0c;为了解决这个问题&#xff0c;地铁公司希望你开发一个程序帮助乘客挑选合适的乘坐线路&#xff0c;使得乘坐时间最短&#xff0c;地铁公司可以提供的数据是各相邻站点…...

征程 6 J6E/M linear 双int16量化支持替代方案

1.背景简介 当发现使用 plugin 精度 debug 工具定位到是某个 linear 敏感时&#xff0c;示例如下&#xff1a; op_name sensitive_type op_type L1 quant_dty…...

深度学习模块缝合拼接方法套路+即插即用模块分享

前言 在深度学习中&#xff0c;模型的设计往往不是从头开始&#xff0c;而是通过组合不同的模块来构建。这种“模块缝合”技术&#xff0c;就像搭积木一样&#xff0c;把不同的功能模块拼在一起&#xff0c;形成一个强大的模型。今天&#xff0c;我们就来聊聊四种常见的模块缝…...

改写视频生产流程!快手SketchVideo开源:通过线稿精准控制动态分镜的AI视频生成方案

Sketch Video 的核心特点 Sketch Video 通过手绘生成动画的形式&#xff0c;将复杂的信息以简洁、有趣的方式展现出来。其核心特点包括&#xff1a; 超强吸引力 Sketch Video 的手绘风格赋予了视频一种质朴而真实的质感&#xff0c;与常见的精致特效视频形成鲜明对比。这种独…...

Graphics——基于.NET 的 CAD 图形预览技术研究与实现——CAD c#二次开发

一、Graphics 类的本质与作用 Graphics 是 .NET 框架中 System.Drawing 命名空间下的核心类&#xff0c;用于在二维画布&#xff08;如 Bitmap 图像&#xff09;上绘制图形、文本或图像。它相当于 “绘图工具”&#xff0c;提供了一系列方法&#xff08;如 DrawLine、FillElli…...

ElasticSearch 8.x 快速上手并了解核心概念

目录 核心概念概念总结 常见操作索引的常见操作常见的数据类型指定索引库字段类型mapping查看索引库的字段类型最高频使用的数据类型 核心概念 在新版Elasticsearch中&#xff0c;文档document就是一行记录(json)&#xff0c;而这些记录存在于索引库(index)中, 索引名称必须是…...

AI神经网络降噪 vs 传统单/双麦克风降噪的核心优势对比

1. 降噪原理的本质差异 对比维度传统单/双麦克风降噪AI神经网络降噪技术基础基于固定规则的信号处理&#xff08;如谱减法、维纳滤波&#xff09;基于深度学习的动态建模&#xff08;DNN/CNN/Transformer&#xff09;噪声样本依赖预设有限噪声类型训练数据覆盖数十万种真实环境…...

04-Web后端基础(基础知识)

而像HTML、CSS、JS 以及图片、音频、视频等这些资源&#xff0c;我们都称为静态资源。 所谓静态资源&#xff0c;就是指在服务器上存储的不会改变的数据&#xff0c;通常不会根据用户的请求而变化。 那与静态资源对应的还有一类资源&#xff0c;就是动态资源。那所谓动态资源&…...

Spring Cloud生态与技术选型指南:如何构建高可用的微服务系统?

引言&#xff1a;为什么选择Spring Cloud&#xff1f; 作为全球开发者首选的微服务框架&#xff0c;Spring Cloud凭借其开箱即用的组件、与Spring Boot的无缝集成&#xff0c;以及活跃的社区生态&#xff0c;成为企业级微服务架构的基石。但在实际项目中&#xff0c;如何从众多…...

手写简单的tomcat

首先&#xff0c;Tomcat是一个软件&#xff0c;所有的项目都能在Tomcat上加载运行&#xff0c;Tomcat最核心的就是Servlet集合&#xff0c;本身就是HashMap。Tomcat需要支持Servlet&#xff0c;所以有servlet底层的资源&#xff1a;HttpServlet抽象类、HttpRequest和HttpRespon…...

高等数学-积分

一、不定积分 定理&#xff1a;如果函数f(x)在区间I上连续&#xff0c;那么f(x)在区间I上一定有原函数&#xff0c;即一定存在区间I上的可导函数F(x)&#xff0c;使得F(x)f(x) &#xff0c;x∈I 简单地说&#xff1a;连续函数必有原函数。 极限lim*0->x {[∫*0^x sin(t^2)…...

IOS平台Unity3D AOT全局模块结构分析

分析背景 由于IOS平台中不允许执行动态代码&#xff0c;Unity 4.6之前的版本在IOS平台中采用了AOT的处理方式&#xff0c;提前将C#代码静态编译为机器识别的二进制机器码。Unity引擎4.6之前的版本中IOS框架采用了Mono的AOT机制实现静态编译和处理&#xff0c;本文针对全局AOT模…...

Vue 3.0中自定义指令

自定义指令是增强 Vue 组件的重要手段。常见的内置指令有&#xff1a; v-if、v-show、v-model、v-bind、v-on等。 本文将详细讲解如何创建和使用自定义指令&#xff0c;关注以下几个关键点&#xff1a; 1. 指令的钩子函数&#xff1a;类似于生命周期钩子函数。 2. 指令钩子函…...

在 语义分割 和 图像分类 任务中,image、label 和 output 的形状会有所不同。

1. 图像分类 (Image Classification) 图像分类 任务是将整个图像分类为一个类别。通常&#xff0c;output 是对整个图像的类别的预测&#xff0c;而 label 是该图像的真实类别。 1.1 image 的形状 image 是输入图像数据&#xff0c;通常是一个四维张量&#xff1a; 形状&…...

C++面试4-sizeof解析

C++sizeof关键字的深度解析 一、本质认知:编译器的尺度 1. 编译期操作符的基因 int arr[5]; cout << sizeof(arr); // 输出20(假设int为4字节)非运行时特性:在编译阶段完成计算,不会生成任何机器指令表达式不求值:sizeof(++i)不会改变i的值类型感知:对类型名使…...

CyberSecAsia专访CertiK首席安全官:区块链行业亟需“安全优先”开发范式

近日&#xff0c;权威网络安全媒体CyberSecAsia发布了对CertiK首席安全官Wang Tielei博士的专访&#xff0c;双方围绕企业在进军区块链领域时所面临的关键安全风险与防御策略展开深入探讨。 Wang博士在采访中指出&#xff0c;跨链桥攻击、智能合约漏洞以及私钥管理不当&#x…...

uniapp如何设置uni.request可变请求ip地址

文章目录 简介方法一&#xff1a;直接在请求URL中嵌入变量方法二&#xff1a;使用全局变量方法三&#xff1a;使用环境变量方法四&#xff1a;服务端配置方法五&#xff1a;使用配置文件&#xff08;如config.js&#xff09;:总结 简介 在uni-app中&#xff0c;uni.request 用…...

文件操作和IO-3 文件内容的读写

文件内容的读写——数据流 流是操作系统提供的概念&#xff0c;Java对操作系统的流进行了封装。 数据流就像水流&#xff0c;生生不息&#xff0c;绵延不断。 水流的特点&#xff1a;比如要100mL的水&#xff0c;可以一次接10mL&#xff0c;分10次接完&#xff0c;也可以一次接…...

架构的设计

搭建架构的最低前提 1.设计清晰&#xff1a; 需求文档&#xff1a; 有哪些界面 每个界面提够了哪些功能 这些功能是怎样操作的 会有哪些反馈 2.技术&#xff1a; 写架构的同学&#xff1a;这次项目设计的技术 都要有料及&#xff08;用到的技术有哪些特点 有哪些缺点&…...