一、提交订单时的各种问题
1. 服务刚启动第一次下单后,orderClient 可能会出现熔断的情况,但orderService等其它服务继续在执行。
1) 问题描述
orderClient 设置了hystrix的超时时间,但是并没有起作用:
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
enabled: true
isolation:
thread:
timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
2)解决方案
后来发现,不仅需要设置hystrix的超时时间,还需要设置feign的超时时间,经过测试,两个需要一起设置
feign:
hystrix:
enabled: true
client:
config:
order-server: #feign的value值, 若为default则为所有feign
connectTimeout: 1000
readTimeout: 5000
loggerLevel: full
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms
会使用其中较小的超时时间。一般情况下,hystrix的超时时间不宜设置过大,最大3秒,下一个问题会说具体原因,且feign的超时时间 要大于 hystrix 的最大超时时间。为什么要说是hystrix 的最大超时时间?因为hystrix可以为单个服务的单个方法设置单独的超时时间(但经过测试,发现并不起作用)。
hystrix:
threadpool:
# 指定服务的配置
# user-service:
# coreSize: 20
# maxQueueSize: 200
# queueSizeRejectionThreshold: 3
# # userThreadPool是UserTimeOutCommand中配置的threadPoolKey
# userThreadPool:
# coreSize: 20
# maxQueueSize: 20
# queueSizeRejectionThreshold: 3
# 这是默认的配置
default:
coreSize: 10
maxQueueSize: 200
queueSizeRejectionThreshold: 200
command:
# 指定feign客户端中具体的方法,格式: 服务名#方法名
UserService#timeout():
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
userCommandKey:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
# 这是默认的配置
default:
execution:
timeout:
enabled: true
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 15000
interruptOnTimeout: true
interruptOnFutureCancel: false
semaphore:
maxConcurrentRequests: 2
2. 提交订单时的问题
1) 问题描述
下单操作使用了事物,隔离级别是 READ_CONMMIT ,即 读已提交。代码如下:
/**
* 购买商品
* @param orderDO 订单信息
* @param buyGoodsList 购买的商品信息
* @return 订单信息
*/
@Transactional
public OrderDTO addOrder(OrderDO orderDO, List<GoodsDTO> buyGoodsList){
// 用统一的时间,避免性能消耗
long time = DateTime.now().getMillis();
insertOrder(orderDO, time);
// 在商品服务检查库存并买商品
List<GoodsDO> goodsList = getGoodsDOList(buyGoodsList);
Map<Integer, Integer> goodsMap = getGoodsMap(buyGoodsList);
// 更新订单状态 和金额商品件数等信息
updateOrderState(orderDO, time, goodsList, goodsMap);
// 插入订单详情
List<OrderDetailDO> detailDOList = addDetailDOList(orderDO, time, goodsList, goodsMap);
// 支付订单
payOrder(orderDO, time);
OrderDTO returnDTO = new OrderDTO();
BeanUtils.copyProperties(orderDO, returnDTO);
returnDTO.setDetailDOList(detailDOList);
return returnDTO;
}
其中 getGoodsDOList 和 payOrder 分别通过feign调用 商品服务 和 支付服务,且这些服务都会使用事物
private List<GoodsDO> getGoodsDOList(List<GoodsDTO> buyGoodsList) {
Response<List<GoodsDO>> buyResponse = orderGoodsFeign.buyGoodsList(buyGoodsList);
if (buyResponse.getCode() != ErrorCodeEnum.SUCCESS.getCode()){
throw new GlobalException(buyResponse);
}
return buyResponse.getData();
}
private void payOrder(OrderDO orderDO, long time) {
Response payResp = payFeign.pay(new PayDTO().setOrderId(orderDO.getId()).setUserId(orderDO.getUserId()));
if (payResp.getCode() != ErrorCodeEnum.SUCCESS.getCode()){
throw new GlobalException(payResp);
}
orderDO.setState(OrderStateEnum.SENDING.code).setUpdateTime(time);
int i1 = orderMapper.updateMoney(orderDO);
if (i1 == 0){
// log.error("更新订单价格和数量时失败,orderDO={}",orderDO);
// throw new GlobalException(ErrorCodeEnum.OPERATE_ERROR);
//TODO 放入消息队列进行重试处理
log.error("更新订单价格和数量时失败,orderDO={}",orderDO);
}
}
这样就会出现分布式事物问题:
- 若这个过程失败了,商品服务修改的数据并不会回滚,目前出现的问题是库存减少后不会回滚
- 由于使用的是读已提交,所以这个订单并没有真正入库,支付服务那边通过传过去的订单id(出于安全考虑只传用户id和订单id)去查询订单信息时(如金额),并没有查到该订单
2) 思考
- 出于对用户体验和系统性能考虑,用户直接操作的相关的接口应尽量实时返回,延迟最多不要超过1秒,少数特殊接口可以不超过3秒。否则做成异步处理,若有必要前端可进行轮询查询处理结果。
- 非用户直接操作的相关接口延迟最多不要超过1-5秒,否则做成异步。依并发量而定,并发量越高延迟需要越小,原因是并发量越高,需要处理的线程越多,线程池中线程数量固定,若每个线程的处理时间越长,则有可能线程被占用完的情况,而并发量比较大,极有可能会导致线程池缓存队列溢出,导致熔断等情况。
3) 解决方案——待解决
想法:
- 首先入库订单,状态是初始状态(即未进行库存检测、支付等),防止漏单,此时订单只有商品件数等信息,并没有支付金额信息,因为还需要去商品服务查询
- 检测商品库存并购买商品 (getGoodsDOList),由于用户下单和支付是连续的,所以支付之前的操作(订单初始入库,库存检测等等)必须是同步的(待考量),故检测商品库存和购买商品需要同步调用。新建一张商品中间状态表,用于存储购买商品的中间状态,如购买数量等,当下单失败时可以进行数据回滚,若回滚失败可通过消息队列进行重试。该表也可用于用户规定时间内未支付,订单自动作废时进行商品数据回滚的依据。
- 购买商品成功后修改订单状态等一系列操作完成后,该下单接口可返回了,然后订单进入待支付状态。剩下的就是支付功能了,与下单功能剥离,所以订单服务并不需要调用支付服务的接口。
- 下单完成后,前端即可无缝进入支付功能(弹出支付密码输入框等),若支付完成,则删除商品中间状态表的记录。
- 订单进入待支付状态后,15分钟内未支付则订单自动作废,商品状态表回滚。前端显示15分钟倒计时,但是后端作废时间应该是 15分钟5秒,即比前端显示时间多出几秒钟,用于解决如下问题:
若刚好在15分钟那一刻,用户支付成功了,程序去查询订单时发现已经作废了,就会出现问题。若多出几秒钟,在那一刻用户支付成功了,该订单也能支付成功。若超过了15分钟,用户就不能再支付了则订单在15分5秒后作废。
网友评论