1.业务使用场景
我们在使用表单动态添加字段,如果新增字段,再保存数据,这个时候就会出错,出错的原因是seata 再本地缓存元数据,修改物理表的时候,这个元数据并没有发生变化,因此需要刷新元数据,因为我们使用的是多服务实例的部署,因此,如果某个表发生变化时,所有的服务实例都需要将元数据进行刷新。
这个我们可以使用 redis 的订阅服务,当某个微服务的元数据发生变化时,我们可以使用redis通知各个微服务实例对数据进行更改。
redis 支持 发布订阅服务。
他有两个端:
- 订阅端
可以有多个订阅端,可以订阅某个频道,当消息发送端发送消息时,订阅端可以接收到消息进行处理 - 消息发送端
可以方某个频道发送消息。
2. 解决方案
在每一个微服务实例启动的时候,我们启用订阅,当元数据发生变化时,我们发布事件进行通知微服务实例。
相关代码:
public class SubPubUtil {
/**
* 发布消息。
* @param channel
* @param message
*/
public static void publishMessage(String channel,String message ){
RedisTemplate<String, Object> redisTemplate= SpringUtil.getBean("redisTemplate");
redisTemplate.execute((RedisCallback<Long>) connection -> {
byte[] chanelBytes = channel.getBytes(StandardCharsets.UTF_8);
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
connection.publish(chanelBytes,messageBytes);
return 1L;
});
}
/**
* 订阅消息。
* @param channel
* @param listener
*/
public static void subscribeMessage(String channel,MessageListener listener){
RedisTemplate<String, Object> redisTemplate= SpringUtil.getBean("redisTemplate");
redisTemplate.execute((RedisCallback<Long>) connection -> {
byte[] chanelBytes = channel.getBytes(StandardCharsets.UTF_8);
connection.subscribe(listener,chanelBytes);
return 1L;
});
}
}
在微服务实例启动时,启用订阅。
public class DbChangeListener implements CommandLineRunner, Ordered {
@Override
public void run(String... args) throws Exception {
SubPubUtil.subscribeMessage("dbChange", new MessageListener() {
@SneakyThrows
@Override
public void onMessage(Message message, byte[] bytes) {
String dataSource =new String(message.getBody(),"utf-8");
clearMedata(dataSource);
}
});
}
/**
* 清理数据源的元数据。
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public void clearMedata(String dataSource) throws NoSuchFieldException, IllegalAccessException {
DataSourceProxy dataSourceProxy = (DataSourceProxy) DataSourceUtil.getDataSourcesByAlias(dataSource);
try (Connection connection = dataSourceProxy.getConnection()) {
TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType());
TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType())
.refresh(connection, dataSourceProxy.getResourceId());
} catch (Exception ignore) {
}
}
@Override
public int getOrder() {
return 0;
}
}
当元数据发生变化时,我们可以使用如下代码发布事件,通知各个微服务实例,对元数据进行清理。
public static void clearDbMetaData(String dataSource){
if(StringUtils.isEmpty(dataSource)){
dataSource=DataSourceUtil.LOCAL;
}
SubPubUtil.publishMessage("dbChange",dataSource);
}