美文网首页
微服务数据交互及数据一致性

微服务数据交互及数据一致性

作者: water_lang | 来源:发表于2019-03-20 17:09 被阅读0次

如果你已经搭建了几个微服务,你可能会同意最困难的部分是数据:微服务不是孤立存在的,而且他们往往需要在彼此之间会进行数据交互。

最常见的例子就是订单服务,货运服务,积分服务他们之间的关系。当一个新订单下单后,订单服务须要记录这条订单信息在自己的db中。而这时后面还有货运服务及积分服务。如果我们采用同步操作如rest,grpc等方式调用的话就会将这几个服务进行了强耦合操作。如果由于积分服务问题导致该方法出错,从而导致用户不能下订单。并且如果要在订单创建完成后给用户发一条短信时,这时我们不得不在原来的代码之处再添加一行代码,像下图:

public void addOrder(Order order){
    orderMapper.save(order);
    pointSerice.addPoint(order);
    //do other something
    sendService.sendSms(order);
}

以后每要添加一个业务的时候都须要在原来这里添加代码,这其实就违背了软件的开闭原则,导致以后的代码越来越难维护。此外,有没有一种更好的方式呢?那就是异步。我们仔细回想上面的场景,其实最核心的服务就是订单服务。只要保证订单服务能正常下单,后面的添加积分,发送短信等操作可以在一定的时间内允许有延时的情况,这对用户的体验不会是很大。那我们就可以这样来实现,当订单服务成功生成一个订单后,订单服务向MQ(比如kafka)发送一条订单创建成功的事件,然后积分服务,货运服务,短信服务只须要监听MQ的这个事件就行了,他们各服务接收到这个事件后就做对应的业务逻辑处理就行了。这样以后他们每添加一个业务,这个新业务也只须监听MQ的相同事件就行了,这样就完全达到了解耦操作,而且对以后的维护成本也小了很多。

异步处理

通过上面的例子我们基本确定在微服务一些数据交互的情况下我们优先使用异步处理。那么异步处理是不是就像我们上面提到的那么简单呢?答案肯定不是的。接下来我们一步步的来说。**

事务问题:

我们来看一个代码,这个代码在很多公司的生产环境中使用:

@Transactional
public void addOrder(Order order){
   try {
       //save to db
       boolean flag =  orderMapper.save(order);

       if(flag){
           mq.send(order);
       }
   }catch (Exception e){
       rollback();
   }
}

根据上述代码,初看没啥问题呢,也就是他可能出现下面3种情况:

1.操作数据库成功,向消息中间件投递事件也成功

2.操作数据库失败,不会向消息中间件中投递事件了

3.操作数据库成功,但是向消息中间件投递事件时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚。

但是仔细分析不难发现上面代码的缺陷所在,在上面的处理过程中存在一段隐患时间窗口。

问题1:
mq.send(order);在投递消息到消息中间件时,中件间消息处理成功,但是返回响应的时候网络异常。导致send()操作抛出异常。最终结果是事件被投递,而数据库确被回滚。

问题2:
在投递完成后到数据库commit操作之间如果该微服务宕机也将造成数据库操作因为连接异常关闭而被回滚。最终结果还是事件被投递,数据库却被回滚。

这个实现在绝大部分时间运行时不会出现问题,出现的问题概率很低,但是一旦出现了将会让人感觉莫名很难发现问题所在,而且几乎找不到什么原因。

那么,这个事务问题该怎么解决呢?

解决方式:

解决方案有很多,我比较喜欢的两种方式有:

1. 使用基于数据库的Log 的CDC(Change Data Capture)方式。

比如像mysql可以通过捕获binlog的方法来实现。通过监听binlog我们可以拿到insert,update等事件,然后将该事件发送到MQ之中。该方式的好处就是通过binlog来处理,时效性也很高,不会遇到事务的问题,而且这个可以抽象出一个服务出来,代码比较集中,便于维护。不会像之前的那种发送MQ的消息代码会散落到各个服务之中。比如可以使用阿里的canal,国外的databus, debezium.个人比较喜欢debezium,他里面的功能相当的强大。

这种方法的升级版叫发件箱模式。通过添加本地事件表的方式来实现。如下图:

图片.png

整体架构如下图所示:


图片.png

我们首先在订单服务生成一个订单时要做两件事。第一件事就是向订单表写入一条新增数据,同时须要向OUTBOX表中写一个事件数据。然后我们抽象出来的db监听服务通过监听OUTBOX表binLog然后解析出来事件数据发送到MQ中去,最终由各监听MQ的各消费者拿到这些Event去做自己的业务逻辑,如添加积分,发短信等。

图片.png

id : 代表业务的唯一的Id,可以用于消费者判断重复消息使用.

aggregatetype : 该事件所属的聚合根的类型。这个源于DDD具体DDD是什么可以百度)。比如他可以是一个订单实体等。

aggregateid : 该事件所属于的聚合根Id。这可以是订单的ID或客户ID;与聚合类型类似,与聚合中包含的子实体相关的事件应使用包含聚合根的id.

Type: 代表事件的类型,比如:OrderCreatedEvent(订单已创建事件),OrderCancledEvent。在DDD一般事件的动作都会加ed代表这个事件已经产生了。

payload :事件内容的JSON结构,例如包含订单有关购买者的信息,包含的订单内容及价格等信息。

2.如果我们使用的是mongo,redis等非关系型数据库,市面上没相关的库来获取这些db log怎么办呢?

我们可以是不是可以自己监听自己呢。当我们订单服务要生成一条订单的时候,我们先不向Order表添加一条数据,而是直接向MQ中发送一个创建订单的事件,然后订单服务再监听这个MQ事件,如果当他收到这个事件时候再去向Order表添加数据。这时这个事件的数据已经表示已经确定到了MQ之中,而其他的服务消费者(比如积分服务,短信服务)也肯定能收到这消息,这样就达到了我们最终一致的效果。

处理我们这里提到的两种方式外,还有其他的比如两阶段提交,TCC,补偿模式。

相关文章

网友评论

      本文标题:微服务数据交互及数据一致性

      本文链接:https://www.haomeiwen.com/subject/nqstvqtx.html