1. 程式人生 > >一次使用 Redis 優化查詢效能的實踐

一次使用 Redis 優化查詢效能的實踐

應用背景

有一個應用需要上傳一組ID到伺服器來查詢這些ID所對應的資料,資料庫中儲存的資料量是7千萬,每次上傳的ID數量一般都是幾百至上千數量級別。

以前的解決方案

  1. 資料儲存在Oracle中,為ID建立了索引;
  2. 查詢時,先將這些上傳的ID資料儲存到臨時表中,然後用表關聯的方法來查詢。

這樣做的優點是減少了查詢次數(不用每個ID都查詢一次),減少了解析SQL的時間(只需要執行1次查詢SQL,但是多了插入資料的SQL處理時間)。

但是這樣的設計仍然存在巨大的提升空間,當併發查詢的數量增加時,資料庫的響應就會很久。雖然建立了索引,但是每個ID查詢的時間複雜度仍是O(logn)級別的,那麼總的查詢時間複雜度就應該是m*O(logn)。不知道Oracle對錶關聯查詢有做了哪些優化,但應該也是改變不了時間複雜度的級別。

解決方法

一遇到讀資料庫存在瓶頸的問題,首先想到的就是要用記憶體資料庫,用快取來解決。首選 Redis,因為Redis是一種提供了豐富資料結構的key-value資料庫,value可以儲存STRING(字串)、HASH(雜湊),LIST(列表),ZSET(有序集)。

首先需要將資料的儲存改成 key-value 架構。簡單的做法就是一個ID對應一個字串的 Value。但是一個 ID 可以對應多條資料,而且一條資料內又可以包含多個欄位。這時候就需要將這些資料重新組合一下,拼在一起,或者是採用列表、雜湊或集合來儲存 Value。

Redis內部採用 HashTable(雜湊表)來實現key-value的資料結構,是一種空間佔用較高的資料結構。而我的應用場景又是ID有幾千萬規模的,如果按上述方法,使用每個ID作為key,那麼記憶體的消耗將是巨大的。每個key-vaulue結構,

Redis本身的維護開銷就要80幾字節,即便value儲存的是純數字(會使用long型別,佔用4個位元組),也依然很大,1000萬的資料,就要佔用快1G記憶體。

使用兩級Hash優化記憶體

依據官方文件的記憶體優化方法,以及這篇文章 節約記憶體:Instagram的Redis實踐,建議對ID分段作為key,並使用 hash 來儲存第一級 key 的 value,第二級儲存較少的資料量(推薦1000),因此第二級的key使用ID的後3位。

為了節約記憶體,Redis預設使用ziplist(壓縮列表)來儲存HASH(雜湊),LIST(列表),ZSET(有序集)這些資料結構。當某些條件被滿足時,自動轉換成 hash table(雜湊表),linkedlist(雙端列表),skiplist(跳錶)。

ziplist是用一個數組來實現的雙向連結串列結構,顧名思義,使用ziplist可以減少雙向連結串列的儲存空間,主要是節省了連結串列指標的儲存,如果儲存指向上一個連結串列結點和指向下一個連結串列結點的指標需要8個位元組,而轉化成儲存上一個結點長度和當前結點長度在大多數情況下可以節省很多空間(最好的情況下只需2個位元組)。但是每次向連結串列增加元素都需要重新分配記憶體。—— 引用自這裡的描述

檢視 Redis 的 .conf 檔案,可以檢視到轉換條件的設定資訊。

# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64

# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

ziplist 查詢的時間複雜度是 O(N),但是資料量較少,第二級Hash的查詢速度依然在O(1)級別。

對第二級Hash儲存的資料再編碼

在我的應用場景中每個ID對應的資料可以有很多個欄位,這些欄位有很多實際上是型別資料,儲存的也是ID。為了進一步節約記憶體,對這些使用數字作為ID的欄位,採用base62編碼(0-9,A-Z,a-z),這樣可以使這些ID的字元長度變短,進一步減少在Redis中第二級hash需要儲存的資料量,從而減少Redis佔用的記憶體。

使用Lua指令碼來處理批量操作

由於每次查詢都上傳幾百上千個ID,如果對這些ID,都單獨呼叫一次HGET命令,那麼一次查詢就需要上千次TCP通訊,速度很慢。這個時候最好的方法就是一次性將所有的查詢都發送到 Redis Server,然後在 Redis Server 處再依次執行HGET命令,這個時候就要用到 Redis 的Pipelining(管道)Lua 指令碼(需要 Redis 2.6以上版本)。這兩項功能可以用來處理批量操作。由於Lua指令碼更簡單好用,因此我就直接選用Lua指令碼。

Redis Lua 指令碼具有原子性,執行過程會鎖住 Redis Server,因此 Redis Server 會全部執行完 Lua 腳本里面的所有命令,才會去處理其他命令請求,不用擔心併發帶來的共享資源讀寫需要加鎖問題。實際上所有的 Redis 命令都是原子的,執行任何 Redis 命令,包括 info,都會鎖住 Redis Server。

不過需要注意的是:

為了防止某個指令碼執行時間過長導致Redis無法提供服務(比如陷入死迴圈),Redis提供了lua-time-limit引數限制指令碼的最長執行時間,預設為5秒鐘(見.conf配置檔案)。當指令碼執行時間超過這一限制後,Redis將開始接受其他命令但不會執行(以確保指令碼的原子性,因為此時指令碼並沒有被終止),而是會返回"BUSY"錯誤——引用自這裡的描述

遇到這種情況,就需要使用 SCRIPT KILL 命令來終止 Lua 指令碼的執行。因此,千萬要注意 Lua 指令碼不能出現死迴圈,也不要用來執行費時的操作。

效能分析

測試環境:

  • 記憶體:1333MHz
  • CPU:Intel Core i3 2330M 2.2GHz
  • 硬碟:三星 SSD

實驗基本設定:

  1. 將7000萬資料按照上面描述的方法,使用兩級Hash以及對資料再編碼,儲存到Redis中。
  2. 模擬資料請求(沒有通過HTTP請求,直接函式呼叫),查詢資料,生成響應的JSON資料。

(資料僅供參考,因為未真正結合Web伺服器進行測試)

儲存空間

使用上述方法,對Redis的記憶體優化效果非常好。

Redis 查詢效能

實驗設定:

  1. 模擬每次查詢500個ID,分批次連續查詢。用於模擬測試併發情況下的查詢效能。

響應速度與查詢的資料量,幾乎是線性相關。30s 的時間就可以處理2000次請求,100W個ID的查詢。由於Oracle速度實在太慢,就不做測試了。

查詢效能 vs. 儲存的資料量

實驗設定:

  1. 連續查詢1W個ID,每次500個,分20次。用於測試Redis中儲存的資料量對查詢效能的影響。

查詢速度受儲存資料量的影響較小。當儲存的資料量較多時,第二級hash儲存的資料量就會更多,因此查詢時間會有略微的上升,但依然很快。

參考文獻

相關推薦

使用 Redis 優化查詢效能實踐

應用背景 有一個應用需要上傳一組ID到伺服器來查詢這些ID所對應的資料,資料庫中儲存的資料量是7千萬,每次上傳的ID數量一般都是幾百至上千數量級別。 以前的解決方案 資料儲存在Oracle中,為ID建立了索引;查詢時,先將這些上傳的ID資料儲存到臨時表中,然後用表關聯的方法來查詢。這樣做的優點是減少了查詢次

漫談千億級資料優化實踐資料優化實錄

0x00 前言 即使沒有資料傾斜,千億級的資料查詢對於系統也是一種巨大負擔,對於資料開發來說,如何來優化它,既是挑戰,也是機遇! 在上一篇文章 《漫談千億級資料優化實踐:資料傾斜(純乾貨)》中,我們分享了一些在千億級資料優化實踐中和資料傾斜相關的內容

線上JVM調優實踐,FullGC40/天到10天一優化過程

通過這一個多月的努力,將FullGC從40次/天優化到近10天才觸發一次,而且YoungGC的時間也減少了一半以上,這麼大的優化,有必要記錄一下中間的調優過程。 對於JVM垃圾回收,之前一直都是處於理論階段,就知道新生代,老年代的晉升關係,這些知識僅夠應付面試

python程式的效能優化說開去

一門程式語言入門是容易的,至少大家都知道從hello world開始。但這次效能優化的經歷告訴我,“換語言”這件事是有門檻的。 這次效能優化是針對資料入庫流程中的一個環節(brief)做的。 我們常說解決問題重要,發現問題更重要。沒錯,這次發現問題就佔用了我

線上 OOM 和效能優化

大家好,我是鴨血粉絲(大家會親切的喊我 「阿粉」),是一位喜歡吃鴨血粉絲的程式設計師,回想起之前線上出現 OOM 的場景,畢竟當時是第一次遇到這麼 緊髒 的大事,要好好記錄下來。 1 事情回顧 在某次週五,通過 Grafana 監控,發現線上環境突然出現CPU和記憶體飆升的情況: 但是看到網路輸出和輸入流

程式碼優化實踐(模板方法+策略+工廠方法模式)

## 前言 好久沒分享工作總結啦,今天來一份程式碼優化總結。用模板方法+策略+工廠方法模式優化了程式碼,耐心點看完,應該對大家有幫助的~ 本文已經收錄到github > https://github.com/whx123/JavaHome **公眾號:撿田螺的小男孩** ### 優化程式碼前 先來

偽*sql查詢結果不一致的

vid eight frame and sna parameter -exec video http 調試代碼發現,兩個操作最後都會調用同一個方法。傳入的參數一致,查詢結果卻不一致。 直接說問題原因:我的兩個操作是aop環繞通知先拿到傳入參數以後,校驗是否需要往另一張表中插

hql語句比對查詢單表中多個字段

end nta 不能 ase color poll 存儲 pen ike 前端輸入客戶名稱,在使用hql查詢時,要同時比對表中,客戶名稱,客戶簡稱,拼音簡寫,客戶編碼等多個字段 hql寫法 String fdCustomerName=cv.poll("docMain.fdC

PHP優化極速、賽車源碼平臺搭建架設案例

文件 日誌 ces www 限制 服務器 自動增加 重新 但是 一、案例分析。極速、賽車源碼平臺搭建架Q:2947702644我們可以想到,既然是訪問緩慢,有時候直接訪問不了,以前是沒問題的,到現在就突然出現了問題,那必定是我們的nginx與php響應不過來導致的,原因可能

記錄redis數據庫搭建過程並詳細說明配置

保存 hashing sys 不能 led active 成功 ber cto redis.conf配置文件詳解 Redis默認不是以守護進程的方式運行,可以通過該配置項修改,使用yes啟用守護進程daemonize no 當Redis以守護進程方式運行時,Redis默

SQLSERVER2008R2資料庫查詢超時問題處理

資料庫環境: WINDOWS2008R2 SQLSERVER2008R2 應用程式環境: REDHAT6.5 TOMCAT JAVA   一、故障現象 某系統應用查詢超時 相關SQL: SELECT v.OBarcode Ba

redis被刪庫跑路,索要0.6比特幣

還在和媳婦兒逛街,突然同事打來電話說redis庫被清空。 於是,媳婦兒說你真是烏鴉嘴,早上還說redis如何被提權的事情。 怎麼剛出來就碰上了? 會不會是你搞的? 於是無形中又背鍋了。 見×××姐如此著急,邊安慰,邊提醒讓同事查一下,是什麼時候發生的事情,受害面積有多大? 但是×××姐很鎮靜的說,

記憶體溢位查詢的問題

情景:今天測試環境發現應用出現記憶體溢位的問題。這是從來沒有出現過的問題,在關閉此次版本新上線的功能後仍發現Perm區的記憶體持續在增長。 jdk版本:1.7 環境:linux ====================================================== 起因:測試環境

Redis任務佇列積壓的問題分析與解決

目前的流程:   兩個Redis: Redis1: 儲存詞條的summary資訊 Redis2:任務佇列,用於暫存Redis中沒有summary,需要進行處理獲取summary, 佇列用的Redis的list結構   兩個程序: 1、 程序1:服務

Minecraft遊戲伺服器搭建實踐經歷

Minecraft簡介 Minecraft是一款沙盒遊戲,整個遊戲沒有劇情,玩家在遊戲中自由建設和破壞,透過像積木一樣來對元素進行組合與拼湊,輕而易舉的就能製作出小木屋、城堡甚至城市,玩家可以通過自己創造的作品來體驗上帝一般的感覺。在這款遊戲裡,不僅可以單人娛樂,還可以多人聯機,玩家也可以安裝一些模組來增加

Redis未授權訪問漏洞入侵分析

0x00 簡介 通過之前部署的蜜罐系統,我近日在滴滴雲上捕獲到了一個通過 Redis 未授權訪問漏洞進行入侵的蠕蟲樣本,該樣本的特點是使用 Python 指令碼進行橫向漏洞掃描,並且具有程序隱藏和解除安裝某些雲上安全產品的功能。 0x01 樣本分析 通過蜜罐日誌得到攻擊者入侵開始

Redis記憶體詭異增長

  一、現象 例項名:r-bp1cxxxxxxxxxd04(主從) 時間:2017-11-16 12:26~12:27 問題:一分鐘記憶體上漲了2G,如下圖所示: 鍵值規模:6000萬左右 二、Redis記憶體分析 1.記憶體組成 上圖中的記憶體統計的是Re

攜程Redis遷移容器後Slowlog“異常”分析

容器化對於Redis自動化運維效率、資源利用率方面都有巨大提升,攜程在對Redis在容器上效能和穩定性進行充分驗證後,啟動了生產Redis遷移容器化的專案。其中第一批次兩臺宿主機,第二批次五臺宿主機。 本次“異常”是第二批次遷移過程中發現的,排查過程一波三折,最終得出讓人吃驚的結論

couchdb模糊查詢功能

記一次couchdb模糊查詢 在使用fabric的過程中,避免不了使用couchdb,記錄一次模糊匹配的使用記錄,方便下次查詢。 在couchdb中存的資料結構: type Account struct

Redis bitmap導致的miss問題

背景描述 大致需求:指令碼批量匯入使用者資料到redis中,使用bitmap標記使用者是否在匯入的白名單中。使用者量級 億。 過程描述  執行指令碼匯入白名單使用者 5分鐘後發現redi