原创

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://linsh-tech.blog.csdn.net/article/details/62420805

前言

在Java的开发中,遇到一些耗时的操作,我们通常会启动一个线程,让新建的线程来完成这个耗时操作而不至于影响主线程工作的正常进行,而当需要同时进行多个耗时操作的时候,就要相应地为其创建多个线程,但是这样显然会造成线程的浪费,所以我们采用了一个优化方案,那就是线程池,我们在需要获取线程实例时不直接进行new创建,而是从线程池ThreadPool中获取。
相同的思想在操作数据库的时候一样是适用的,这里假设我们每次要进行Redis的读写操作时,都创建一个Jedis实例,那是相当浪费的,所以我们适用Jedis连接池来管理多个Jedis,并在需要获取Jedis对象的地方通过从Jedis连接池中获取而非直接new Jedis()

设计和实现:

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");
文章最后发布于: 2017-03-16 14:24:25
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览