1、背景
当两个系统之间存在比较频繁的数据交互,那么就需要考虑两个系统之间的数据传输方式。第一次考虑使用rest接口调用的方式,但是缺点很明显,一个系统宕机时,另外一个系统的业务流程就没法进行下去,耦合性太强;继而想到使用异步队列的形式来解决耦合的问题,也就有了后续的基于Redis实现的消息队列
2、缺陷
- Redis也可以看做为一个数据库, 那么业务单据的保存和发送消息到redis上其实属于两个事务,那么必然会出现消息发送成功时,本地事务提交失败导致本地的业务数据回滚,而消息已经发送出去,无法保证可靠性。
- 发送消息与业务单据之间依赖性太强: 发送消息需要连接redis, 如果redis宕机,那么会导致业务单据也无法进行下去,系统与redis之间没有解耦。
- 无法进行集群部署,可拓展性比较差## 3、解决方案
- 切换成现有的RumbaMQ可实现最终一致的分布式事务。本身它不提供消息服务, 而是提供了消息服务的抽象访问层,实现了多种主流的消息框架,比如:RabbitMQ、RocketMQ等, 本次改造选择RabbitMQ作为底层消息服务,并通过新的evCall服务实现对它的访问, 它保证了业务单据和发送消息在同一个事务中,同时也将发送消息与业务单据之间实现解耦。
- rumba-mq版本: 1.16.0; evCall版本: 1.1.0
4、消息切换过程记录
4.1. 组件升级
- rumba-commons-biz:1.16.0 =》 1.23.1
- rumba-commons-mini:1.21.0 =》 1.29.2问题:vaidator校验
Caused by: org.hibernate.HibernateException: Unable to get the default Bean Validation factory
at org.hibernate.cfg.beanvalidation.BeanValidationActivator.applyDDL(BeanValidationActivator.java:127)
.....
Caused by: javax.validation.ValidationException: Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:271)
at javax.validation.Validation.buildDefaultValidatorFactory(Validation.java:110)
at org.hibernate.cfg.beanvalidation.TypeSafeActivator.getValidatorFactory(TypeSafeActivator.java:380)
... 91 more
原因
由于新版本mini组件依赖了validation-api,但未依赖实现。jpa检查选择默认为auto,检测到validation-api后,无实现则报错。
解决方案
- 引入validation实现
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
- jpa属性显式的指示不检查。
<bean id="store.jpaProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
... 省略其他配置 ...
<prop key="javax.persistence.validation.mode">none</prop>
</props>
</property>
</bean>
4.2 动态数据源
应用系统实际部署时会存在多个数据库的情况,目前客户使用中最多能达到8个数据库。在应用系统中会根据不同的门店切换到不同的数据库中执行该门店的业务。那么evCall保存到调用任务表时就需要与业务事务保持一致。
问题1: 数据源切换
系统中使用DsRouter类作为数据源,使用门店号作为数据源分片的key值,通过AOP切面的形式设置不同的keyHolder,从而切换到不同的数据源DataSource。而evCall暂时不支持该种方式的动态数据源。
public static final String BUYPOOL = "BUYPOOL"; // 根据storeCode分片的BUYPOOL
public static final String HDPOS = "HDPOS"; // 根据storeCode分片的HDPOS
@Value("${stos.dsrouter.singleStoreUseStoreCode:false}")
private boolean singleStoreUseStoreCode; /** key: cat value : key:storeCode value:ds */
private Map<String, Map<String, DataSource>> dataSourceCache;
/** key: cat value : key:storeCode value:dburl */
private Map<String, Map<String, String>> dataSourceUrlCache;
/** key: cat value : key:dbkey value:ds */
private Map<String, Map<String, DataSource>> dataSourceByDbKeyCache;
/** key: cat value : key:dbkey value:dburl */
private Map<String, Map<String, String>> dataSourceUrlByDbKeyCache;