如何修改Series和DataFrame类型中的元素值_Redis的HSCAN命令中COUNT参数的失效场景
前提
?
這是一篇Redis命令使用不當?shù)牟瓤咏?jīng)歷分享
?
筆者最近在做一個項目時候使用Redis存放客戶端展示的訂單列表,列表需要進行分頁。由于筆者先前對Redis的各種數(shù)據(jù)類型的使用場景并不是十分熟悉,于是先入為主地看到Hash類型的數(shù)據(jù)結構,假定:
USER_ID:1 ORDER_ID:ORDER_XX: {"amount": "100","orderId":"ORDER_XX"} ORDER_ID:ORDER_YY: {"amount": "200","orderId":"ORDER_YY"}感覺Hash類型完全滿足需求實現(xiàn)的場景。然后想當然地考慮使用HSCAN命令進行分頁,引發(fā)了后面遇到的問題。
SCAN和HSCAN命令
SCAN命令如下:
SCAN?cursor?[MATCH?pattern]?[COUNT?count]?[TYPE?type]//?返回值如下://?1.?cursor,數(shù)值類型,下一輪的起始游標值,0代表遍歷結束//?2.?遍歷的結果集合,列表SCAN命令在Redis2.8.0版本中新增,時間復雜度計算如下:每一輪遍歷的時間復雜度為O(1),所有元素遍歷完畢直到游標cursor返回0的時間復雜度為O(N),其中N為集合內元素的數(shù)量。SCAN是針對整個Database內的所有KEY進行漸進式的遍歷,它不會一直阻塞Redis,也就是使用SCAN命令遍歷KEY的性能有可能會優(yōu)于KEY *命令。對于Hash類型有一個衍生的命令HSCAN專門用于遍歷Hash類型及其相關屬性(Field)的字段:
HSCAN?key?cursor?[MATCH?pattern]?[COUNT?count]//?返回值如下://?1.?cursor,數(shù)值類型,下一輪的起始游標值,0代表遍歷結束//?2.?遍歷的結果集合,是一個映射筆者當時沒有仔細查閱Redis的官方文檔,想當然地認為Hash類型的分頁簡單如下(偏激一點假設每頁數(shù)據(jù)只有1條):
//?第一頁HSCAN?USER_ID:1?0?COUNT?1????<=?這里認為返回的游標值為1//?第二頁HSCAN?USER_ID:1?1?COUNT?1????<=?這里認為返回的游標值為0,結束迭代實際上,執(zhí)行的結果如下:
HSCAN?USER_ID:1?0?COUNT?1//?結果0??ORDER_ID:ORDER_XX?{"amount":?"100","orderId":"ORDER_XX"}?ORDER_ID:ORDER_YY?{"amount":?"200","orderId":"ORDER_YY"}也就是在第一輪遍歷的時候,KEY對應的所有Field-Value已經(jīng)全量返回。筆者嘗試增加哈希集合KEY = USER_ID:1里面的元素,但是數(shù)據(jù)量相對較大的時候,依然沒有達到預期的分頁效果;另一個方面,嘗試修改命令中的COUNT值,發(fā)現(xiàn)無論如何修改COUNT值都不會對遍歷的結果產(chǎn)生任何影響(也就是還是在第一輪迭代返回全部結果)。百思不得其解的情況下,只能仔細翻閱官方文檔尋找解決方案。在SCAN命令的COUNT屬性描述中找到了原因:
r-h-p-1
簡單翻譯理解一下:
SCAN命令以及其衍生命令并不保證每一輪迭代返回的元素數(shù)量,但是可以使用COUNT屬性憑經(jīng)驗調整SCAN命令的行為。COUNT指定每次調用應該完成遍歷的元素的數(shù)量,以便于遍歷集合,「本質只是一個提示值」(just a hint,hint意思為暗示)。
注意第3點,這個就是在Hash集合中使用HSCAN命令COUNT屬性失效的根本原因。Redis配置中有兩個和Hash類型ziplist編碼的相關配置值:
hash-max-ziplist-entries 512hash-max-ziplist-value 64在如下兩個條件之一滿足的時候,Hash集合的編碼會由ziplist會轉成dict(字典類型編碼是哈希表,即hashtable):
- 當Hash集合中的數(shù)據(jù)項(即Field-Value對)的「數(shù)目超過512」的時候。
- 當Hash集合中插入的任意一個Field-Value對中的「Value長度超過64」的時候。
當Hash集合的編碼會由ziplist會轉成dict,Redis為Hash類型的內存空間占用優(yōu)化相當于失敗了,降級為相對消耗更多內存的字典類型編碼,這個時候,HSCAN命令COUNT屬性才會起效。
案例驗證
?
查詢Redis中Key的編碼類型的命令為:object encoding $KEY
?
簡單驗證一下上一節(jié)得出的結論,寫入一個測試數(shù)據(jù)如下:
//?70個XHSET?USER_ID:2?ORDER_ID:ORDER_XXX?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX???//?70個YHSET?USER_ID:2?ORDER_ID:ORDER_YYY?YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY接著開始測試一下HSCAN命令:
//?查看編碼object?encoding?USER_ID:2//?編碼結果hashtable//?第一輪迭代HSCAN?USER_ID:2?0?COUNT?1//?第一輪迭代返回結果2??ORDER_ID:ORDER_YYY?YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY//?第二輪迭代?HSCAN?USER_ID:2?2?COUNT?10??ORDER_ID:ORDER_XXX?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX測試案例中故意讓兩個值的長度為70,大于64,也就是讓Hash集合轉變?yōu)閐ict(hashtable)類型,使得COUNT屬性生效。但是,這種做法是放棄了Redis為Hash集合的內存優(yōu)化。此前驗證的是hash-max-ziplist-value配置項的臨界值,還可以編寫一個例子驗證hash-max-ziplist-entries的臨界值:
//?下面的代碼需要確保本地安裝了Redis,并且引入Redis的客戶端依賴:io.lettuce:lettuce-core:5.3.3.RELEASEpublic?class?HashScanCountSample?{????static?String?KEY?=?"HS";????static?int?THRESHOLD?=?513;????static?int?COUNT?=?5;????public?static?void?main(String[]?args)?throws?Exception?{????????ScanArgs?scanArgs?=?new?ScanArgs().limit(COUNT);????????RedisURI?redisUri?=?RedisURI.create("127.0.0.1",?6379);????????RedisClient?redisClient?=?RedisClient.create(redisUri);????????RedisCommands?commands?=?redisClient.connect().sync();????????commands.del(KEY);????????int?total?=?10;????????for?(int?i?=?1;?i?<=?total;?i++)?{????????????String?fv?=?String.valueOf(i);????????????commands.hset(KEY,?fv,?fv);????????}????????ScanCursor?scanCursor?=?ScanCursor.INITIAL;????????int?idx?=?1;????????processScan(total,?scanArgs,?commands,?scanCursor,?idx);????????for?(int?i?=?11;?i?<=?THRESHOLD;?i++)?{????????????String?fv?=?String.valueOf(i);????????????commands.hset(KEY,?fv,?fv);????????}????????scanCursor?=?ScanCursor.INITIAL;????????total?=?THRESHOLD;????????idx?=?1;????????processScan(total,?scanArgs,?commands,?scanCursor,?idx);????}????private?static?void?processScan(int?total,?ScanArgs?scanArgs,?RedisCommands?commands,?ScanCursor?scanCursor,?int?idx)?{????????System.out.println(String.format("%d個F-V的HS的編碼:%s",?total,?commands.objectEncoding(KEY)));????????System.out.println(String.format("%d個F-V的HS進行HSCAN...",?total));????????MapScanCursor?result;????????while?(!(result?=?commands.hscan(KEY,?scanCursor,?scanArgs)).isFinished())?{????????????System.out.println(String.format("%d個F-V的HS進行HSCAN第%d次遍歷,size=%d",?total,?idx,?result.getMap().size()));????????????scanCursor?=?new?ScanCursor(result.getCursor(),?result.isFinished());????????????idx++;????????}????????System.out.println(String.format("%d個F-V的HS進行HSCAN第%d次遍歷,size=%d",?total,?idx,?result.getMap().size()));????}}//?某次輸出結果10個F-V的HS的編碼:ziplist10個F-V的HS進行HSCAN...10個F-V的HS進行HSCAN第1次遍歷,size=10......513個F-V的HS的編碼:hashtable513個F-V的HS進行HSCAN...513個F-V的HS進行HSCAN第1次遍歷,size=5......513個F-V的HS進行HSCAN第92次遍歷,size=6513個F-V的HS進行HSCAN第93次遍歷,size=6513個F-V的HS進行HSCAN第94次遍歷,size=5這里看到,最終遍歷513個F-V的Hash類型的KEY,最多每次能遍歷出9個F-V對,這里只是其中一次的測試數(shù)據(jù),也就是說COUNT值即使固定為一個常量,但是遍歷出來的數(shù)據(jù)集合中的元素數(shù)量不一定為COUNT,但是大多數(shù)情況下為COUNT。
?
不過可以推斷出一點,如果Hash中的F-V對的數(shù)量小于512,并且所有的V的長度都比較短,HSCAN命令會一次遍歷出該KEY的所有的F-V對
?
顯然,HSCAN命令天然不是為了做數(shù)據(jù)分頁而設計的,而是為了漸進式的迭代(也就是如果需要迭代的集合很大,也不會一直阻塞Redis服務)。所以筆者最后放棄了使用HSCAN命令,尋找更適合做數(shù)據(jù)分頁查詢的其他Redis命令。
小結
通過這簡單的踩坑案例,筆者得到一些經(jīng)驗:
- 切忌先入為主,使用中間件的時候要結合實際的場景。
- 使用工具的之前要仔細閱讀工具的使用手冊。
- 要通過一些案例驗證自己的猜想或者推導的結果。
HSCAN命令中的COUNT屬性的功能和Redis服務的配置項hash-max-ziplist-value、hash-max-ziplist-entries以及KEY的編碼類型息息相關。Redis提供的API十分豐富,這些API的版本兼容性做得十分優(yōu)秀,后面應該還會遇到更多的踩坑經(jīng)驗。
總結
以上是生活随笔為你收集整理的如何修改Series和DataFrame类型中的元素值_Redis的HSCAN命令中COUNT参数的失效场景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全屋软装设计公司太子家居软装做得怎么样?
- 下一篇: echarts symbol 回调函数_