- Atomicity:原子性,改变数据状态要么是一起完成,要么一起失败
- Consistency:一致性,数据的状态是完整一致的
- Isolation:隔离线,即使有并发事务,互相之间也不影响
- Durability:持久性, 一旦事务提交,不可撤销
大家可以到此篇文章去了解一下:
CAP定理
- Consistency:一致性
- Availability:可用性
- Partition tolerance:分区容错
微服务中,不同模块之间使用的数据库是不同的,不同模块之间部署的服务去也有可能是不用的,那么分区容错是无法避免的,因为服务之间的调用不能保证百分百的没问题,所以系统设计必须考虑这种情况。因此,我们可以认为CAP的P总是成立的,剩下的C和A无法同时做到。
实际上根据分布式系统中CAP原则,当P(分区容忍)发生的时候,强行追求C(一致性),会导致(A)可用性、吞吐量下降,此时我们一般用最终一致性来保证我们系统的AP能力。当然不是放弃C,而是放弃强一致性,而且在一般情况下CAP都能保证,只是在发生分区容错的情况下,我们可以通过最终一致性来保证数据一致。
事件驱动实现最终一致性
例子:
项目架构:SpringBoot2+Mybatis+tk-Mybatis+ActiveMQ【因为小例子,不做成Spring Cloud架构】
首先,我们来看看正常的服务之间调用:
代码:
(rollbackFor = Exception.class)public Result placeOrder(OrderQuery query) { Result result =
Result();
RestTemplate restTemplate =
RestTemplate();
HttpHeaders headers =
HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity =
HttpEntity(query, headers);
Result stockResult = restTemplate.postForObject(
,entity,Result.class);
(stockResult.getCode() == Result.ResultConstants.SUCCESS){ Order order =
Order(); BeanUtils.copyProperties(query,order); order.setOrderStatus(1); Integer insertCount = orderMapper.insertSelective(order);
(insertCount == 1){ result.setMsg(
); }
{ result.setMsg(
); } }
{ result.setCode(Result.ResultConstants.FAIL); result.setMsg(
+stockResult.getMsg()); }
result;}
1、订单服务需同步等待库存服务的返回结果,接口结果返回延误。2、订单服务直接依赖于库存服务,只要库存服务崩了,订单服务不能再正常运行。3、订单服务需考虑并发问题,库存最后可能为负。下面开始利用事件驱动实现最终一致性
代码:
(rollbackFor = Exception.class)public Result placeOrderByMQ(OrderQuery query) { Result result =
Result();
Order order =
Order(); BeanUtils.copyProperties(query,order); order.setOrderStatus(0); Integer insertCount = orderMapper.insertSelective(order);
(insertCount == 1){
MqOrderMsg mqOrderMsg =
MqOrderMsg(); mqOrderMsg.setId(order.getId()); mqOrderMsg.setGoodCount(query.getGoodCount()); mqOrderMsg.setGoodName(query.getGoodName()); mqOrderMsg.setStockId(query.getStockId()); jmsProducer.sendOrderCreatedMsg(mqOrderMsg);
result.setMsg(
); }
result;}
代码:
(destination = ORDER_CREATE,containerFactory =
)
(rollbackFor = Exception.class)public void receiveOrderCreatedMsg(Message message, Session session){
{
(message
ActiveMQObjectMessage){ MqStockMsg result =
MqStockMsg(); ActiveMQObjectMessage objectMessage=(ActiveMQObjectMessage)message; MqOrderMsg msg = (MqOrderMsg)objectMessage.getObject(); Integer updateCount = stockMapper.updateNumByStockId(msg.getStockId(),msg.getGoodCount());
(updateCount >= 1){ result.setSuccess(
); result.setOrderId(msg.getId()); }
{ result.setSuccess(
); }
message.acknowledge();
jmsProducer.sendStockLockedMsg(result); } }
(JMSException e) { log.error(
+e.getMessage()); }}
3、订单服务接收到Stock Locked事件,将订单的状态改为“已确认”
代码:
(destination = STOCK_LOCKED,containerFactory =
)
(rollbackFor = Exception.class)public void receiveStockLockedMsg(Message message, Session session){
{
(message
ActiveMQObjectMessage){ ActiveMQObjectMessage objectMessage=(ActiveMQObjectMessage)message; MqStockMsg msg = (MqStockMsg)objectMessage.getObject();
(msg.isSuccess()){ Order updateOrder =
Order(); updateOrder.setId(msg.getOrderId()); updateOrder.setOrderStatus(1); orderMapper.updateByPrimaryKeySelective(updateOrder); log.info(
+msg.getOrderId()+
); }
{ Order updateOrder =
Order(); updateOrder.setId(msg.getOrderId()); updateOrder.setOrderStatus(2); orderMapper.updateByPrimaryKeySelective(updateOrder);
log.error(
+msg.getOrderId()+
); }
message.acknowledge(); } }
(JMSException e) { log.error(
+e.getMessage()); }}
至此,已经全部搞定了。我们看一下和正常的服务调用对比如何:
1、订单服务不再直接依赖于库存服务,而是将下单事件发送到MQ中,让库存监听。2、订单服务能真正的作为一个模块独立运行。3、解决了并发问题,而且MQ的队列处理效率非常的高。
1、用户体验改变了:因为使用事件机制,订单是立即生成的,可是很有可能过一会,系统会提醒你没货了。。这就像是排队抢购一样,排着排着就被通知没货了,不用再排队了。2、数据库可能会存在很对没有完成下单的订单。
那就把订单服务和库存服务聚合在一起吧。解决当前的问题应当是首先要考虑的,我们设计微服务的目的是本想是解决业务并发量。而现在面临的却是用户体验的问题,所以架构设计也是需要妥协的。
最主要是,我们是经过思考和分析的,每个方案能做到哪种程度,能应用到哪种场景。正所谓,技术要和实际场景结合,我们不能为了追求新技术而生搬硬套。