自定义多数据源的redis-stater
2022-06-01 本文已影响0人
wavefreely
背景
在我们实际开发过程中,可能我们一个项目里面在使用redis,不同模块的数据存储到不同的redis服务器中,如果公司多个项目都是这种情况,那我们就可以自定义一个stater来支持这种实现
话不多说,开始!!!
自定义redis-stater
- 创建 maven 工程,工程名为dy-commons-redis-starter,导入依赖:
<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 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<optional>true</optional>
</dependency>
<!--jedis 依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
- 新建Properties类
/**
* @Description:
* @author: dy
*/
@Data
@ConditionalOnClass(RedisProperties.class)
@ConfigurationProperties(prefix = "dy.redis")
public class CustomRedisProperties {
/**
* 使用 Map <K,V> 结构存储多个 Redis 驱动的连接信息
*/
private Map<String, RedisProperties> instances = new HashMap<>();
/**
* 通过 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 + "].");
}
}
}
- 新建配置类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进行RedisTemplate的构建
/**
* @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 设置为字符串类型,方便维护
// value序列化 使用 JSON 序列化方式(库是 Jackson
template.setValueSerializer(RedisSerializer.json());
//key序列化 设置key为字符串类型
template.setKeySerializer(new StringRedisSerializer());
// Hash value序列化 使用 JSON 序列化方式(库是 Jackson
template.setHashValueSerializer(RedisSerializer.json());
//hashKey序列化设置hashKey为字符串类型
template.setHashKeySerializer(new StringRedisSerializer());
//设置redis使用连接池,这里采用的是lettuce
template.setConnectionFactory(createJedisConnectionFactory());
//初始化 RedisTemplate (必须调用)
template.afterPropertiesSet();
return template;
}
/**
* 创建一个连接池 工厂对象
*
* @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 {
//单实例模式
lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration(), clientConfig);
}
//初始化 lettuceConnectionFactory (必须调用)
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;
}
/**
* 单实例 配置
*
* @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;
}
}
- 对redis的增删改查进行封装
/**
* @Description:
* @author: dy
*/
public interface CacheService {
/**
* 删除
*
* @param keys
*/
void del(String... keys);
/**
* 为key设置超时时间
*
* @param key
* @param seconds
* @return
*/
boolean expire(String key, long seconds);
/**
* 根据key获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
long getExpire(String key);
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @param expireTime 超时时间(秒)
* @return true成功 false失败
*/
Boolean set(String key, Object value, int expireTime);
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
Boolean set(String key, Object value);
/**
* 存储数据
*
* @param key
* @param value
* @return
*/
Boolean set(String key, String value);
/**
* 获取数据
*
* @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);
/**
* 在原有的值基础上新增字符串到末尾。
* @param key
* @param value
*/
void append(String key, String value);
/**
* 设置map集合到redis。
* @param map
*/
void multiSet(Map map);
/**
* 根据集合取出对应的value值。
* @param list
* @return
*/
List multiGet(List list);
/**
* 如果键不存在则新增,存在则不改变已经有的值
* @param key
* @param value
* @return
*/
boolean setIfAbsent(String key, String value);
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
boolean hashSet(String key, String item, Object value);
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
boolean hashSet(String key, String item, Object value, long time);
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
void hashDel(String key, Object... item);
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
boolean hHasKey(String key, String item);
/**
* 使用lua脚本的优势:
* 1.原子性。redis执行lua脚本的时候,会将它作为一个整体执行,要么全部执行成功,如果出现异常则执行结果不会更新到redis中,
* 可以代替redis中的事务操作。
*
* 2.节省网络开销。通过脚本的方式执行多个命令,一次传输返回结果。
* redis的pipeline也同样有这样的优点,相比lua脚本中执行的多个命令,
* pipe中某个命令执行出现异常不会影响其他的命令的更新到redis中;但lua脚本使用更加灵活。
*
* 3.脚本的复用。如果每次请求都要传输脚本,存在一定的网络开销,
* 通过 SCRIPT LOAD 命令进行Redis 将脚本缓存到服务器的操作,
* 并且返回脚本内容的SHA1校验和,Evalsha 命令根据给定的 sha1 校验码,执行缓存在服务器中的脚本。
*
* 执行 lua 脚本
*
* @param luaScript lua 脚本
* @param returnType 返回的结构类型
* @param keys KEYS
* @param argv ARGV
* @param <T> 泛型
*
* @return 执行的结果
*/
<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);
}
- 实现类如下
/**
* @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 超时时间(秒)
* @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;
}
}
/**
* 在原有的值基础上新增字符串到末尾。
*
* @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);
}
/**
* 如果键不存在则新增,存在则不改变已经有的值
*
* @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));
}
}
}
/**
* 根据key设置过期时间
*
* @param key 键
* @param seconds 超时时间(秒)
* @return true成功 false失败
*/
@Override
public boolean expire(String key, long seconds) {
return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 根据key获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回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);
}
/**
* 设置map集合到redis。
*
* @param map
*/
@Override
public void multiSet(Map map) {
redisTemplate.opsForValue().multiSet(map);
}
/**
* 根据集合取出对应的value值。
*
* @param list
* @return
*/
@Override
public List multiGet(List list) {
return redisTemplate.opsForValue().multiGet(list);
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @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表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @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 项 可以使多个 不能为null
*/
public void hashDel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* 执行 lua 脚本
*
* @param luaScript lua 脚本
* @param returnType 返回的结构类型
* @param keys KEYS
* @param argv ARGV
* @param <T> 泛型
* @return 执行的结果
*/
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文件下创建META-INF文件夹,新建spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dy.commons.data.redis.starter.CustomRedisAutoConfiguration
至此stater我们就定义好了,发布到maven仓库
redis-stater的使用
- 引入依赖
<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存储数据如下
@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:{} 用时==========>>:{} ,===>>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