使用redis 的訂閱服務

1.業務使用場景

我們在使用表單動態添加字段,如果新增字段,再保存數據,這個時候就會出錯,出錯的原因是seata 再本地緩存元數據,修改物理表的時候,這個元數據並沒有發生變化,因此需要刷新元數據,因爲我們使用的是多服務實例的部署,因此,如果某個表發生變化時,所有的服務實例都需要將元數據進行刷新。

這個我們可以使用 redis 的訂閱服務,當某個微服務的元數據發生變化時,我們可以使用redis通知各個微服務實例對數據進行更改。

redis 支持 發佈訂閱服務。
他有兩個端:

  1. 訂閱端
    可以有多個訂閱端,可以訂閱某個頻道,當消息發送端發送消息時,訂閱端可以接收到消息進行處理
  2. 消息發送端
    可以方某個頻道發送消息。

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);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章