Springboot2.x整合Redis以及連接哨兵模式/集群模式


依賴:

<!--spirngboot版本為2.x-->
<!-- 加載spring boot redis包,springboot2.0中直接使用jedis或者lettuce配置連接池,默認為lettuce連接池,這里使用jedis連接池 -->
<!-- 加載spring boot redis包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 排除lettuce包,使用jedis代替-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
application.properties配置

##配置redis的連接信息
#spring.redis.host=192.168.184.135
#spring.redis.port=6379
#spring.redis.password=123456
#連接超時時間
#spring.redis.timeout=6000ms
##Redis數據庫索引(默認為0)
#spring.redis.database=0
## 連接池配置,springboot2.0中直接使用jedis或者lettuce配置連接池,默認為lettuce連接池
##連接池最大連接數(使用負值表示沒有限制)
#spring.redis.jedis.pool.max-active=8
##連接池最大阻塞等待時間(使用負值表示沒有限制)
#spring.redis.jedis.pool.max-wait=-1s
##連接池中的最大空閑連接
#spring.redis.jedis.pool.max-idle=8
##接池中的最小空閑連接
#spring.redis.jedis.pool.min-idle=0

##################################

#哨兵模式redis集群配置,就是為了通過redis找主節點,做到無感切換
#spring.redis.password=123456
#spring.redis.sentinel.master=mymaster
#spring.redis.sentinel.nodes=192.168.184.133:26379,192.168.184.135:26379,192.168.184.136:26379
##連接超時時間
#spring.redis.timeout=6000ms
##Redis數據庫索引(默認為0)
#spring.redis.database=0
## 連接池配置,springboot2.0中直接使用jedis或者lettuce配置連接池,默認為lettuce連接池
##連接池最大連接數(使用負值表示沒有限制)
#spring.redis.jedis.pool.max-active=8
##連接池最大阻塞等待時間(使用負值表示沒有限制)
#spring.redis.jedis.pool.max-wait=-1s
##連接池中的最大空閑連接
#spring.redis.jedis.pool.max-idle=8
##接池中的最小空閑連接
#spring.redis.jedis.pool.min-idle=0


#############################

#連接超時時間
spring.redis.cluster.nodes=192.168.184.133:7000,192.168.184.133:7001,192.168.184.133:7002,192.168.184.133:7003,192.168.184.133:7004,192.168.184.133:7005
spring.redis.password=123456
spring.redis.timeout=6000ms
#Redis數據庫索引(默認為0)
spring.redis.database=0
# 連接池配置,springboot2.0中直接使用jedis或者lettuce配置連接池,默認為lettuce連接池
#連接池最大連接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=8
#連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1s
#連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=8
#接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=0
 說明:Jedis和Lettuce的比較:

     Jedis :

      直連模式,在多個線程間共享一個 Jedis 實例時是線程不安全的,如果想要在多線程環境下使用 Jedis,需要使用連接池,

      每個線程都去拿自己的 Jedis 實例,當連接數量增多時,物理連接成本較高。

     Lettuce:

      連接是基於Netty的,連接實例可以在多個線程間共享,

      所以,一個多線程的應用可以使用同一個連接實例,而不用擔心並發線程的數量。當然這個也是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。

      通過異步的方式可以讓我們更好的利用系統資源,而不用浪費線程等待網絡或磁盤I/O。

/////////////////////////哨兵相關內容////////////////////////////////////// 

一、客戶端訪問哨兵系統
哨兵系統的作用: 監控、自動故障轉移、配置提供者、通知。

一、代碼示例

在介紹客戶端的原理之前,先以Java客戶端Jedis為例,演示一下使用方法:下面代碼可以連接我們剛剛搭建的哨兵系統,並進行各種讀寫操作:

public static void testSentinel() throws Exception {

String masterName = "mymaster";

Set<String> sentinels = new HashSet<>();

sentinels.add("192.168.92.128:26379");

sentinels.add("192.168.92.128:26380");

sentinels.add("192.168.92.128:26381");



JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels);

Jedis jedis = pool.getResource();

jedis.set("key1", "value1");

pool.close();

}
(注:代碼中只演示如何連接哨兵,異常處理、資源關閉等未考慮)

二、客戶端原理

Jedis客戶端對哨兵提供了很好的支持。如上述代碼所示,我們只需要向Jedis提供哨兵節點集合和masterName,構造Jedis SentinelPool對象;然后便可以像使用普通Redis連接池一樣來使用了:通過pool.getResource()獲取連接,執行具體的命令。

在整個過程中,我們的代碼不需要顯式的指定主節點的地址,就可以連接到主節點;代碼中對故障轉移沒有任何體現,就可以在哨兵完成故障轉移后自動的切換主節點。之所以可以做到這一點,是因為在JedisSentinelPool的構造器中,進行了相關的工作,主要包括以下兩點:

遍歷哨兵節點,獲取主節點信息:遍歷哨兵節點,通過其中一個哨兵節點+masterName獲得主節點的信息;該功能是通過調用哨兵節點的sentinel get-master-addr-by-name命令實現,該命令示例如下:

 

 

可以看到192.168.184.135的確是master

一旦獲得主節點信息,停止遍歷(因此一般來說遍歷到第一個哨兵節點,循環就停止了)。

增加對哨兵的監聽:這樣當發生故障轉移時,客戶端便可以收到哨兵的通知,從而完成主節點的切換。具體做法是:利用Redis提供的發布訂閱功能,為每一個哨兵節點開啟一個單獨的線程,訂閱哨兵節點的+switch-master頻道,當收到消息時,重新初始化連接池。

三、總結

通過客戶端原理的介紹,可以加深對哨兵功能的理解,如下:

配置提供者:客戶端可以通過哨兵節點+masterName獲取主節點信息,在這里哨兵起到的作用就是配置提供者。

需要注意的是,哨兵只是配置提供者,而不是代理。二者的區別在於:

1、如果是配置提供者,客戶端在通過哨兵獲得主節點信息后,會直接建立到主節點的連接,后續的請求(如set/get)會直接發向主節點;

2、如果是代理,客戶端的每一次請求都會發向哨兵,哨兵再通過主節點處理請求。

舉一個例子可以很好的理解哨兵的作用是配置提供者,而不是代理。在前面部署的哨兵系統中,將哨兵節點的配置文件進行如下修改:
 

sentinel monitor   mymaster 192.168.92.128 6379 2

改為

sentinel monitor mymaster 127.0.0.1 6379 2
然后,將前述客戶端代碼在局域網的另外一台機器上運行,會發現客戶端無法連接主節點;這是因為哨兵作為配置提供者,客戶端通過它查詢到主節點的地址為127.0.0.1:6379,客戶端會向127.0.0.1:6379建立Redis連接,自然無法連接。如果哨兵是代理,這個問題就不會出現了。

通知:哨兵節點在故障轉移完成后,會將新的主節點信息發送給客戶端,以便客戶端及時切換主節點。

二、基本原理
一、哨兵節點支持的命令

哨兵節點作為運行在特殊模式下的Redis節點,其支持的命令與普通的Redis節點不同。在運維中,我們可以通過這些命令查詢或修改哨兵系統;不過更重要的是,哨兵系統要實現故障發現、故障轉移等各種功能,離不開哨兵節點之間的通信,而通信的很大一部分是通過哨兵節點支持的命令來實現的。下面介紹哨兵節點支持的主要命令:

基礎查詢:

通過這些命令,可以查詢哨兵系統的拓撲結構、節點信息、配置信息等。

1、info sentinel:獲取監控的所有主節點的基本信息。

2、sentinel masters:獲取監控的所有主節點的詳細信息。

3、sentinel master mymaster:獲取監控的主節點mymaster的詳細信息。

4、sentinel slaves mymaster:獲取監控的主節點mymaster的從節點的詳細信息。

5、sentinel sentinels mymaster:獲取監控的主節點mymaster的哨兵節點的詳細信息。

6、sentinel get - master - addr - by- name mymaster:獲取監控的主節點mymaster的地址信息,前文已有介紹。

7、sentinel is-master-down-by-addr:哨兵節點之間可以通過該命令詢問主節點是否下線,從而對是否客觀下線做出判斷。

增加/移除對主節點的監控:

sentinel monitor mymaster2 192.168.92.128 16379 2:與部署哨兵節點時配置文件中的sentinel monitor功能完全一樣,不再詳述。

sentinel remove mymaster2:取消當前哨兵節點對主節點mymaster2的監控。

強制故障轉移:

sentinel failover mymaster:該命令可以強制對mymaster執行故障轉移,即便當前的主節點運行完好;例如,如果當前主節點所在機器即將報廢,便可以提前通過failover命令進行故障轉移。

二、基本原理

關於哨兵的原理,關鍵是了解以下幾個概念:

定時任務:每個哨兵節點維護了3個定時任務。定時任務的功能分別如下:通過向主從節點發送info命令獲取最新的主從結構;通過發布訂閱功能獲取其他哨兵節點的信息;通過向其他節點發送ping命令進行心跳檢測,判斷是否下線。

主觀下線:在心跳檢測的定時任務中,如果其他節點超過一定時間沒有回復,哨兵節點就會將其進行主觀下線。顧名思義,主觀下線的意思是一個哨兵節點“主觀地”判斷下線;與主觀下線相對應的是客觀下線。

客觀下線:哨兵節點在對主節點進行主觀下線后,會通過sentinel is-master-down-by-addr命令詢問其他哨兵節點該主節點的狀態;如果判斷主節點下線的哨兵數量達到一定數值,則對該主節點進行客觀下線。

需要特別注意的是,客觀下線是主節點才有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線后,不會再有后續的客觀下線和故障轉移操作。

選舉領導者哨兵節點:當主節點被判斷客觀下線以后,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。

監視該主節點的所有哨兵都有可能被選為領導者,選舉使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一輪選舉中,哨兵A向B發送成為領導者的申請,如果B沒有同意過其他哨兵,則會同意A成為領導者。選舉的具體過程這里不做詳細描述,一般來說,哨兵選擇的過程很快,誰先完成客觀下線,一般就能成為領導者。

故障轉移:選舉出的領導者哨兵,開始進行故障轉移操作,該操作大體可以分為3個步驟:

1、在從節點中選擇新的主節點:選擇的原則是,首先過濾掉不健康的從節點;然后選擇優先級最高的從節點(由slave-priority指定);如果優先級無法區分,則選擇復制偏移量最大的從節點;如果仍無法區分,則選擇runid最小的從節點。

2、更新主從狀態:通過slaveof no one命令,讓選出來的從節點成為主節點;並通過slaveof命令讓其他節點成為其從節點。

3、將已經下線的主節點(即6379)設置為新的主節點的從節點,當6379重新上線后,它會成為新的主節點的從節點。

通過上述幾個關鍵概念,可以基本了解哨兵的工作原理。為了更形象的說明,下圖展示了領導者哨兵節點的日志,包括從節點啟動到完成故障轉移。

 

三、配置與實踐建議

一、配置

下面介紹與哨兵相關的幾個配置。

配置1:sentinel monitor {masterName} {masterIp} {masterPort} {quorum}

sentinel monitor是哨兵最核心的配置,在前文講述部署哨兵節點時已說明,其中:masterName指定了主節點名稱,masterIp和masterPort指定了主節點地址,quorum是判斷主節點客觀下線的哨兵數量閾值:當判定主節點下線的哨兵數量達到quorum時,對主節點進行客觀下線。建議取值為哨兵數量的一半加1。

配置2:sentinel down-after-milliseconds {masterName} {time}

sentinel down-after-milliseconds與主觀下線的判斷有關:哨兵使用ping命令對其他節點進行心跳檢測,如果其他節點超過down-after-milliseconds配置的時間沒有回復,哨兵就會將其進行主觀下線。該配置對主節點、從節點和哨兵節點的主觀下線判定都有效。

down-after-milliseconds的默認值是30000,即30s;可以根據不同的網絡環境和應用要求來調整:值越大,對主觀下線的判定會越寬松,好處是誤判的可能性小,壞處是故障發現和故障轉移的時間變長,客戶端等待的時間也會變長。例如,如果應用對可用性要求較高,則可以將值適當調小,當故障發生時盡快完成轉移;如果網絡環境相對較差,可以適當提高該閾值,避免頻繁誤判。

配置3:sentinel parallel - syncs {masterName} {number}

sentinel parallel-syncs與故障轉移之后從節點的復制有關:它規定了每次向新的主節點發起復制操作的從節點個數。例如,假設主節點切換完成之后,有3個從節點要向新的主節點發起復制;如果parallel-syncs=1,則從節點會一個一個開始復制;如果parallel-syncs=3,則3個從節點會一起開始復制。

parallel-syncs取值越大,從節點完成復制的時間越快,但是對主節點的網絡負載、硬盤負載造成的壓力也越大;應根據實際情況設置。例如,如果主節點的負載較低,而從節點對服務可用的要求較高,可以適量增加parallel-syncs取值。parallel-syncs的默認值是1。

配置4:sentinel failover - timeout {masterName} {time}

sentinel failover-timeout與故障轉移超時的判斷有關,但是該參數不是用來判斷整個故障轉移階段的超時,而是其幾個子階段的超時,例如如果主節點晉升從節點時間超過timeout,或從節點向新的主節點發起復制操作的時間(不包括復制數據的時間)超過timeout,都會導致故障轉移超時失敗。

failover-timeout的默認值是180000,即180s;如果超時,則下一次該值會變為原來的2倍。

配置5:除上述幾個參數外,還有一些其他參數,如安全驗證相關的參數,這里不做介紹。

二、實踐建議

1、哨兵節點的數量應不止一個。一方面增加哨兵節點的冗余,避免哨兵本身成為高可用的瓶頸;另一方面減少對下線的誤判。此外,這些不同的哨兵節點應部署在不同的物理機上。

2、哨兵節點的數量應該是奇數,便於哨兵通過投票做出“決策”:領導者選舉的決策、客觀下線的決策等。

3、各個哨兵節點的配置應一致,包括硬件、參數等;此外,所有節點都應該使用ntp或類似服務,保證時間准確、一致。

4、哨兵的配置提供者和通知客戶端功能,需要客戶端的支持才能實現,如前文所說的Jedis;如果開發者使用的庫未提供相應支持,則可能需要開發者自己實現。

5、當哨兵系統中的節點在Docker(或其他可能進行端口映射的軟件)中部署時,應特別注意端口映射可能會導致哨兵系統無法正常工作,因為哨兵的工作基於與其他節點的通信,而Docker的端口映射可能導致哨兵無法連接到其他節點。例如,哨兵之間互相發現,依賴於它們對外宣稱的IP和port,如果某個哨兵A部署在做了端口映射的Docker中,那么其他哨兵使用A宣稱的port無法連接到A。

三、總結
在主從復制的基礎上,哨兵引入了主節點的自動故障轉移,進一步提高了Redis的高可用性;但是哨兵的缺陷同樣很明顯:哨兵無法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會導致讀服務不可用,需要我們對從節點做額外的監控、切換操作。

此外,哨兵仍然沒有解決寫操作無法負載均衡、及存儲能力受到單機限制的問題;這些問題的解決需要使用集群。

/////////////////////////////////////////////////////////////// 

 

RedisAutoConfiguration源碼
通過源碼可以看出,SpringBoot自動幫我們在容器中生成了一個RedisTemplate和一個StringRedisTemplate。但是,這個RedisTemplate的泛型是<Object,Object>,寫代碼不方便,需要寫好多類型轉換的代碼;我們需要一個泛型為<String,Object>形式的RedisTemplate。並且,這個RedisTemplate沒有設置數據存在Redis時,key及value的序列化方式。

        看到這個@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate對象了,這個自動配置的RedisTemplate不會實例化。因此我們可以直接自己寫個配置類,配置RedisTemplate。

/////////////////////////////////////////////////

既然自動配置不好用,就重新配置一個RedisTemplate。

package com.bootredis.bootredis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

//RedisAutoConfiguration類中的默認的RedisTemplate不好用,直接重寫一個
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);

// 使用Jackson2JsonRedisSerialize 替換默認的jdkSerializeable序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;

}
}
/////////////////////////////////////////////////

如果不重新注入到spring容器中一個RedisTemplate對象,在程序中使用泛型必須為<String, String>、<Object, Object>,因為默認就這兩種。。。。

//spring boot幫我們注入的redisTemplate類,泛型里面只能寫 <String, String>、<Object, Object>
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/////////////////////////////////////////////////

一般使用封裝簡單的工具類

package com.bootredis.bootredis.util;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定緩存失效時間
*
* @param key
* 鍵
* @param time
* 時間(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根據key 獲取過期時間
*
* @param key
* 鍵 不能為null
* @return 時間(秒) 返回0代表為永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判斷key是否存在
*
* @param key
* 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 刪除緩存
*
* @param key
* 可以傳一個值 或多個
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}

// ============================String=============================
/**
* 普通緩存獲取
*
* @param key
* 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通緩存放入
*
* @param key
* 鍵
* @param value
* 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}

/**
* 普通緩存放入並設置時間
*
* @param key
* 鍵
* @param value
* 值
* @param time
* 時間(秒) time要大於0 如果time小於等於0 將設置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 遞增
*
* @param key
* 鍵
* @param delta
* 要增加幾(大於0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大於0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 遞減
*
* @param key
* 鍵
* @param delta
* 要減少幾(小於0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大於0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}

// ================================Map=================================
/**
* HashGet
*
* @param key
* 鍵 不能為null
* @param item
* 項 不能為null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 獲取hashKey對應的所有鍵值
*
* @param key
* 鍵
* @return 對應的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
*
* @param key
* 鍵
* @param map
* 對應多個鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 並設置時間
*
* @param key
* 鍵
* @param map
* 對應多個鍵值
* @param time
* 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key
* 鍵
* @param item
* 項
* @param value
* 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key
* 鍵
* @param item
* 項
* @param value
* 值
* @param time
* 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 刪除hash表中的值
*
* @param key
* 鍵 不能為null
* @param item
* 項 可以使多個 不能為null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

/**
* 判斷hash表中是否有該項的值
*
* @param key
* 鍵 不能為null
* @param item
* 項 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

/**
* hash遞增 如果不存在,就會創建一個 並把新增后的值返回
*
* @param key
* 鍵
* @param item
* 項
* @param by
* 要增加幾(大於0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash遞減
*
* @param key
* 鍵
* @param item
* 項
* @param by
* 要減少記(小於0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}

// ============================set=============================
/**
* 根據key獲取Set中的所有值
*
* @param key
* 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 根據value從一個set中查詢,是否存在
*
* @param key
* 鍵
* @param value
* 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 將數據放入set緩存
*
* @param key
* 鍵
* @param values
* 值 可以是多個
* @return 成功個數
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 將set數據放入緩存
*
* @param key
* 鍵
* @param time
* 時間(秒)
* @param values
* 值 可以是多個
* @return 成功個數
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 獲取set緩存的長度
*
* @param key
* 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 移除值為value的
*
* @param key
* 鍵
* @param values
* 值 可以是多個
* @return 移除的個數
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================

/**
* 獲取list緩存的內容
*
* @param key
* 鍵
* @param start
* 開始
* @param end
* 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 獲取list緩存的長度
*
* @param key
* 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 通過索引 獲取list中的值
*
* @param key
* 鍵
* @param index
* 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 將list放入緩存
*
* @param key
* 鍵
* @param value
* 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 將list放入緩存
*
* @param key
* 鍵
* @param value
* 值
* @param time
* 時間(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 將list放入緩存
*
* @param key
* 鍵
* @param value
* 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 將list放入緩存
*
* @param key
* 鍵
* @param value
* 值
* @param time
* 時間(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根據索引修改list中的某條數據
*
* @param key
* 鍵
* @param index
* 索引
* @param value
* 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 移除N個值為value
*
* @param key
* 鍵
* @param count
* 移除多少個
* @param value
* 值
* @return 移除的個數
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM