上一篇文章《Redis爲何這麼快》介紹了Redis性能評估工具,以及Redis高性能的緣由。詳細請見: 這篇咱們將從業務的視角,講解下影響Redis性能的因素以及如何提高Redis使用的性能。html
以最經常使用場景緩存爲例,流量從用戶到Redis Server的過程以下所示:redis
從上面時序圖能夠看出,用戶請求經過Redis client經由網路到達Redis Server。後端
所以在考慮使用Redis性能的時候要從客戶端和服務端兩個角度考慮。 對於業務方來講, 合理使用Redis特性比Redis服務器的優化可操做性更強,也更容易得到好的效果。 緩存
下面將從業務優化和服務器優化兩個方面介紹Redis的優化。 服務器
查詢本地redis的延遲一般低於1毫秒,而查詢同一個數據中心的redis的延遲一般低於5毫秒。也就是說,網絡傳輸的損耗爲實際操做用時的5倍。微信
所以,從客戶端角度,如何減小網絡耗時相當重要。網絡
Jedis是Java語言使用最多的Redis客戶端。 Jedis支持直連和鏈接池的兩種方式。架構
直連的方式:併發
# 1. 生成一個Jedis對象,這個對象負責和指定Redis實例進行通訊
Jedis jedis = new Jedis("127.0.0.1", 6379);
# 2. jedis執行set操做
jedis.set("hello", "world");
# 3. jedis執行get操做 value="world"
String value = jedis.get("hello");複製代碼
所謂直連是指Jedis每次都會新建TCP 鏈接,使用後再斷開鏈接。 咱們都知道新建TCP鏈接通過3次握手,釋放TCP鏈接通過4次揮手,新建和回收是很是耗時操做。對於頻繁訪問Redis的場景顯然不是高效的使用方式。運維
Jedis也提供了鏈接池的方式。
// common-pool鏈接池配置,這裏使用默認配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); // 初始化Jedis鏈接池
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedis = null; try {
// 1. 從鏈接池獲取jedis對象
jedis = jedisPool.getResource();
// 2. 執行操做
jedis.get("hello");
} catch (Exception e) {
logger.error(e.getMessage(),e);
} finally {
if (jedis != null) {
// 若是使用JedisPool,close操做不是關閉鏈接,表明歸還鏈接池
jedis.close();
}
}複製代碼
經過鏈接池,減小創建和斷開TCP鏈接的時間開銷。 另外,redis提供了其餘三種方式,經過減小請求次數提高性能。 (1) 批量操做的命令,如mget,mset等 (2) pipeline方式 (3) Lua腳本
使用redis-benchmark在Intel(R) Xeon(R) CPU E5520 @ 2.27GHz對比pipeline(每次16個命令)和普通請求。
使用pipeline的狀況:
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -P 16 -q
SET: 552028.75 requests per second
GET: 707463.75 requests per second
LPUSH: 767459.75 requests per second
LPOP: 770119.38 requests per second
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz (without pipelining)複製代碼
無pipeline的狀況:
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
SET: 122556.53 requests per second
GET: 123601.76 requests per second
LPUSH: 136752.14 requests per second
LPOP: 132424.03 requests per second複製代碼
從benchmark的結果能夠看出,使用pipeline技術比沒有使用性能提高5-10倍左右。
Jedis支持Pipeline特性,咱們知道 Redis提供了mget、mset方法,可是並無提供mdel方法,若是想實現這個功 能,能夠藉助Pipeline來模擬批量刪除,雖然不會像mget和mset那樣是一個原 子命令,可是在絕大數場景下可使用。
public void mdel(List<String> keys) {
Jedis jedis = new Jedis("127.0.0.1");
// 1)生成pipeline對象 Pipe
line pipeline = jedis.pipelined();
// 2)pipeline執行命令,注意此時命令並未真正執行
for (String key : keys) {
pipeline.del(key);
}
// 3)執行命令
pipeline.sync();
}複製代碼
將del命令封裝到pipeline中,能夠調用pipeline.del(String key),此時不會真正的 執行命令。
使用pipeline.sync()完成這次pipeline對象的調用。
除了pipeline.sync(),還可使用pipeline.syncAndReturnAll()將 pipeline的命令進行返回。
pipeline提高性能的一個緣由是減小了命令總的RTT時間(往返時延), 另一方面減小 總的系統調用的次數。
RTT(Round-Trip Time): 往返時延。在計算機網絡中它是一個重要的性能指標,表示從發送端發送數據開始,到發送端收到來自接收端的確認(接收端收到數據後便當即發送確認),總共經歷的時延。往返延時(RTT)由三個部分決定:即鏈路的傳播時間、末端系統的處理時間以及路由器的緩存中的排隊和處理時間。其中,前面兩個部分的值做爲一個TCP鏈接相對固定,路由器的緩存中的排隊和處理時間會隨着整個網絡擁塞程度的變化而變化。因此RTT的變化在必定程度上反映了網絡擁塞程度的變化。簡單來講就是發送方從發送數據開始,到收到來自接受方的確認信息所經歷的時間。
Redis原生支持Lua語言,而且提供了經過客戶端執行lua腳本的命令。
好比咱們能夠用Lua腳本在低版本的Redis上實現分佈式鎖。
local current current = redis.call('incr',KEYS[1])
if tonumber(current) == 1
then
redis.call('expire',KEYS[1], ARGV[1])
end
return current複製代碼
調用EVAL命令能夠傳入不定的KEY和ARGS的值, 這些值被能夠經過KEY[i]和ARGV[i]訪問對應的入參,而且經過return返回執行結果。
更多的Lua腳本,會在其餘文章中介紹。
能夠關注微信公衆號:非典型理科男,查看所有文章列表閱讀Lua腳本相關的文章。
pipeline和Lua比較:
(1) 返回結果不一樣: pipeline會把命令執行結果都返回出來, lua腳本只有一個返回結果。
(2) 使用場景不一樣: lua腳本能夠提供複雜邏輯運算而且提供了緩存腳本的功能,提高像原生命令同樣的性能體驗。 所以lua腳本能夠用在處理邏輯複雜,不須要返回或者只返回操做結果的場景。 pipeline用在合併命令減小執行開銷和redis server壓力的場景下。
在使用pipeline時有幾個注意事項:
(1) pipeline執行命令雖然沒有明確的執行命令數量的限制,可是建議限制執行命令數量。 執行命令數量過多一方面佔用網絡帶寬,另外一方面會阻塞客戶端。
影響Redis Server性能主要有硬件、數據分佈和配置有關。
Redis喜歡下面的硬件條件:
包大小影響Redis的相應速度。 以太網網數據包在 1500 bytes 如下時, 將多條命令包裝成 pipelining 能夠大大提升效率。事實上,處理 10 bytes,100 bytes, 1000 bytes 的請求時候,吞吐量是差很少的,詳細能夠見下圖。
因此,當大value(>10k)存在時要及時優化掉。
參考文檔:
回覆「資料」,免費獲取 一份獨家嘔心整理的技術資料!
回覆「資料」,免費獲取 一份獨家嘔心整理的技術資料!