前面我们搭建了一个抢年货的微服务环境,并且实现了下单和支付的功能,本篇我们来测试下整体环境的性能如何。看看有哪些地方时可以优化的点,最终我们再次验证优化后,性能是否有所提升呢?
一、环境简介
以下所有服务都是单机部署的。
本文直接采用本地环境测试了,我的电脑配置如下:需要启动的基础服务如下:部署的服务:
二、编写测试方法
在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
标签:
凡注有"三板富 | 专注于新三板的第一垂直服务平台"或电头为"三板富 | 专注于新三板的第一垂直服务平台"的稿件,均为三板富 | 专注于新三板的第一垂直服务平台独家版权所有,未经许可不得转载或镜像;授权转载必须注明来源为"三板富 | 专注于新三板的第一垂直服务平台",并保留"三板富 | 专注于新三板的第一垂直服务平台"的电头。
- 脂肪粒如何消除视频(脂肪粒如何消除)
- 百达智能拟将持有宁波百秦14%的140万股权转让给宁波源盛 交易价格不超过140万
- 信邦智能:已与广汽丰田、东风日产、比亚迪等客户在新能源行业开展合作-环球关注
- 上海凯宝:公司子公司顺捷医药拥有《医疗器械经营许可证》资质证书 可开拓医疗器械相关领域的业务-焦点观察
- 翰宇药业醋酸加尼瑞克原料药在美获批
- 东方雨虹(002271)龙虎榜数据(11-11)-全球视点
- 博世科:与华友钴业方面签署新能源电池材料回收利用合作协议-世界热文
- 丢掉苹果订单后 歌尔还将失去面包房-每日视讯
- 菲鹏生物携手华润医疗器械 共同打造IVD开放平台生态
- 格林斯达控股股东戴恩平配偶沈娇拟以9.5万的价格向安徽格林斯达环保设备有限公司转让别克牌汽车-关注
- 罗 牛 山:截止2022年9月末 公司生猪存栏数 45.75万头 其中种猪4.20万头-全球要闻
- 美股盘前要点 | 美股盘前走高 美元指数进一步走低 美国金融条件指数单日跌超50基点-当前简讯
- 工信部等部门:支持培育一批智能光伏示范企业-全球百事通
- “第二支箭”支持民营房企发债:美的置业、碧桂园等房企正沟通对接发债注册意向-今日快讯
- 方天股份拟增加与江苏纳邦数字技术有限公司的关联交易额 交易总金额不超过1300万-天天看点
- 湖北能源:拟54亿元投建江陵电厂二期扩建项目及新能源配套项目-当前要闻
- 电子税务局怎样导出财务报表(厦门市电子税务局财务报表在哪里导出)
- 说说心里话!-天天即时看
- 中青宝:拟购买广州宝云100%股权 14日起复牌-环球动态
- 润贝航科与南方航空签署合作项目,前三季度研发投入同比增30%
- 华联医疗拟向银行申请4500万承兑汇票 全资控股子公司提供连带责任担保-全球热推荐
- 雅化集团:拟向DMCC公司采购锂辉石矿产品-每日速看
- 当代置业(01107.HK):重组生效日期预期为11月18日或之前-每日观点
- 速腾聚创公布新一轮战略融资 多个产业投资方齐聚
- 英记茶庄集团(08241)发布中期业绩 净亏损366.1万港元 同比减少23.54%-天天微头条
- 顺鑫农业:拟对5款牛栏山陈酿产品提价-天天快资讯
- 云天化新能源材料项目加速落地 2023年起磷酸铁锂产能将逐步释放-天天信息
- 百达智能拟购买袁迪欢所持有的金百达10%的100万股权 交易完成后公司持有金百达60%股权-世界观天下
- 中科云网(002306.SZ):正与江苏省包括徐州市在内的多地市政府部门洽谈项目执行相关事宜-天天头条
- 京唐城际铁路开始试运行 北京通州至河北唐山最快39分钟
- 花架子什么意思简解(花架子什么意思)
- 北交所将于11月12日开展新增北交所证券类别通关测试-世界焦点
- 大禹节水:联合体收到37亿元江西省梅江灌区项目成交通知书-时快讯
- 南都电源:子公司拟6000万元向关联方购买锂离子电池绿色高效循环利用项目机器设备及无形资产-世界通讯
- 中国奥园10月销售额约9亿元 尚未披露去年年报及今年中报
- 北交所推出融资融券制度
- 十厘米参照物示意图(十厘米)
- 住房公积金上调意味着什么?有哪些好处?-热推荐
- 移为通信(300590.SZ):11.1825万股限制性股票将归属、上市流通-天天精选
- 百胜中国恢复开店步伐 近8个交易日股价涨幅超过20%
- 砍单风波中的歌尔股份-每日视讯
- 公告精选:顺鑫农业拟对5款牛栏山陈酿进行提价;三七互娱拟3亿元-6亿元回购股份-天天热点评
- 中指快评:杭州取消“认房又认贷”,二套首付最低四成-实时
- 中指快评:《天津津城城市更新规划指引(2021—2035年)》公开征求意见-当前热点
- 探寻数字化浪潮下的数字化转型之路-世界快播报
- 中大力德(002896)龙虎榜数据(11-11)-每日时讯
- “10连板”上涨1.6倍!供销社概念股天鹅股份将停牌核查
- 南都电源(300068.SZ):拟开展融资租赁业务、融资合计不超8.7亿元
- 港股创新药ETF(513120)涨4.86%,康希诺生物涨超14%-天天观热点
- 粤海饲料:公司与国内从事水产养殖、水产饲料经销业务的供销社及农民养殖专业合作社有业务合作-当前简讯
- 首届全球数字贸易博览会将在浙江杭州开幕
- 南都电源:子公司拟购买锂离子电池绿色高效循环利用项目资产-每日快播
- 瑞风新能源(00527.HK):已成立合营公司红松新能源(东营)有限公司-世界热头条
- 办理房产证所需要的材料有哪些?房产证土地使用权证办理程序-天天热头条
- 上峰水泥:出资5000万元与傲林科技、南方水泥合资设立建材数智研究院-环球视讯
- 今年双11中小主播日子难过:商家难被割韭菜,免坑位费免佣金抢资源,明星光环也不好使
- 银涛控股(01943.HK)料中期录得亏损净额约1000万-1500万港元-天天观速讯
- 中国金融投资管理(00605.HK)向上市覆核委员会提出覆核有关撤销上市的决定请求-全球报资讯
- 北京特种机械研究所携三大体系亮相中国航展
- 走进深圳坪山生物医药研究院 感受产业创新活力
- 1060显卡吃鸡设置最佳性能(1060显卡吃鸡设置最佳)
- 微信收不到消息(微信收不到消息)
- 河南电气每10股派现0.41元 共计派发现金红利206万
- 孚能科技完成超33亿元定增 为何被三家广州国资企业包揽?-每日消息
- 中达安:公司控制权拟发生变更
- 德联集团(002666.SZ):部分董监高拟合计减持不超84.58万股-天天热议
- 申朴信息每10股派现6.8元 共计派发现金红利2053.26万-焦点观察
- 永成股份权益分派实施 每10股转增2.1股 共计转增210万股-世界聚看点
- 圣邦股份(300661.SZ):荣基香港拟减持不超47.04万股-世界头条
- 鑫宇科技每10股派现3.3元 共计派发现金红利3033.25万-环球热推荐
- PF GROUP(08221)附属太平基业证券授出4600万港元融资贷款-视点
- 注意!怡亚通将于11月28日召开股东大会-最新快讯
- 一顿操作猛如虎 最后便宜两毛八 波司登这种价格套路怎么破?
- 宏景科技(301396)龙虎榜数据(11-11)-当前资讯
- 东方国信:公司有Web3.0底层技术栈储备 包括区块链、云计算、人工智能、数字孪生/元宇宙-观焦点
- 中南文化(002445.SZ):华润信托自6月6日起已累计减持5%股份-天天热资讯
- 信安世纪:拟收购普世科技80%股权 14日起停牌
- 榕基软件跌停 龙虎榜上机构买入5076.33万元 卖出35.88万元-世界讯息
- 隔夜美股史诗级暴涨,港股互联网ETF(513770)大涨超7%!
- 实益达(002137)龙虎榜数据(10-27)-全球消息
- 比优集团(08053)发布中期业绩 股东应占溢利9643.1万元 同比增加12.74%-全球头条
- 大宗交易:联创光电成交892.15万元,折价9.03%(11-11)
- 湖北能源:拟约54亿元投建江陵电厂二期扩建项目及新能源配套项目
- 联君科技每10股派现6.8元 共计派发现金红利1700万-每日焦点
- 什么是阳光工程(什么是阳包阴)
- 酷我音乐vip账号2021(酷我音乐vip账号密码)
- 麦捷科技:比亚迪是公司重点拓展的客户之一
- 百事达注销控股孙公司深圳百思童科技有限公司-焦点报道
- 丰岛食品及子公司以抵押、保证方式向银行合计申请1.34亿授信额度-环球观天下
- 国美1亿持股被冻结,创始人黄光裕夫妇密集套现-今日快讯
- 安道设计拟利用闲置募集资金不超过2000万(含2000万)购买银行理财产品-观点
- 星湖科技:公司目前尚未开通微博
- 恒大深圳湾地块75亿元挂牌转让:处于抵押状态,已停工超1年-环球热闻
- YGM TRADING(00375.HK)盈警:预计中期税后综合亏损2500万港元
- 青海华鼎(600243)龙虎榜数据(11-11)-世界微头条
- 博大新材全资子公司惠州天选拟购买土地使用权 预计总价不超过2300万-环球热推荐
- 小商品城:拟减免小微企业或个体工商户租金
- 川发龙蟒(002312.SZ):就收购重钢矿业49%的股权已完成过户-天天看热讯
- 普门科技(688389.SH):瀚钰生物、瑞普医疗、瑞源成健康拟减持合计不超6%股份-观察
- 聚焦粤港澳大湾区生物医药发展,2022粤港澳大湾区生物医药企业十强揭晓-天天即时看
公司
焦点
精彩推送
- 大港股份(002077)龙虎榜数据(11-11)-环球讯息
- 潮州社保缴费标准基数及比例2022一览表(潮州2022年个人社保缴费标准表)-每日视讯
- 那些加盟连锁咖啡店的人,现在怎么样了?-今日观点
- 大宗交易:长春高新成交207.99万元,折价9.37%(11-11)
- 雅化集团:全资子公司签署锂辉石矿产品承购协议-天天新资讯
- ST大集(000564)龙虎榜数据(11-11)-全球热门
- 青岛胶州“一网统管” 有效破解“停车难”
- 龙津药业(002750)龙虎榜数据(11-11)-全球最资讯
- 天鸿新材拟向浦东发展银行阜阳分行申请专精特新“小巨人”信用贷款 贷款金额总计1000万-全球速讯
- 云浮社保缴费标准基数及比例2022年一览表(2022年个人社保缴费标准表)
- 复盘66只涨停股:天鹅股份10板 众生药业天地板 天下秀炸板回封-天天热文
- 贵阳贵安2023医保怎么缴费?居民医保缴费方式-聚焦
- 金银河(300619.SZ)股东梁可累计减持比例达1.35% 减持数量过半-今日报
- 华峰测控(688200.SH)部分股东及董监高拟合计减持不超1.79万股-环球滚动
- 许家印再卖资产:恒大总部大楼退租一年后,75.43亿转让深圳湾总部地块-环球即时看
- 农民失地保险金如何领取?失地保险金有多少?-环球时讯
- 农民失地保险金怎么领取?有多少钱?
- 多型功勋航天惯导首次亮相中国航展
- 信用卡存款能取出来吗(信用卡存款)
- ST易购:公司苏宁易购广场(百货业态)有美妆、美容相关业态-天天热资讯
- 黑马•产业丨解构聚玻网:数智化重塑玻璃产业链,创立7年营收过百亿-焦点精选
- 天目湖振幅16.43% 机构龙虎榜净卖出4937.51万元
- 光荣之家门牌发放规定河北(光荣之家门牌发放的意义)
- 大港股份今日涨8.21%,上榜营业部席位近3日合计成交12.72亿元-新要闻
- 宁波韵升:参股公司中韵矿业拟收购非洲稀土矿权
- 国网信通:子公司中标3.87亿元国家电网采购项目
- 港股强劲拉升,恒生科技30ETF(513010)大涨5.8%-每日快报
- 深交所:本周对近期涨幅异常的“竞业达”“天地在线”进行重点监控
- 互太纺织(01382)发盈警,预计中期股东应占溢利可能同比下降约28.6%-短讯
- 天鹅股份:公司股价近期波动较大 停牌核查-天天资讯
- 宁波韵升:参股公司以500万美元收购中矿香港稀土100%股份
- 华纳药厂(688799.SH):监事金焰拟减持不超75.9万股
- 好消息!好消息!黄生就市论势本周分享多股止盈,实力杠杠的!-世界资讯
- 科美诊断股东拟合计减持不超9%股份-环球观点
- 龙虎榜丨荣联科技今日涨停 知名游资孙哥净买入4357.57万元-当前快报
- 华密新材“双11”北交所顺利过会:国家级专精特新“小巨人”企业 去年实现净利润4420万元
- 三七互娱拟斥资3亿元至6亿元回购股份 彰显长期信心
- 犀牛午讯:恒大挂牌转让原深圳总部地块 瑞斯康达回应被立案-天天热门
- 天鹅股份:股价异动 停牌核查-当前视点
- 天鹅股份:公司股价近期波动较大 停牌核查-天天速讯
- 华光环能:拟收购汕头益鑫股权并投建澄海益鑫天然气分布式能源项目
- 宁波韵升:参股公司中韵矿业拟收购非洲稀土矿权
- 顺鑫农业:公司部分产品提价
- 时隔5年杭州回归“认房不认贷” 二套首付降至四成-全球今热点
- 11月11日沪深两市主力资金净流入451.23亿元,加仓房地产、医药生物、非银金融-全球速看
- 宏润建设:公司与安徽省新能创业投资有限责任公司投资设立的上海皖宏新能源有限公司已完成工商登记手续
- 若羽臣(003010.SZ):朗姿股份减持比例达1.7887%-世界今日报
- 许家印再卖资产:恒大总部大楼退租一年后,75.43亿转让深圳湾总部地块-热点在线
- 普甜食品(01699):2021年年度业绩将于11月22日刊发 继续停牌
- 信安世纪(688201.SH)筹划增发及现金收购普世科技80%股权事宜 11月14日起停牌
- 外交部:中方出台进一步优化疫情防控工作二十条措施绝不是躺平
- 若羽臣:公司目前未和阿富汗、非洲等地有业务往来
- 微信否认“微信支持撤回5分钟内消息”
- 外交部:美方应该立即停止对中国光伏企业的无理打压-环球热消息
- 南大光电:公司电子特气产品可以应用于chiplet技术工艺-每日讯息
- 良莠不齐怎么读(良莠)
- 协鑫集成:目前芜湖电池项目已经取得项目备案证 确定了项目土地,能评、环评等正在办理中-全球微资讯
- 富士康拟大举扩建印度工厂 未来两年计划新招5.3万名员工
- 中欣氟材:公司主要对京新药业、国邦药业、浙江医药、天宇药业等公司提供医药中间体-环球讯息
- 安记食品(603696.SH):实控人林肖芳以大宗交易减持1.99%的股份-今热点
- 大森控股(01580.HK):财政年度年结日由12月31日变更为3月31日
- 广电运通:公司在Web3.0涉及的区块链、数字货币、NFT等方面拥有技术储备-今亮点
- 北美木制品规模预计在2026年达到24389亿美元-每日精选
- 苏宁家电加盟(苏宁家电)
- 家居卖场“转战”直播做“副业”-当前热门
- 机构:煤电基准价上调,电源均有望获益!电力ETF基金飘红
- 前三季度6家上市定制家居企业净利下滑 大宗业务加持不再?-环球微速讯
- STYLAND HOLD(00211.HK)拟11月25日举行董事会会议 审批中期业绩
- 债转股是否需要验资 债转股对股民是好是坏?
- 天阳科技:北京时间及其一致行动人珠海时间拟清仓减持合计6.94%公司股份
- 医疗保险和商业保险的区别是什么?哪个更好?
- 惠云钛业:上调各型号钛白粉销售价格-天天快讯
- 医保电子凭证亲情账户怎么使用?激活家庭成员流程一览-天天热头条
- 光线传媒控股股东拟减持降低负债,Q3亏损9876万元-环球热点
- 三七互娱:拟斥资3亿元至6亿元回购股份
- 老三板股票在哪里看行情分析 老三板交易费用怎么算?
- 芬兰木材进出口出现大幅缩减-全球动态
- 东方园林:股东何巧女所持4774万股公司股份将被司法拍卖-全球快看
- 75.4亿!原恒大深圳总部地块挂牌,成交款将用于还债-最资讯
- 机构:医药行业有望反转,美诺转债涨超15%!可转债ETF涨近1%-微速讯
- 这届“双11”:主播通宵卖货,但“狂欢”不再-全球信息
- 深圳市抽查家具、人造板等产品34批次不合格
- 合肥社保缴费基数2022是多少?2022年合肥社保一个月要交多少钱?-全球关注
- 场内基金是指什么 场内基金在哪个平台买比较好一点?
- 安井食品(603345.SH):董事长刘鸣鸣、总经理张清苗拟合计减持不超1.6707%股份-环球简讯
- 国网信通(600131.SH)四家子公司共计中标3.87亿元国家电网招标采购项目-实时
- 上海社会保险缴费基数2022下限6520元-环球要闻
- 农民失地保险金有多少?需要哪些资料?-速递
- 热继电器型号(热继电器符号)
- 公积金联名卡有什么用处 没有联名卡怎么提取公积金余额?
- 个人独资企业注册资金最低多少 注册一个空壳公司一年交多少费用合适?
- 优先股是指什么意思 优先股的优缺点是什么?
- 知名英语培训机构全国多个分校突然关门!学员索要学费 老师:我们也是受害者
- 办信用卡哪个银行好批额度高 有什么容易通过的信用卡?
- 收评:三大指数全天放量大涨 两市成交额突破1.2万亿-速读
- 太湖新城污水厂凸显互联互通优势 污水管“长途奔袭”15公里
- Web3.0概念热度不减 天地在线11日斩获9板 公司否认迎合热点概念炒作-世界快消息
- 北向资金净买入146.67亿元,中止连续4日净卖出-今日报
- NTT DATA业务解决方案荣获2022年SAP创新奖
- 新威凌11月11日14时北交所IPO路演: 国内锌粉生产领域龙头企业 打破国外垄断实现进口替代-当前要闻