1. 程式人生 > >Redis單機搭建主從(1)

Redis單機搭建主從(1)

Redis單機主從切換部署說明

準備工作

  • redis.io下載部署包 eg:redis-3.2.8.tar.gz

  • 新建主從目錄

mkdir -p /usr/local/redis/master/
mkdir -p /usr/local/redis/slave/
  • 分別在兩個目錄下面安裝redis
tar –zxf redis-3.2.8.tar.gz
cd redis-3.2.8
make **#編譯**
make test    **#期望全部測試通過**
  • ==可能出現問題:缺少gcc或者tcl元件,使用命令yum install gcc或者 yum install tcl==

修改配置引數

修改master_redis配置

cd /usr/local/redis/master/redis-3.2.8/
vi redis.conf
bind 127.0.0.1  ----> bind 本機IP(繫結地址)
daemonize no   ----> daemonize yes(不影響當前會話,啟動過程隱藏,守護程序)
protected-mode yes ---> protected-mode no(關閉保護模式,其他伺服器可訪問)

修改slave_redis配置

cd /usr/local/redis/slave/redis-3.2.8/
vi redis.conf
bind 127.0
.0.1 ----> bind 本機IP(繫結地址) daemonize no ----> daemonize yes(不影響當前會話,啟動過程隱藏,守護程序) protected-mode yes ---> protected-mode no(關閉保護模式,其他伺服器可訪問) port 6379 ---> port 6380(修改埠) slaveof master_redis所在機器IP 6379 pidfile /var/run/redis_ 6379.pid ----> pidfile /var/run/redis_ 6380.pid

redis以守護程序方式執行時,系統預設會把pid寫入/var/run/redis.pid,可以通過pidfile指 定pid檔案

啟動master_ redis和slave_ redis並使用客戶端連線

cd /usr/local/redis/master/redis-3.2.8/src/
./redis-server  /usr/local/redis/master/redis-3.2.8/redis.conf(載入配置檔案)
./redis-cli -h IP -p 6379 (客戶端連線master_redis)
cd /usr/local/redis/slave/redis-3.2.8/src/
./redis-server  /usr/local/redis/slave/redis-3.2.8/redis.conf`(載入配置檔案)
./redis-cli -h IP -p 6380` (客戶端連線slave_redis)

使用 info 命令檢視redis主從資訊

  • master_redis客戶端連線下執行:
172.20.1.47:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.20.1.47,port=6380,state=online,offset=38335325,lag=0
master_repl_offset:38335461
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:37286886
repl_backlog_histlen:1048576
  • slave_redis客戶端連線下執行:
172.20.1.47:6380> info replication
# Replicatio
role:slave
master_host:172.20.1.47
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:38343419
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

測試主從複製,讀寫分離

  • master_redis客戶端連線下執行:set name zhangsan
  • master_redis客戶端連線下執行:get name 結果: zhangsan
  • slave_redis客戶端連線下執行:get name 結果: zhangsan
  • slave_redis客戶端連線下執行:set name lisi (error)READONLY You can`t write against a read only slave.(slave_redis只讀)

配置主從切換

準備sentinel.conf配置檔案

#守護程序,隱藏啟動,不影響當前session
daemonize yes  

#關閉保護模式,類似於防火牆的功能
protected-mode no   

#sentinel 埠預設26379
port  

#哨兵監控的主redis 的IP 和埠,會自動監控到slave
#sentinel monitor <master-name> <ip> <redis-port> <quorum>
#告訴sentinel去監聽地址為ip:port的一個master,quorum是一個數字,指明當有多少個sentinel認為一個master失效時,master才算真正失效         
sentinel monitor master1 IP 6379 1

#master被當前sentinel例項認定為“失效”的間隔時間.
#sentinel down-after-milliseconds <mastername> <millseconds>  
#如果當前sentinel與master直接的通訊中(不斷髮送程式包,並接受響應),在指定時間內沒有響應或者響應錯誤程式碼,那麼當前sentinel就認為master失效  
sentinel down-after-milliseconds master1 5000

#當failover(故障轉移)開始後,在此時間內仍然沒有觸發任何failover操作,當前sentinel將會認為此次failover失敗
sentinel failover-timeout master1 15000

#當新master產生時,可以同時進行slaveof到新master並進行“SYNC”(同步)的slave個數。(建議使用預設值1)
#在salve執行salveof與同步時,將會終止客戶端請求。此值較大,意味著“叢集”終止客戶端請求的時間總和和較大.
#此值較小,意味著“叢集”在故障轉移期間,多個salve向客戶端提供服務時仍然使用舊資料.
sentinel parallel-syncs master1 1

配置主從切換

  • kill掉當前所有redis程序
ps -ef | grep redis
kill -9 pid
rm -f /usr/local/redis/master/redis-3.2.8/sentinel.conf 
rm -f /usr/local/redis/slave/redis-3.2.8/sentinel.conf 
  • 將準備好的sentinel.conf分別放置於對應目錄下面(替換剛剛刪除的兩個conf檔案)
cd /usr/local/redis/slave/redis-3.2.8/ 
vi sentinel.conf  修改 port 26379 ---> port 26380
  • 重新啟動主從redis
cd /usr/local/redis/master/redis-3.2.8/src/ 
./redis-server /usr/local/redis/master/redis-3.2.8/redis.conf 
cd /usr/local/redis/slave/redis-3.2.8/src/ 
./redis-server /usr/local/redis/slave/redis-3.2.8/redis.conf 
  • 啟動主從redis的sentinel(哨兵)
cd /usr/local/redis/master/redis-3.2.8/src/ 
./redis-sentinel /usr/local/redis/master/redis-3.2.8/sentinel.conf
cd /usr/local/redis/slave/redis-3.2.8/src/
./redis-sentinel /usr/local/redis/slave/redis-3.2.8/sentienl.conf
ps -ef | grep redis  #此時應該有四個程序(redis主從 + 兩個哨兵)

使用客戶端檢視哨兵監控情況

  • 使用客戶端連線兩個sentinel
cd /usr/local/redis/master/redis-3.2.8/src/
./redis-cli -h IP 26379
cd /usr/local/redis/slave/redis-3.2.8/src/
./redis-cli -h IP 26380
使用 `info sentinel `檢視稍定監控詳情,顯示name=master1,status=ok,address=IP:6379(兩個哨兵共同監控master_redis)

測試主從自動切換(具體操作參考上面命令)

  • kill 掉master_redis服務
  • 然後使用客戶端連線slave_redis
  • 使用info replication 檢視slave_ redis連線資訊,會發現,slave_ redis已經升級為master_ redis
  • 再使用客戶端重新連線sentinel,使用info sentinel命令檢視兩個哨兵監控資訊,會發現監控地址變成了address=IP:6380
  • 重新啟動kill 掉的master_ redis服務,啟動後客戶端連線,使用info replication命令檢視,會發現role:slave (重新啟動後自動變成slave_ redis)

Java程式碼實現redis主從

public class JedisUtil {

     private final static String REDIS_HOST = "172.20.1.47";
     private final static Integer REDIS_PORT = 6379;
     private final static Integer REDIS_MaxActive = 200;
     private final static Integer REDIS_MaxIdle = 1000;
     private final static Integer REDIS_MaxWait = 512;
     private final static Integer REDIS_ConnTimeout = 2000;
     private final static Integer REDIS_RetryNum = 3;
     private final static String SENTINEL_HOST_1 = "172.20.1.47:26381";
     private final static String SENTINEL_HOST_2 = "172.20.1.47:26380";
     private final static String CLUSTER_NAME = "master1";

    /**
     * 私有構造器.
     */
    private JedisUtil() {

    }

    private static Map<String, JedisSentinelPool> maps = new HashMap<String, JedisSentinelPool>();

    /**
     * 獲取連線池.
     * 
     * @return 連線池例項
     */
    private static JedisSentinelPool getPool() {
        String key = REDIS_HOST + ":" + REDIS_PORT;
        Set<String> sentinels = new HashSet<String>();
        String hostAndPort1 = SENTINEL_HOST_1;
        String hostAndPort2 = SENTINEL_HOST_2;
        sentinels.add(hostAndPort1);
        sentinels.add(hostAndPort2);
        String clusterName = CLUSTER_NAME;

        JedisSentinelPool redisSentinelJedisPool = null;
        if (!maps.containsKey(key)) {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(REDIS_MaxActive);
            config.setMaxIdle(REDIS_MaxIdle);
            config.setMaxWaitMillis(REDIS_MaxWait);
            config.setTestOnBorrow(true);
            config.setTestOnReturn(true);
            try {
                /**
                 * 如果你遇到 java.net.SocketTimeoutException: Read timed out exception的異常資訊 請嘗試在構造JedisPool的時候設定自己的超時值. JedisPool預設的超時時間是2秒(單位毫秒)
                 */
                redisSentinelJedisPool = new JedisSentinelPool(clusterName, sentinels, config, REDIS_ConnTimeout);

                maps.put(key, redisSentinelJedisPool);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            redisSentinelJedisPool = maps.get(key);
        }
        return redisSentinelJedisPool;
    }

    /**
     * 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項 沒有繫結關係,而且只有被呼叫到時才會裝載,從而實現了延遲載入。
     */
    private static class RedisUtilHolder {
        /**
         * 靜態初始化器,由JVM來保證執行緒安全
         */
        private static JedisUtil instance = new JedisUtil();
    }

    /**
     * 當getInstance方法第一次被呼叫的時候,它第一次讀取 RedisUtilHolder.instance,導致RedisUtilHolder類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜
     * 態域,從而建立RedisUtil的例項,由於是靜態的域,因此只會在虛擬機器裝載類的時候初始化一次,並由虛擬機器來保證它的執行緒安全性。 這個模式的優勢在於,getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本。
     */
    public static JedisUtil getInstance() {
        return RedisUtilHolder.instance;
    }

    /**
     * 獲取Redis例項.
     * 
     * @return Redis工具類例項
     */
    public Jedis getJedis() {
        Jedis jedis = null;
        int count = 0;
        do {
            try {
                jedis = getPool().getResource();
            } catch (Exception e) {
                e.printStackTrace();
                // 銷燬物件
                if (jedis != null) {

                    jedis.close();
                }
            }
            count++;
        } while (jedis == null && count < REDIS_RetryNum);
        return jedis;
    }

    /**
     * 釋放redis例項到連線池.
     * 
     * @param jedis redis例項
     */
    public void closeJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

}