JFinal

登录 注册
JFinal 已连续多年蝉联最受欢迎中国开源软件冠军,投出您宝贵的一票今年继续夺冠! 评选传送门

jFinal Redis插件,支持单机和集群模式

jFinal自带的redis插件只支持单机模式,不支持集群,我写了个支持集群模式的插件

这个插件不但支持集群模式,还修复了集群模式下,没有keys、flushDB、dbSize等方法的调用。
注意:这个插件依赖阿里的fastjson。


集群模式与单机模式,完全不同的东西,单机是先创建JedisPool,然后调用里面getResource()方法来获得Jedis对象,然后使用Jedis来操作redis。

集群使用的是JedisCluster,他本身就实现了pool功能,所以不需要自己关闭连接,但是创建的方式与单机完全不一样,他是需要构造

Set<HostAndPort> jedisClusterNodes

来告诉redis使用了哪些服务器,而且集群模式是不支持密码的。


首先定义插件类,用于初始化集群模式的JedisCluster对象

package test.sunyu.tools.redis;
import com.alibaba.fastjson.JSON;
import com.jfinal.plugin.IPlugin;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * jFinal的redis插件不支持redis集群模式,我自己扩展一个吧
 *
 * @author 孙宇
 */
public class RedisClusterPlugin implements IPlugin {
    private Integer maxTotal = 1000;
    private Integer maxIdle = 200;
    private Long maxWaitMillis = 2000L;
    private Boolean testOnBorrow = true;
    private String cluster;
    private String defaultName = "main";
    public RedisClusterPlugin(String cluster) {
        this.cluster = cluster;
    }
    @Override
    public boolean start() {
        if (cluster != null) {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(maxTotal);
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setTestOnBorrow(testOnBorrow);
            Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
            List<Map> clusterList = JSON.parseArray(cluster, Map.class);
            if (clusterList != null && clusterList.size() > 0) {
                for (Map<String, String> c : clusterList) {
                    jedisClusterNodes.add(new HostAndPort(c.get("host"), Integer.parseInt(c.get("port"))));
                }
                JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, jedisPoolConfig);
                RedisClusterTools.addJedisCluster(defaultName, jedisCluster);
            }
            return true;
        }
        return false;
    }
    @Override
    public boolean stop() {
        return true;
    }
    public Integer getMaxTotal() {
        return maxTotal;
    }
    public void setMaxTotal(Integer maxTotal) {
        this.maxTotal = maxTotal;
    }
    public Integer getMaxIdle() {
        return maxIdle;
    }
    public void setMaxIdle(Integer maxIdle) {
        this.maxIdle = maxIdle;
    }
    public Long getMaxWaitMillis() {
        return maxWaitMillis;
    }
    public void setMaxWaitMillis(Long maxWaitMillis) {
        this.maxWaitMillis = maxWaitMillis;
    }
    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }
    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }
    public String getCluster() {
        return cluster;
    }
    public void setCluster(String cluster) {
        this.cluster = cluster;
    }
    public String getDefaultName() {
        return defaultName;
    }
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }
}


然后编写集群模式的工具类,其实这里我也可以将所有的方法写在这个类中,比如get、put等等,就像jFinal的Cache那样,但是我嫌麻烦,所以就没那样写,只写了keys、flushDB、dbSize这三个方法的直接调用。

package test.sunyu.tools.redis;
import org.nutz.lang.Lang;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import sunyu.tools.redis.JedisClusterCallback;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
 * redis集群工具类
 *
 * @author 孙宇
 */
public class RedisClusterTools {
    private static String defaultName = "main";
    private static final ConcurrentHashMap<String, JedisCluster> jedisClusterMap = new ConcurrentHashMap<>();
    public static void addJedisCluster(String jedisClusterName,
                                       JedisCluster jedisCluster) {
        if (jedisCluster != null && jedisClusterName != null && !jedisClusterMap.containsKey(jedisClusterName)) {
            jedisClusterMap.put(jedisClusterName, jedisCluster);
        }
    }
    /**
     * 获得一个JedisCluster
     *
     * @param jedisClusterName
     *
     * @return
     */
    public static JedisCluster getJedisCluster(String jedisClusterName) {
        return jedisClusterMap.get(jedisClusterName);
    }
    /**
     * 执行集群指令
     *
     * @param jedisClusterName
     * @param action
     * @param <T>
     *
     * @return
     */
    public static <T> T clusterExecute(String jedisClusterName,
                                       JedisClusterCallback<T> action) {
        JedisCluster jedis = getJedisCluster(jedisClusterName);
        try {
            return action.doInJedisCluster(jedis);
        } catch (Throwable throwable) {
            throw Lang.wrapThrow(throwable);
        }
    }
    /**
     * 执行集群指令
     *
     * @param action
     * @param <T>
     *
     * @return
     */
    public static <T> T clusterExecute(JedisClusterCallback<T> action) {
        JedisCluster jedis = getJedisCluster(defaultName);
        try {
            return action.doInJedisCluster(jedis);
        } catch (Throwable throwable) {
            throw Lang.wrapThrow(throwable);
        }
    }
    /**
     * 由于JedisCluster没有实现keys操作,这里自己实现以下
     *
     * @param pattern
     *
     * @return
     */
    public static Set<String> clusterKeys(String jedisClusterName,
                                          String pattern) {
        Set<String> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        return clusterKeys(pattern, keys, clusterNodes);
    }
    private static Set<String> clusterKeys(String pattern,
                                           Set<String> keys,
                                           Map<String, JedisPool> clusterNodes) {
        for (String k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            Jedis connection = jp.getResource();
            try {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
        }
        return keys;
    }
    /**
     * 由于JedisCluster没有实现keys操作,这里自己实现以下
     *
     * @param pattern
     *
     * @return
     */
    public static Set<String> clusterKeys(String pattern) {
        Set<String> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(defaultName).getClusterNodes();
        return clusterKeys(pattern, keys, clusterNodes);
    }
    public static Set<byte[]> clusterKeys(String jedisClusterName,
                                          byte[] pattern) {
        Set<byte[]> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        return clusterKeys(pattern, keys, clusterNodes);
    }
    private static Set<byte[]> clusterKeys(byte[] pattern,
                                           Set<byte[]> keys,
                                           Map<String, JedisPool> clusterNodes) {
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            Jedis connection = jp.getResource();
            try {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
        }
        return keys;
    }
    public static Set<byte[]> clusterKeys(byte[] pattern) {
        Set<byte[]> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(defaultName).getClusterNodes();
        return clusterKeys(pattern, keys, clusterNodes);
    }
    public static void clusterFlushDB(String jedisClusterName) {
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        clusterFlushDb(clusterNodes);
    }
    private static void clusterFlushDb(Map<String, JedisPool> clusterNodes) {
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            Jedis connection = jp.getResource();
            try {
                connection.flushDB();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
        }
    }
    public static void clusterFlushDB() {
        Map<String, JedisPool> clusterNodes = getJedisCluster(defaultName).getClusterNodes();
        clusterFlushDb(clusterNodes);
    }
    public static Long clusterDbSize(String jedisClusterName) {
        Long total = 0L;
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        return clusterDbSize(total, clusterNodes);
    }
    private static Long clusterDbSize(Long total,
                                      Map<String, JedisPool> clusterNodes) {
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            Jedis connection = jp.getResource();
            try {
                total += connection.dbSize();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
        }
        return total;
    }
    public static Long clusterDbSize() {
        Long total = 0L;
        Map<String, JedisPool> clusterNodes = getJedisCluster(defaultName).getClusterNodes();
        return clusterDbSize(total, clusterNodes);
    }
}



因为上面工具类没有将所有jedis集群模式的方法全都写出来,所以我使用了一个回调,将方法的调用交给了程序员,偷懒的做法。

package sunyu.tools.redis;
import redis.clients.jedis.JedisCluster;
/**
 * 懒得写所有方法,回调一下得了
 *
 * @author 孙宇
 */
public interface JedisClusterCallback<T> {
    T doInJedisCluster(JedisCluster jedis) throws Throwable;
}



测试类,能直接调用的只有keys、flushDB、dbSize这三个方法。。如果想调用get/put/hget等等。。。。请传递回调方法,然后自己在里面实现,当然,集群模式是不需要自己close链接的。并且集群模式内部实现了连接池,也不用用户去管理了。


集群模式需要知道所有的服务器节点,所以为了初始化方便,我使用了json格式

String clusterJson = "[{host:'192.168.11.81',port:'7001'},{host:'192.168.11.81',port:'7002'},{host:'192.168.11.81',port:'7003'},{host:'192.168.11.81',port:'7007'},{host:'192.168.11.81',port:'7008'},{host:'192.168.11.82',port:'7004'},{host:'192.168.11.82',port:'7005'},{host:'192.168.11.82',port:'7006'},{host:'192.168.11.82',port:'7009'},{host:'192.168.11.82',port:'7010'}]";

就是个集合,每一项都是一个节点,必须有host和port属性


package test.sunyu.tools.redis;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import redis.clients.jedis.JedisCluster;
import sunyu.tools.redis.JedisClusterCallback;
import java.util.Set;
/**
 * @author 孙宇
 */
public class TestRedisClusterTools {
    @Test    public void t1() {
        String clusterJson = "[{host:'192.168.11.81',port:'7001'},{host:'192.168.11.81',port:'7002'},{host:'192.168.11.81',port:'7003'},{host:'192.168.11.81',port:'7007'},{host:'192.168.11.81',port:'7008'},{host:'192.168.11.82',port:'7004'},{host:'192.168.11.82',port:'7005'},{host:'192.168.11.82',port:'7006'},{host:'192.168.11.82',port:'7009'},{host:'192.168.11.82',port:'7010'}]";
        RedisClusterPlugin p = new RedisClusterPlugin(clusterJson);
        p.start();
        //集群模式本身是没有keys命令的,这里我自己实现了
        Set<String> allKeys = RedisClusterTools.clusterKeys("*");
        System.out.println(JSON.toJSONString(allKeys));
        //大小
        //RedisClusterTools.clusterDbSize();
        //删除
        //RedisClusterTools.clusterFlushDB();
        //其他操作,其他操作全都是回调方式,因为我懒得写里面所有方法了,只要是jedis支持的方法,集群模式这个工具都支持
        RedisClusterTools.clusterExecute(new JedisClusterCallback<Object>() {
            @Override
            public Object doInJedisCluster(JedisCluster jedis) throws Throwable {
                //jedis.get("");
                //jedis.set("", "");
                //jedis.del("");
                //jedis.hget("", "");
                //....不写了,自己看文档吧
                return null;
            }
        });
    }
}







评论

  • 09-09 14:58
    能否兼容 jfinal 自带的 redis 插件,能给出更详细的使用例子不? 我看到上面的例子是在 callback 中使用的,这样的用法代码比较多,有没有更简单用法?
  • 09-09 15:25
    集群的搭建可以参考
    http://blog.csdn.net/myrainblues/article/details/25881535/
  • 09-09 16:34
    好东西,@JFinal 官方支持下吧... 同时建议出一个官方的、类红薯的j2cache的ehcache+redis 插件。

    ehcache 和 redis 在项目中,实在是太常用了...
  • 09-09 16:38
    @海哥 redis 集群支持,很久以前就备忘了,肯定会做进去,但不一定是 jfinal 2.3去做,现在 jfinal 2.3 的待开发功能已经非常非常之多了
  • 09-10 15:59
    集群模式需要知道所有的服务器节点,所以为了初始化方便,我使用了json格式
    --其实并不需要知道所有的服务器节点,只需要一个就好了。集群会自己发现其他节点然后自己存到set里面。
    当然如果你希望启动肯定成功,哪怕单独这个被选作连入的节点挂了也有其他选择的话,那配多几个也可以。反正最终取东西他都是自动发现所有节点然后重定向的
  • 09-10 17:03
    @JFinal jfinal现在是几个人开发呢?
  • 09-10 17:07
    @玄伶 基本上是一人开发,有少数用户直接提交过代码,jfinal 是极简设计,代码才一万行左右,目前不需要多少人
  • 09-10 17:13
    @JFinal 强!我记得最开始是大一还是大二时听说过jfinal,后来学ssh/ssm被搞晕了,于是我想起了jfinal,就试用了,感觉非常好,不需要什么配置。后来毕业设计服务端用的jfinal,省了我很多时间,感谢jfinal,感谢波总。国产的好东西不多,jfinal是其中一个,波总加油,如果可以的话,必要的时候我觉得还是可以多借助社区力量,这样jfinal才会发展更快。
  • 09-10 17:18
    @玄伶 社区还有一部分核心功能上线以后,就会全力丰富社区内容,让大家可以获取到很多对开发有价值的经验、源码等资源
    也会依靠广大用户都去分享自己的资源,形成一个正能力、高品质的极速开发社区,进一步提升开发效率、代码质量以及开发体验
  • 09-10 17:19
    @玄伶 多多关注社区动态,今天社区要更换新服务器,偶尔会有停服的情况
  • 发送