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