在spring中基于利用动态代理整合单机,集群与哨兵的redis客户端(基于jedis)
简述
此篇文章为基于jedis利用动态代理整合单机,集群和哨兵模式的redis客户端,通过在application.properties内设置redis.client.mode可以启用不同的客户端。
动态代理
通过配置文件生成不同模式的客户端
@Configuration
public class JedisConfig {
@Value("${redis.client.mode}")
private String clientMode;
@Value("${redis.host}")
private String hosts;
@Value("${password}")
private String password;
@Value("${timeout}")
private String timeout;
private JedisPool jedisPool;
private JedisCluster jedisCluster;
private JedisSentinelPool jedisSentinelPool;
@PostConstruct
public void create() {
List<HostAndPort> hnps = new ArrayList<>();
for (String host : hosts.split(",")) {
String[] hostAndPort = host.split(":");
HostAndPort hnp = new HostAndPort(hostAndPort[0], Integer.parseInt(hostAndPort[1]));
hnps.add(hnp);
}
if ("single".equals(clientMode)) {
HostAndPort hnp = hnps.iterator().next();
jedisPool = new JedisPool(new JedisPoolConfig(), hnp.getHost(), hnp.getPort(), Integer.parseInt(timeout), password);
} else if ("cluster".equals(clientMode)) {
jedisCluster = new JedisCluster(new HashSet<>(hnps), 0, 2, 5, "cluster", new JedisPoolConfig());
} else if ("sentinel".equals(clientMode)) {
//此处需指定哨兵模式的参数,此例仅为示意。
HostAndPort sentinel1 = hnps.get(0);
HostAndPort sentinel2 = hnps.get(1);
Set<String> sentinels = new HashSet<String>();
sentinels.add(sentinel1.toString());
sentinels.add(sentinel2.toString());
jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, new GenericObjectPoolConfig(), 1000,
password, 2);
} else {
throw new IllegalArgumentException("illegal redis client mode, only support single, cluster and sentinel.");
}
}
public String getClientMode() {
return clientMode;
}
public JedisPool getJedisPool() {
return jedisPool;
}
public JedisCluster getJedisCluster() {
return jedisCluster;
}
public JedisSentinelPool getJedisSentinelPool() {
return jedisSentinelPool;
}
}
利用动态代理动态指定不同模式的客户端执行jedisCommand
@Component
class JedisProxy implements InvocationHandler {
@Resource
private JedisConfig jedisConfig;
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
if ("single".equals(jedisConfig.getClientMode())) {
return execSingleJedisCommand(m, args);
} else if ("cluster".equals(jedisConfig.getClientMode())) {
return execClusterCommand(m, args);
} else if ("sentinel".equals(jedisConfig.getClientMode())) {
return execSentinelCommand(m, args);
} else {
throw new IllegalArgumentException("illegal redis client mode, only support single, cluster and sentinel.");
}
}
private Object execSentinelCommand(Method m, Object[] args) throws IllegalAccessException, InvocationTargetException {
try (Jedis jedis0 = jedisConfig.getJedisSentinelPool().getResource()) {
return m.invoke(jedis0, args);
} catch (Exception e) {
System.out.println("sentinel redis client " + m.getName() + " error, detail:\n" + e);
throw e;
}
}
private Object execClusterCommand(Method m, Object[] args) throws IllegalAccessException, InvocationTargetException {
try {
return m.invoke(jedisConfig.getJedisCluster(), args);
} catch (Exception e) {
System.out.println("cluster redis client " + m.getName() + " error, detail:\n" + e);
throw e;
}
}
private Object execSingleJedisCommand(Method m, Object[] args) throws IllegalAccessException, InvocationTargetException {
try (Jedis jedis0 = jedisConfig.getJedisPool().getResource()) {
return m.invoke(jedis0, args);
} catch (Exception e) {
System.out.println("single redis client " + m.getName() + " error, detail:\n" + e);
throw e;
}
}
}
利用依赖注入以及Proxy动态生成JedisCommand接口的实现类
@Component
public class JedisClient {
@Bean
private JedisCommands generateClient() {
return (JedisCommands) Proxy.newProxyInstance(JedisProxy.class.getClassLoader(), new Class[]{JedisCommands.class}, new JedisProxy());
}
}
最后经过依赖注入jedisCommand就可以直接使用了。