自定义多数据源的redis-stater

背景

在我们实际开发过程中,可能我们一个项目里面在使用redis,不同模块的数据存储到不同的redis服务器中,如果公司多个项目都是这种情况,那我们就可以自定义一个stater来支持这种实现

话不多说,开始!!!

自定义redis-stater

  • 创建 maven 工程,工程名为dy-commons-redis-starter,导入依赖:
    
        
            org.springframework.boot
            spring-boot-configuration-processor
        

        
            org.springframework.boot
            spring-boot-configuration-processor
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
        
            org.projectlombok
            lombok
            1.18.6
            true
        

        
        
            redis.clients
            jedis
            3.3.0
        

    
  • 新建Properties类
/**
 * @Description:
 * @author: dy
 */
@Data
@ConditionalOnClass(RedisProperties.class)
@ConfigurationProperties(prefix = "dy.redis")
public class CustomRedisProperties {

    /**
     * 使用 Map  结构存储多个 Redis 驱动的连接信息
     */
    private Map 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 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         泛型
     *
     * @return 执行的结果
     */
     T executeLuaScript(String luaScript, Class 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 redisTemplate;

    public CacheServiceImpl() {

    }

    public CacheServiceImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate 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         泛型
     * @return 执行的结果
     */
    public  T executeLuaScript(String luaScript, Class 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的使用

  • 引入依赖
 
      io.gitee.dai-yong-1
       dy-commons-redis-starter
       1.0.2-SNAPSHOT

  • 配置文件如下
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

你可能感兴趣的:(自定义多数据源的redis-stater)