《从零开始搭建游戏服务器》优化——Jedis连接池

发表于2017-09-28
评论0 3.3k浏览

设计和实现:

1.引入Redis连接时的一些必要的参数:

例如IP、端口(PORT)以及权限验证密码auth等,可以通过写死在代码里,但为了后期修改方便,通常是配置在一个配置文件中,然后再运行时从配置文件中读取,这样就算需要在多处调用这些配置信息,修改时也只需修改配置文件即可。 
在当前工程的src目录下新建一个配置文件,取名为Redis.properties,其内容如下:

 
PORT = 6739
SERVER_ARRAY = 127.0.0.1,196.168.201.1
AUTH = 123456
TIMEOUT = 1000
MAX_ACTIVE = 8
MAX_IDLE = 8
MAX_WAIT = 2000
TEST_ON_BORROW = true

通过通过类加载目录getClassLoader()加载属性文件,这里我们也写一个文件工具类FileUtil,内容如下:

 
package com.tw.login.tools;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/***
 * 文件操作工具类
 * @author linsh
 *
 */
public class FileUtil {
    /**
     * 读取整数配置数据
     * @param path
     * @param name
     * @return
     */
    public static int getPropertyValueInt(String path,String name) {
        int result = -1;
        result = Integer.valueOf(getPropertyValueString(path,name));
        return result;
    }
    /**
     * 读取字符型配置数据
     * @param path
     * @param name
     * @return
     */
    public static String getPropertyValueString(String path,String name) {
        String result = "";
        // 方法二:通过类加载目录getClassLoader()加载属性文件  
        InputStream in = FileUtil.class.getClassLoader()  
                .getResourceAsStream(path);  
        Properties prop = new Properties();  
        try {  
            prop.load(in);  
            result = prop.getProperty(name).trim();  
            System.out.println(name  ":"   result);  
        } catch (IOException e) {  
            System.out.println("读取配置文件出错");  
            e.printStackTrace();  
        } 
        return result;
    }
    /**
     * 读取布尔型配置数据
     * @param path
     * @param name
     * @return
     */
    public static boolean getPropertyValueBool(String path,String name) {
        boolean result = false;
        result = Boolean.parseBoolean(getPropertyValueString(path,name));
        return result;
    }
}

调用方法:

 
FileUtil.getPropertyValueInt("Redis.properties", "PORT")

2.创建Jedis连接池:

除了之前使用的jedis.jar包之外,我们还需要引入一个通用的池管理commons-pool.jar,记得要下载1.x的版本。

注意: 
Java端在使用jedispool连接redis的时候,在高并发的时候经常卡死,或报连接异常,JedisConnectionException或者getResource异常等各种问题。

在使用Jedispool的时候一定要注意两点:

  • 在获取 jedisPool和jedis的时候加上线程同步,保证不要创建过多的jedispool和jedis;
  • 用完Jedis实例后需要返还给JedisPool;

JedisPool线程池管理类源码:

 
package com.tw.login.redis;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.tw.login.tools.FileUtil;
import io.netty.util.internal.StringUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/***
 * Redis数据库的连接池操作类
 * @author linsh
 *
 */
public class RedisUtil {
    //日志控制器
    protected static Log logger = LogFactory.getLog(RedisUtil.class);
    /**从配置文件中读取配置信息**/
    //Redis服务器端口号
    private static int Port = FileUtil.getPropertyValueInt("Redis.properties", "PORT");
    //Redis服务器端Ip地址组(多个redis数据中心的Ip,备用)
    private static String Server_address_array = FileUtil.getPropertyValueString("Redis.properties", "SERVER_ARRAY");
    //Redis服务器端访问密码
    private static String Auth = FileUtil.getPropertyValueString("Redis.properties", "AUTH");
    //Redis连接超时时长(单位:毫秒)
    private static int TimeOut = FileUtil.getPropertyValueInt("Redis.properties", "TIMEOUT");
    //可用连接实例的最大数目,默认值为8,如果赋值为-1,则表示不限制;如果pool已经分配了MAX_ACTIVE个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = FileUtil.getPropertyValueInt("Redis.properties", "MAX_ACTIVE");
    //一个Pool最多有多少个状态为idle(空闲)的jedis实例,默认值为8个
    private static int MAX_IDLE = FileUtil.getPropertyValueInt("Redis.properties", "MAX_IDLE");
    //待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException异常
    private static int MAX_WAIT = FileUtil.getPropertyValueInt("Redis.properties", "MAX_WAIT");
    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的
    private static boolean TEST_ON_BORROW = FileUtil.getPropertyValueBool("Redis.properties", "TEST_ON_BORROW");
    /**
     * redis数据有效时长
     */
    public final static int EXRP_HOUR = 60*60;          //1小时
    public final static int EXRP_DAY = 60*60*24;        //1天
    public final static int EXRP_MOUTH = 60*60*24*30;   //1个月
    //Jedis连接池
    private static JedisPool jedisPool = null;
    /**
     * 初始化连接池
     */
    private static void InitPoolConfig() {
        //假如第一个ip的redis访问异常,则选用第二个
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxActive(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWait((long)MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, Server_address_array.split(",")[0],Port,TimeOut);
        } catch (Exception e) {
            logger.error("first redis server fail:" e);
            try {
                JedisPoolConfig config = new JedisPoolConfig();
                config.setMaxActive(MAX_ACTIVE);
                config.setMaxIdle(MAX_IDLE);
                config.setMaxWait((long)MAX_WAIT);
                config.setTestOnBorrow(TEST_ON_BORROW);
                jedisPool = new JedisPool(config, Server_address_array.split(",")[1],Port,TimeOut);
            } catch (Exception e2) {
                logger.error("add redis servers fail:" e);
            }
        }
    }
    /**
     * 在多线程环境下,避免重复初始化
     */
    private static synchronized void PoolInit() {
        if(jedisPool == null){
            InitPoolConfig();
        }
    }
    /**
     * 同步获取Jedis实例
     * @return
     */
    public synchronized static Jedis getJedis() {
        if(jedisPool == null){
            InitPoolConfig();
        }
        Jedis jedis = null;
        try {
            if(jedisPool!=null){
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            logger.error("Get redis fail:" e);
        }finally {
            returnResource(jedis);
        }
        return jedis;
    }
    /**
     * 释放Jedis资源
     * @param jedis
     */
    private static void returnResource(Jedis jedis) {
        if(jedis!=null&&jedisPool!=null){
            jedisPool.returnBrokenResource(jedis);
        }
    }
    /**
     * 设置 String
     * @param key
     * @param value
     */
    public static void setString(String key ,String value){
        try {
            value = StringUtil.isNullOrEmpty(value) ? "" : value;
            getJedis().set(key,value);
        } catch (Exception e) {
            logger.error("Set key error : " e);
        }
    }
    /**
     * 设置 过期时间
     * @param key
     * @param seconds 以秒为单位
     * @param value
     */
    public static void setString(String key ,int seconds,String value){
        try {
            value = StringUtil.isNullOrEmpty(value) ? "" : value;
            getJedis().setex(key, seconds, value);
        } catch (Exception e) {
            logger.error("Set keyex error : " e);
        }
    }
    /**
     * 获取String值
     * @param key
     * @return value
     */
    public static String getString(String key){
        if(getJedis() == null || !getJedis().exists(key)){
            return null;
        }
        return getJedis().get(key);
    }
}

3.使用案例:

直接将之前使用单独创建Jedis实例进行Redis操作的代码:

 
    //连接redis服务器,127.0.0.1:6379
    Jedis jedis = new Jedis("127.0.0.1",6379);
    //权限认证
    jedis.auth("123456");
    jedis.set("IdNum", "123456");

替换为:

 
   Jedis jedis = RedisUtil.getJedis();
    jedis.set("IdNum", "123456");

或者替换为RedisUtil中封装的静态方法:

 
  RedisUtil.setString("IdNum", "123456");

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引