1.Java類
package org.atm.dc.app.util;
import java.io.Closeable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.BinaryJedisCluster;
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisClusterConnectionHandler;
import redis.clients.jedis.JedisClusterInfoCache;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisSlotBasedConnectionHandler;
import redis.clients.jedis.PipelineBase;
import redis.clients.jedis.exceptions.JedisMovedDataException;
import redis.clients.jedis.exceptions.JedisRedirectionException;
import redis.clients.util.JedisClusterCRC16;
import redis.clients.util.SafeEncoder;
/**
* @program: message
* @description: JedisClusterPipeLine 因爲pipeline是不支持在集羣模式下使用的。所以繼承PipelineBase接口,重寫這個方法了。
* @author: Irving Wei
* @create: 2018-11-27 10:22
**/
public class JedisClusterPipeline extends PipelineBase implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(JedisClusterPipeline.class);
// 部分字段沒有對應的獲取方法,只能採用反射來做
// 你也可以去繼承JedisCluster和JedisSlotBasedConnectionHandler來提供訪問接口
private static final Field FIELD_CONNECTION_HANDLER;
private static final Field FIELD_CACHE;
static {
FIELD_CONNECTION_HANDLER = getField(BinaryJedisCluster.class, "connectionHandler");
FIELD_CACHE = getField(JedisClusterConnectionHandler.class, "cache");
}
private JedisSlotBasedConnectionHandler connectionHandler;
private JedisClusterInfoCache clusterInfoCache;
private Queue<Client> clients = new LinkedList<Client>();// 根據順序存儲每個命令對應的Client
private Map<JedisPool, Jedis> jedisMap = new HashMap<>();// 用於緩存連接
private boolean hasDataInBuf = false;// 是否有數據在緩存區
/*
* 根據jedisCluster實例生成對應的JedisClusterPipeline*
* @param
* @return
*/
public static JedisClusterPipeline pipelined(JedisCluster jedisCluster) {
JedisClusterPipeline pipeline = new JedisClusterPipeline();
pipeline.setJedisCluster(jedisCluster);
return pipeline;
}
public JedisClusterPipeline() {
}
public void setJedisCluster(JedisCluster jedis) {
connectionHandler = getValue(jedis, FIELD_CONNECTION_HANDLER);
clusterInfoCache = getValue(connectionHandler, FIELD_CACHE);
}
/**
* 刷新集羣信息,當集羣信息發生變更時調用
*
* @param
* @return
*/
public void refreshCluster() {
connectionHandler.renewSlotCache();
}
/**
* 同步讀取所有數據. 與syncAndReturnAll()相比,sync()只是沒有對數據做反序列化
*/
public void sync() {
innerSync(null);
}
/**
* 同步讀取所有數據 並按命令順序返回一個列表
*
* @return 按照命令的順序返回所有的數據
*/
public List<Object> syncAndReturnAll() {
List<Object> responseList = new ArrayList<Object>();
innerSync(responseList);
return responseList;
}
private void innerSync(List<Object> formatted) {
HashSet<Client> clientSet = new HashSet<Client>();
try {
for (Client client : clients) {
// 在sync()調用時其實是不需要解析結果數據的,但是如果不調用get方法,發生了JedisMovedDataException這樣的錯誤應用是不知道的,因此需要調用get()來觸發錯誤。
// 其實如果Response的data屬性可以直接獲取,可以省掉解析數據的時間,然而它並沒有提供對應方法,要獲取data屬性就得用反射,不想再反射了,所以就這樣了
Object data = generateResponse(client.getOne()).get();
if (null != formatted) {
formatted.add(data);
}
// size相同說明所有的client都已經添加,就不用再調用add方法了
if (clientSet.size() != jedisMap.size()) {
clientSet.add(client);
}
}
} catch (JedisRedirectionException jre) {
if (jre instanceof JedisMovedDataException) {
// if MOVED redirection occurred, rebuilds cluster's slot cache,
// recommended by Redis cluster specification
refreshCluster();
}
throw jre;
} finally {
if (clientSet.size() != jedisMap.size()) {
// 所有還沒有執行過的client要保證執行(flush),防止放回連接池後後面的命令被污染
for (Jedis jedis : jedisMap.values()) {
if (clientSet.contains(jedis.getClient())) {
continue;
}
flushCachedData(jedis);
}
}
hasDataInBuf = false;
close();
}
}
@Override
public void close() {
clean();
clients.clear();
for (Jedis jedis : jedisMap.values()) {
if (hasDataInBuf) {
flushCachedData(jedis);
}
jedis.close();
}
jedisMap.clear();
hasDataInBuf = false;
}
private void flushCachedData(Jedis jedis) {
try {
jedis.getClient().getAll();
} catch (RuntimeException ex) {
}
}
@Override
protected Client getClient(String key) {
byte[] bKey = SafeEncoder.encode(key);
return getClient(bKey);
}
@Override
protected Client getClient(byte[] key) {
Jedis jedis = getJedis(JedisClusterCRC16.getSlot(key));
Client client = jedis.getClient();
clients.add(client);
return client;
}
private Jedis getJedis(int slot) {
JedisPool pool = clusterInfoCache.getSlotPool(slot);
// 根據pool從緩存中獲取Jedis
Jedis jedis = jedisMap.get(pool);
if (null == jedis) {
jedis = pool.getResource();
jedisMap.put(pool, jedis);
}
hasDataInBuf = true;
return jedis;
}
private static Field getField(Class<?> cls, String fieldName) {
try {
Field field = cls.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException | SecurityException e) {
throw new RuntimeException("cannot find or access field '" + fieldName + "' from " + cls.getName(), e);
}
}
@SuppressWarnings({ "unchecked" })
private static <T> T getValue(Object obj, Field field) {
try {
return (T) field.get(obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.error("get value fail", e);
throw new RuntimeException(e);
}
}
}
package org.atm.dc.app.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
@Primary
public class RedisClusterProperties {
List<String> nodes;
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
package org.atm.dc.app.config;
import java.lang.reflect.Method;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisClusterProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
@Autowired
RedisClusterProperties clusterProperties;
public @Bean
RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(
new RedisClusterConfiguration(clusterProperties.getNodes()));
}
/**
* 生成key的策略
*
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@SuppressWarnings("rawtypes")
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// 設置緩存過期時間
// rcm.setDefaultExpiration(60);//秒
return rcm;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
package org.atm.dc.app.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.atm.dc.app.config.RedisClusterProperties;
import org.atm.dc.utils.ApplicationContants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.Response;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
@Service
public class RedisUtils {
private Logger logger = LoggerFactory.getLogger(RedisUtils.class);
@SuppressWarnings("rawtypes")
@Autowired
private RedisTemplate redisTemplate;
@Autowired
RedisClusterProperties clusterProperties;
/**
* 寫入緩存
*
* @param key
* @param value
* @return
*/
@SuppressWarnings("unchecked")
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 寫入緩存設置時效時間
*
* @param key
* @param value
* @return
*/
@SuppressWarnings("unchecked")
public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, timeUnit);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@SuppressWarnings("unchecked")
public Map<String, Object> hgetAll(String key) {
Map<String, Object> hgetAll = redisTemplate.opsForHash().entries(key);
return hgetAll;
}
/**
* 批量刪除對應的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量刪除key
*
* @param pattern
*/
@SuppressWarnings("unchecked")
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 刪除對應的value
*
* @param key
*/
@SuppressWarnings("unchecked")
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判斷緩存中是否有對應的value
*
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 讀取緩存
*
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 模糊匹配key,讀取所有符合條件的數據
* @param key
* @return List<Object>
*/
@SuppressWarnings({ "unchecked" })
public List<Object> getRedis2(String key) {
List<Object> list = new ArrayList<Object>();
try {
Set<String> keys = redisTemplate.keys("*" + key + "*");
list = redisTemplate.opsForValue().multiGet(keys);
} catch (Exception e) {
logger.info("getRedis2,報錯太頻繁,所以簡寫。可能是這個錯誤:java.net.SocketTimeoutException: Read timed out");
}
return list;
}
/**
* 模糊匹配key,讀取所有符合條件的數據【查詢一次大約10ms,查詢一萬次需要10s,不建議使用此方法】
* @param key
* @return Map<String, Object>
*/
@SuppressWarnings("unchecked")
public Map<String, Object> getRedis(final String key) {
Map<String, Object> map = new HashMap<String, Object>();
try {
Set<String> keys = redisTemplate.keys("*" + key + "*");
for (String string : keys) {
Object value = redisTemplate.opsForValue().get(string);
map.put(string, value);
}
} catch (Exception e) {
logger.info("getRedis,報錯太頻繁,所以簡寫。可能是這個錯誤:java.net.SocketTimeoutException: Read timed out");
}
return map;
}
/**
* 模糊匹配key,讀取所有符合條件的數據【查詢一次大約10ms,查詢一萬次需要10s,不建議使用此方法】
* @param key
* @return hash類型
*/
@SuppressWarnings("unchecked")
public Map<String, Map<Object, Object>> getRedisHash(final String key) {
Map<String, Map<Object, Object>> map = new HashMap<String, Map<Object, Object>>();
Set<String> keys = redisTemplate.keys("*" + key + "*");
for (String string : keys) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
Map<Object, Object> value = hash.entries(string);
map.put(string, value);
}
return map;
}
/**
* 模糊匹配key,讀取所有符合條件的數據【】
* @param key
* @return Map<String, List<Object>>
*/
@SuppressWarnings("unchecked")
public Map<String, List<Object>> getRedisList(final String key) {
Map<String, List<Object>> map = new HashMap<String, List<Object>>();
try {
Set<String> keys = redisTemplate.keys("*" + key + "*");
for (String string : keys) {
List<Object> list = lRange(string, 0, -1);
map.put(string, list);
}
} catch (Exception e) {
logger.info("getRedisList,報錯太頻繁,所以簡寫。可能是這個錯誤:java.net.SocketTimeoutException: Read timed out");
}
return map;
}
/**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
@SuppressWarnings("unchecked")
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希獲取數據
*
* @param key
* @param hashKey
* @return
*/
@SuppressWarnings("unchecked")
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
@SuppressWarnings("unchecked")
public Map<String, Object> hmEntries(String key) {
HashOperations<String, String, Object> hash = redisTemplate.opsForHash();
return hash.entries(key);
}
/**
* 列表添加
*
* @param k
* @param v
*/
@SuppressWarnings("unchecked")
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表獲取
*
* @param k
* @param l
* @param l1
* @return
*/
@SuppressWarnings("unchecked")
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* 集合添加
*
* @param key
* @param value
*/
@SuppressWarnings("unchecked")
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合獲取
*
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
@SuppressWarnings("unchecked")
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合獲取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
@SuppressWarnings("unchecked")
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, scoure, scoure1);
}
/**
* pipeline獲取數據。Set集合
* @param str
* @return
*/
@SuppressWarnings("unchecked")
public Map<String, Set<String>> usePipeline2(String str) {
Map<String, Set<String>> result = new HashMap<String, Set<String>>();
JedisCluster jedis = getJedisCluster();
JedisClusterPipeline pipeline = null;
try {
// long aa = System.currentTimeMillis();
Map<String, Response<Set<String>>> responses = new HashMap<String, Response<Set<String>>>();
Set<String> keys = redisTemplate.keys("*" + str + "*");
// long bb = System.currentTimeMillis();
pipeline = JedisClusterPipeline.pipelined(jedis);
for(String key : keys) {
responses.put(key, pipeline.smembers(key));
}
long cc = System.currentTimeMillis();
pipeline.sync();
long dd = System.currentTimeMillis();
logger.info("responses.size()-----" + responses.size() + ",pipeline.sync()------" + (dd - cc) + "ms");
for(String k : responses.keySet()) {
result.put(k, responses.get(k).get());
}
// long ee = System.currentTimeMillis();
} catch (Exception e) {
logger.info("usePipeline2,報錯太頻繁,所以簡寫。可能是這個錯誤:java.net.SocketTimeoutException: Read timed out");
}finally {
pipeline.close();
}
return result;
}
/**
* pipeline獲取數據。List集合
* @param str
* @return
*/
@SuppressWarnings("unchecked")
public Map<String, List<String>> usePipeline(String str) {
Map<String, List<String>> result = new HashMap<String, List<String>>();
JedisCluster jedis = getJedisCluster();
JedisClusterPipeline pipeline = null;
try {
// long aa = System.currentTimeMillis();
Map<String, Response<List<String>>> responses = new HashMap<String, Response<List<String>>>();
Set<String> keys = redisTemplate.keys("*" + str + "*");
// long bb = System.currentTimeMillis();
// System.out.println("redisTemplate.keys------" + (bb - aa) + "ms");
pipeline = JedisClusterPipeline.pipelined(jedis);
for(String key : keys) {
responses.put(key, pipeline.lrange(key, 0, -1));
}
long cc = System.currentTimeMillis();
// System.out.println("pipeline.lrange------" + (cc - bb) + "ms");
pipeline.sync();
long dd = System.currentTimeMillis();
logger.info("responses.size()-----" + responses.size() + ",pipeline.sync()------" + (dd - cc) + "ms");
for(String k : responses.keySet()) {
result.put(k, responses.get(k).get());
}
// long ee = System.currentTimeMillis();
// System.out.println("result.put------" + (ee - dd) + "ms");
} catch (Exception e) {
logger.info("usePipeline,報錯太頻繁,所以簡寫。可能是這個錯誤:java.net.SocketTimeoutException: Read timed out");
}finally {
pipeline.close();
}
return result;
}
/**
* 集羣
* @return
*/
private JedisCluster getJedisCluster() {
// 添加集羣的服務節點Set集合
Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>();
// 添加節點
String[] arr = null;
List<String> list = clusterProperties.getNodes();
for (String string : list) {
arr = string.split(":");
hostAndPortsSet.add(new HostAndPort(arr[0], Integer.parseInt(arr[1])));
}
// Jedis連接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空閒連接數, 默認8個
jedisPoolConfig.setMaxIdle(100);
// 最大連接數, 默認8個
jedisPoolConfig.setMaxTotal(500);
//最小空閒連接數, 默認0
jedisPoolConfig.setMinIdle(0);
// 獲取連接時的最大等待毫秒數(如果設置爲阻塞時BlockWhenExhausted),如果超時就拋異常, 小於零:阻塞不確定的時間, 默認-1
jedisPoolConfig.setMaxWaitMillis(2000); // 設置2秒
//對拿到的connection進行validateObject校驗
jedisPoolConfig.setTestOnBorrow(true);
JedisCluster jedis = new JedisCluster(hostAndPortsSet, jedisPoolConfig);
return jedis;
}
/**
* 單節點、非集羣
* @return
*/
@SuppressWarnings("resource")
public ShardedJedis getShardedJedis() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(2);
poolConfig.setMaxIdle(1);
poolConfig.setMaxWaitMillis(2000);
poolConfig.setTestOnBorrow(false);
poolConfig.setTestOnReturn(false);
JedisShardInfo info1 = new JedisShardInfo(ApplicationContants.redisHost, ApplicationContants.redisPort);
info1.setSoTimeout(3000);
info1.setPassword(ApplicationContants.redisPwd);//這裏是密碼
JedisShardInfo info2 = new JedisShardInfo(ApplicationContants.redisHost, ApplicationContants.redisPort);
info2.setSoTimeout(3000);
info2.setPassword(ApplicationContants.redisPwd);//這裏是密碼
ShardedJedisPool pool = new ShardedJedisPool(poolConfig, Arrays.asList(info1, info2));
return pool.getResource();
}
}
2.備註
<!--集成redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
報錯可以註釋掉RedisConfig.java
// @SuppressWarnings("rawtypes")
// @Bean
// public CacheManager cacheManager(RedisTemplate redisTemplate) {
// RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// // 設置緩存過期時間
// // rcm.setDefaultExpiration(60);//秒
// return rcm;
// }
後綴爲:.properties
spring.redis.cluster.nodes=IP1:6379,IP2:6379,IP3:6379
spring.redis.password=
後綴爲:.yml
spring:
redis:
cluster:
nodes: IP1:6379,IP2:6379,IP3:6379
password: