1. 程式人生 > >《從零開始搭建遊戲伺服器》優化——Jedis連線池

《從零開始搭建遊戲伺服器》優化——Jedis連線池

前言

在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");