試了很多種錯誤的方法,現將自己測試成功redis管道pipeline批量操作的方法和redis常用操作以及一些關於springboot+redis的概念分享給大家
開發環境準備:
spring boot 2.x
使用RedisTemplate 操作
springboot項目pom引入redis依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
Jedis是Redis官方推薦的面向Java的操作Redis的客戶端,而RedisTemplate是SpringDataRedis中對JedisApi的高度封裝。
SpringDataRedis相對於Jedis來說可以方便地更換Redis的Java客戶端,比Jedis多了自動管理連接池的特性,方便與其他Spring框架進行搭配使用.
redisTemplate
1. redisTemplate
- redisTemplate默認使用的是JDK序列化,但是可以主動設置
- redisTemplate執行兩條命令其實是在兩個連接裏完成的,因爲redisTemplate執行完一個命令就會對其關閉,但是redisTemplate額外爲什麼提供了RedisCallback和SessionCallBack兩個接口
StringRedisTemplate
- StringRedisTemplate繼承RedisTemplate,只是提供字符串的操作,複雜的Java對象還要自行處理
RedisCallback和SessionCallBack:
- 作用: 讓RedisTemplate進行回調,通過他們可以在同一條連接中執行多個redis命令
- SessionCalback提供了良好的封裝,優先使用它,redisCallback使用起來有點複雜(很多工作需要我們自己來完成)還是優先選擇SessionCalback
redis 基礎操作
redisTemplate模糊匹配刪除
String key = "userRole:*";
redisTemplate.delete(key);
Redis模糊查詢
可以通過Redis中keys命令進行獲取key值,具體命令格式:keys pattern
文中提到redis中允許模糊查詢的有3個通配符,分別是:*,?,[]
其中:
*
:通配任意多個字符
?
:通配單個字符
[]
:通配括號內的某一個字符
使用通配符拿到keys
Set<String> keysUserRole = redisTemplate.keys("userRole:" + "*");
批量查詢
Set<String> keysList = stringRedisTemplate.keys(keys);
List<String> strings = stringRedisTemplate.opsForValue().multiGet(keysList);
Redis管道(pipeline)流操作
總的來說Redis的管道可以在大量數據需要一次性操作完成的時候,使用Pipeline進行批處理,將多次操作合併成一次操作,可以減少鏈路層的時間消耗。
流水線:
redis的讀寫速度十分快,所以系統的瓶頸往往是在網絡通信中的延遲。
redis可能會在很多時候處於空閒狀態而等待命令的到達。
爲了解決這個問題,可以使用redis的流水線,流水線是一種通訊協議,類似一個隊列批量執行一組命令。
redis的管道 pipeline批量set
//耗時:309;
@RequestMapping(value = "/redisPipeline", method = RequestMethod.POST)
@ApiOperation(value = "redis的管道 pipeline 添加數據測試")
public void redistest(){
log.info("redistest開始");
// 開始時間
long start = System.currentTimeMillis();
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
List<String> result = redisTemplate.executePipelined(new SessionCallback() {
//執行流水線
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//批量處理的內容
for (int i = 0; i < 10000; i++) {
operations.opsForValue().set("redistest:" + "k" + i, "v" + i);
}
//注意這裏一定要返回null,最終pipeline的執行結果,纔會返回給最外層
return null;
}
});
// 結束時間
long end = System.currentTimeMillis();
log.info("運行時間:"+(end-start));
}
此處與未使用管道流水線操作做對比後續其他操作就不一一對比了
未使用流水線處理10000次請求:
//耗時:5692;
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test(){
// 開始時間
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
redisTemplate.opsForValue().set("k"+i,"v"+i);
}
// 結束時間
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
redis的管道 pipeline批量 delete
@RequestMapping(value = "/redisPipeline", method = RequestMethod.DELETE)
@ApiOperation(value = "redis的管道 pipeline刪除測試")
public void redisDeletetest(){
log.info("redistest開始");
// 開始時間
long start = System.currentTimeMillis();
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.executePipelined(new SessionCallback() {
//執行流水線
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//批量處理的內容
for (int i = 0; i < 20000; i++) {
//operations.opsForValue().set("redistest:"+"k"+i,"v"+i);
operations.delete("redistest:"+"k"+i);
System.out.println(i);
}
return null;
}
});
// 結束時間
long end = System.currentTimeMillis();
log.info("運行時間:"+(end-start));
}
redis的管道 pipeline批量 GET
/**
* redis 批量操作其中一種方式
* redis pipeline 管道技術
*/
@RequestMapping(value = "/redisPipeline", method = RequestMethod.GET)
@ApiOperation(value = "redis的管道 pipeline GET測試")
public void redisPipeline(){
RedisSerializer stringSerializer = new StringRedisSerializer();
stringRedisTemplate.setKeySerializer(stringSerializer);
stringRedisTemplate.setValueSerializer(stringSerializer);
List<String> keys=new ArrayList();
for (int i = 0; i < 200; i++) {
keys.add("redistest:"+"k"+i);
}
//調用 通道批量獲取
Map<String, Object> stringObjectMap = batchQueryByKeys(keys, true);
System.out.println(stringObjectMap.size());
}
/**
*
* @param keys
* @param useParallel 是否使用並行平行流
* @return
*/
public Map<String,Object> batchQueryByKeys(List<String> keys,Boolean useParallel){
if(null == keys || keys.size() == 0 ){
return null;
}
if(null == useParallel){
useParallel = true;
}
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(String key:keys) {
stringRedisConn.get(key);
}
return null;
}
});
if(null == results || results.size() == 0 ){return null;}
Map<String,Object> resultMap = null;
if(useParallel){
Map<String,Object> resultMapOne = Collections.synchronizedMap(new HashMap<String,Object>());
keys.parallelStream().forEach(t -> {
resultMapOne.put(t,results.get(keys.indexOf(t)));
});
resultMap = resultMapOne;
}else{
Map<String,Object> resultMapTwo = new HashMap<>();
for(String t:keys){
resultMapTwo.put(t,results.get(keys.indexOf(t)));
}
resultMap = resultMapTwo;
}
return resultMap;
}
在這裏要說明下我實現的管道pipeline批量獲取使用的是RedisCallback對象實現的,原因是我使用SessionCalback對象來實現時調用get方法總是獲取null最後也沒找到原因所以使用了RedisCallback對象來實現的批量獲取,如果有哪位大神瞭解SessionCalback對象的實現方法求指點一二 哈哈。。。。
批量操作multi和pipeline效率的比較
multi和pipeline的區別在於multi會將操作都即刻的發送至redis服務端queued起來,每條指令queued的操作都有一次通信開銷,執行exec時redis服務端再一口氣執行queued隊列裏的指令,pipeline則是在客戶端本地queued起來,執行exec時一次性的發送給redis服務端,這樣只有一次通信開銷。比如我有5個incr操作,multi的話這5個incr會有5次通信開銷,但pipeline只有一次。
所以在批量操作使用pipeline效率會更高。
關於文章提到的並行執行的流的說明請移步文章:
地址:https://blog.csdn.net/u011001723/article/details/52794455/