本文共 9113 字,大约阅读时间需要 30 分钟。
当两个系统之间存在比较频繁的数据交互,那么就需要考虑两个系统之间的数据传输方式。第一次考虑使用rest接口调用的方式,但是缺点很明显,一个系统宕机时,另外一个系统的业务流程就没法进行下去,耦合性太强;继而想到使用异步队列的形式来解决耦合的问题,也就有了后续的
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后,无实现则报错。 解决方案org.hibernate.validator hibernate-validator
... 省略其他配置 ... none
应用系统实际部署时会存在多个数据库的情况,目前客户使用中最多能达到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> dataSourceCache; /** key: cat value : key:storeCode value:dburl */ private Map > dataSourceUrlCache; /** key: cat value : key:dbkey value:ds */ private Map > dataSourceByDbKeyCache; /** key: cat value : key:dbkey value:dburl */ private Map > dataSourceUrlByDbKeyCache; @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } private DataSource determineTargetDataSource() throws SQLException { DataSourceKey key = keyHolder.get(); if (key == null) { for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { if (org.hibernate.tool.hbm2ddl.SchemaUpdate.class.getName().equals(ste.getClassName()) && "execute".equals(ste.getMethodName()) || (org.hibernate.cfg.SettingsFactory.class.getName().equals(ste.getClassName()) && "buildSettings".equals(ste.getMethodName())) ) { Iterator > iter = getTargetDataSources().entrySet().iterator(); return iter.next().getValue(); } } throw new RuntimeException("No DataSourceKey in current thread, call setKey() first."); } return getDataSource(key.cat, key.storeCode); } private final ThreadLocal keyHolder = new ThreadLocal ();
解决方案 EvCall提供了一个真正意义上的多通道能力。因为当你在应用系统中建立多个通道的时候,工具内部的各种设施也都是多份的,包括调度器、线程池,甚至在数据库中的数据表也是多份的。这样设计的目的就是尽量减少不同通道之间出现资源争抢的现象,从而在最大程度上做到资源隔离。
利用这种特性,在应用启动的时候根据DsRouter.json文件(如下图)配置的数据源分片,按每个数据源配置不同的name(用dbkey属性)启动多个evCallManager,每个manager都会启动一个守护线程,对应每个不同的数据源。
[ { "cat": "CENTER", "url": "jdbc:postgresql://localhost:5432/dev_h4csct", "username": "username", "password": "password", "dbkey": "db1", "storecodes": [ "0901", "9787", "0001" ] }, { "cat": "HDPOS", "url": "jdbc:postgresql://localhost:5432/dev_h4csct", "username": "username", "password": "password", "dbkey": "db2", "storecodes": [ "0103", "0105", "0106", "0107", "0110" ] }]
1. 根据数据源分片创建多个evCallManager
private void prepare() { // 取到所有的数据源 DsRouter dsRouter = appCtx.getBean(DsRouter.class); MapdataSourceMap = dsRouter.getTargetDataSourceByDbKeyCache(DsRouter.HDPOS); MQConnection conn = getConnection(); managers = new ConcurrentHashMap (); // 按数据源分片添加多个evCall管道 for(String dbKey : dataSourceMap.keySet()) { ReliableEventExecutor executor = new ReliableEventExecutor(conn, getMessageWrapperCodec()); EvCallManager evCallManager = new EvCallManager.Builder(dataSourceMap.get(dbKey)) .properties(properties.getProperties()) .autoStart(false) .name(dbKey) .executor(new EvCallBatchExecutorBinder(executor) .batchSize(properties.getExecutorBatchSize())) .build(); managers.put(dbKey, evCallManager); } assert !managers.isEmpty() && managers.get(0) != null; }
2. 启动evCall
public void start() { if (isRunning()) { return; } synchronized (this) { if (isRunning()) { return; } if (StringUtil.toBoolean(PropertiesUtils.getPropertiesValue("${h4cs.rumbamq.autoStartup:false}"))) { debug("Start ReliableEventManager ..."); try { prepare(); for(EvCallManager evCallManager : managers.values()) { evCallManager.start(); } } catch (Exception e) { throw new ReliableEventException(e, "Failed to start ReliableEventManager."); } } } }```**问题2:jpa和jdbcTemplate同时使用的时候是否存在事务不一致**系统目前还是使用JPA来操作单据,集成evCall后,业务单据通过JPA来保存、evCall通过jdbcTemplate来保存消息到调用任务表,是否会存在事务的问题。**排查** 通过查看evCall的源代码可以发现,它是通过注册TransactionAdapter来控制消息的插入,而事务提交由业务代码部分来做控制,所以就不存在事务不一致的问题,经过测试也确认了该场景。 ```java private class TransactionAdapter extends TransactionSynchronizationAdapter { @Override public void beforeCommit(boolean readOnly) { // 将requestsCache中内容插入数据库。 Listrequests = requestsCache.get(); if (requests == null || requests.isEmpty()) { return; } debug("Save {} requests to database: {}", requests.size(), requests); dbContext.getEvcRequestDao().batchInsert(requests); // 跟踪日志 for (EvcRequest request : requests) { trace(LogMessage.byValue(TRACE_EVENT_COMMIT, request)); } } @Override public void afterCompletion(int status) { List requests = requestsCache.get(); if (requests == null) { return; } try { if (TransactionSynchronization.STATUS_COMMITTED == status) { if (!requests.isEmpty()) { try { debug("Transaction committed, try to schedule {} requests: {}", requests.size(), requests); metricsAgent.onSubmit(requests); scheduler.trySchedule(requests); } catch (Exception e) { LOGGER.warn(e.getMessage(), e); } } metricsAgent.onCommit(); } else { for (EvcRequest r : requests) { trace(LogMessage.byValue(TRACE_EVENT_ROLLBACK, r)); } } } finally { requestsCache.remove(); } } }
超市门店与总部之间业务单据交互时, 同一张单据的不同状态流会发起不同的消息,在业务上就需要保证这张单据的几条消息是有序消费的,**而MQ消费端是不保证有序消费的。**解决方案
整个改造后,消息中间件RabbitMQ与应用之间进行了解耦,减少了应用系统对中间件的依赖,执行效率比Redis要提升不少,整体消费性能有所提高。
转载地址:http://rqani.baihongyu.com/