Java客户端对Redis Cluster模式的支持(解决Redis使用Spring缓存时RedisClusterConfiguration not found)

最近项目中使用Spring并使用Redis作为缓存,参考网上的一些做法。发现Spring根本启动不了,先贴出网上的做法:

导入包

        
            org.springframework.data
            spring-data-redis
            {version}/version>
        
        
            redis.clients
            jedis
            {version}
        

配置核心类

@Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        redisConnectionFactory.setUsePool(false);
        redisConnectionFactory.setHostName(xxx);
        redisConnectionFactory.setPort(xxx);
        return redisConnectionFactory;
    }
    @Bean
    public RedisTemplate redisTemplate(JedisConnectionFactory jf) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jf);
        return redisTemplate;
    }
    @Bean(name="redisCacheManager")
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(3000); // Sets the default expire time
                                                    // (in seconds)
        return cacheManager;
    }

完成如上配置后就是在业务逻辑层的注解配置:

    @CachePut(key="#student.id",value="student")
    @Override
    public void add(Student student) {
       //xxxx
    }
    @CacheEvict(value="student",key="#id") 
    public void delete(Integer id) {
       //xxx
    }
    @Cacheable(key="#id",value="student")
    @Override
    public Student queryById(Integer id) {
       //xxx
    }

@Cacheable(key=”#id”,value=”student”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的 key 就是参数 id

@CacheEvict 注释来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。注意其中一个

@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。

我们不难看出,缓存的key为方法参数的一部分,为了避免两个数据其实不一样但方法参数有可能一致的情况我们需要自定义缓存的key来避免。

 @Bean  
    public KeyGenerator wiselyKeyGenerator(){  
        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();  
            }  
        };  

    }  

 @Cacheable(value = "usercache",keyGenerator = "wiselyKeyGenerator")  

如上是通过注解在方法级别来配置缓存。现在我们来自定义操作缓存,即手动管理缓存中的数据:

redis缓存工具类

ValueOperations   ——基本数据类型和实体类的缓存 ListOperations   ——list的缓存 SetOperations   ——set的缓存 HashOperations   ——Map的缓存
 @Autowired @Qualifier("jedisTemplate")
    public RedisTemplate redisTemplate;



    /**
     * 缓存基本的对象,Integer、String、实体类等
     * @param key    缓存的键值
     * @param value    缓存的值
     * @return        缓存的对象
     */
    public  ValueOperations setCacheObject(String key,T value)
    {

        ValueOperations operation = redisTemplate.opsForValue(); 
        operation.set(key,value);
        return operation;
    }

    /**
     * 获得缓存的基本对象。
     * @param key        缓存键值
     * @param operation
     * @return            缓存键值对应的数据
     */
    public  T getCacheObject(String key/*,ValueOperations operation*/)
    {
        ValueOperations operation = redisTemplate.opsForValue(); 
        return operation.get(key);
    }

    /**
     * 缓存List数据
     * @param key        缓存的键值
     * @param dataList    待缓存的List数据
     * @return            缓存的对象
     */
    public  ListOperations setCacheList(String key,List dataList)
    {
        ListOperations listOperation = redisTemplate.opsForList();
        if(null != dataList)
        {
            int size = dataList.size();
            for(int i = 0; i < size ; i ++)
            {

                listOperation.rightPush(key,dataList.get(i));
            }
        }

        return listOperation;
    }

    /**
     * 获得缓存的list对象
     * @param key    缓存的键值
     * @return        缓存键值对应的数据
     */
    public  List getCacheList(String key)
    {
        List dataList = new ArrayList();
        ListOperations listOperation = redisTemplate.opsForList();
        Long size = listOperation.size(key);

        for(int i = 0 ; i < size ; i ++)
        {
            dataList.add((T) listOperation.leftPop(key));
        }

        return dataList;
    }

    /**
     * 缓存Set
     * @param key        缓存键值
     * @param dataSet    缓存的数据
     * @return            缓存数据的对象
     */
    public  BoundSetOperations setCacheSet(String key,Set dataSet)
    {
        BoundSetOperations setOperation = redisTemplate.boundSetOps(key);    
        /*T[] t = (T[]) dataSet.toArray();
             setOperation.add(t);*/


        Iterator it = dataSet.iterator();
        while(it.hasNext())
        {
            setOperation.add(it.next());
        }

        return setOperation;
    }

    /**
     * 获得缓存的set
     * @param key
     * @param operation
     * @return
     */
    public Set getCacheSet(String key/*,BoundSetOperations operation*/)
    {
        Set dataSet = new HashSet();
        BoundSetOperations operation = redisTemplate.boundSetOps(key);    

        Long size = operation.size();
        for(int i = 0 ; i < size ; i++)
        {
            dataSet.add(operation.pop());
        }
        return dataSet;
    }

    /**
     * 缓存Map
     * @param key
     * @param dataMap
     * @return
     */
    public  HashOperations setCacheMap(String key,Map dataMap)
    {

        HashOperations hashOperations = redisTemplate.opsForHash();
        if(null != dataMap)
        {

            for (Map.Entry entry : dataMap.entrySet()) {  

                /*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  */
                hashOperations.put(key,entry.getKey(),entry.getValue());
            } 

        }

        return hashOperations;
    }

    /**
     * 获得缓存的Map
     * @param key
     * @param hashOperation
     * @return
     */
    public  Map getCacheMap(String key/*,HashOperations hashOperation*/)
    {
        Map map = redisTemplate.opsForHash().entries(key);
        /*Map map = hashOperation.entries(key);*/
        return map;
    }







    /**
     * 缓存Map
     * @param key
     * @param dataMap
     * @return
     */
    public  HashOperations setCacheIntegerMap(String key,Map dataMap)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        if(null != dataMap)
        {

            for (Map.Entry entry : dataMap.entrySet()) {  

                /*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  */
                hashOperations.put(key,entry.getKey(),entry.getValue());
            } 

        }

        return hashOperations;
    }

    /**
     * 获得缓存的Map
     * @param key
     * @param hashOperation
     * @return
     */
    public  Map getCacheIntegerMap(String key/*,HashOperations hashOperation*/)
    {
        Map map = redisTemplate.opsForHash().entries(key);
        /*Map map = hashOperation.entries(key);*/
        return map;
    }

如上似乎没有如何问题,但实际配置启动项目却发现抛出如下错误:

ClassNotFound :  org.springframework.data.redis.connection.RedisClusterConfiguration

查阅相关资料Spring-data-redis在1.7版本的时候才开始执行Redis Cluster模式。不知道是不是Redis3.X必须要求的,现在要配置Spring对Redis的操作必须配置RedisClusterConfiguration。

Java对Redis Cluster模式的支持

现在目前主要有二种方式:

一、以直接调用jedis来实现; 二、使用spring-data-redis 1.7X版本

下面分别对这二种方式如何操作Redis进行说明:

       //   通过Jedis操作Redis Cluster的模型可以参考Redis官网,具体如下:

        Set  jedisClusterNodes = new HashSet();

          //Jedis Cluster will attempt to discover cluster nodes automatically

         jedisClusterNodes.add(new HostAndPort("10.96.5.183",9001));

         jedisClusterNodes.add(new HostAndPort("10.96.5.183",9002));

         jedisClusterNodes.add(new HostAndPort("10.96.5.183",9003));

        JedisCluster jc = new JedisCluster(jedisClusterNodes);

使用Spring-data-redis:

    @Bean
    public RedisClusterConfiguration getRedisCluster() {

        Set jedisClusterNodes = new HashSet();

        // Jedis Cluster will attempt to discover cluster nodes automatically

        jedisClusterNodes.add(new RedisNode(redisHostName, Integer.valueOf(redisPort)));
        //jedisClusterNodes.add( xxx );
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();

        return redisClusterConfiguration;
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(getRedisCluster());
        /*
         * Defaults
        redisConnectionFactory.setUsePool(false);
        redisConnectionFactory.setHostName(redisHostName);
        redisConnectionFactory.setPort(Integer.valueOf(redisPort));
        *
        */
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate redisTemplate(JedisConnectionFactory cf) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(cf);
        return redisTemplate;
    }

Redis Cluster调试中常见错误

(1)当客户端与集群服务器不在同一台服务器上时,有如下错误Could not get a resource from the Cluster

一般当客户端与集群服务器在同一台服务器上时,操作Redis Cluster正常; 当二者不在同一台服务器上时报如上错误,可能是clusterTimeOut时间设置过小;

(2)操作Redis时报Too many cluster redirections

初始化JedisCluster时,设定JedisCluster的maxRedirections.

JedisCluster(Set jedisClusterNode, int timeout, int maxRedirections) ;
JedisCluster jc = new JedisCluster(jedisClusterNodes,5000,1000);

请参考:https://gitHub.com/xetorthio/jedis/issues/659

(3)Redis Cluster数据写入慢

检查在通过./redis-trib命令建立集群时,如果是通过127.0.0.1的方式建立的集群,那么在往Redis Cluster中写入数据时写入速度比较慢。可以通过配置真实的IP来规避此问题。