1. 程式人生 > >在windows下的Jeesite框架下整合Redis叢集

在windows下的Jeesite框架下整合Redis叢集

最近想使用Redis叢集做快取,由於公司一直在用Jeesite框架,所以嘗試了一下兩者進行整合。

Jeesite原本就支援EhCache和Redis兩種方式做資料快取,但並沒有做Redis叢集。

本以為簡單的配置就能實現,沒想到調查了整整一天,所以覺得有必要把整個配置過程和踩過的坑記錄下來,方便大家使用。

一. Redis安裝與配置:

2. 解壓後可以在redis.windows.conf中修改配置,預設埠是6379


3. 在cmd中執行以下命令啟動Redis,我這裡的埠是6378

redis-server.exe redis.windows.conf

4. 另外開一個cmd,輸入以下命令啟動Redis客戶端:

redis-cli.exe -h 127.0.0.1 -p 6378

6. 當Redis伺服器會定期持久化到硬碟,這些都可以在redis.windows.conf裡面配置。

預設儲存在當前Redis目錄下,檔名是dump.rdb



二. Redis叢集的配置與啟動:

我這裡啟動6個Redis做3主3從的叢集。

1. 先將Redis複製6份


修改各自的redis.windows.conf中下列屬性,並啟動各個Redis服務:

port 6379(6379-6384)
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
appendonly yes

2. win64版的Redis包下面由於沒有redis-trib.rb,所以無法啟動叢集,需要去Redis官網下載Linux版的包後,在src資料夾下找到這個檔案。

安裝並執行下面的命令進行配置


3. 將redis-trib.rb拷到RailsInstaller下面啟動叢集

ruby redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384

--replicas 1 :表示在1個主節點後建立1個從節點

我在測試中遇到下面這個錯誤:


這是因為我已經啟動了6380伺服器並且手動給它分配過節點的關係,假如遇到這種情況就把Redis服務停止,刪除Redis目錄下面的appendonly.aof,dump.rdb,nodes.conf,重啟即可。

配置正確的情況下,會提示你叢集資訊,確認無誤輸入yes,redis-trib就會開始啟動叢集了。



這時候去各個Redis服務下面,可以看到畫面不停的有訊息在滾動,表明Redis之間開始通訊了:


4. 測試叢集

在啟動redis-cli.exe的時候,記得加上-c,表示以叢集模式啟動。

redis-cli.exe -c -h 127.0.0.1 -p 6379

輸入多個key-value,可以看到叢集會自動將他們分配到不同的Redis伺服器:


從各個伺服器上可以看到分別儲存了不同的key:


5. 用命令檢視和控制叢集:

(1). 顯示叢集狀態:

cluster info

(2). 顯示叢集中所有的節點資訊:

cluster nodes

(3). 手動在叢集中增加一個節點:

cluster meet 127.0.0.1 6378

(4). 手動刪除一個節點:

cluster forget nodeid(就是用cluster nodes命令看到的前面那串數字+字母的ID)

三. Jeesite中的配置修改:

我的jeesite版本是v1.2,需要修改的檔案如下:

1. spring-context-shiro.xml中,將EhCache相關配置改成Jedis:

	<!-- 自定義Session儲存容器 -->
	<bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.JedisSessionDAO">
		<property name="sessionIdGenerator" ref="idGen" />
		<property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" />
	</bean>
<!-- 	<bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.CacheSessionDAO">
		<property name="sessionIdGenerator" ref="idGen" />
		<property name="activeSessionsCacheName" value="activeSessionsCache" />
		<property name="cacheManager" ref="shiroCacheManager" />
	</bean> -->
	
	<!-- 自定義系統快取管理器-->
	<bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.JedisCacheManager">
		<property name="cacheKeyPrefix" value="${redis.keyPrefix}_cache_" />
	</bean>
<!-- 	<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManager" ref="cacheManager"/>
	</bean> -->

2. spring-context-jedis.xml中增加叢集配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
		http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.1.xsd"
	default-lazy-init="true">

	<description>Jedis Configuration</description>

    <!-- 載入配置屬性檔案 -->
	<context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" />
	
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
	    <!-- 最大連線數 -->  
	    <property name="maxTotal" value="60000" />  
	    <!-- 最大空閒連線數 -->  
	    <property name="maxIdle" value="300" />  
	    <!-- 每次釋放連線的最大數目 -->  
	    <property name="numTestsPerEvictionRun" value="1024" />  
	    <!-- 釋放連線的掃描間隔(毫秒) -->  
	    <property name="timeBetweenEvictionRunsMillis" value="30000" />  
	    <!-- 連線最小空閒時間 -->  
	    <property name="minEvictableIdleTimeMillis" value="1800000" />  
	    <!-- 連線空閒多久後釋放, 當空閒時間>該值 且 空閒連線>最大空閒連線數 時直接釋放 -->  
	    <property name="softMinEvictableIdleTimeMillis" value="10000" />  
	    <!-- 獲取連線時的最大等待毫秒數,小於零:阻塞不確定的時間,預設-1 -->  
	    <property name="maxWaitMillis" value="1500" />  
	    <!-- 在獲取連線的時候檢查有效性, 預設false -->  
	    <property name="testOnBorrow" value="true" />  
	    <!-- 在空閒時檢查有效性, 預設false -->  
	    <property name="testWhileIdle" value="true" />  
	    <!-- 連線耗盡時是否阻塞, false報異常,ture阻塞直到超時, 預設true -->  
	    <property name="blockWhenExhausted" value="false" />  
	</bean>
	
	<!-- jedis單機版配置 -->
	<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
		<constructor-arg index="0" ref="jedisPoolConfig" />
		<constructor-arg index="1" value="${redis.host}" />
		<constructor-arg index="2" value="${redis.port}" type="int" />
		<constructor-arg index="3" value="${redis.timeout}" type="int" />
		<constructor-arg index="4" value="${redis.password}"/>
		<constructor-arg index="5" value="${redis.database}" type="int" />
		<constructor-arg index="6" value="${redis.clientName}"/>
	</bean>
	
	<!-- jedis叢集版配置 -->
    <bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
        <constructor-arg name="nodes" index="0">
            <set>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6379"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6380"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6381"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6382"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6383"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" index="0" value="127.0.0.1"></constructor-arg>
                    <constructor-arg name="port" index="1" value="6384"></constructor-arg>
                </bean>
            </set>
        </constructor-arg>
        <!-- <constructor-arg index="1" name="poolConfig" ref="jedisPoolConfig"></constructor-arg> -->
    </bean>
</beans>

3. 順帶一提,單機版的Redis只要改jeesite.properties即可:

#redis settings
redis.keyPrefix=jeesite
redis.host=127.0.0.1
redis.port=6379

四. Jeesite中的程式碼修改:

由於jeesite中自帶了Redis單機版,所以我這裡是在單機版的基礎上修改程式碼。

其實主要是因為Jedis裡面,單機版的程式碼和叢集版的是兩套東西

單機版的寫法:

Jedis jedis = jedisPool.getResource();
String value = jedis.get(key);

叢集版的寫法:

String value = jedisCluster.get(key); // 不需要先getResource

另外,我用的jeesite自帶的jedis的版本是2.5.0,需要在pom.xml裡面,將版本改為2.9.0,否則有些方法在叢集中沒有。

1. 增加JedisClusterUtils.java,與JedisUtils.java放在一起:

/**
 * Copyright &copy; 2012-2016 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package com.thinkgem.jeesite.common.utils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.thinkgem.jeesite.common.config.Global;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * Jedis Cache 工具類
 * 
 * @author ThinkGem
 * @version 2014-6-29
 */
public class JedisClusterUtils {

	private static Logger logger = LoggerFactory.getLogger(JedisClusterUtils.class);
	
	private static JedisCluster jedisCluster = SpringContextHolder.getBean(JedisCluster.class);

	public static final String KEY_PREFIX = Global.getConfig("redis.keyPrefix");
	
	/**
	 * 獲取快取
	 * @param key 鍵
	 * @return 值
	 */
	public static String get(String key) {
		String value = null;
		try {
			if (jedisCluster.exists(key)) {
				value = jedisCluster.get(key);
				value = StringUtils.isNotBlank(value) && !"nil".equalsIgnoreCase(value) ? value : null;
				logger.debug("get {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("get {} = {}", key, value, e);
		}
		
		return value;
	}
	
	/**
	 * 獲取快取
	 * @param key 鍵
	 * @return 值
	 */
	public static Object getObject(String key) {
		Object value = null;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				value = toObject(jedisCluster.get(getBytesKey(key)));
				logger.debug("getObject {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getObject {} = {}", key, value, e);
		} 
		return value;
	}
	
	/**
	 * 設定快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static String set(String key, String value, int cacheSeconds) {
		String result = null;
		try {
			result = jedisCluster.set(key, value);
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("set {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("set {} = {}", key, value, e);
		}
		
		return result;
	}
	
	/**
	 * 設定快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static String setObject(String key, Object value, int cacheSeconds) {
		String result = null;
		try {
			result = jedisCluster.set(getBytesKey(key), toBytes(value));
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setObject {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setObject {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 獲取List快取
	 * @param key 鍵
	 * @return 值
	 */
	public static List<String> getList(String key) {
		List<String> value = null;
		try {
			if (jedisCluster.exists(key)) {
				value = jedisCluster.lrange(key, 0, -1);
				logger.debug("getList {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getList {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 獲取List快取
	 * @param key 鍵
	 * @return 值
	 */
	public static List<Object> getObjectList(String key) {
		List<Object> value = null;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				List<byte[]> list = jedisCluster.lrange(getBytesKey(key), 0, -1);
				value = Lists.newArrayList();
				for (byte[] bs : list){
					value.add(toObject(bs));
				}
				logger.debug("getObjectList {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getObjectList {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 設定List快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static long setList(String key, List<String> value, int cacheSeconds) {
		long result = 0;
		Jedis jedis = null;
		try {
			if (jedisCluster.exists(key)) {
				jedisCluster.del(key);
			}
			result = jedisCluster.rpush(key, (String[])value.toArray());
			if (cacheSeconds != 0) {
				jedis.expire(key, cacheSeconds);
			}
			logger.debug("setList {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setList {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 設定List快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static long setObjectList(String key, List<Object> value, int cacheSeconds) {
		long result = 0;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				jedisCluster.del(key);
			}
			List<byte[]> list = Lists.newArrayList();
			for (Object o : value){
				list.add(toBytes(o));
			}
			result = jedisCluster.rpush(getBytesKey(key), (byte[][])list.toArray());
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setObjectList {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setObjectList {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 向List快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long listAdd(String key, String... value) {
		long result = 0;
		try {
			result = jedisCluster.rpush(key, value);
			logger.debug("listAdd {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("listAdd {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 向List快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long listObjectAdd(String key, Object... value) {
		long result = 0;
		try {
			List<byte[]> list = Lists.newArrayList();
			for (Object o : value){
				list.add(toBytes(o));
			}
			result = jedisCluster.rpush(getBytesKey(key), (byte[][])list.toArray());
			logger.debug("listObjectAdd {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("listObjectAdd {} = {}", key, value, e);
		}
		return result;
	}

	/**
	 * 獲取快取
	 * @param key 鍵
	 * @return 值
	 */
	public static Set<String> getSet(String key) {
		Set<String> value = null;
		try {
			if (jedisCluster.exists(key)) {
				value = jedisCluster.smembers(key);
				logger.debug("getSet {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getSet {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 獲取快取
	 * @param key 鍵
	 * @return 值
	 */
	public static Set<Object> getObjectSet(String key) {
		Set<Object> value = null;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				value = Sets.newHashSet();
				Set<byte[]> set = jedisCluster.smembers(getBytesKey(key));
				for (byte[] bs : set){
					value.add(toObject(bs));
				}
				logger.debug("getObjectSet {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getObjectSet {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 設定Set快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static long setSet(String key, Set<String> value, int cacheSeconds) {
		long result = 0;
		try {
			if (jedisCluster.exists(key)) {
				jedisCluster.del(key);
			}
			result = jedisCluster.sadd(key, (String[])value.toArray());
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setSet {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setSet {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 設定Set快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static long setObjectSet(String key, Set<Object> value, int cacheSeconds) {
		long result = 0;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				jedisCluster.del(key);
			}
			Set<byte[]> set = Sets.newHashSet();
			for (Object o : value){
				set.add(toBytes(o));
			}
			result = jedisCluster.sadd(getBytesKey(key), (byte[][])set.toArray());
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setObjectSet {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setObjectSet {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 向Set快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long setSetAdd(String key, String... value) {
		long result = 0;
		try {
			result = jedisCluster.sadd(key, value);
			logger.debug("setSetAdd {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setSetAdd {} = {}", key, value, e);
		}
		return result;
	}

	/**
	 * 向Set快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long setSetObjectAdd(String key, Object... value) {
		long result = 0;
		try {
			Set<byte[]> set = Sets.newHashSet();
			for (Object o : value){
				set.add(toBytes(o));
			}
			result = jedisCluster.rpush(getBytesKey(key), (byte[][])set.toArray());
			logger.debug("setSetObjectAdd {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setSetObjectAdd {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 獲取Map快取
	 * @param key 鍵
	 * @return 值
	 */
	public static Map<String, String> getMap(String key) {
		Map<String, String> value = null;
		try {
			if (jedisCluster.exists(key)) {
				value = jedisCluster.hgetAll(key);
				logger.debug("getMap {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getMap {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 獲取Map快取
	 * @param key 鍵
	 * @return 值
	 */
	public static Map<String, Object> getObjectMap(String key) {
		Map<String, Object> value = null;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				value = Maps.newHashMap();
				Map<byte[], byte[]> map = jedisCluster.hgetAll(getBytesKey(key));
				for (Map.Entry<byte[], byte[]> e : map.entrySet()){
					value.put(StringUtils.toString(e.getKey()), toObject(e.getValue()));
				}
				logger.debug("getObjectMap {} = {}", key, value);
			}
		} catch (Exception e) {
			logger.warn("getObjectMap {} = {}", key, value, e);
		}
		return value;
	}
	
	/**
	 * 設定Map快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static String setMap(String key, Map<String, String> value, int cacheSeconds) {
		String result = null;
		try {
			if (jedisCluster.exists(key)) {
				jedisCluster.del(key);
			}
			result = jedisCluster.hmset(key, value);
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setMap {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setMap {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 設定Map快取
	 * @param key 鍵
	 * @param value 值
	 * @param cacheSeconds 超時時間,0為不超時
	 * @return
	 */
	public static String setObjectMap(String key, Map<String, Object> value, int cacheSeconds) {
		String result = null;
		try {
			if (jedisCluster.exists(getBytesKey(key))) {
				jedisCluster.del(key);
			}
			Map<byte[], byte[]> map = Maps.newHashMap();
			for (Map.Entry<String, Object> e : value.entrySet()){
				map.put(getBytesKey(e.getKey()), toBytes(e.getValue()));
			}
			result = jedisCluster.hmset(getBytesKey(key), (Map<byte[], byte[]>)map);
			if (cacheSeconds != 0) {
				jedisCluster.expire(key, cacheSeconds);
			}
			logger.debug("setObjectMap {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("setObjectMap {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 向Map快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static String mapPut(String key, Map<String, String> value) {
		String result = null;
		try {
			result = jedisCluster.hmset(key, value);
			logger.debug("mapPut {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("mapPut {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 向Map快取中新增值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static String mapObjectPut(String key, Map<String, Object> value) {
		String result = null;
		try {
			Map<byte[], byte[]> map = Maps.newHashMap();
			for (Map.Entry<String, Object> e : value.entrySet()){
				map.put(getBytesKey(e.getKey()), toBytes(e.getValue()));
			}
			result = jedisCluster.hmset(getBytesKey(key), (Map<byte[], byte[]>)map);
			logger.debug("mapObjectPut {} = {}", key, value);
		} catch (Exception e) {
			logger.warn("mapObjectPut {} = {}", key, value, e);
		}
		return result;
	}
	
	/**
	 * 移除Map快取中的值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long mapRemove(String key, String mapKey) {
		long result = 0;
		try {
			result = jedisCluster.hdel(key, mapKey);
			logger.debug("mapRemove {}  {}", key, mapKey);
		} catch (Exception e) {
			logger.warn("mapRemove {}  {}", key, mapKey, e);
		}
		return result;
	}
	
	/**
	 * 移除Map快取中的值
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static long mapObjectRemove(String key, String mapKey) {
		long result = 0;
		try {
			result = jedisCluster.hdel(getBytesKey(key), getBytesKey(mapKey));
			logger.debug("mapObjectRemove {}  {}", key, mapKey);
		} catch (Exception e) {
			logger.warn("mapObjectRemove {}  {}", key, mapKey, e);
		}
		return result;
	}
	
	/**
	 * 判斷Map快取中的Key是否存在
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static boolean mapExists(String key, String mapKey) {
		boolean result = false;
		try {
			result = jedisCluster.hexists(key, mapKey);
			logger.debug("mapExists {}  {}", key, mapKey);
		} catch (Exception e) {
			logger.warn("mapExists {}  {}", key, mapKey, e);
		}
		return result;
	}
	
	/**
	 * 判斷Map快取中的Key是否存在
	 * @param key 鍵
	 * @param value 值
	 * @return
	 */
	public static boolean mapObjectExists(String key, String mapKey) {
		boolean result = false;
		try {
			result = jedisCluster.hexists(getBytesKey(key), getBytesKey(mapKey));
			logger.debug("mapObjectExists {}  {}", key, mapKey);
		} catch (Exception e) {
			logger.warn("mapObjectExists {}  {}", key, mapKey, e);
		}
		return result;
	}
	
	/**
	 * 刪除快取
	 * @param key 鍵
	 * @return
	 */
	public static long del(String key) {
		long result = 0;
		try {
			if (jedisCluster.exists(key)){
				result = jedisCluster.del(key);
				logger.debug("del {}", key);
			}else{
				logger.debug("del {} not exists", key);
			}
		} catch (Exception e) {
			logger.warn("del {}", key, e);
		}
		return result;
	}

	/**
	 * 刪除快取
	 * @param key 鍵
	 * @return
	 */
	public static long delObject(String key) {
		long result = 0;
		try {
			if (jedisCluster.exists(getBytesKey(key))){
				result = jedisCluster.del(getBytesKey(key));
				logger.debug("delObject {}", key);
			}else{
				logger.debug("delObject {} not exists", key);
			}
		} catch (Exception e) {
			logger.warn("delObject {}", key, e);
		}
		return result;
	}
	
	/**
	 * 快取是否存在
	 * @param key 鍵
	 * @return
	 */
	public static boolean exists(String key) {
		boolean result = false;
		try {
			result = jedisCluster.exists(key);
			logger.debug("exists {}", key);
		} catch (Exception e) {
			logger.warn("exists {}", key, e);
		}
		return result;
	}
	
	/**
	 * 快取是否存在
	 * @param key 鍵
	 * @return
	 */
	public static boolean existsObject(String key) {
		boolean result = false;
		try {
			result = jedisCluster.exists(getBytesKey(key));
			logger.debug("existsObject {}", key);
		} catch (Exception e) {
			logger.warn("existsObject {}", key, e);
		}
		return result;
	}

	/**
	 * 獲取byte[]型別Key
	 * @param key
	 * @return
	 */
	public static byte[] getBytesKey(Object object){
		if(object instanceof String){
    		return StringUtils.getBytes((String)object);
    	}else{
    		return ObjectUtils.serialize(object);
    	}
	}
	
	/**
	 * 獲取byte[]型別Key
	 * @param key
	 * @return
	 */
	public static Object getObjectKey(byte[] key){
		try{
			return StringUtils.toString(key);
		}catch(UnsupportedOperationException uoe){
			try{
				return JedisClusterUtils.toObject(key);
			}catch(UnsupportedOperationException uoe2){
				uoe2.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * Object轉換byte[]型別
	 * @param key
	 * @return
	 */
	public static byte[] toBytes(Object object){
    	return ObjectUtils.serialize(object);
	}

	/**
	 * byte[]型轉換Object
	 * @param key
	 * @return
	 */
	public static Object toObject(byte[] bytes){
		return ObjectUtils.unserialize(bytes);
	}
	
	public static String hget(String hkey, String key) {
        return jedisCluster.hget(hkey, key);
    }
 
    public static long hset(String hkey, String key, String value) {
        return jedisCluster.hset(hkey, key, value);
    }
 
    public static long incr(String key) {
        return jedisCluster.incr(key);
    }
 
    public static long expire(String key, int second) {
        return jedisCluster.expire(key, second);
    }
 
    public static long ttl(String key) {
        return jedisCluster.ttl(key);
    }
 
    public static long hdel(String hkey, String key) {
         
        return jedisCluster.hdel(hkey, key);
    }
	
	public static Map<String, String> hgetAll(String hkey) {
        return jedisCluster.hgetAll(hkey);
    }
	
	public static byte[] get(byte[] bytes) {
		return jedisCluster.get(bytes);
	}
	
	public static String set(byte[] key, byte[] value) {
		return jedisCluster.set(key, value);
	}
	
	public static Long hdel(byte[] key) {
		return jedisCluster.hdel(key);
	}
	
	public static Long hlen(byte[] key) {
		return jedisCluster.hlen(key);
	}
	
	public static Set<byte[]> hkeys(byte[] key) {
		return jedisCluster.hkeys(key);
	}
	
	public static Collection<byte[]> hvals(byte[] key) {
		return jedisCluster.hvals(key);
	}
	
	public static Long hdel(byte[] key, byte[] value) {
		return jedisCluster.hdel(key, value);
	}
	
	public static Long del(byte[] key) {
		return jedisCluster.del(key);
	}
	
	public static byte[] hget(byte[] key, byte[] value) {
		return jedisCluster.hget(key, value);
	}
	
	public static Long hset(byte[] key, byte[] field, byte[] value) {
		return jedisCluster.hset(key, field, value);
	}
	
}

2. 修改JedisSessionDAO.java,主要是將有JedisUtils和jedis的地方,全部改成JedisClusterUtils:

/**
 * Copyright &copy; 2012-2016 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package com.thinkgem.jeesite.common.security.shiro.session;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.JedisCluster;

import com.google.common.collect.Sets;
import com.thinkgem.jeesite.common.config.Global;
import com.thinkgem.jeesite.common.utils.DateUtils;
import com.thinkgem.jeesite.common.utils.JedisClusterUtils;
import com.thinkgem.jeesite.common.utils.SpringContextHolder;
import com.thinkgem.jeesite.common.utils.StringUtils;
import com.thinkgem.jeesite.common.web.Servlets;

/**
 * 自定義授權會話管理類
 * @author ThinkGem
 * @version 2014-7-20
 */
public class JedisSessionDAO extends AbstractSessionDAO implements SessionDAO {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private String sessionKeyPrefix = "shiro_session_";

	@Override
	public void update(Session session) throws UnknownSessionException {
		if (session == null || session.getId() == null) {  
            return;
        }
		
		HttpServletRequest request = Servlets.getRequest();
		if (request != null){
			String uri = request.getServletPath();
			// 如果是靜態檔案,則不更新SESSION
			if (Servlets.isStaticFile(uri)){
				return;
			}
			// 如果是檢視檔案,則不更新SESSION
			if (StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
					&& StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))){
				return;
			}
			// 手動控制不更新SESSION
			if (Global.NO.equals(request.getParameter("updateSession"))){
				return;
			}
		}
		
		try {			
			// 獲取登入者編號
			PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
			String principalId = pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY;
			
			JedisClusterUtils.hset(sessionKeyPrefix, session.getId().toString(), principalId + "|" + session.getTimeout() + "|" + session.getLastAccessTime().getTime());
			JedisClusterUtils.set(JedisClusterUtils.getBytesKey(sessionKeyPrefix + session.getId()), JedisClusterUtils.toBytes(session));
			
			// 設定超期時間
			int timeoutSeconds = (int)(session.getTimeout() / 1000);
			JedisClusterUtils.expire((sessionKeyPrefix + session.getId()), timeoutSeconds);

			logger.debug("update {} {}", session.getId(), request != null ? request.getRequestURI() : "");
		} catch (Exception e) {
			logger.error("update {} {}", session.getId(), request != null ? request.getRequestURI() : "", e);
		} 
	}

	@Override
	public void delete(Session session) {
		if (session == null || session.getId() == null) {
			return;
		}
		
		try {
			
			JedisClusterUtils.hdel(JedisClusterUtils.getBytesKey(sessionKeyPrefix), JedisClusterUtils.getBytesKey(session.getId().toString()));
			JedisClusterUtils.del(JedisClusterUtils.getBytesKey(sessionKeyPrefix + session.getId()));

			logger.debug("delete {} ", session.getId());
		} catch (Exception e) {
			logger.error("delete {} ", session.getId(), e);
		} 
	}
	
	@Override
	public Collection<Session> getActiveSessions() {
		return getActiveSessions(true);
	}
	
	/**
	 * 獲取活動會話
	 * @param includeLeave 是否包括離線(最後訪問時間大於3分鐘為離線會話)
	 * @return
	 */
	@Override
	public Collection<Session> getActiveSessions(boolean includeLeave) {
		return getActiveSessions(includeLeave, null, null);
	}
	
	/**
	 * 獲取活動會話
	 * @param includeLeave 是否包括離線(最後訪問時間大於3分鐘為離線會話)
	 * @param principal 根據登入者物件獲取活動會話
	 * @param filterSession 不為空,則過濾掉(不包含)這個會話。
	 * @return
	 */
	@Override
	public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession){
		Set<Session> sessions = Sets.newHashSet();
		
		try {
			Map<String, String> map = JedisClusterUtils.hgetAll(sessionKeyPrefix);
			for (Map.Entry<String, String> e : map.entrySet()){
				if (StringUtils.isNotBlank(e.getKey()) && StringUtils.isNotBlank(e.getValue())){
					
					String[] ss = StringUtils.split(e.getValue(), "|");
					if (ss != null && ss.length == 3){// jedis.exists(sessionKeyPrefix + e.getKey())){
						// Session session = (Session)JedisClusterUtils.toObject(jedis.get(JedisClusterUtils.getBytesKey(sessionKeyPrefix + e.getKey())));
						SimpleSession session = new SimpleSession();
						session.setId(e.getKey());
						session.setAttribute("principalId", ss[0]);
						session.setTimeout(Long.valueOf(ss[1]));
						session.setLastAccessTime(new Date(Long.valueOf(ss[2])));
						try{
							// 驗證SESSION
							session.validate();
							
							boolean isActiveSession = false;
							// 不包括離線並符合最後訪問時間小於等於3分鐘條件。
							if (includeLeave || DateUtils.pastMinutes(session.getLastAccessTime()) <= 3){
								isActiveSession = true;
							}
							// 符合登陸者條件。
							if (principal != null){
								PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
								if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY)){
									isActiveSession = true;
								}
							}
							// 過濾掉的SESSION
							if (filterSession != null && filterSession.getId().equals(session.getId())){
								isActiveSession = false;
							}
							if (isActiveSession){
								sessions.add(session);
							}
							
						}
						// SESSION驗證失敗
						catch (Exception e2) {
							JedisClusterUtils.hdel(sessionKeyPrefix, e.getKey());
						}
					}
					// 儲存的SESSION不符合規則
					else{
						JedisClusterUtils.hdel(sessionKeyPrefix, e.getKey());
					}
				}
				// 儲存的SESSION無Value
				else if (StringUtils.isNotBlank(e.getKey())){
					JedisClusterUtils.hdel(sessionKeyPrefix, e.getKey());
				}
			}
			logger.info("getActiveSessions size: {} ", sessions.size());
		} catch (Exception e) {
			logger.error("getActiveSessions", e);
		}
		return sessions;
	}

	@Override
	protected Serializable doCreate(Session session) {
		HttpServletRequest request = Servlets.getRequest();
		if (request != null){
			String uri = request.getServletPath();
			// 如果是靜態檔案,則不建立SESSION
			if (Servlets.isStaticFile(uri)){
		        return null;
			}
		}
		Serializable sessionId = this.generateSessionId(session);
		this.assignSessionId(session, sessionId);
		this.update(session);
		return sessionId;
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {

		Session s = null;
		HttpServletRequest request = Servlets.getRequest();
		if (request != null){
			String uri = request.getServletPath();
			// 如果是靜態檔案,則不獲取SESSION
			if (Servlets.isStaticFile(uri)){
				return null;
			}
			s = (Session)request.getAttribute("session_"+sessionId);
		}
		if (s != null){
			return s;
		}

		Session session = null;
		try {
//			if (jedis.exists(sessionKeyPrefix + sessionId)){
				session = (Session)JedisClusterUtils.toObject(JedisClusterUtils.get(
						JedisClusterUtils.getBytesKey(sessionKeyPrefix + sessionId)));
//			}
			logger.debug("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "");
		} catch (Exception e) {
			logger.error("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "", e);
		} 
		
		if (request != null && session != null){
			request.setAttribute("session_"+sessionId, session);
		}
		
		return session;
	}
	
	@Override
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
    	try{
        	return super.readSession(sessionId);
    	}catch (UnknownSessionException e) {
			return null;
		}
    }

	public String getSessionKeyPrefix() {
		return sessionKeyPrefix;
	}
	public void setSessionKeyPrefix(String sessionKeyPrefix) {
		this.sessionKeyPrefix = sessionKeyPrefix;
	}

}
3. 修改JedisCacheManager.java,改法同JedisSessionDAO.java。
/**
 * Copyright &copy; 2012-2016 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package com.thinkgem.jeesite.common.security.shiro.cache;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.JedisCluster;

import com.google.common.collect.Sets;
import com.thinkgem.jeesite.common.utils.JedisClusterUtils;
import com.thinkgem.jeesite.common.utils.SpringContextHolder;
import com.thinkgem.jeesite.common.web.Servlets;

/**
 * 自定義授權快取管理類
 * @author ThinkGem
 * @version 2014-7-20
 */
public class JedisCacheManager implements CacheManager {

	private String cacheKeyPrefix = "shiro_cache_";
	
	@Override
	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
		return new JedisCache<K, V>(cacheKeyPrefix + name);
	}

	public String getCacheKeyPrefix() {
		return cacheKeyPrefix;
	}

	public void setCacheKeyPrefix(String cacheKeyPrefix) {
		this.cacheKeyPrefix = cacheKeyPrefix;
	}
	
	/**
	 * 自定義授權快取管理類
	 * @author ThinkGem
	 * @version 2014-7-20
	 */
	public class JedisCache<K, V> implements Cache<K, V> {

		private Logger logger = LoggerFactory.getLogger(getClass());

		private String cacheKeyName = null;

		public JedisCache(String cacheKeyName) {
			this.cacheKeyName = cacheKeyName;
//			if (!JedisClusterUtils.exists(cacheKeyName)){
//				Map<String, Object> map = Maps.newHashMap();
//				JedisClusterUtils.setObjectMap(cacheKeyName, map, 60 * 60 * 24);
//			}
//			logger.debug("Init: cacheKeyName {} ", cacheKeyName);
		}
		
		@SuppressWarnings("unchecked")
		@Override
		public V get(K key) throws CacheException {
			if (key == null){
				return null;
			}
			
			V v = null;
			HttpServletRequest request = Servlets.getRequest();
			if (request != null){
				v = (V)request.getAttribute(cacheKeyName);
				if (v != null){
					return v;
				}
			}
			
			V value = null;
			try {
				value = (V)JedisClusterUtils.toObject(JedisClusterUtils.hget(JedisClusterUtils.getBytesKey(cacheKeyName), JedisClusterUtils.getBytesKey(key)));
				logger.debug("get {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "");
			} catch (Exception e) {
				logger.error("get {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "", e);
			}
			
			if (request != null && value != null){
				request.setAttribute(cacheKeyName, value);
			}
			
			return value;
		}

		@Override
		public V put(K key, V value) throws CacheException {
			if (key == null){
				return null;
			}
			
			try {
				JedisClusterUtils.hset(JedisClusterUtils.getBytesKey(cacheKeyName), JedisClusterUtils.getBytesKey(key), JedisClusterUtils.toBytes(value));
				logger.debug("put {} {} = {}", cacheKeyName, key, value);
			} catch (Exception e) {
				logger.error("put {} {}", cacheKeyName, key, e);
			}
			return value;
		}

		@SuppressWarnings("unchecked")
		@Override
		public V remove(K key) throws CacheException {
			V value = null;
			try {
				value = (V)JedisClusterUtils.toObject(JedisClusterUtils.hget(JedisClusterUtils.getBytesKey(cacheKeyName), JedisClusterUtils.getBytesKey(key)));
				JedisClusterUtils.hdel(JedisClusterUtils.getBytesKey(cacheKeyName), JedisClusterUtils.getBytesKey(key));
				logger.debug("remove {} {}", cacheKeyName, key);
			} catch (Exception e) {
				logger.warn("remove {} {}", cacheKeyName, key, e);
			}
			return value;
		}

		@Override
		public void clear() throws CacheException {
			try {
				JedisClusterUtils.hdel(JedisClusterUtils.getBytesKey(cacheKeyName));
				logger.debug("clear {}", cacheKeyName);
			} catch (Exception e) {
				logger.error("clear {}", cacheKeyName, e);
			}
		}

		@Override
		public int size() {
			int size = 0;
			try {
				size = JedisClusterUtils.hlen(JedisClusterUtils.getBytesKey(cacheKeyName)).intValue();
				logger.debug("size {} {} ", cacheKeyName, size);
				return size;
			} catch (Exception e) {
				logger.error("clear {}",  cacheKeyName, e);
			}
			return size;
		}

		@SuppressWarnings("unchecked")
		@Override
		public Set<K> keys() {
			Set<K> keys = Sets.newHashSet();
			try {
				Set<byte[]> set = JedisClusterUtils.hkeys(JedisClusterUtils.getBytesKey(cacheKeyName));
				for(byte[] key : set){
					Object obj = (K)JedisClusterUtils.getObjectKey(key);
					if (obj != null){
						keys.add((K) obj);
					}
	        	}
				logger.debug("keys {} {} ", cacheKeyName, keys);
				return keys;
			} catch (Exception e) {
				logger.error("keys {}", cacheKeyName, e);
			}
			return keys;
		}

		@SuppressWarnings("unchecked")
		@Override
		public Collection<V> values() {
			Collection<V> vals = Collections.emptyList();
			try {
				Collection<byte[]> col = JedisClusterUtils.hvals(JedisClusterUtils.getBytesKey(cacheKeyName));
				for(byte[] val : col){
					Object obj = JedisClusterUtils.toObject(val);
					if (obj != null){
						vals.add((V) obj);
					}
	        	}
				logger.debug("values {} {} ", cacheKeyName, vals);
				return vals;
			} catch (Exception e) {
				logger.error("values {}",  cacheKeyName, e);
			}
			return vals;
		}
	}
}

五. 測試:

啟動專案並登陸後,去Redis客戶端用命令可以看到Session和快取在各Redis伺服器中已被分別儲存:

檢視所有key:

>keys *

六. 相關下載:

文章中用到的Redis相關工具包的下載:Redis相關工具包

專案我就不上傳了,資料庫配置都是我自己的,上傳了也沒用,去jeesite的網站下載v1.2版本即可。