一、场景设计
在微服务开发中,微服务之间的数据同步是一个比较麻烦的问题。
先设计一个简单的应用场景,当然不一定合理,将就一下。
基础数据微服务,员工资料需要同步到其他微服务,当员工信息的任何变更,其他微服务可能会使用到,例如联系方式、部门等信息的变更。
销售管理微服务,销售一般都要核算部门的绩效,当员工的部门信息改变,这个绩效也会随之改变,销售微服务中,从基础数据微服务中,选择部分员工作为销售员,那么权限管理也解决了,这里不一一说明。当这部分人的数据发生update或者delete的时候,绩效和权限必然受影响,例如员工晋升了,要及时调整角色等。
其实这个例子非常简单,因为一般这种情况不涉及到数据一致性,就是说,员工信息的变更与业务的发生没有实时的关系,也就是说,不会参与到事务中来。
升级一下场景,为了计算实时的考核成绩,在技术数据中接收到不同微服务的人员信息,然后根据不同岗位计算不同的绩效成绩,那么,这样就可能涉及到业务的数据一致性,万一业务数据回滚了呢?
二、解决这个场景涉及到的技术
1、RabbitMQ,作为消息通知的中间件
2、Redis,作为缓存员工业绩的缓存
3、Aop技术,同一管控事务以及提供分布式事务的方法,参考文章:在微服务中,利用webapi实现分布式事务_kaka9的博客-CSDN博客_webapi 微服务
4、IOC,自己写的IOCHelper,其启动方式通过基类来引导,虽然有些限制,但是在框架内是没有什么问题了,在net461,net5,netstandard2中通过。
好像没有其他了
三、解决方法
我们这里说的是涉及事务的场景。
首先,业务层对象必须共享一个内存缓存,我们当且称为Session,注意这个Session是根据当前登录用户共享的内存空间,不同用户不是互通的,参考文章:.net中利用线程锁实现缓存自动超时_kaka9的博客-CSDN博客
利用AOP技术,例如定义方法
[Transaction]
public RValue
{
//此处忽略代码一万行,反正是看不懂的
return "提示信息"; //第一种返回情况,事务将回滚
return new Exception("异常信息"); //第二种返回情况,事务将回滚
return hdr; //第三种返回情况,事务将提交
}
简单说明一下,RValue<>是一个自动构建类,根据不同的传入参数构建不同的类状态,里边带了一个success属性,表示此方法执行是否成功,若不成功,则会保存Message或者Exception对象,并记录错误日志,就是把message和exception写入日志,这个东东很简单的,参考nullable<>就可以了。
当方法完成后,
var r = Confirm(hdr);
if(r)
{
Publish(r.Value); //好吧,这里是通过MQ发布同步数据
}
接下来就是最关键的地方
定义的这个Publish方法,内置检查当前执行方法是否在事务当中。
当AOP开始的时候,我们在Session中定义Session["TransactionKey"]=Guid.Empty.ToString();
AOP完成时候,就移除掉这个TransactionKey,这样,不管在业务层或者是数据层,都非常简单的利用这个标记来实现事务共享了,也就是说,在发布同步信息的时候,先检查是否在事务中,如果没有,直接发布,如果在事务中,那么把要发布的数据缓存到Session中,然后等事务提交成功后,在调用一个方法同步出去即可。
四、总结
说白了,这个思路非常简单,无需要监视数据库的数据变更这么麻烦,而且Publish是业务层基类的方法,使用起来也简单,程序员也无需了解MQ的具体情况,至于发布和订阅规则,在基类中定义好就可以了,建议把订阅方法打上一个标签,这里不涉及规则,不同的思路不同实现,例如
public class BillItem
{
public THdr hdr{get;set;}
public List
}
[MQConsume("订阅的参数,一般和Exchange有关")]
public RValue
{
//处理订阅消息
return saleItem.hdr;
}
这整个系统中,大量使用标注,有些地方是参考java的实现方式,觉得挺方便的,例如
[autowire]
IDALSaleHdr dalhdr; //系统自动注入数据层实体
这里使用的IOC没有使用autofac,觉得没必要,自己写了一个IOCHelper就觉得了,主要是同时注入考虑依赖的问题,这个工具类无需做任何前期配置,自动扫描程序集来匹配。还是自己整的顺手,哈哈。