大纲
1.订单系统核心业务流程
2.Spring Cloud Alibaba在订单业务中的落地方案
3.面向分布式全链路故障设计的高可靠架构方案
4.分布式订单系统的技术栈与代码规范
1.订单系统核心业务流程
(1)生成订单时序图
(2)支付订单流程图
(3)取消订单流程图
这里主要介绍生单和退款两个核心链路:一是订单正向核心链路,二是订单逆向核心链路。
(1)生成订单时序图
(2)支付订单流程图
(3)取消订单流程图
2.Spring Cloud Alibaba在订单业务中的落地方案
(1)基于Dubbo + Nacos实现全RPC调用链路
(2)基于Seata实现正向核心链路的数据强一致性
(3)基于RocketMQ延迟消息取消超时未支付订单
(4)基于Builder和Template模式构建对象和发通知
(5)基于Redisson分布式锁解决并发预支付问题
(6)订单履约强一致解决方案
(7)订单履约幂等性解决方案
(8)基于Seata实现逆向核心链路数据强一致性
SCA包含了:Dubbo、Nacos、Sentinel、Seata、RocketMQ等组件,SCA技术栈在订单业务中的落地方案包括如下:
(1)基于Dubbo + Nacos实现全RPC调用链路
(2)基于Seata实现正向核心链路数据强一致性
针对生单 -> 优惠券锁定 -> 库存锁定等正向核心链路流程,使用Seata的AT模式确保涉及分布式事务的正向核心链路的数据强一致性。
(3)基于RocketMQ延迟消息取消超时未支付订单
通过Redisson分布式锁 + Elastic-Job实现补偿。
(4)基于Builder和Template模式构建对象和发通知
基于Builder模式实现复杂订单对象的构建、复杂查询规则对象的构建,基于Template模式实现物流配送的通知。
(5)基于Redisson分布式锁解决并发预支付问题
(6)订单履约强一致解决方案
基于RocketMQ的柔性分布式事务解决方案。
(7)订单履约幂等性解决方案
基于Redisson分布式锁 + 前置状态检查实现幂等。
(8)基于Seata实现逆向核心链路数据强一致性
针对取消订单 -> 取消履约 -> 释放资产等逆向核心链路流程,使用Seata的AT模式确保涉及分布式事务的逆向核心链路的数据强一致性。
简单介绍完SCA在复杂订单业务中的基础技术方案,接下来简单介绍在分布式架构中面向分布式全链路故障而设计的高可靠架构方案。
3.面向分布式全链路故障设计的高可靠架构方案
(1)订单系统Dubbo服务高并发压力优化
(2)订单系统引入Spring Cloud Alibaba Sentinel
(3)大促活动网关层限流解决方案
(4)订单系统自适应流控解决方案
(5)订单集群柔性限流解决方案
(6)订单核心链路雪崩场景保护方案
(7)防恶意刷单自动发现与黑名单方案
(8)库存系统异构存储的TCC分布式事务解决方案
(9)仓储系统老旧代码的Saga分布式事务解决方案
(10)物流系统多数据库的XA分布式事务解决方案
(11)Nacos+ZooKeeper双注册中心高可用方案
(12)Dubbo+Nacos多机房部署流量Mesh管控方案
分布式系统的技术难点,其实就是分布式系统链路长、故障多,如果任何一个环节出了故障就会导致系统出问题。所以要分析全链路里会有哪些问题,要用哪些方案来确保系统运行稳定。
分布式订单系统就采用了如下方案来保证系统稳定:
Dubbo服务高并发压力生产优化、大促活动网关层限流解决方案、订单集群柔性限流解决方案、订单系统自适应流控解决方案、订单系统核心链路雪崩解决方案、防恶意刷单与黑名单解决方案、库存系统异构存储架构TCC分布式事务解决方案、仓储系统老旧代码Saga分布式事务解决方案、物流系统多数据库XA分布式事务解决方案、Nacos + ZooKeeper双注册中心高可用方案、Dubbo + Nacos多机房部署流量Mesh管控方案。
(1)订单系统Dubbo服务高并发压力优化
首先针对订单系统核心接口逐个进行高并发压测,然后逐步分析Linux OS、Dubbo线程池、数据库连接池、数据库索引和SQL语句、业务逻辑效率等各个环节存在的问题。并得出在机器负载可控情况下,订单系统可以接受的最大并发压力。
(2)订单系统引入Spring Cloud Alibaba Sentinel
此时需要将Sentinel客户端引入订单系统工程,同时完成Sentinel Dashboard的搭建。
(3)大促活动网关层限流解决方案
首先演示在大促场景下,瞬时高并发是如何击垮订单系统的,同时评估出订单集群部署下的最大可抗压力,然后设计网关大促限流方案。可以基于Spring Cloud Gateway实现一个订单系统前置网关,对订单系统集群部署做负载均衡 + 部署网关集群。通过在网关引入Sentinel来实现限流,在流量入口处保护订单系统不被击垮。
(4)订单系统自适应流控解决方案
首先演示订单系统单实例流量超载的问题。然后根据订单系统部署机器的配置、高压下的机器负载、业务逻辑的复杂度,基于Sentinel设计订单系统的自适应流控方案。也就是根据机器负载、响应时间、请求速率,自适应调整机器的流量阈值,从而实现柔性流控效果。最后演示大流量下,流量被网关层限流后穿透到订单层,各个机器上的自适应流控效果。
(5)订单集群柔性限流解决方案
首先针对订单系统各核心接口,评估出集群模式下每个接口的最大负载压力。然后演示出集群模式下的接口,在访问超载时引发的问题。接着基于Sentinel设计各个核心接口的柔性限流方案。最后对订单接口进行超压力访问,演示接口级的柔性流控效果。
(6)订单核心链路雪崩场景保护方案
首先演示订单核心链路的服务雪崩场景,单服务崩溃是如何引发服务链路全面崩溃的。接着对订单核心链路的各个服务,基于Sentinel设计服务链路防雪崩方案,避免核心链路任何一个服务崩溃引发的服务链路雪崩问题。最后演示单服务崩溃时,整个订单服务的防雪崩效果。
订单核心链路的各个服务有:订单服务、库存服务、营销服务、仓储服务、物流服务、风控服务。
(7)防恶意刷单自动发现与黑名单方案
首先演示单用户恶意刷单行为和效果,接着基于基于Sentinel设计自动识别用户恶意刷单的方案,将恶意刷单的用户ID自动加入访问控制黑名单。从而实现自动化识别 + 防止恶意刷单 + 黑名单控制的机制。
(8)库存系统异构存储的TCC分布式事务解决方案
常见的库存架构是Redis + 数据库异构存储架构。由于订单系统又会强依赖库存系统,所以库存系统的异构存储架构的分布式事务解决方案,通常是基于Seata的TCC模式来实现的。
(9)仓储系统老旧代码的Saga分布式事务解决方案
由于仓储系统的逻辑通常会非常复杂,而且会包含多个服务协同工作,所以对这类系统做分布式事务改造的难度比较大。仓储系统的多服务链路老旧代码的分布式事务解决方案,通常是基于Seata的Saga模式来实现的。
(10)物流系统多数据库的XA分布式事务解决方案
(11)Nacos+ZooKeeper双注册中心高可用方案
(12)Dubbo+Nacos多机房部署流量Mesh管控方案
4.分布式订单系统的技术栈与代码规范
(1)订单系统的技术栈
(2)各层方法的命名规范
(3)领域模型POJO类命名规范
(4)统一异常处理规范
(5)统一返回结果处理规范
(6)Service层开发规范
(1)订单系统的技术栈
(2)各层方法的命名规范
开发规范基于阿⾥巴巴的《Java开发⼿册》:
一.总的原则是⽤动词做前缀,名词在后⾯
二.获取单个对象时使⽤get做前缀
三.获取多个对象时使⽤list做前缀如listOrders
四.获取统计值时使⽤count做前缀
五.插⼊数据时使⽤save/insert做前缀
六.删除数据时使⽤remove/delete做前缀
七.修改数据时使⽤update做前缀
(3)领域模型POJO类命名规范
一.数据对象:xxxDO,xxx即为数据表名
二.Controller层返回结果,展示对象⽤xxxVO
请求⼊参的命名格式⽤xxxRequest,查询条件封装对象⽤xxxQuery。
三.Dubbo API层返回结果,返回对象⽤xxxDTO
请求的命名格式为xxxRequest,查询条件封装对象⽤xxxQuery。
四.业务层内部的数据传输对象⼀般⽤xxxDTO,不做强制规定。
每个POJO类都会继承AbstractObject类,方便不同POJO之间的属性克隆。- //基础POJO类
- //浅克隆:
- //复制对象时仅仅复制对象本身,包括基本属性;
- //但该对象的属性引用其他对象时,该引用对象不会被复制;
- //即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个;
- //深克隆:
- //复制对象本身的同时,也复制对象包含的引用指向的对象;
- //即修改被克隆对象的任何属性都不会影响到克隆出来的对象。
- @SuppressWarnings({"rawtypes", "unchecked"})
- public abstract class AbstractObject {
- //深度克隆
- //@param targetClazz 目标对象的Class类型
- //@return 目标对象实例
- public <T> T clone(Class<T> targetClazz) {
- try {
- T target = targetClazz.newInstance();
- BeanCopierUtil.copyProperties(this, target);
- return getTarget(target);
- } catch (Exception e) {
- throw new RuntimeException("error", e);
- }
- }
- //浅度克隆
- //@param target 目标对象实例
- //@return 目标对象实例
- public <T> T clone(T target) {
- try {
- BeanCopierUtil.copyProperties(this, target);
- return getTarget(target);
- } catch (Exception e) {
- throw new RuntimeException("error", e);
- }
- }
- ...
- }
复制代码 (4)统一异常处理规范
一.定义⼀个GlobalExceptionHandler组件
通过对该组件添加@RestControllerAdvice注解,让该组件成为默认的Controller全局异常处理增强组件。在这个组件中,会分别对系统级别未知系统、客户端异常、服务端异常都做了统⼀处理。- //默认的Controller全局异常处理增强组件
- @RestControllerAdvice
- @Order
- public class GlobalExceptionHandler {
- // =========== 系统级别未知异常 =========
- @ExceptionHandler(value = Exception.class)
- public JsonResult<Object> handle(Exception e) {
- log.error("[ 系统未知错误 ]", e);
- return JsonResult.buildError(CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR);
- }
- // =========== 客户端异常 =========
- //1001 HTTP请求方法类型错误
- @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
- public JsonResult<Object> handle(HttpRequestMethodNotSupportedException e) {
- log.error("[客户端HTTP请求方法错误]", e);
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_HTTP_METHOD_ERROR);
- }
- //1002 客户端请求体参数校验不通过
- @ExceptionHandler(value = MethodArgumentNotValidException.class)
- public JsonResult<Object> handle(MethodArgumentNotValidException e) {
- log.error("[客户端请求体参数校验不通过]", e);
- String errorMsg = this.handle(e.getBindingResult().getFieldErrors());
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_CHECK_ERROR.getErrorCode(), errorMsg);
- }
- private String handle(List<FieldError> fieldErrors) {
- StringBuilder sb = new StringBuilder();
- for (FieldError obj : fieldErrors) {
- sb.append(obj.getField());
- sb.append("=[");
- sb.append(obj.getDefaultMessage());
- sb.append("] ");
- }
- return sb.toString();
- }
- //1003 客户端请求体JSON格式错误或字段类型不匹配
- @ExceptionHandler(value = HttpMessageNotReadableException.class)
- public JsonResult<Object> handle(HttpMessageNotReadableException e) {
- log.error("[客户端请求体JSON格式错误或字段类型不匹配]", e);
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_FORMAT_ERROR);
- }
- //1004 客户端URL中的参数类型错误
- @ExceptionHandler(value = BindException.class)
- public JsonResult<Object> handle(BindException e) {
- log.error("[客户端URL中的参数类型错误]", e);
- FieldError fieldError = e.getBindingResult().getFieldError();
- String errorMsg = null;
- if (fieldError != null) {
- errorMsg = fieldError.getDefaultMessage();
- if (errorMsg != null && errorMsg.contains("java.lang.NumberFormatException")) {
- errorMsg = fieldError.getField() + "参数类型错误";
- }
- }
- if (errorMsg != null && !"".equals(errorMsg)) {
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR.getErrorCode(), errorMsg);
- }
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR);
- }
- //1005 客户端请求参数校验不通过
- @ExceptionHandler(value = ConstraintViolationException.class)
- public JsonResult<Object> handle(ConstraintViolationException e) {
- log.error("[客户端请求参数校验不通过]", e);
- Iterator<ConstraintViolation<?>> it = e.getConstraintViolations().iterator();
- String errorMsg = null;
- if (it.hasNext()) {
- errorMsg = it.next().getMessageTemplate();
- }
- if (errorMsg != null && !"".equals(errorMsg)) {
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR.getErrorCode(), errorMsg);
- }
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR);
- }
- //1006 客户端请求缺少必填的参数
- @ExceptionHandler(value = MissingServletRequestParameterException.class)
- public JsonResult<Object> handle(MissingServletRequestParameterException e) {
- log.error("[客户端请求缺少必填的参数]", e);
- String errorMsg = null;
- String parameterName = e.getParameterName();
- if (!"".equals(parameterName)) {
- errorMsg = parameterName + "不能为空";
- }
- if (errorMsg != null) {
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR.getErrorCode(), errorMsg);
- }
- return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR);
- }
- // =========== 服务端异常 =========
- //2001 业务方法参数检查不通过
- @ExceptionHandler(value = IllegalArgumentException.class)
- public JsonResult<Object> handle(IllegalArgumentException e) {
- log.error("[业务方法参数检查不通过]", e);
- return JsonResult.buildError(CommonErrorCodeEnum.SERVER_ILLEGAL_ARGUMENT_ERROR);
- }
- //系统自定义业务异常
- @ExceptionHandler(value = BaseBizException.class)
- public JsonResult<Object> handle(BaseBizException e) {
- log.error("[ 业务异常 ]", e);
- return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
- }
- }
复制代码 规范三:如果某接⼝就想返回原样的对象,不想让返回结果被JsonResult封装,那么可以让Controller⽅法返回一个JsonMap对象。
GlobalResponseBodyAdvice.beforeBodyWrite()⽅法会对类型为JsonMap的body进行直接返回。
[code]//自定义Map实现,完全兼容java.util.HashMappublic class JsonMap extends HashMap { public JsonMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); } public JsonMap(int initialCapacity) { super(initialCapacity); } public JsonMap() { } public JsonMap(Map |