1、背景java
redis經過tcp來對外提供服務,client經過socket鏈接發起請求,每一個請求在命令發出後會阻塞等待redis服務器進行處理,處理完畢後將結果返回給client。web
其實和一個http的服務器相似,一問一答,請求一次給一次響應。而這個過程在排除掉redis服務自己作複雜操做時的耗時的話,能夠看到最耗時的就是這個網絡傳輸過程。每個命令都對應了發送、接收兩個網絡傳輸,假如一個流程須要0.1秒,那麼一秒最多隻能處理10個請求,將嚴重製約redis的性能。redis
在不少場景下,咱們要完成一個業務,可能會對redis作連續的多個操做,譬如庫存減1、訂單加1、餘額扣減等等,這有不少個步驟是須要依次連續執行的。spring
能夠引入pipeline了,pipeline管道就是解決執行大量命令時、會產生大量同窗次數而致使延遲的技術。緩存
其實原理很簡單,pipeline就是把全部的命令一次發過去,避免頻繁的發送、接收帶來的網絡開銷,redis在打包接收到一堆命令後,依次執行,而後把結果再打包返回給客戶端。服務器
根據項目中的緩存數據結構的實際狀況,數據結構爲string類型的,使用RedisTemplate的multiGet方法;數據結構爲hash,使用Pipeline(管道),組合命令,批量操做redis。2、操做網絡
RedisTemplate的multiGet的操做數據結構
針對數據結構爲String類型app
示例代碼socket
List<String> keys = new ArrayList<>(); for (Book e : booklist) { String key = generateKey.getKey(e); keys.add(key); } List<Serializable> resultStr = template.opsForValue().multiGet(
2.RedisTemplate的Pipeline使用
爲何Pipelining這麼快?
先看看原來的多條命令,是如何執行的:
Redis Client->>Redis Server: 發送第1個命令
Redis Server->>Redis Client: 響應第1個命令
Redis Client->>Redis Server: 發送第2個命令
Redis Server->>Redis Client: 響應第2個命令
Redis Client->>Redis Server: 發送第n個命令
Redis Server->>Redis Client: 響應第n個命令
Pipeling機制是怎樣的呢:
Redis Client->>Redis Server: 發送第1個命令(緩存在Redis Client,未即時發送)
Redis Client->>Redis Server: 發送第2個命令(緩存在Redis Client,未即時發送)
Redis Client->>Redis Server: 發送第n個命令(緩存在Redis Client,未即時發送)
Redis Client->>Redis Server: 發送累積的命令
Redis Server->>Redis Client: 響應第一、二、n個命令
示例代碼
package cn.chinotan.controller; import cn.chinotan.service.RedisService; import lombok.extern.java.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @program: test * @description: redis批量數據測試 * @author: xingcheng * @create: 2019-03-16 16:26 **/ @RestController @RequestMapping("/redisBatch") @Log public class RedisBatchController { @Autowired StringRedisTemplate redisTemplate; @Autowired Map<String, RedisService> redisServiceMap; /** * VALUE緩存時間 3分鐘 */ public static final Integer VALUE_TIME = 1; /** * 測試列表長度 */ public static final Integer SIZE = 100000; @GetMapping(value = "/test/{model}") public Object hello(@PathVariable("model") String model) { List<Map<String, String>> saveList = new ArrayList<>(SIZE); List<String> keyList = new ArrayList<>(SIZE); for (int i = 0; i < SIZE; i++) { Map<String, String> objectObjectMap = new HashMap<>(); String key = String.valueOf(i); objectObjectMap.put("key", key); StringBuilder sb = new StringBuilder(); objectObjectMap.put("value", sb.append("value").append(i).toString()); saveList.add(objectObjectMap); // 記錄所有key keyList.add(key); } // 獲取對應的實現 RedisService redisService = redisServiceMap.get(model); long saveStart = System.currentTimeMillis(); redisService.batchInsert(saveList, TimeUnit.MINUTES, VALUE_TIME); long saveEnd = System.currentTimeMillis(); log.info("插入耗時:" + (saveEnd - saveStart) + " ms"); // 批量獲取 long getStart = System.currentTimeMillis(); List<String> valueList = redisService.batchGet(keyList); long getEnd = System.currentTimeMillis(); log.info("獲取耗時:" + (getEnd - getStart) + " ms"); return valueList; } }
package cn.chinotan.controller; import cn.chinotan.service.RedisService; import lombok.extern.java.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @program: test * @description: redis批量數據測試 * @author: xingcheng * @create: 2019-03-16 16:26 **/ @RestController @RequestMapping("/redisBatch") @Log public class RedisBatchController { @Autowired StringRedisTemplate redisTemplate; @Autowired Map<String, RedisService> redisServiceMap; /** * VALUE緩存時間 3分鐘 */ public static final Integer VALUE_TIME = 1; /** * 測試列表長度 */ public static final Integer SIZE = 100000; @GetMapping(value = "/test/{model}") public Object hello(@PathVariable("model") String model) { List<Map<String, String>> saveList = new ArrayList<>(SIZE); List<String> keyList = new ArrayList<>(SIZE); for (int i = 0; i < SIZE; i++) { Map<String, String> objectObjectMap = new HashMap<>(); String key = String.valueOf(i); objectObjectMap.put("key", key); StringBuilder sb = new StringBuilder(); objectObjectMap.put("value", sb.append("value").append(i).toString()); saveList.add(objectObjectMap); // 記錄所有key keyList.add(key); } // 獲取對應的實現 RedisService redisService = redisServiceMap.get(model); long saveStart = System.currentTimeMillis(); redisService.batchInsert(saveList, TimeUnit.MINUTES, VALUE_TIME); long saveEnd = System.currentTimeMillis(); log.info("插入耗時:" + (saveEnd - saveStart) + " ms"); // 批量獲取 long getStart = System.currentTimeMillis(); List<String> valueList = redisService.batchGet(keyList); long getEnd = System.currentTimeMillis(); log.info("獲取耗時:" + (getEnd - getStart) + " ms"); return valueList; } }
package cn.chinotan.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.StringRedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.SessionCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @program: test * @description: redis管道操做 * @author: xingcheng * @create: 2019-03-16 16:47 **/ @Service("pipe") public class RedisPipelineService implements RedisService { @Autowired StringRedisTemplate redisTemplate; @Override public void batchInsert(List<Map<String, String>> saveList, TimeUnit unit, int timeout) { /* 插入多條數據 */ redisTemplate.executePipelined(new SessionCallback<Object>() { @Override public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException { for (Map<String, String> needSave : saveList) { redisTemplate.opsForValue().set(needSave.get("key"), needSave.get("value"), timeout,unit); } return null; } }); } @Override public List<String> batchGet(List<String> keyList) { /* 批量獲取多條數據 */ List<Object> objects = redisTemplate.executePipelined(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection redisConnection) throws DataAccessException { StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection; for (String key : keyList) { stringRedisConnection.get(key); } return null; } }); List<String> collect = objects.stream().map(val -> String.valueOf(val)).collect(Collectors.toList()); return collect; } }
package cn.chinotan.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @program: test * @description: redis普通遍歷操做 * @author: xingcheng * @create: 2019-03-16 16:47 **/ @Service("generic") public class RedisGenericService implements RedisService { @Autowired StringRedisTemplate redisTemplate; @Override public void batchInsert(List<Map<String, String>> saveList, TimeUnit unit, int timeout) { for (Map<String, String> needSave : saveList) { redisTemplate.opsForValue().set(needSave.get("key"), needSave.get("value"), timeout,unit); } } @Override public List<String> batchGet(List<String> keyList) { List<String> values = new ArrayList<>(keyList.size()); for (String key : keyList) { String value = redisTemplate.opsForValue().get(key); values.add(value); } return values; } }
測試結果:
能夠看到性能提高了20倍之多
基於其特性,它有兩個明顯的侷限性: