背景
在我們實(shí)際開(kāi)發(fā)過(guò)程中,可能我們一個(gè)項(xiàng)目里面在使用redis,不同模塊的數(shù)據(jù)存儲(chǔ)到不同的redis服務(wù)器中,如果公司多個(gè)項(xiàng)目都是這種情況,那我們就可以自定義一個(gè)stater來(lái)支持這種實(shí)現(xiàn)
話(huà)不多說(shuō),開(kāi)始?。?!
自定義redis-stater
- 創(chuàng)建 maven 工程,工程名為dy-commons-redis-starter,導(dǎo)入依賴(lài):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--redis 依賴(lài)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lombok 依賴(lài)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<optional>true</optional>
</dependency>
<!--jedis 依賴(lài)-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
- 新建Properties類(lèi)
/**
* @Description:
* @author: dy
*/
@Data
@ConditionalOnClass(RedisProperties.class)
@ConfigurationProperties(prefix = "dy.redis")
public class CustomRedisProperties {
/**
* 使用 Map <K,V> 結(jié)構(gòu)存儲(chǔ)多個(gè) Redis 驅(qū)動(dòng)的連接信息
*/
private Map<String, RedisProperties> instances = new HashMap<>();
/**
* 通過(guò) ID 刷選出配置信息
*
* @param id
* @return
*/
public RedisProperties findById(String id) {
try {
return instances.get(id);
} catch (Exception e) {
throw new RuntimeException("Not found the redis instance configuration info with id[" + id + "].");
}
}
}
- 新建配置類(lèi)CustomRedisAutoConfiguration
/**
* @Description:
* @author: dy
*/
@Configuration
@EnableConfigurationProperties({CustomRedisProperties.class})
public class CustomRedisAutoConfiguration {
private final static Logger log = LoggerFactory.getLogger(CustomRedisAutoConfiguration.class);
private CustomRedisProperties customRedisProperties;
public CustomRedisAutoConfiguration(CustomRedisProperties customRedisProperties) {
this.customRedisProperties = customRedisProperties;
}
@Bean
public CustomRedisProperties customRedisProperties() {
log.debug("===>> Start Configuration Custom RedisAutoConfiguration.");
return new CustomRedisProperties();
}
}
- 新建RedisConfigBuilder進(jìn)行RedisTemplate的構(gòu)建
/**
* @Description:
* @author: dy
*/
public class RedisConfigBuilder {
private RedisProperties properties;
private RedisSentinelConfiguration sentinelConfiguration;
private RedisClusterConfiguration clusterConfiguration;
public RedisConfigBuilder(RedisProperties properties) {
this.properties = properties;
}
/**
* @param properties
* @return RedisConfigBuilder
*/
public static RedisConfigBuilder create(RedisProperties properties) {
return new RedisConfigBuilder(properties);
}
/**
* RedisTemplate build
*
* @return RedisTemplate
*/
public RedisTemplate build() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 將 Key 設(shè)置為字符串類(lèi)型,方便維護(hù)
// value序列化 使用 JSON 序列化方式(庫(kù)是 Jackson
template.setValueSerializer(RedisSerializer.json());
//key序列化 設(shè)置key為字符串類(lèi)型
template.setKeySerializer(new StringRedisSerializer());
// Hash value序列化 使用 JSON 序列化方式(庫(kù)是 Jackson
template.setHashValueSerializer(RedisSerializer.json());
//hashKey序列化設(shè)置hashKey為字符串類(lèi)型
template.setHashKeySerializer(new StringRedisSerializer());
//設(shè)置redis使用連接池,這里采用的是lettuce
template.setConnectionFactory(createJedisConnectionFactory());
//初始化 RedisTemplate (必須調(diào)用)
template.afterPropertiesSet();
return template;
}
/**
* 創(chuàng)建一個(gè)連接池 工廠(chǎng)對(duì)象
*
* @return 連接池
*/
private LettuceConnectionFactory createJedisConnectionFactory() {
//GenericObjectPoolConfig 連接池配置
GenericObjectPoolConfig genericObjectPoolConfig = genericObjectPoolConfig();
// 配置池
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(properties.getTimeout())
.poolConfig(genericObjectPoolConfig)
.build();
LettuceConnectionFactory lettuceConnectionFactory = null;
if (null != properties.getSentinel()) {
//哨兵模式
lettuceConnectionFactory = new LettuceConnectionFactory(redisSentinelConfiguration(), clientConfig);
}
if (null != properties.getCluster()) {
//集群模式
lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration(), clientConfig);
} else {
//單實(shí)例模式
lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration(), clientConfig);
}
//初始化 lettuceConnectionFactory (必須調(diào)用)
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
/**
* GenericObjectPoolConfig 連接池配置
*
* @return 連接池配置
*/
private GenericObjectPoolConfig genericObjectPoolConfig() {
//GenericObjectPoolConfig 連接池配置
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
if (null != properties.getLettuce() && null != properties.getLettuce().getPool()) {
genericObjectPoolConfig.setMaxIdle(properties.getLettuce().getPool().getMaxIdle());
genericObjectPoolConfig.setMinIdle(properties.getLettuce().getPool().getMinIdle());
genericObjectPoolConfig.setMaxTotal(properties.getLettuce().getPool().getMaxActive());
genericObjectPoolConfig.setMaxWaitMillis(properties.getLettuce().getPool().getMaxWait().toMillis());
}
return genericObjectPoolConfig;
}
/**
* 單實(shí)例 配置
*
* @return RedisStandaloneConfiguration
*/
private RedisStandaloneConfiguration redisStandaloneConfiguration() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(properties.getHost(), properties.getPort());
redisStandaloneConfiguration.setPassword(properties.getPassword());
return redisStandaloneConfiguration;
}
/**
* 集群配置
*
* @return RedisClusterConfiguration
*/
private RedisClusterConfiguration redisClusterConfiguration() {
// 集群
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(properties.getCluster().getNodes());
redisClusterConfiguration.setMaxRedirects(properties.getCluster().getMaxRedirects());
redisClusterConfiguration.setPassword(properties.getPassword());
return redisClusterConfiguration;
}
/**
* 哨兵配置
*
* @return RedisSentinelConfiguration
*/
private RedisSentinelConfiguration redisSentinelConfiguration() {
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(properties.getSentinel().getMaster(),
new HashSet<>(properties.getSentinel().getNodes()));
redisSentinelConfiguration.setPassword(properties.getPassword());
return redisSentinelConfiguration;
}
}
- 對(duì)redis的增刪改查進(jìn)行封裝
/**
* @Description:
* @author: dy
*/
public interface CacheService {
/**
* 刪除
*
* @param keys
*/
void del(String... keys);
/**
* 為key設(shè)置超時(shí)時(shí)間
*
* @param key
* @param seconds
* @return
*/
boolean expire(String key, long seconds);
/**
* 根據(jù)key獲取過(guò)期時(shí)間
*
* @param key 鍵 不能為null
* @return 時(shí)間(秒) 返回0代表為永久有效
*/
long getExpire(String key);
/**
* 普通緩存放入
* @param key 鍵
* @param value 值
* @param expireTime 超時(shí)時(shí)間(秒)
* @return true成功 false失敗
*/
Boolean set(String key, Object value, int expireTime);
/**
* 普通緩存放入
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
Boolean set(String key, Object value);
/**
* 存儲(chǔ)數(shù)據(jù)
*
* @param key
* @param value
* @return
*/
Boolean set(String key, String value);
/**
* 獲取數(shù)據(jù)
*
* @param key
* @return
*/
Object getObject(String key);
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return
*/
long incr(String key, long delta) ;
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return
*/
long decr(String key, long delta);
/**
* 在原有的值基礎(chǔ)上新增字符串到末尾。
* @param key
* @param value
*/
void append(String key, String value);
/**
* 設(shè)置map集合到redis。
* @param map
*/
void multiSet(Map map);
/**
* 根據(jù)集合取出對(duì)應(yīng)的value值。
* @param list
* @return
*/
List multiGet(List list);
/**
* 如果鍵不存在則新增,存在則不改變已經(jīng)有的值
* @param key
* @param value
* @return
*/
boolean setIfAbsent(String key, String value);
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @return true 成功 false失敗
*/
boolean hashSet(String key, String item, Object value);
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @param time 時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間
* @return true 成功 false失敗
*/
boolean hashSet(String key, String item, Object value, long time);
/**
* 刪除hash表中的值
* @param key 鍵 不能為null
* @param item 項(xiàng) 可以使多個(gè) 不能為null
*/
void hashDel(String key, Object... item);
/**
* 判斷hash表中是否有該項(xiàng)的值
* @param key 鍵 不能為null
* @param item 項(xiàng) 不能為null
* @return true 存在 false不存在
*/
boolean hHasKey(String key, String item);
/**
* 使用lua腳本的優(yōu)勢(shì):
* 1.原子性。redis執(zhí)行l(wèi)ua腳本的時(shí)候,會(huì)將它作為一個(gè)整體執(zhí)行,要么全部執(zhí)行成功,如果出現(xiàn)異常則執(zhí)行結(jié)果不會(huì)更新到redis中,
* 可以代替redis中的事務(wù)操作。
*
* 2.節(jié)省網(wǎng)絡(luò)開(kāi)銷(xiāo)。通過(guò)腳本的方式執(zhí)行多個(gè)命令,一次傳輸返回結(jié)果。
* redis的pipeline也同樣有這樣的優(yōu)點(diǎn),相比lua腳本中執(zhí)行的多個(gè)命令,
* pipe中某個(gè)命令執(zhí)行出現(xiàn)異常不會(huì)影響其他的命令的更新到redis中;但lua腳本使用更加靈活。
*
* 3.腳本的復(fù)用。如果每次請(qǐng)求都要傳輸腳本,存在一定的網(wǎng)絡(luò)開(kāi)銷(xiāo),
* 通過(guò) SCRIPT LOAD 命令進(jìn)行Redis 將腳本緩存到服務(wù)器的操作,
* 并且返回腳本內(nèi)容的SHA1校驗(yàn)和,Evalsha 命令根據(jù)給定的 sha1 校驗(yàn)碼,執(zhí)行緩存在服務(wù)器中的腳本。
*
* 執(zhí)行 lua 腳本
*
* @param luaScript lua 腳本
* @param returnType 返回的結(jié)構(gòu)類(lèi)型
* @param keys KEYS
* @param argv ARGV
* @param <T> 泛型
*
* @return 執(zhí)行的結(jié)果
*/
<T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv);
/**
* 分布式鎖
* @param key
* @param timeout
* @param unit
* @return
*/
boolean tryLock(String key, long timeout, TimeUnit unit);
/**
* 釋放鎖
* @param key
*/
void releaseLock(String key);
}
- 實(shí)現(xiàn)類(lèi)如下
/**
* @Description:
* @author: dy
*/
public class CacheServiceImpl implements CacheService {
private RedisTemplate<String, Object> redisTemplate;
public CacheServiceImpl() {
}
public CacheServiceImpl(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @param expireTime 超時(shí)時(shí)間(秒)
* @return true成功 false失敗
*/
@Override
public Boolean set(String key, Object value, int expireTime) {
try {
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
@Override
public Boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
@Override
public Boolean set(String key, String value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 在原有的值基礎(chǔ)上新增字符串到末尾。
*
* @param key 鍵
* @param value 追加的值
*/
@Override
public void append(String key, String value) {
redisTemplate.opsForValue().append(key, value);
}
/**
* 普通緩存獲取
*
* @param key 鍵
* @return 值
*/
@Override
public Object getObject(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 如果鍵不存在則新增,存在則不改變已經(jīng)有的值
*
* @param key
* @param value
* @return true成功 false失敗
*/
@Override
public boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 刪除指定KEY的緩存
*
* @param keys
*/
@Override
public void del(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
redisTemplate.delete(keys[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(keys));
}
}
}
/**
* 根據(jù)key設(shè)置過(guò)期時(shí)間
*
* @param key 鍵
* @param seconds 超時(shí)時(shí)間(秒)
* @return true成功 false失敗
*/
@Override
public boolean expire(String key, long seconds) {
return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 根據(jù)key獲取過(guò)期時(shí)間
*
* @param key 鍵 不能為null
* @return 時(shí)間(秒) 返回0代表為永久有效
*/
@Override
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return 返回增加后的值
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return 返回減少后的值
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 設(shè)置map集合到redis。
*
* @param map
*/
@Override
public void multiSet(Map map) {
redisTemplate.opsForValue().multiSet(map);
}
/**
* 根據(jù)集合取出對(duì)應(yīng)的value值。
*
* @param list
* @return
*/
@Override
public List multiGet(List list) {
return redisTemplate.opsForValue().multiGet(list);
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @return true 成功 false失敗
*/
public boolean hashSet(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項(xiàng)
* @param value 值
* @param time 時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間
* @return true 成功 false失敗
*/
public boolean hashSet(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除hash表中的值
*
* @param key 鍵 不能為null
* @param item 項(xiàng) 可以使多個(gè) 不能為null
*/
public void hashDel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項(xiàng)的值
*
* @param key 鍵 不能為null
* @param item 項(xiàng) 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* 執(zhí)行 lua 腳本
*
* @param luaScript lua 腳本
* @param returnType 返回的結(jié)構(gòu)類(lèi)型
* @param keys KEYS
* @param argv ARGV
* @param <T> 泛型
* @return 執(zhí)行的結(jié)果
*/
public <T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv) {
return (T) redisTemplate.execute(RedisScript.of(luaScript, returnType),
new StringRedisSerializer(),
new GenericToStringSerializer<>(returnType),
Arrays.asList(keys),
argv);
}
@Override
public boolean tryLock(String key, long timeout, TimeUnit unit) {
String uuid = UUID.randomUUID().toString();
return redisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
}
@Override
public void releaseLock(String key) {
redisTemplate.delete(key);
}
}
- 在resources文件下創(chuàng)建META-INF文件夾,新建spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dy.commons.data.redis.starter.CustomRedisAutoConfiguration
至此stater我們就定義好了,發(fā)布到maven倉(cāng)庫(kù)
redis-stater的使用
- 引入依賴(lài)
<dependency>
<groupId>io.gitee.dai-yong-1</groupId>
<artifactId>dy-commons-redis-starter</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>
- 配置文件如下
dy.redis:
instances:
redis-stack:
host: xxx.xxx.xxx.xxx
password: xxxx
port: 6379
lettuce:
pool:
max-active: 200
max-idle: 30
min-idle: 10
max-wait: 3000ms
timeout: 3000
redis-2:
database: 1
host: xxx.xxx.xxx.xxx
password: xxxx
port: 6379
lettuce:
pool:
max-active: 200
max-idle: 30
min-idle: 10
max-wait: 3000ms
timeout: 3000
- 新建RedisConfig把我們的CacheService裝入容器
/**
* @Description:
* @author: dy
*/
@Configuration
public class RedisConfig {
@Autowired
private CustomRedisProperties customRedisProperties;
@Bean(name="cacheService")
@Qualifier("cacheService")
public CacheService cacheService() {
CacheService cacheService = new CacheServiceImpl(RedisConfigBuilder.create(customRedisProperties.findById("redis-stack")).build());
return cacheService;
}
@Bean(name="cacheService2")
@Qualifier("cacheService2")
public CacheService cacheService2() {
CacheService cacheService2 = new CacheServiceImpl(RedisConfigBuilder.create(customRedisProperties.findById("redis-2")).build());
return cacheService2;
}
}
- 具體使用redis存儲(chǔ)數(shù)據(jù)如下
@Slf4j
@RestController
public class UserController implements UserApi {
@Autowired
private CacheService cacheService;
@Autowired
private CacheService cacheService2;
@Override
public String testRedis() {
UserDTO userDTO = new UserDTO();
userDTO.setId("111");
userDTO.setName("小紅");
cacheService.set("hello", userDTO);
return "" + cacheService.getObject("hello");
}
@Override
public String testRedis2() {
for (int i=0;i<500;i++){
int finalI = i;
MyThreadPoolExecutor.open(5).execute(() -> {
getNumber("errorId10", finalI);
});
}
return "success";
}
private Long getNumber(String key,int i) {
long startTime = System.currentTimeMillis();
long num = 0l;
if (null == cacheService.getObject(key)) {
num = getNum(key);
} else {
num = cacheService.incr(key, 1);
}
log.info("===>> pos:{} 用時(shí)==========>>:{} ,===>>num:{}" , i ,(System.currentTimeMillis() - startTime),num);
return num;
}
private Long getNum(String key) {
String lockKey = "lockKey";
while (true) {
if (cacheService.tryLock(lockKey, 5, TimeUnit.SECONDS)) {
log.info("num===ddddddddddddddddddddddddddddd==加鎖成功============");
break;
}
}
if (null != cacheService.getObject(key)) {
cacheService.del(lockKey);
return cacheService.incr(key, 1);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
cacheService.set(key, 100);
cacheService.del(lockKey);
log.info("num===ddddddddddddddddddddddddddddd==============="+cacheService.getObject(lockKey));
return 100l;
}
}
大功告成?。。。?!
源碼地址:https://gitee.com/dai-yong-1/dy-commons-redis-starter