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);
}