Micro-service architecture - the use of event-driven achieve eventual consistency

Transactional consistency

First, let's review the ACID principles:

  • Atomicity: Atomicity, changing the data is either completed state together or fail together
  • Consistency: consistency, the state data is complete and consistent
  • Isolation: the line of separation, even if there is a concurrent transaction, does not affect each other
  • Durability: Persistence, once the transaction commits, irrevocable

In the single application, we can take advantage of the characteristics of a relational database to complete the transaction consistency, but once the application development services to micro, according to split the business into unused modules, and each module has a database separate from, and this time we have to face is distributed transaction, and needs its own complete ACID in your code inside. The more popular solutions include: two-phase commit, compensation mechanisms, local message table (use of local affairs and MQ), MQ transactional messages (RocketMQ).
You can go to this article to find out: a distributed transaction four solutions

CAP theorem

In 1998, a computer scientist at the University of California, Eric Brewer proposed a distributed system has three indicators.

  • Consistency: consistency
  • Availability: Availability
  • Partition tolerance: Fault-tolerant partition

Eric Brewer said that the three indicators impossible at the same time. This conclusion is called CAP theorem.
Micro service, the database used between different modules are different, the deployment of services between the different modules is also possible to do, then the fault-tolerant partition can not be avoided, because the call between the service can not guarantee 100 per cent did not problem, so the system design must consider this case. Therefore, we can say that the CAP P is always true, the rest of the C and A can not be done at the same time.
The distributed system actually CAP principle, when P (partition tolerance) occurs, forced pursuit C (consistency), leads to (A) the availability, throughput drops, then we generally use the final consistency to ensure our AP capability of the system. Of course not give up C, but gave up strong consistency, but also in general CAP can guarantee, just in case of a fault-tolerant partition, we can finally consistency to ensure data consistency.

Event-driven achieve eventual consistency

Event-driven architecture in between domain objects to synchronize asynchronous status messages, some messages can also be released simultaneously to multiple services at the news caused a synchronization service may cause additional news, events will spread. Event-driven in the strict sense is not synchronous call.

example:

In the electricity business which, under the single-user must determine whether the order based on inventory turnover.
Project architecture: SpringBoot2 + Mybatis + tk-Mybatis + ActiveMQ [because small example, do not make Spring Cloud architecture]

First, let's take a look at calls between normal service:

invoke
Code:

@Override
@Transactional(rollbackFor = Exception.class)
public Result placeOrder(OrderQuery query) {
    Result result = new Result();
    // 先远程调用Stock-Service去减少库存
    RestTemplate restTemplate = new RestTemplate();
    //请求头
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    //封装成一个请求对象
    HttpEntity entity = new HttpEntity(query, headers);
    // 同步调用库存服务的接口
    Result stockResult = restTemplate.postForObject("http://127.0.0.1:8081/stock/reduceStock",entity,Result.class);
    if (stockResult.getCode() == Result.ResultConstants.SUCCESS){
        Order order = new Order();
        BeanUtils.copyProperties(query,order);
        order.setOrderStatus(1);
        Integer insertCount = orderMapper.insertSelective(order);
        if (insertCount == 1){
            result.setMsg("下单成功");
        }else {
            result.setMsg("下单失败");
        }
    }else {
        result.setCode(Result.ResultConstants.FAIL);
        result.setMsg("下单失败:"+stockResult.getMsg());
    }
    return result;
}

We can see, so a lot of the shortcomings of the service call:

1、订单服务需同步等待库存服务的返回结果,接口结果返回延误。
2、订单服务直接依赖于库存服务,只要库存服务崩了,订单服务不能再正常运行。
3、订单服务需考虑并发问题,库存最后可能为负。
Here began using event-driven achieve eventual consistency

1, after the order service new orders, order status is "turned on" and then publish an Order Created event to the message queue on
Order Created
the code:

@Transactional(rollbackFor = Exception.class)
public Result placeOrderByMQ(OrderQuery query) {
    Result result = new Result();
    // 先创建订单,状态为下单0
    Order order = new Order();
    BeanUtils.copyProperties(query,order);
    order.setOrderStatus(0);
    Integer insertCount = orderMapper.insertSelective(order);
    if (insertCount == 1){
        // 发送 订单消息
        MqOrderMsg mqOrderMsg = new MqOrderMsg();
        mqOrderMsg.setId(order.getId());
        mqOrderMsg.setGoodCount(query.getGoodCount());
        mqOrderMsg.setGoodName(query.getGoodName());
        mqOrderMsg.setStockId(query.getStockId());
        jmsProducer.sendOrderCreatedMsg(mqOrderMsg);
        // 此时的订单只是开启状态
        result.setMsg("下单成功");
    }
    return result;
}

2, inventory service in listening to the message queue OrderCreated messages, inventory of goods in inventory table subtracting the number of orders, and then sent to a Stock Locked event message queue.
Stock Locked
Code:

/**
 * 接收下单消息
 * @param message 接收到的消息
 * @param session 上下文
 */
@JmsListener(destination = ORDER_CREATE,containerFactory = "myListenerContainerFactory")
@Transactional(rollbackFor = Exception.class)
public void receiveOrderCreatedMsg(Message message, Session session){
    try {
        if (message instanceof ActiveMQObjectMessage){
            MqStockMsg result = new MqStockMsg();
            ActiveMQObjectMessage objectMessage=(ActiveMQObjectMessage)message;
            MqOrderMsg msg = (MqOrderMsg)objectMessage.getObject();
            Integer updateCount = stockMapper.updateNumByStockId(msg.getStockId(),msg.getGoodCount());
            if (updateCount >= 1){
                result.setSuccess(true);
                result.setOrderId(msg.getId());
            }else {
                result.setSuccess(false);
            }
            // 手动ack,使消息出队列,不然会不断消费
            message.acknowledge();
            // 发送库存锁定消息到MQ
            jmsProducer.sendStockLockedMsg(result);
        }
    } catch (JMSException e) {
        log.error("接收订单创建消息报错:"+e.getMessage());
    }
}

Careful friends may see: message.acknowledge (), that is, manual confirmation message. Because the logical inventory services can ensure normal execution after the confirmation message has consumed, can guarantee the reliability of message delivery, in the event of abnormal Times inventory service execution, we can do it again consumes the single message.
3, service orders received Stock Locked event, order status to "confirmed"
Order Change
Code:

/**
 * 判断是否还有库存,有库存更新订单状态为1,无库存更新订单状态为2,并且通知用户(WebSocket)
 * @param message
 */
@JmsListener(destination = STOCK_LOCKED,containerFactory = "myListenerContainerFactory")
@Transactional(rollbackFor = Exception.class)
public void receiveStockLockedMsg(Message message, Session session){
    try {
        if (message instanceof ActiveMQObjectMessage){
            ActiveMQObjectMessage objectMessage=(ActiveMQObjectMessage)message;
            MqStockMsg msg = (MqStockMsg)objectMessage.getObject();
            if (msg.isSuccess()){
                Order updateOrder = new Order();
                updateOrder.setId(msg.getOrderId());
                updateOrder.setOrderStatus(1);
                orderMapper.updateByPrimaryKeySelective(updateOrder);
                log.info("订单【"+msg.getOrderId()+"】下单成功");
            }else {
                Order updateOrder = new Order();
                updateOrder.setId(msg.getOrderId());
                updateOrder.setOrderStatus(2);
                orderMapper.updateByPrimaryKeySelective(updateOrder);
                // 通知用户库存不足,订单被取消
                log.error("订单【"+msg.getOrderId()+"】因库存不足被取消");
            }
            // 手动ack,使消息出队列,不然会不断消费
            message.acknowledge();
        }
    } catch (JMSException e) {
        log.error("接收库存锁定消息报错:"+e.getMessage());
    }
}

Here again, we will also make use of a manual confirmation message to assure the reliability of message delivery.
So far, they have all buttoned up. We look at the comparison of normal service calls and how:

1、订单服务不再直接依赖于库存服务,而是将下单事件发送到MQ中,让库存监听。
2、订单服务能真正的作为一个模块独立运行。
3、解决了并发问题,而且MQ的队列处理效率非常的高。

But there are the following problems:

1、用户体验改变了:因为使用事件机制,订单是立即生成的,可是很有可能过一会,系统会提醒你没货了。。这就像是排队抢购一样,排着排着就被通知没货了,不用再排队了。
2、数据库可能会存在很对没有完成下单的订单。

最后,如果真的要考虑用户体验,并且不想数据库存在很多不必要的数据,该怎么办?
那就把订单服务和库存服务聚合在一起吧。解决当前的问题应当是首先要考虑的,我们设计微服务的目的是本想是解决业务并发量。而现在面临的却是用户体验的问题,所以架构设计也是需要妥协的。
最主要是,我们是经过思考和分析的,每个方案能做到哪种程度,能应用到哪种场景。正所谓,技术要和实际场景结合,我们不能为了追求新技术而生搬硬套。

Guess you like

Origin www.cnblogs.com/Howinfun/p/11612799.html