前面我们搭建了一个抢年货的微服务环境,并且实现了下单和支付的功能,本篇我们来测试下整体环境的性能如何。看看有哪些地方时可以优化的点,最终我们再次验证优化后,性能是否有所提升呢?

一、环境简介

以下所有服务都是单机部署的。

本文直接采用本地环境测试了,我的电脑配置如下:需要启动的基础服务如下:部署的服务:

二、编写测试方法

在rob-necessities-test服务当中,编写测试方法,使用多线程方式调用下单和支付的接口,代码如下:

@GetMapping("/concurrent/order")

public Long concurrentOrder() {

// 使用cyclicBarrier模拟200线程同时到达

CyclicBarrier cyclicBarrier = new CyclicBarrier(200);

for (int i = 0; i < 200; i++) {

new Thread(() -> {

try {

cyclicBarrier.await();

log.info("开始时间:{}", LocalDateTime.now());

Long orderId = orderService.order();

concurrentHashMap.put(orderId, orderId);

tradingService.pay(orderId);

log.info("完成时间:{},订单id: {}", LocalDateTime.now(), orderId);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

}).start();

}

return null;

}

使用cyclicBarrier,等待200个线程同时到达后,进行下单操作,每完成一个订单,直接调用支付服务,打印每个线程的任务开始结束时间。

三、测试验证

3.1 执行测试

测试接口:http://localhost:8010/test/concurrent/order

使用上面的接口,执行测试程序,然后查看结果,会打印很多日志,我们只关注最先开始和最终结束的:

2022-02-17 14:27:59 INFO Thread-62 com.wjbgn.test.controller.TestController 开始时间:2022-02-17T14:27:59.680 ... ... 2022-02-17 14:28:19 INFO Thread-91 com.wjbgn.test.controller.TestController 完成时间:2022-02-17T14:28:19.899,订单id: 200 复制代码

上面的结果表明,最终的200个线程完成全部任务,大约用时:20秒,平均完成一个订单需要0.1秒。

3.2 数据验证

因为我们是在多线程的场景下,模拟200线程并发的情况,那么会不会出现数据问题呢?我们直接到数据库看结果,我写了如下的查询sql,用来验证结果:

-- 验证订单金额

select sum(order_amount) from rob_order;

-- 验证订单详情金额

select sum(goods_num * goods_unit_price) from rob_order_detail; 复制代码

-- 验证支付金额

select sum(trading_amount) from rob_trading;

-- 验证用户账号扣款金额

select COUNT(*) * 3000 - sum(user_amount) from rob_user_account where user_amount < 3000; 复制代码

-- 验证扣款用户数

select count(*) from rob_user_account where user_amount <3000; 复制代码

-- 验证商品售出库存

select 1000000* count(*) - sum(goods_inventory) from rob_goods; 复制代码

-- 验证订单购买商品数量

select sum(goods_num) from rob_order_detail

经过验证,数据没有出现并发问题,其实是可以预想的到的,因为在订单和支付接口,都增加了同步机制,我使用的是ReentrantLock,以订单接口为例,如下所示:

@Slf4j

@Service

public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderDO> implements OrderService {

@Autowired

private GoodsClient goodsClient;

@Autowired

private OrderDetailService orderDetailService;

private ReentrantLock lock = new ReentrantLock();

@Override

public Result saveOrder(OrderDTO orderDTO) {

OrderDO orderDO = new OrderDO();

// 锁,防止下单数据错误

lock.lock();

try {

// 获取商品

Result info = goodsClient.info(orderDTO.getGoodsId());

// 扣减库存

GoodsDTO goodsDTO = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(info.getData())), GoodsDTO.class);

if (goodsDTO.getGoodsInventory() > orderDTO.getGoodsNum()) {

goodsDTO.setGoodsInventory(goodsDTO.getGoodsInventory() - orderDTO.getGoodsNum());

Result update = goodsClient.update(goodsDTO);

if (update.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

//计算订单总金额

double amount = goodsDTO.getGoodsPrice() * orderDTO.getGoodsNum();

orderDTO.setOrderAmount(amount);

orderDTO.setCreateUser(1L);

orderDTO.setOrderStatus(OrderStatusEnum.NO_PAY);

orderDO = OrderDoConvert.dtoToDo(orderDTO);

// 保存订单主表

boolean save = this.save(orderDO);

if (!save) {

return Result.failed("生成主订单失败");

}

// 处理订单详情数据

OrderDetailDO orderDetailDO = new OrderDetailDO();

orderDetailDO.setCreateUser(1L);

orderDetailDO.setOrderId(orderDO.getId());

orderDetailDO.setGoodsId(orderDTO.getGoodsId());

orderDetailDO.setGoodsNum(orderDTO.getGoodsNum());

orderDetailDO.setGoodsUnitPrice(goodsDTO.getGoodsPrice());

// 保存订单详情

boolean detail = orderDetailService.save(orderDetailDO);

if (!detail) {

return Result.failed("订单详情保存失败");

}

} else {

return Result.failed("商品库存不足");

}

} catch (Exception e) {

log.info("生成订单异常,msg:{}", e);

} finally {

lock.unlock();

}

return Result.success(orderDO);

}

}

3.3 最大并发验证

关于并发这个说法,只能说基于本次环境下的测试,包括后续的优化都是基于此环境,所以大家不要怀疑是否实验结果准确,只能说相对于我的环境,每次测试的结果是准确的。

上面的200并发没有问题,那么我们逐渐加大并发数试下,此处过程省略,我直接给出结论:我在添加到500个并发时,就会出现如下问题了,在交易服务当中抛出异常:

2022-02-17 15:29:00 INFO http-nio-8005-exec-167 com.wjbgn.trading.service.impl.TradingServiceImpl 支付异常,msg:{}

feign.RetryableException: Read timed out executing GET http://rob-necessities-order/order/info/getById?id=1

描述很清晰,在支付时,调用订单查询接口超时了。

而出现的时机是在订单正在创建的时候,当订单都创建完成后,只有支付业务在跑时,就不会出现此异常了。

我们看看数据的状态:

订单总金额:329316.54支付总金额:327581.49未支付金额:1735.05

刚好有两个订单未支付,金额匹配,如下所示:

并且执行500的订单的时间大约是:48秒。

四、性能优化

在此章节,我们进行一些性能优化,看看能不能够正常的完成500订单的数量,同时将时间有效的缩短。

4.1 mysql优化

本文当中的源码全部使用的单表查询,以java代码处理逻辑,所以不存在关联查询所带来的问题。

另外本文只优化肉眼可见的内容,对于mysql服务的配置,等等,暂时不作为优化点。

优化结果其实不明显,因为sql的性能不是主要的问题。但是流程还是要走的

4.1.1 索引

相信一说到sql的优化,那么大家都想到的索引,没错,我们接下来也来看看代码当中是否有可以添加索引的位置。

Result listResult =

userAccountClient.list(new UserAccountDTO(orderDTO.getUserId()));

在查询方面,除了上面这一出,都是使用主键进行查询的,索引我们在上面的位置,给用户账户表的user_id创建索引。

未添加索引时,走了全表扫描:

添加索引后:

除了以上一处,我没有发现另外一处,可以建立索引的点了,而且基于目前的数据量,以及前面创建订单处出现的问题,效果似乎不是很明显,只能算是锦上添花。

4.1.2 慢sql查询

比较常见的mysql问题查询,慢sql是一个重点。

我的慢sql已经开启了,可以使用下面的命令查看日志位置和状态:

show variables like "slow_query%";

未开启的使用下面的命令开启,注意这是临时修改,重启失效,永久需要修改my.ini文件:

set global slow_query_log=on;

另外,需要看下你的慢sql时间阈值,我设置的0.5秒:

show variables like "long_query_time";

最终结果,没有慢sql,因为都是根据主键查询的,效率很快。

4.2 代码优化

经过mysql的优化,我们发现没有什么可以优化,且造成较大问题的优化点。所以我们从最根本的代码上来找问题。

4.2.1 ReentrantLock优化

4.2.1.1 创建订单

我们在创建订单、支付的代码中使用了ReentrantLock,并且是直接锁住所有的业务代码,此处看看是否可以降低锁的粒度,来获得更大的并发度。

public Result saveOrder(OrderDTO orderDTO) { GoodsDTO goodsDTO = new GoodsDTO();

// 锁,防止下单数据错误

lock.lock();

try {

// 获取商品

Result info = goodsClient.info(orderDTO.getGoodsId());

// 扣减库存

goodsDTO = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(info.getData())), GoodsDTO.class);

if (goodsDTO.getGoodsInventory() > orderDTO.getGoodsNum()) {

goodsDTO.setGoodsInventory(goodsDTO.getGoodsInventory() - orderDTO.getGoodsNum());

Result update = goodsClient.update(goodsDTO);

if (update.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

} else {

return Result.failed("商品库存不足");

}

} catch (Exception e) {

log.info("生成订单异常,msg:{}", e);

} finally {

lock.unlock();

}

//计算订单总金额

double amount = goodsDTO.getGoodsPrice() * orderDTO.getGoodsNum();

orderDTO.setOrderAmount(amount);

orderDTO.setCreateUser(1L);

orderDTO.setOrderStatus(OrderStatusEnum.NO_PAY);

OrderDO orderDO = OrderDoConvert.dtoToDo(orderDTO);

// 保存订单主表

boolean save = this.save(orderDO);

if (!save) {

return Result.failed("生成主订单失败");

}

// 处理订单详情数据

OrderDetailDO orderDetailDO = new OrderDetailDO();

orderDetailDO.setCreateUser(1L);

orderDetailDO.setOrderId(orderDO.getId());

orderDetailDO.setGoodsId(orderDTO.getGoodsId());

orderDetailDO.setGoodsNum(orderDTO.getGoodsNum());

orderDetailDO.setGoodsUnitPrice(goodsDTO.getGoodsPrice());

// 保存订单详情

boolean detail = orderDetailService.save(orderDetailDO);

if (!detail) {

return Result.failed("订单详情保存失败");

}

return Result.success(orderDO);

}

在扣除库存的位置,我们使用的是业务代码计算,之后更新数据库的库存值:

goodsDTO.setGoodsInventory(goodsDTO.getGoodsInventory() - orderDTO.getGoodsNum());Result update = goodsClient.update(goodsDTO);

如果此处不加锁,那么一定会出现库存问题。整体看下此处代码,会出现问题的似乎只有一处扣减库存的位置,而新增订单和订单详情的时候,不会产生并发问题。所以我们在此处只对扣减库存加锁,降低锁的粒度:

public Result saveOrder(OrderDTO orderDTO) {

GoodsDTO goodsDTO = new GoodsDTO();

// 锁,防止下单数据错误

lock.lock();

try {

// 获取商品

Result info = goodsClient.info(orderDTO.getGoodsId());

// 扣减库存

goodsDTO = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(info.getData())), GoodsDTO.class);

if (goodsDTO.getGoodsInventory() > orderDTO.getGoodsNum()) {

goodsDTO.setGoodsInventory(goodsDTO.getGoodsInventory() - orderDTO.getGoodsNum());

Result update = goodsClient.update(goodsDTO);

if (update.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

} else {

return Result.failed("商品库存不足");

}

} catch (Exception e) {

log.info("生成订单异常,msg:{}", e);

} finally {

lock.unlock();

}

//计算订单总金额

double amount = goodsDTO.getGoodsPrice() * orderDTO.getGoodsNum();

orderDTO.setOrderAmount(amount);

orderDTO.setCreateUser(1L);

orderDTO.setOrderStatus(OrderStatusEnum.NO_PAY);

OrderDO orderDO = OrderDoConvert.dtoToDo(orderDTO);

// 保存订单主表

boolean save = this.save(orderDO);

if (!save) {

return Result.failed("生成主订单失败");

}

// 处理订单详情数据

OrderDetailDO orderDetailDO = new OrderDetailDO();

orderDetailDO.setCreateUser(1L);

orderDetailDO.setOrderId(orderDO.getId());

orderDetailDO.setGoodsId(orderDTO.getGoodsId());

orderDetailDO.setGoodsNum(orderDTO.getGoodsNum());

orderDetailDO.setGoodsUnitPrice(goodsDTO.getGoodsPrice());

// 保存订单详情

boolean detail = orderDetailService.save(orderDetailDO);

if (!detail) {

return Result.failed("订单详情保存失败");

}

return Result.success(orderDO);

}

结果发现,速度快了很多,相比于前面的48秒,只用了31秒,但是还是有支付失败的订单。

其中创建订单的过程只用了1秒多,支付用了25秒左右,大概消失的5秒,就是在超时等待了:

4.2.1.2 支付订单

那么接下来的重点就是优化支付订单的位置了。在支付的过程中,实际只有扣除用户的账户金额是需要加锁的,而其他的部分都是根据账单的id,更新账单状态:

lock.lock();

try {

// 用户账号扣款

Result listResult = userAccountClient.list(new UserAccountDTO(orderDTO.getUserId()));

if (ObjectUtil.isEmpty(listResult.getData())) {

log.info("未查询到当前用户,支付失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("未查询到当前用户,支付失败");

}

final Double[] userAmount = {0d};

final Long[] userAccountId = {0L};

JSONObject.parseArray(JSONObject.toJSONString(listResult.getData())).forEach(json -> {

JSONObject userAccount = JSONObject.parseObject(JSONObject.toJSONString(json));

userAmount[0] = userAccount.getDouble("userAmount");

userAccountId[0] = userAccount.getLong("id");

});

if (userAmount[0] < orderDTO.getOrderAmount()) {

//修改用户订单状态 - 支付失败

orderClient.update(new OrderDTO(tradingDO.getOrderId(), OrderStatusEnum.PAY_FAILED.toString()));

log.info("订单支付失败,用户余额不足,订单id :{}", tradingDO.getOrderId());

return Result.failed("订单支付失败,用户余额不足");

}

Result userAccount = userAccountClient.update(new UserAccountDTO(userAccountId[0], userAmount[0] - orderDTO.getOrderAmount()));

if (userAccount.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

log.info("用户账户扣款失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("用户账户扣款失败");

}

} catch (Exception e) {

log.info("支付异常,msg:{}", e);

return Result.failed("支付失败");

} finally {

lock.unlock();

}

时间是又一次的大幅度提升了,但是新问题又出现了,未成功支付的订单刚好达到了200,支付成功的订单使用了7秒,那么换算一下,全部成功就需要大概12或13秒吧。总共可能也就需要15秒左右。相比于前面第一版代码的48秒,只用了其三分之一。

结论:看来锁的使用,带来性能的损耗是巨大的。

4.2.2 替换ReentrantLock为update语句

那么我们能否直接使用mysql的锁来控制呢,因为在多实例的微服务场景下,如ReentrantLock,Synchronized这种锁也是不适用的。

4.2.2.1 创建订单

我对创建订单进行了修改,将扣减库存的功能变成通过mysql的update语句去实现

@Update("update rob_goods set goods_inventory =

goods_inventory - #{num, jdbcType=INTEGER}

where id = #{id, jdbcType=BIGINT}

and goods_inventory >= #{num, jdbcType=INTEGER}")

int inventoryDeducting(Long id, Integer num);

订单业务代码如下:

@Overridepublic Result saveOrder(OrderDTO orderDTO) {

GoodsDTO goodsDTO = new GoodsDTO(orderDTO.getGoodsId(), orderDTO.getGoodsNum());

// 扣减库存

Result result = goodsClient.inventoryDeducting(goodsDTO);

if (result.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

//获取商品

Result info = goodsClient.info(orderDTO.getGoodsId());

// 扣减库存

goodsDTO = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(info.getData())), GoodsDTO.class);

//计算订单总金额

double amount = goodsDTO.getGoodsPrice() * orderDTO.getGoodsNum();

orderDTO.setOrderAmount(amount);

orderDTO.setCreateUser(1L);

orderDTO.setOrderStatus(OrderStatusEnum.NO_PAY);

OrderDO orderDO = OrderDoConvert.dtoToDo(orderDTO);

// 保存订单主表

boolean save = this.save(orderDO);

if (!save) {

return Result.failed("生成主订单失败");

}

// 处理订单详情数据

OrderDetailDO orderDetailDO = new OrderDetailDO();

orderDetailDO.setCreateUser(1L);

orderDetailDO.setOrderId(orderDO.getId());

orderDetailDO.setGoodsId(orderDTO.getGoodsId());

orderDetailDO.setGoodsNum(orderDTO.getGoodsNum());

orderDetailDO.setGoodsUnitPrice(goodsDTO.getGoodsPrice());

// 保存订单详情

boolean detail = orderDetailService.save(orderDetailDO);

if (!detail) {

return Result.failed("订单详情保存失败");

}

return Result.success(orderDO);

}

执行代码,结果发现扣减库存有时候会超时导致订单生成失败,这个问题是压力过大,我们后面会专门去解决。

经过上面的修改后,我们发现整体时间在17秒左右,基本变化不大,下面我们继续去修改支付订单。

4.2.2.1 支付订单

支付订单,同样使用mysql的update语句去更新用户的账户金额。

更新账户sql:

@Update("update rob_user_account set user_amount = user_amount - #{num} where user_id = #{userId} and user_amount >= #{num}")int accountDeducting(Long userId,Double num);

注意:根据用户id更新,不要忘记了对用户id添加索引, 其实前面优化的时候我们已经添加过了。

支付业务代码:

public Result trading(TradingDO tradingDO) {

// 获取订单信息

Result info = orderClient.info(tradingDO.getOrderId());

if (ObjectUtil.isEmpty(info.getData())) {

log.info("订单不存在,支付失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("订单不存在,支付失败", tradingDO.getOrderId());

}

OrderDTO orderDTO = JSONObject.parseObject(JSONObject.toJSONString(info.getData())).toJavaObject(OrderDTO.class);

//已完成订单不能再次支付

if (orderDTO.getOrderStatus().equals(OrderStatusEnum.FINNISH.getCode())) {

log.info("订单已完成,不能再次支付,订单id :{}", tradingDO.getOrderId());

return Result.failed("订单已完成,不能再次支付");

}

//修改用户订单状态 - 支付中

Result update = orderClient.update(new OrderDTO(tradingDO.getOrderId(), OrderStatusEnum.PAYING.toString()));

if (update.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

log.info("支付失败,修改订单状态失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("支付失败,修改订单状态失败");

}

// 扣减用户账户金额

Result result = userAccountClient.accountDeducting(new UserAccountDTO(orderDTO.getUserId(), orderDTO.getOrderAmount()));

if (result.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

log.info("用户账户扣款失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("用户账户扣款失败");

}

// 生成支付订单

tradingDO.setCreateUser(1L);

tradingDO.setTradingAmount(orderDTO.getOrderAmount());

tradingDO.setTradingStatus(TradingStatusEnum.SUCCESS);

tradingDO.setUserId(orderDTO.getUserId());

boolean save = this.save(tradingDO);

if (!save) {

//修改用户订单状态 - 支付失败

orderClient.update(new OrderDTO(tradingDO.getOrderId(), OrderStatusEnum.PAY_FAILED.toString()));

//TODO 回滚用户的账户金额

}

//修改用户订单状态 - 订单完成

orderClient.update(new OrderDTO(tradingDO.getOrderId(), OrderStatusEnum.FINNISH.toString()));

return Result.success("订单完成");

}

本次的执行时间有了大幅度的提升,全部完成时间大概是7秒左右。

但是仍然存在订单支付失败的问题,同时存在用户账户扣库存成功,但是支付失败的情况,那是因为我没有做回滚操作导致的。

上面的问题其实是连接超时所导致的,说到底还是业务处理不过来了。

4.2.3 代码逻辑

那么除了上面的优化以外,我们还可以对代码逻辑进行优化。

如下所示:

图片存在马赛克,建议更换或删除 × 图片存在马赛克,建议更换或删除 × 图片存在马赛克,建议更换或删除 ×

扣减库存后,又去查询,执行了两次http请求,同时请求了两次数据库,其实我们可以将其变成一次http请求,更新后同时返回商品信息,修改更新库存位置如下所示:

@Override

public Result inventoryDeducting(Long goodsId, Integer num) {

int i = this.baseMapper.inventoryDeducting(goodsId, num);

if (i > 0) {

return Result.success(this.getById(goodsId));

} else {

return Result.failed("库存不足");

}

}

总结来说,尽量减少网络IO,能够提升性能和系统的稳定性。

经过前面的优化过程,500个订单从下单到支付完成,从初版代码的48秒,已经优化为当前的6~7秒,效果还是显而易见的。我们没有使用任何的中间件,如redis,mq。es等等,否则还可以有更多的优化空间,后面我们会逐渐的将他们引入进来。

五、解决问题

从前面的初版代码一路走来,我们还遗留了几个问题,最后来解决一下。

订单并发创建时,会有更新库存失败的情况,最终导致下单失败。其实这个问题至少不会造成超卖,订单金额错误的问题,因为订单根本没有创建。订单在进行支付时,回查订单数据,或更新订单状态为支付中时失败,因为订单服务访问量高,导致订单回查失败,最终支付失败。用户扣款成功,但是支付失败。

问题1解决方案: 此问题是由于更新库存失败,报出超时异常,我们可以在此处进行异常捕获,然后重新去创建订单,此处就重试创建一次:

// 扣减库存

Result result = null;

try {

result = goodsClient.inventoryDeducting(goodsDTO);

if (result.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

} catch (Exception e) {

log.info("商品库存扣减失败,请重试");

// 临时方案,重试一次

result = goodsClient.inventoryDeducting(goodsDTO);

if (result.getCode() != CommonReturnEnum.SUCCESS.getCode()) {

return Result.failed("扣除商品库存失败");

}

}

如下所示,虽然失败了一次,但是仍然生成了500个订单:

2022-02-18 14:52:50 INFO http-nio-8002-exec-561

com.wjbgn.order.service.impl.OrderServiceImpl

商品库存扣减失败,请重试

问题2解决方案: 此问题与第一个问题是一样的,我们只需要捕获到异常后进行重试就可以了。不同在于支付是必须要成功的,订单已经是存在的,所以我们采用死循环的重试,直到成功为止:

try {

orderDTO = this.updateOrder(tradingDO);

if (ObjectUtil.isNull(orderDTO)) {

log.info("支付失败,修改订单状态失败,订单id :{}", tradingDO.getOrderId());

return Result.failed("支付失败,修改订单状态失败");

}

} catch (Exception e) {

for (; ; ) {

orderDTO = this.updateOrder(tradingDO);

if (ObjectUtil.isNotNull(orderDTO)) {

break;

}

log.info("支付失败,修改订单状态失败,订单id :{}", tradingDO.getOrderId());

}

}

结果很理想,经验证订单全部完成了,总时间仍然维持在6秒左右。

问题3解决方案: 这个就是比较严重的问题了,金额对不上了。在我们单体服务的时候只需要事务就可以很完美的解决了,那是因为数据库帮你做了这件麻烦事。

在分布式环境下,数据的事务时无效的,我们需要使用分布式事务,如阿里背书的seata等,使用起来会导致系统整体性能下降,且规模变庞大,我们小型系统,需要尽量去避免分布式事物的出现。当然我们也可以使用RocketMq实现可靠消息最终一致性的方案。

可以参考分布式事务专题:https://juejin.cn/column/7020215710609571876我们这里只能使用手动回滚的方式了,当捕获到支付失败的时候,需要去将用户账户的金额退还;或用户账户金额扣除成功,支付失败,那么需要去重复支付。

六、总结

关于整个微服务系统的测试,优化,以及问题解决就暂时写到这里吧,不知不觉四五千字了。无聊的同学们可以慢慢看看,自己尝试一下,多少会有些帮助的。

预告下后面的内容,此系统会集成一些监控总监,用来监控整体系统的运行状态,毕竟出现问题不能用脑袋猜。引入监控组件可以更好地发现系统的瓶颈在哪里。

欢迎阅读本专题内的文章,

专题地址:https://juejin.cn/column/7053985131475763236

本文项目代码gitee地址: https://gitee.com/wei_rong_xin/rob-necessities

作者:我犟不过你链接:https://juejin.cn/post/7066296725664710669

推荐内容