或在Service层使用注解,如
@Service @CacheConfig(cacheNames = "users") public class UserService { private static Map<String, User> userMap = new HashMap<>(); @CachePut(key = "#user.username") public User addUser(User user){ user.setUid(UUID.randomUUID().toString()); System.out.println("add user: " + user); userMap.put(user.getUsername(), user); return user; } @Caching(put = { @CachePut( key = "#user.username"), @CachePut( key = "#user.uid") }) public User addUser2(User user) { user.setUid(UUID.randomUUID().toString()); System.out.println("add user2: " + user); userMap.put(user.getUsername(), user); return user; } ... } Spring Boot 2 整合Redis哨兵模式Spring Boot 2 整合Redis哨兵模式除了配置稍有差异,其它与整合单实例模式类似,配置示例为
spring: redis: password: passw0rd timeout: 5000 sentinel: master: mymaster nodes: 192.168.40.201:26379,192.168.40.201:36379,192.168.40.201:46379 # 哨兵的IP:Port列表 jedis: # 或lettuce pool: max-active: 8 max-wait: -1 max-idle: 8 min-idle: 0完整示例可查阅源码: https://github.com/ronwxy/springboot-demos/tree/master/springboot-redis-sentinel
上述配置只指定了哨兵节点的地址与master的名称,但Redis客户端最终访问操作的是master节点,那么Redis客户端是如何获取master节点的地址,并在发生故障转移时,如何自动切换master地址的呢?我们以Jedis连接池为例,通过源码来揭开其内部实现的神秘面纱。
在 JedisSentinelPool 类的构造函数中,对连接池做了初始化,如下
public JedisSentinelPool(String masterName, Set<String> sentinels, final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, final String password, final int database, final String clientName) { this.poolConfig = poolConfig; this.connectionTimeout = connectionTimeout; this.soTimeout = soTimeout; this.password = password; this.database = database; this.clientName = clientName; HostAndPort master = initSentinels(sentinels, masterName); initPool(master); } private HostAndPort initSentinels(Set<String> sentinels, final String masterName) { for (String sentinel : sentinels) { final HostAndPort hap = HostAndPort.parseString(sentinel); log.fine("Connecting to Sentinel " + hap); Jedis jedis = null; try { jedis = new Jedis(hap.getHost(), hap.getPort()); List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); // connected to sentinel... sentinelAvailable = true; if (masterAddr == null || masterAddr.size() != 2) { log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + "."); continue; } master = toHostAndPort(masterAddr); log.fine("Found Redis master at " + master); break; } catch (JedisException e) { // resolves #1036, it should handle JedisException there's another chance // of raising JedisDataException log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + e + ". Trying next one."); } finally { if (jedis != null) { jedis.close(); } } } //省略了非关键代码 for (String sentinel : sentinels) { final HostAndPort hap = HostAndPort.parseString(sentinel); MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); // whether MasterListener threads are alive or not, process can be stopped masterListener.setDaemon(true); masterListeners.add(masterListener); masterListener.start(); } return master; }initSentinels 方法中主要干了两件事:
遍历哨兵节点,通过get-master-addr-by-name命令获取master节点的地址信息,找到了就退出循环。get-master-addr-by-name命令执行结果如下所示
[root@dev-server-1 master-slave]# redis-cli -p 26379 127.0.0.1:26379> sentinel get-master-addr-by-name mymaster 1) "192.168.40.201" 2) "7001" 127.0.0.1:26379>对每一个哨兵节点通过一个 MasterListener 进行监听(Redis的发布订阅功能),订阅哨兵节点+switch-master频道,当发生故障转移时,客户端能收到哨兵的通知,通过重新初始化连接池,完成主节点的切换。
MasterListener.run方法中监听哨兵部分代码如下