重复下单问题
生成订单号,前端传订单id,后端判断是否处理过,处理过可以抛异常;
前端可能点多次,判断订单id,不要查数据库,使用布隆过滤器,把订单id添加布隆过滤器中。
订单ABA问题
例如修改邮编地址,前端没控制好,异步提交等待,再次修改;
订单表加个版本控制,点击确定版本加一。
分库分表
3年内数据超过五百万行或2GB就需要分库分表。
作用:
提高数据存储性能;
考虑服务器查询承载,提升数据承载量与数据安全。读写分离:
为每个数据库搭建从数据库,从数据库基于主从同步机制进行数据同步;
应用层把写的请求路由到主库,读的请求路由到从库;
写入通常较慢,集中到主库可避免多节点写入冲突。主库只需处理写请求,压力更集中,避免因读写混合导致的性能下降。
读请求通常占数据库流量的70%~90%,分散到多个从库可大幅降低主库负载。从库支持水平扩展(增加从库实例),轻松应对高并发查询。分库分表(比如按月分库,按天/用户id分表):
分库分表建议2的幂:
取模友好;
再次分表时更改时只需要分一半数据。分片键选择例如订单id,还需要用户,因此采用订单id拼凑用户id字段后两位形成订单id,之后只传订单id或用户id只需要后两位id取模32的那个表即可(配置绑定表关系,取后两位进行分片)。
弊端:只支持两个id。
多个id采用倒排索引,记录某个id到分片id的倒排索引(里面记录存到了哪些分片)/数据放ES。数据存储设计需要做冷热分离:
热数据:存储最近30天的订单数据,存储在mysql数据库或者ES
冷数据:超过30天的历史订单数据,可以存储在Hadoop或者其他存储,用于离线分析。纯手工方式:修改应用程序的DAO层代码,定义多个数据源,在代码中需要访问数据库的每个地方指定每个数据库请求的数据源。
组件方式(一般采用):使用像Sharding-JDBC这些组件集成在应用程序内,用于代理应用程序的所有数据库请求,并把请求自动路由到对应的数据库实例上。
代理方式(功能写死了,不方便调整):在应用程序和数据库实例之间部署一组数据库代理实例,比如Atlas或Sharding-Proxy。对于应用程序来说,数据库代理把自己伪装成一个单节点的MySQL实例,应用程序的所有数据库请求都将发送给代理,代理分离请求,然后将分离后的请求转发给对应的数据库实例。数据迁移,迁移完要休眠一段时间方便数据整理,删过多会调整树,留时间方便调整完,任务定时,在凌晨调度,业务同步时到业务繁忙时要能即时停止。
优惠券
总发行量;面额;有效期;使用门槛;可用商品。
16位优惠码生成:时间载后8位+4位随机数+用户id后4位
体现订单系统分库分表思想,方便定位分片。
总发行量扣减ABA问题:版本号控制,不用数值计算,使用count-1方式。
使用了优惠券,会把状态改为使用,并记录用在了哪。
分布式id
全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
趋势递增、单调递增:保证下一个ID一定大于上一个ID。
信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
UUID
UUID(UniversallyUniqueIdentifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF 发布的 UUID规范A Universally Unique IDentifier(UUID)URNNamespace。
优点:
性能非常高:本地生成,没有网络消耗。
缺点:
不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
雪花算法
雪花算法:第一个占位符为0且默认为0,若为1即变为负,之后的41是目前调用雪花算法时精确到毫秒的时间戳,再之后的十位表示是哪个计算机,前五位为群组标记,后五位为机器标记,最后十二位是序列化,机器每接到一个请求即获得一个编号。
Leaf-segment
批量获取,每次获取一个 segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。
各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。
读写分离
“我的订单”这个功能,用户看到的都是自己的订单数据。在这种情况下,缓存的命中率就比较低了,会有相当一部分查询请求因为命中不了缓存,穿透到MySQL数据库中。
对数据库节点配置备份节点,备份master数据,写数据请求路由到master,然后master同步slave,读请求路由到slave。
水平扩展多个备份,共同承担客户端请求。数据库层面主从备份机制:
MySQL为例,master记录所有操作到binlog(需要手动打开),binlog更改后,拉取写入中继日志,启动另一个线程,重新执行操作。
同步数据时,设置一个同步间隔时间,保证同步完成。
应用层面对不同SQL语句发到不同数据库:
配置不同数据源,根据语句路由到数据源中(ShardingSphere框架实现的读写分离)。同步过程跨网络,未同步完就查询会出现数据不一致问题,通常配置3到4个从节点,通过消息提示的业务手段,尽可能保证有一定同步的缓冲时间。
历史数据归档
数据迁移,迁移完要休眠一段时间方便数据整理,删过多会调整树,留时间方便调整完,任务定时,在凌晨调度,业务同步时到业务繁忙时要能即时停止迁移(通过XXLJOB调度)。
过程:从MySQL查询,迁移到另一个数据库,在MySQL中对迁移的数据进行删除。
批次数据迁移,一次性迁移数据量过大,影响服务器,要有时间和limit判断;
数据安全迁移保证:主要防止MySQL数据删除结果没迁移成功,存数据时,不光存数据,还记录上次存到哪一个订单。
支付(支付宝为例)
要下载,用沙箱版本支付宝扫描;
网关地址,有dev是沙箱,没有是本地;
zfbinfo.properties按照沙箱应用中的应用信息修改appid与pid;
RSA非对称加密
客户端应用需要往支付宝发送请求的时候,请求需要加密,采用非对称加密机制,由支付宝生成一对密钥public_key与private_key,private_key会保存在支付包服务器中无法看到,public_key分发给应用,应用拿到后可以和工具包一起对要发的数据进行加密,然后往支付宝传输,传输后,支付宝用private_key对报文解密,得到业务数据,进行一系列操作。
支付包完成业务后还会往客户端发起响应,通知客户端请求成功还是失败,发起响应的方式还是加密,要求客户端生成自己的公钥私钥,然后把公钥上传支付宝,支付宝发送通过公钥加密,然后传输,客户端通过私钥解密得到信息。数据获取:
沙箱环境选择加密方式,默认公钥,然后点查看,之后配置到对应zfbinfo.properties的对应位置。
非沙箱环境自定义,通过密钥生成工具生成密钥然后粘贴,公钥上传平台,私钥配置到对应zfbinfo.properties的对应位置。
当面付流程表
用户下单后生成一个本地订单信息,本地订单信息需要进行分库分表处理,然后将这一个商品锁定,直到用户完成支付或不支付取消订单。
之后将订单信息封装成支付宝支付订单,通过支付宝SDK与支付宝完成预下单流程,完成后支付宝在本地记录一个支付宝订单,然后返回二维码图片,二维码图片保存在本地(不合理,最好OSS),之后动态将二维码图片展现在前端页面,让用户可以看到,用户看到后可以使用支付宝扫描。
支付完成后支付宝进行支付金额处理,支付宝支付完成后返回一个通知,告诉支付订单完成支付,之后本地更新订单信息。超时情况,定时任务,五分钟后检查订单状态(查询订单支付管理,从支付宝查询,需要手动进行流程控制,考虑使用RocketMQ的事务消息机制,修改回查次数与回查间隔,根据生产者组回查,回查次数存在redis中),看是否完成,已支付判断正常状态,未支付释放。
本地图片放久了超时,二维码图片发送到聚合支付平台,聚合支付平台转接到支付宝,已经超时,就告诉超时。
再十分钟执行一次检测超时订单的命令,发现超时手动订单回滚,兜底。
分布式事务
在微服务架构中,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。
分布式事务基础理论
CAP理论:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(PartitionTolerance)这三项中的两项。
CP架构:对于CP来说,放弃可用性,追求一致性和分区容错性。
AP架构:对于AP来说,放弃强一致性,追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的Base也是根据AP来扩展的。BASE理论:
BA(Basically Available,基本可用):系统在出现部分故障时,仍能保证核心功能的可用性(如降级服务、限流),而非完全不可用。例如,电商大促时关闭商品评论功能以保障交易流程。
S(Soft State,软状态):允许系统中的数据存在中间状态(如缓存、异步同步),不同节点的数据可能暂时不一致,但最终会通过补偿机制达到一致。
E(Eventually Consistent,最终一致性):数据在经过一段时间的异步同步后,最终会达成一致状态。例如,支付成功后订单状态可能延迟更新,但最终会同步。Base理论是在CAP上发展的,CAP理论描述了分布式系统中数据一致性、可用性、分区容错性之间的制约关系,当你选择了其中的两个时,就不得不对剩下的一个做一定程度的牺牲。
Base理论则是对CAP理论的实际应用,也就是在分区和副本存在的前提下,通过一定的系统设计方案,放弃强一致性,实现基本可用,这是大部分分布式系统的选择,比如NoSQL系统、微服务架构。
分布式事务事务模型-强一致模型
DTP模型(Distributed Transaction Processing Model,分布式事务处理模型)是由 X/Open组织(现为The Open Group)提出的标准框架,用于规范分布式系统中的事务处理。它是传统ACID事务在跨数据库、跨服务场景下的扩展,核心目标是保证分布式事务的原子性和一致性。
DTP模型三个核心组件:
AP(ApplicationProgram)应用程序,AP组件定义了分布式事务(即全局事务)的边界(即事务的开始和结束)以及组成事务的具体操作(Actions);
RM(ResourceManagers)资源管理器,RM指的是诸如MySQL、Oracle这样的数据库或者相应的数据库驱动或可访问的文件系统或者打印机服务器,用以提供访问数据库资源的接口;
TM(TransactionManager)事务管理器,TM是分布式事务的协调者,其负责为分布式事务分配事务ID,监控事务的执行过程,负责事务的完成和容错工作。TM管理的分布式事务可以跨多个RM,TM还管理2PC协议,协调分布式事务的提交/回滚决策。在DTP模型中使用了2种通信规范:
TX规范,其定义了用于在AP组件和TM组件之间的通讯API规范,tx_begin()、tx_end()、tx_info()等接口用以开启、结束和查询分布式事务;
XA(eXtendedArchitecture)规范(借助于两阶段提交),其定义了TM和RM之间通信的API规范。XA协议中规定了DTP模型中RM需要提供prepare、commit、rollback接口给TM调用,以实现两阶段提交。2PC:
两阶段提交(Two Phase Commit),就是将提交(commit)过程划分为2个阶段(Phase):
阶段1:
TM(事务管理器)通知各个RM(资源管理器)准备提交它们的事务分支。如果RM判断自己进行的工作可以被提交,那就对工作内容进行持久化,再给TM肯定答复;要是发生了其他情况,那给TM的都是否定答复。
阶段2:
TM根据阶段1各个RMprepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RMprepare失败的话,则TM通知所有RM回滚自己的事务分支。
问题:阶段一未提交会一直持有数据库连接,可能出现都连接对象被持有同步阻塞;事务管理器如果宕机无法收集事务状态,事务无法进行;网络问题,二阶段通知库存库成功,订单库失败。3PC加了能不能提交的判断等,但解决不了2PC的问题,反而增加了复杂性。
Seata
Seata的整体执行流程设计为两阶段提交,其执行流程如下:
第一阶段:
启动全局事务,GTM分配一个全局事务ID,与本地事务绑定;
所有RM(Resource Manager,资源管理者,业务代码中被远程调用的部分)执行自己的本地事务;
在执行本地事务时,seata使用数据源代理,在执行SQL前,对SQL进行解析,生成前置镜像SQL和后置镜像SQL;
同时向undo log插入一条数据,方便后期出现异常做回流;
然后向TC(Transaction Coordinator,事务协调器)注册分支事务,提交本地事务,最后向TC提交它的分支事务状态。
第二阶段:
所有RM本地事务执行成功,此时TM(Transaction Manager,事务管理器)会向TC发起全局事务提交,TC会立马释放全局锁然后异步驱动所有RM做分支事务的提交;
存在一个RM本地事务不成功,此时TM会向TC发起全局事务回滚,TC会驱动所有的RM做回滚操作,等待所有的RM回滚成功后然后再释放全局锁。
分布式事务事务模型-最终一致模型
TCC模式:
在try confirm cancel等方法自己定义逻辑
Try阶段(预处理阶段):尝试执行事务操作,但不提交。通常会做一些准备工作,比如锁定资源、检查条件等
Confirm阶段(确认阶段):如果Try阶段成功,则进入Confirm阶段,提交事务并真正执行操作
Cancel阶段(取消阶段):如果Try阶段失败,或者在Confirm阶段出现问题,则进入Cancel阶段,回滚事务并释放资源
可靠消息最终一致性-本地消息表
本地消息表(Local Message Table)是一种用于保证分布式系统最终一致性的技术方案,主要用于解决跨服务事务或异步消息传递的问题。它的核心思想是在业务数据库中存储待发送的消息,通过定时任务或事件驱动的方式确保消息可靠投递,避免数据不一致。
可靠消息最终一致性-RocketMQ事务消息
最大努力通知
最大努力通知:一种最终一致性的分布式事务解决方案,适用于跨系统数据同步或异步事件通知场景。它的核心思想是:发送方尽最大努力通知接收方,但允许部分失败,并通过重试机制保证最终一致性。
| 2PC | TCC | 可靠消息 | 最大努力通知 | |
|---|---|---|---|---|
| —致性 | 强一致性 | 最终一致 | 最终一致 | 最终一致 |
| 呑吐量 | 低 | 中 | 高 | 高 |
| 实现复杂度 | 易 | 难 | 中 | 易 |
尽量避免分布式事务,单进程用数据库事务,跨进程用消息队列。
互联网业务主流实现分布式系统事务一致性的方案:
1.基于MQ的可靠消息投递的机制
2.基于重试加确认的的最犬努力通知方案。
大厂生产落地的方案:自研补偿/MQ方案+人工介入。
用户下单冻结库存
Seata接入微服务
引入Seata依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.8.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency><dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>微服务对应数据库中添加undo_log表;
微服务application.yml中添加seata配置
全局事务发起者开启全局事务配置(分库分表技术,seata不能对逻辑表进行解析。不能简单的在全局事务发起方使用@GlobalTransactional,需要使用SeataATShardingTransactionManager整合seate事务,此时不使用@GlobalTransactional而是使用@ShardingTransactionType(TransactionType.BASE)与@Transactional)
支付成功后修改订单状态,异步扣减真实库存
可靠消息最终一致性方案实现
可靠消息最终一致性方案是指当事务发起执行完成本地事务后并发出一条消
息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强
调的是只要消息发给事务参与方最终事务要达到一致。用户注册,新增用户与新增优惠券日志场景,定时任务扫描未发送的优惠券信息,扫描到,发送优惠券信息到MQ,之后优惠券监听MQ消息,监听到发送优惠券信息赠送优惠券给用户。(采用MQ事务机制)