如何测试redis sentienl

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何测试redis sentienl相关的知识,希望对你有一定的参考价值。

自Redis增加Sentinel集群工具以来,本博主就从未尝试过使用该工具。最近在调研目前主流的Redis集群部署方案,所以详细地看了一遍官方对于Sentinel的介绍并在自己的台式机上完成了三Redis实例+三Sentinel实例的部署,这里做一下简单的总结。
首先,下载安装Redis。目前随Redis 2.8发布的Sentinel版本被antirez称为Sentinel 2,是在Sentinel 1的基础上重写的。因为Sentinel 1已经废弃而且BUG太多,所以antirez强烈建议将Redis和Sentinel均升级到2.8版本,本博主安装的版本为最新的2.8.17。
其次,配置并启动Redis实例。分别在6379、6380和6381三个本地端口上启动三个Redis实例,其中6379为Master,其余两个为Slave。关于Redis的主从配置这里就不再赘述了,但其中需要指出的是两个Slave在配置参数slave-priority上的区别:6380实例该配置参数为50,6381实例该配置参数为100,这样当Master挂掉的时候Sentinel会优先选择slave-priority值较小的作为新的Master。
最后,配置并启动Sentinel实例。分别在26379、26380和26381三个本地端口上启动三个Sentinel实例,这三个Sentinel实例用来监控上面已经启动的三个Redis实例。以下是26379上Sentinel实例的配置文件内容,参考官方文档仅配置几个主要的参数,其他两个实例的配置文件只是端口号和数据目录不同。
port 26379
dir /home/liangzhichao/data/redis/sentinels/26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs master 1
sentinel failover-timeout mymaster 180000
在启动Sentinel实例时,因为想将日志信息打印到文件,但是没有找到在配置文件中设置日志文件的方法,所以直接采用以下方式启动:./redis-sentinel /home/liangzhichao/data/redis/confs/sentinel.26379.conf >> /home/liangzhichao/data/redis/logs/26379.log 2>&1 &。
完成Sentinel实例的启动后,我们不妨先看看Sentinel的日志,看看它启动之后究竟做了哪些工作,以下为26379实例的日志内容,从日志内容可以看到Sentinel启动之后至少做了四件事:1)为自己生成一个runid来唯一地标识本实例;2)开始监控运行在6379端口上的Master Redis实例;3)获取Master Redis实例的所有Slave Redis实例信息,以便在Master Redis实例挂掉之后可以从所有Salve Redis实例中选择出一个新的Master;4)向监控相同Master Redis实例的Sentinel实例发布自己的存在,以便让所有Sentinel实例认识并记住彼此。
[8229] 18 Nov 11:18:46.677 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
[8229] 18 Nov 11:18:46.677 # Redis can't set maximum open files to 10032 because of OS error: Operation not permitted.
[8229] 18 Nov 11:18:46.677 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
[8229] 18 Nov 11:18:46.679 # Sentinel runid is 2262ed911e9414208af4b1c48ad2b449fd4e0b89
[8229] 18 Nov 11:18:46.679 # +monitor master mymaster 127.0.0.1 6379 quorum 2
[8229] 18 Nov 11:18:46.679 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[8229] 18 Nov 11:18:46.679 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
[8229] 18 Nov 11:19:27.260 * +sentinel sentinel 127.0.0.1:26380 127.0.0.1 26380 @ mymaster 127.0.0.1 6379
[8229] 18 Nov 11:19:36.069 * +sentinel sentinel 127.0.0.1:26381 127.0.0.1 26381 @ mymaster 127.0.0.1 6379
这里需要注意的是,在我们观察到以上日志内容的同时,各个Sentinel实例也都更新了自己的配置文件,以记录目前最新的配置信息,此时每个Sentinel实例的配置文件内容与启动之前就大不相同了。以下列举了26379实例的配置文件的主要内容,从中可知所有Slave Redis实例的信息、其他Sentinel实例的信息都已经添加完成。另外,当前配置文件的版本号为0。
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-slave mymaster 127.0.0.1 6381
sentinel known-sentinel mymaster 127.0.0.1 26381 22b65a4796e6ece6b76284558a071cc83df71098
sentinel known-sentinel mymaster 127.0.0.1 26380 59616326f3c539ff3301098e1bf708350e6dd45d
sentinel current-epoch 0
至此,一主两从的Redis集群和三实例的Sentinel集群就全部启动完成并开始正常工作了。接下来我们不妨通过Jedis客户端来验证一下Sentinel集群的正确性,以下为测试代码,功能很简单:首先建立到Sentinel集群的连接,然后通过Sentinel集群获取当前Master Redis实例的信息,最后向Master Redis实例写入一条数据并查询该数据以确保数据写入成功。
package redis.clients.mytest;
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
public class MyJedisSentinelTest
public static void main(String[] args)
Set sentinels = new HashSet();
sentinels.add(new HostAndPort("localhost", 26379).toString());
sentinels.add(new HostAndPort("localhost", 26380).toString());
sentinels.add(new HostAndPort("localhost", 26381).toString());
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", sentinels);
System.out.println("Current master: " + sentinelPool.getCurrentHostMaster().toString());
Jedis master = sentinelPool.getResource();
master.set("username","liangzhichao");
sentinelPool.returnResource(master);
Jedis master2 = sentinelPool.getResource();
String value = master2.get("username");
System.out.println("username: " + value);
master2.close();
sentinelPool.destroy();


执行以上代码,我们会得到如下结果信息,由此可知通过Sentinel集群成功获取到了Master Redis实例的信息,到Master Redis实例的读写请求可以正常处理。
2014-11-20 16:39:00 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Trying to find master from available Sentinels...
2014-11-20 16:39:00 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Redis master running at 127.0.0.1:6379, starting Sentinel listeners...
2014-11-20 16:39:00 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 127.0.0.1:6379
Current master: 127.0.0.1:6379
username: liangzhichao
目前为止,Sentinel集群都是正常工作的,接下来我们再来看一看Sentinel集群是如何处理Master Redis实例挂掉的。我们通过kill掉运行在6379端口上的Redis实例进程来触发这一情况,同时观察Sentinel集群各个实例的日志信息,以下为各个实例处理Master Redis实例挂掉的日志信息。
26379实例:
[8229] 19 Nov 14:41:32.033 # +sdown master mymaster 127.0.0.1 6379
[8229] 19 Nov 14:41:32.116 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
[8229] 19 Nov 14:41:32.116 # +new-epoch 1
[8229] 19 Nov 14:41:32.116 # +try-failover master mymaster 127.0.0.1 6379
[8229] 19 Nov 14:41:32.286 # +vote-for-leader 2262ed911e9414208af4b1c48ad2b449fd4e0b89 1
[8229] 19 Nov 14:41:32.286 # 127.0.0.1:26381 voted for 22b65a4796e6ece6b76284558a071cc83df71098 1
[8229] 19 Nov 14:41:32.387 # 127.0.0.1:26380 voted for 22b65a4796e6ece6b76284558a071cc83df71098 1
[8229] 19 Nov 14:41:33.326 # +config-update-from sentinel 127.0.0.1:26381 127.0.0.1 26381 @ mymaster 127.0.0.1 6379
[8229] 19 Nov 14:41:33.326 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380
[8229] 19 Nov 14:41:33.326 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
[8229] 19 Nov 14:41:33.430 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
[8229] 19 Nov 14:42:03.507 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
26380实例:
[8243] 19 Nov 14:41:32.023 # +sdown master mymaster 127.0.0.1 6379
[8243] 19 Nov 14:41:32.336 # +new-epoch 1
[8243] 19 Nov 14:41:32.386 # +vote-for-leader 22b65a4796e6ece6b76284558a071cc83df71098 1
[8243] 19 Nov 14:41:33.151 # +odown master mymaster 127.0.0.1 6379 #quorum 3/2
[8243] 19 Nov 14:41:33.151 # Next failover delay: I will not start a failover before Wed Nov 19 14:47:32 2014
[8243] 19 Nov 14:41:33.327 # +config-update-from sentinel 127.0.0.1:26381 127.0.0.1 26381 @ mymaster 127.0.0.1 6379
[8243] 19 Nov 14:41:33.328 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380
[8243] 19 Nov 14:41:33.328 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
[8243] 19 Nov 14:41:33.558 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
[8243] 19 Nov 14:42:03.616 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
26381实例:
[8247] 19 Nov 14:41:32.042 # +sdown master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.094 # +odown master mymaster 127.0.0.1 6379 #quorum 3/2
[8247] 19 Nov 14:41:32.094 # +new-epoch 1
[8247] 19 Nov 14:41:32.094 # +try-failover master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.194 # +vote-for-leader 22b65a4796e6ece6b76284558a071cc83df71098 1
[8247] 19 Nov 14:41:32.286 # 127.0.0.1:26379 voted for 2262ed911e9414208af4b1c48ad2b449fd4e0b89 1
[8247] 19 Nov 14:41:32.387 # 127.0.0.1:26380 voted for 22b65a4796e6ece6b76284558a071cc83df71098 1
[8247] 19 Nov 14:41:32.396 # +elected-leader master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.396 # +failover-state-select-slave master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.459 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.459 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:32.522 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:33.307 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:33.307 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:33.326 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:33.851 # -odown master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:34.356 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:34.356 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:34.426 # +failover-end master mymaster 127.0.0.1 6379
[8247] 19 Nov 14:41:34.426 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380
[8247] 19 Nov 14:41:34.427 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
[8247] 19 Nov 14:41:34.479 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
[8247] 19 Nov 14:42:04.531 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
由以上日志内容我们大致可以看到Sentinel集群处理Master Redis实例挂掉的基本流程:1)每个Sentinel实例通过监控发现6379端口的Master Redis实例不工作,于是将该实例的状态设置为sdown;2)通过Sentinel彼此之间通信确认大多数Sentinel实例均认为Master Redis挂掉,于是将该实例的状态设置为odown;3)准备触发Master Redis实例的failover,要选举一个Sentinel实例进行首次failover操作;4)选举出来的Sentinel实例从Slave Redis实例中选择一个出来成为新的Master Redis实例;5)完成Master Redis实例的切换之后,在各个Sentinel实例间同步最新的配置信息;6)让落选的Slave Redis实例切换到新的Master Redis实例,开始同步数据。
具体到我们的环境就是运行在端口26381上的Sentinel实例获得了执行此次failover的权限,于是它选择运行在端口6380上的Slave Redis实例成为新的Master Redis实例(因为6380实例的slave-priority比6381实例的值小),切换完成后落选的6381实例开始转而备份6380实例的数据。此时我们再看一看Sentinel实例的配置文件,以确认配置信息确实进行了更新。以下同样为26379实例的配置文件的主要内容,对比之前的配置文件内容我们可以知道Master Redis实例确实发生了切换,当前的配置信息版本已经变为1。
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel known-slave mymaster 127.0.0.1 6381
sentinel known-slave mymaster 127.0.0.1 6379
sentinel known-sentinel mymaster 127.0.0.1 26381 22b65a4796e6ece6b76284558a071cc83df71098
sentinel known-sentinel mymaster 127.0.0.1 26380 59616326f3c539ff3301098e1bf708350e6dd45d
sentinel current-epoch 1
参考技术A

1. 你挂掉redis主服务器。

2. 可以看到sentinel 服务器的信息,显示一个redis主服务器失去连接,随后提升一个从服务器为主服务器。

3. 试着在提升为主服务器的从服务器进行写操作,发现是可以进行写操作了。

详细操作信息,部署sentinel,测试sentinel。apeit-程序猿IT的《redis集群主从复制哨兵sentinel和代理twemproxy》文章讲的比较详细易懂。

sentienl控制台客户端持久化到Apollo

sentinel 学习笔记

前段时间大致用了一下sentinel。但是阿里云的sentinel开源并没有实现持久化,针对这个问题我采用了apollo去实现sentinel的持久化。下面就记录一下实现sentinel持久化的过程。

Sentinel 是什么?

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

github项目地址

如何使用sentinel?

对于初学者来说,使用sentinel只需要限流功能就可以了,所以在本篇文章中,仅针对SpringCloud项目的限流功能进行阐述。

开启sentinel控制台

可以先去官网下载源代码,然后在本地打开sentinel控制台,账号密码都是sentinel,端口是8001(都可以在application配置里面改)

打开后如图所示,因为没有任何服务连接sentinel控制台,所以现在是一片空白。

开启一个项目连接sentinel控制台

开启一个SpringCloud项目,写一个最简单的controller

添加sentinel依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>0.9.0.RELEASE</version>
        </dependency>

添加application配置

# port settings
server:
  port: 8081
spring:
  application:
    name: sentinel-web-demo
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8001 # sentinel客户端服务的地址
      eager: true

这时候再登录sentinel就可以看到名为sentinel-web-demo的服务注册上去了

sentinel实现限流

如图所示,在流控规则下新增一个流控规则,资源名中填写请求路径,然后设置阈值就可以了。

成功时正常显示,失败时则会输出提示信息。

是的,完成了这步我们就实现了最简单的sentinel控流。

sentinel的持久化

很快我们就能发现存在这样的问题,一旦服务器或者客户端重启了,sentinel的控流规则就失效了。这对于一个上线的项目来说无疑是致命的,万一机器出现故障,那么我们大量的sentinel控流规则都会消失。这是因为开源的sentinel控流规则是存储在内存中的,我们需要通过一些方法来实现sentinel的持久化,在这里就只简单说一下使用apollo实现sentinel持久化。

感兴趣的同学可以看一下官方的文档

推送模式说明优点缺点
原始模式API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource简单,无任何依赖不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;快速引入第三方依赖

sentinel 控制台代码改造

pom.xml文件先把Apollo包的test作用域去掉,顺便log4j更新成 2.15(防止恶意代码注入)

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
        </dependency>

		<dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-openapi</artifactId>
            <version>1.2.0</version>
<!--            <scope>test</scope>-->
        </dependency>

写一个Apollo的config,Converter<T,S>指的是将S转换为T类型,portalUrl是Apollo的地址,token是Apollo生成的token

public class ApolloConfig 

    @Value("$apollo.meta")
    private String portalUrl;
    @Value("$apollo.token")
    private String token;

    /**
     * 流控规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 流控规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() 
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    

    /**
     * 降级规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 降级规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() 
        return s -> JSON.parseArray(s, DegradeRuleEntity.class);
    

    /**
     * 授权规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<AuthorityRuleEntity>, String> authorityRuleEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 授权规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<AuthorityRuleEntity>> authorityRuleEntityDecoder() 
        return s -> JSON.parseArray(s, AuthorityRuleEntity.class);
    

    /**
     * 系统规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<SystemRuleEntity>, String> systemRuleEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 系统规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<SystemRuleEntity>> systemRuleEntityDecoder() 
        return s -> JSON.parseArray(s, SystemRuleEntity.class);
    

    /**
     * 热点规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 热点规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() 
        return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
    

    /**
     * 集群流控规则编码
     *
     * @return
     */
    @Bean
    public Converter<List<ClusterAppAssignMap>, String> clusterGroupEntityEncoder() 
        return JSON::toJSONString;
    

    /**
     * 集群流控规则解码
     *
     * @return
     */
    @Bean
    public Converter<String, List<ClusterAppAssignMap>> clusterGroupEntityDecoder() 
        return s -> JSON.parseArray(s, ClusterAppAssignMap.class);
    

    @Bean
    public ApolloOpenApiClient apolloOpenApiClient() 
        ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder()
                .withPortalUrl(portalUrl)
                .withToken(token)
                .build();
        return client;

    

建一个Apollo的Util类

public final class ApolloConfigUtil 

    /**
     * 流控规则id
     */
    public static final String FLOW_DATA_ID_POSTFIX = "sentinel-flow-rules";
    /**
     * 降级规则id
     */
    public static final String DEGRADE_DATA_ID_POSTFIX = "sentinel-degrade-rules";
    /**
     * 热点规则id
     */
    public static final String PARAM_FLOW_DATA_ID_POSTFIX = "sentinel-param-flow-rules";
    /**
     * 系统规则id
     */
    public static final String SYSTEM_DATA_ID_POSTFIX = "sentinel-system-rules";
    /**
     * 授权规则id
     */
    public static final String AUTHORITY_DATA_ID_POSTFIX = "sentinel-authority-rules";
    /**
     * 集群流控id
     */
    public static final String CLUSTER_GROUP_DATA_ID_POSTFIX = "sentinel-cluster-group-rules";
    /**
     * 规则存储nameSpace
     */
    public static final String NAMESPACE_NAME = "application";

    private ApolloConfigUtil() 
    

    public static String getClusterGroupDataId(String appName) 
        return String.format("%s_%s",appName,CLUSTER_GROUP_DATA_ID_POSTFIX);
    

    public static String getFlowDataId(String appName) 
        return String.format("%s_%s",appName,FLOW_DATA_ID_POSTFIX);
    

    public static String getDegradeDataId(String appName) 
        return String.format("%s_%s",appName,DEGRADE_DATA_ID_POSTFIX);
    

    public static String getParamFlowDataId(String appName) 
        return String.format("%s_%s",appName,PARAM_FLOW_DATA_ID_POSTFIX);
    

    public static String getSystemDataId(String appName) 
        return String.format("%s_%s",appName,SYSTEM_DATA_ID_POSTFIX);
    

    public static String getAuthorityDataId(String appName) 
        return String.format("%s_%s",appName,AUTHORITY_DATA_ID_POSTFIX);
    

    public static String getNamespaceName(String appName) 
        return String.format("%s_%s",appName,NAMESPACE_NAME);
    

接下来写一个控流规则的Provider和Publisher

@Component("flowRuleApolloProvider")
public class FlowRuleApolloProvider implements DynamicRuleProvider<List<FlowRuleEntity>> 

    @Autowired
    private ApolloOpenApiClient apolloOpenApiClient;
    @Autowired
    private Converter<String, List<FlowRuleEntity>> converter;
    @Value("$app.id")
    private String appId;
    @Value("$spring.profiles.active")
    private String env;
    @Value("$apollo.clusterName")
    private String clusterName;
    @Value("$apollo.namespaceName")
    private String namespaceName;
    @Override
    public List<FlowRuleEntity> getRules(String appName)
        String flowDataId = ApolloConfigUtil.getFlowDataId(appName);
        OpenNamespaceDTO openNamespaceDTO = apolloOpenApiClient.getNamespace(appId, env, clusterName, namespaceName);
        String rules = openNamespaceDTO
                .getItems()
                .stream()
                .filter(p -> p.getKey().equals(flowDataId))
                .map(OpenItemDTO::getValue)
                .findFirst()
                .orElse("");
        if (StringUtil.isEmpty(rules)) 
            return new ArrayList<>();
        
        return converter.convert(rules);
    

@Component("flowRuleApolloPublisher")
public class FlowRuleApolloPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> 

    @Autowired
    private ApolloOpenApiClient apolloOpenApiClient;
    @Autowired
    private Converter<List<FlowRuleEntity>, String> converter;
    @Value("$app.id")
    private String appId;
    @Value("$spring.profiles.active")
    private String env;
    @Value("$apollo.user")
    private String user;
    @Value("$apollo.clusterName")
    private String clusterName;
    @Value("$apollo.namespaceName")
    private String namespaceName;

    @Override
    public void publish(String app, List<FlowRuleEntity> rules)
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) 
            return;
        
        filterField(rules);
        // Increase the configuration
        String flowDataId = ApolloConfigUtil.getFlowDataId(app);
        OpenItemDTO openItemDTO = new OpenItemDTO();
        openItemDTO.setKey(flowDataId);
        openItemDTO.setValue(converter.convert(rules));
        openItemDTO.setComment("Program auto-join");
        openItemDTO.setDataChangeCreatedBy(user);
        apolloOpenApiClient.createOrUpdateItem(appId, env, clusterName, namespaceName, openItemDTO);
        // Release configuration
        NamespaceReleaseDTO namespaceReleaseDTO = new NamespaceReleaseDTO();
        namespaceReleaseDTO.setEmergencyPublish(true);
        namespaceReleaseDTO.setReleaseComment("Modify or add configurations");
        namespaceReleaseDTO.setReleasedBy(user);
        namespaceReleaseDTO.setReleaseTitle("Modify or add configurations");
        apolloOpenApiClient.publishNamespace(appId, env, clusterName, namespaceName, namespaceReleaseDTO);
//        System.out.println("publish");
    

    /**
     * 过滤不必要的字段
     *
     * @param rules
     */
    private void filterField(List<FlowRuleEntity> rules) 
        // 对不必要的信息进行过滤
        for (FlowRuleEntity rule : rules) 
            rule.setGmtCreate(null);
            rule.setGmtModified(null);
            // rule.setIp(null);git
            // rule.setPort(null);
        
    

然后就是改造Controller代码,在这里我就只针对FlowControllerV1做修改。

在这之前我们先把刚刚写的provider和publisher注入进Controller

@Autowired
@Qualifier("flowRuleApolloProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;

@Autowired
@Qualifier("flowRuleApolloPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

首先是列出所有规则的接口,我们可以直接从apollo中获取规则,这里用的是ruleProvider.getRules(app);

@GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
                                                             @RequestParam String ip,
                                                             @RequestParam Integer port) 

        if (StringUtil.isEmpty(app)) 
            return Result.ofFail(-1, "app can't be null or empty");
        
        if (StringUtil.isEmpty(ip)) 
            return Result.ofFail(-1, "ip can't be null or empty");
        
        if (port == null) 
            return Result.ofFail(-1, "port can't be null");
        
        try 
            List<FlowRuleEntity> rules = ruleProvider.getRules(app);
            repository.saveAll(rules);
            if (rules != null && !rules.isEmpty()) 
                for (FlowRuleEntity entity : rules) 
                    entity.setApp以上是关于如何测试redis sentienl的主要内容,如果未能解决你的问题,请参考以下文章

sentienl持久化到Apollo

sentienl控制台客户端持久化到Apollo

如何测试redis是不是安装成功

sentienl控制台客户端持久化到Apollo

sentienl控制台客户端持久化到Apollo

如何保证Redis性能与安全?看这篇Redis数据库性能测试及安全优化配置指南就够了