DotNetCore CAP到底在干嘛?

背景

DotNetCore CAP(以下简称cap) 通常运用在分布式事务的场景,主要解决的是不同程序之间远程调用的事务一致性;
举例:程序X负责订单相关的接口,程序Y负责商品相关的接口,两者独立部署,数据库也是独立的,当用户下订单时,需要去调程序X的下订单接口,创建后,再去调程序Y的扣减用商品库存的接口;
常规的做法是用HTTP远程去调程序Y的这个接口,但是当程序Y出现异常,比如正在发布或者挂了,那就会出现程序Y的接口调用成功,订单已创建,但是商品库存并未扣减的情况,为了解决这种情况我们就要考虑用分布式事务来做;
cap的方案叫最终一致性:程序X执行完后,程序Y最终也一定会执行完成;

ps:这里的场景是程序X在调用程序Y时,并不需要实时等待程序Y的执行完毕才返回给前端,如果是需要实时等待建议用TCC模式的分布式事务,复杂度和代码量会高很多(有兴趣可以去查一下);


解决办法

cap的做法是,保证程序X在调用程序Y的接口这一步是有可靠性的,也就是调用程序Y这一步一定会成功,即使程序Y此时是挂了的状态,所以这个时候需要一个稳定且有可靠性的“中间人”来保证这一点;cap通常用的是rabbitmq来当“中间人”,
mq不是自己的程序所以不用发布,具有稳定且可靠的特点;程序X把调用信息发给mq后,程序Y再从mq中拿出这条信息去执行对应的接口方法,可以看出来程序X甚至不需要知道程序Y的存在,只需要在“中间人”那里约定一个key,程序Y拿着
同样的key去“中间人”那里去取调用信息就可以了;可以看出来程序X只关心自己发消息出去和收消息进来,也就是只关心自己的 in 和 out,这样也达到了不同程序之间的调用解耦

如果mq也挂了呢?通常cap的做法是把mq的数据持久化到数据库中(比如sql server),当mq挂了,当它再次启动的时候会去sql server中重新读取出来发到mq中;

如果程序Y从mq读取信息后执行出错了呢?cap就不会返回mq消息已消费的通知,程序Y会不断从mq中取这条消息执行,直到执行成功(有重试次数);或者已经达到重试次数了还没有执行成功,这个时候就需要我们自己实现通知逻辑,
通知相关开发去查看是什么问题导致并修复程序和数据;

如果重试次数了还没有执行成功要怎么查看问题呢?由于mq中存有调用信息并持久化到了sql server中,所以我们在sql server中对应的表中可以查到所有的调用信息;

如果采用cap的方式,代码改动会很大吗?只需要把远程http的调用这一步改成cap的发布消息的方式就可以了,代码侵入性很低;


实战

1) 配置:
  从配置来看,cap会连接mq用来收发消息,连接sql server来持久化消息;
  消息的有效期、重试次数均可设置;
  有个cap的dashboard页面(管理页面)可以简单查看和管理消息;
  Version是设置不同环境之间的隔离性,后面会详细讲解;
  各个程序的配置是一样的,因为各个程序都有可能会收和发消息;

2)常规用法: 
发送方:

接收方:

3)初次启动程序时:
  当初次启动程序的时候,cap就会去mq中创建一个exchange,同时以程序X的名字为关键字创建一个queue;
  这里的exchange是用来收和发消息都会用到,queue则是只用来收消息用的;
  mq基础:发消息给mq时,只需要给exchange和routingkey就可以了;收mq的消息时,需要在queue中关联exchange和routingkey,才能取到对应的消息;这里的rouingkey就是我们上面说到的key;
  同一个环境只需要一个exchange,各个程序都是用同一个exchange,每个程序各自一个queue监听发给自己的消息;
  数据库中会在各自的库中自动创建两张表,代表自己发出去的和收到的消息记录,当做mq的数据持久化;
  ps:当出现问题时,就可以去读调用方的cap.Published表 和 被调用方的cap.Received表,拿出调用信息去调试具体代码;
  

 4)当程序X调用程序Y时,程序Y没有启动:
  从下图可以看出来,程序Y的queue会插入一条消息,rouingkey为收发双方约定的key;

    

 

5)启动程序Y:

 这时程序Y的queue的消息已被消费;

6)同时启动程序X和程序Y:
这个时候就是一次执行以上步骤,如同远程Http调用一样;

7)Version配置:
从上面第一步来看,Version配置主要改动点在于,exchange和queue都会带version的后缀,这里主要解决的问题是不同环境之间的调用隔离;
隔离的具体原因在于:如果不配置Version,那在本地开发和正式环境就是同一个Version,因为mq实际有另一个用途是内网穿透,
所以在正式环境发布的消息,在本地也能消费,这种情况不同于http调用,使用http正式环境是调不了本地的API的,因为本地是内网,
而mq就能轻松实现内网穿透,因为不管是内网还是外网的程序,都能与在外网部署的mq建立连接,借由这个“中间人”就能实现内网穿透了,即在外网调用内网的API;
为了避免内网穿透造成最终执行的是本地正式运行的开发代码,使用不同的Version是很有必要的;

8)出问题后如何解决:

 当程序X调用程序Y,先去查看程序Y的库里的cap.Received表,Content里存的是整个请求的内容,根据上述解决方案中的通知内容去where查询cap.Received的Content字段(根据发生的错误时间也是一种查询方式),从而查到整个请求的内容,拿着这个内容去重新调试一次并发现问题在哪里;
 如果程序Y的库里的cap.Received表也查不到,那可以去程序X的cap.Published表去查一下内容,如果有数据,有可能是mq挂了、或者程序Y挂了或者收消息时报错了;程序X的cap.Published表里也查不到说明程序X并没有发消息出去;


拿到Content的值可以放到json在线化的网站上格式化看一下

 

 9)dashboard页面(上面的配置的地址是域名/cap)

 可以方便的查看消息的收发状态、操作删除和重新执行等;
 但是要看到详细的请求数据,还是得按上一步来查询;
 ps:这个页面在老版本上和hangfire长的一模一样,把它当hangfire的dashboard来理解也没问题;

 

 



 

posted @ 2022-10-24 19:26  willardzmh  阅读(705)  评论(0编辑  收藏  举报