java lambda 画蛇添足_技术史上的画蛇添足: Redis HGETALL 排序问题
一個系列
在技術史上,有很多彼時的 bug 歷經歲月錘煉最后化身 feature 的事兒發生。
在閱讀一些熱門開源框架代碼時,經常能發現狗血的、難以置信的、童真的、幽默的、這也能寫的,代碼。
以上這些,都值得吐槽。吐槽是為了解構“權威”,此處的權威是應用廣泛,少有人提出異議的框架工具、架構設計、代碼組織等。通過解構,進一步了解軟件工程的本質。
今天要說的,是 Redis …… 里面一個很小的特性。
如果時間允許的話,我想寫成一個系列。
一個例子
我們利用 Redis Hash 實現一個簡單的需求——
存儲一些 tweets,然后讀取出來,順序地展示在頁面上。
技術選型我們采用 Redis 和 Python。
第一步,順序存儲 tweets
import redis
tweets = [{
"id": 250075927172759552,
"geo": null,
"retweeted": false,
"in_reply_to_user_id": null,
"place": null,
...
},
...
]
rc = redis.Redis(host='localhost', port='6379', db=0)
for tweet in tweets:
rc.hsetnx('tweets', tweet['id'], tweet)
登入 redis,查看:
127.0.0.1:6379> HGETALL 'tweets'
1) "250075927172759552"
2) "{...}"
3) "250075927172759553"
4) "{...}"
5) "250075927172759554"
6) "{...}"
7) "250075927172759555"
8) "{...}"
看上去是順序輸出,有點譜,繼續下邊的邏輯實現。
第二步,順序展示 tweets
import redis
rc = redis.Redis(host='localhost', port='6379', db=0)
tweets = rc.hgetall('tweets')
...
這時候我們會發現,tweets 變成了這樣:
tweets = {
'250075927172759552': {...},
'250075927172759555': {...},
...
}
一個無序的 dict。
顯然,這完成不了我們的順序輸出的需求。
難道,是 redis-py 這個框架實現錯了嗎?
Redis HGETALL 和它的 Python 實現
redis-py 的 hgetall
'HGETALL': lambda r: r and pairs_to_dict(r) or {},
def pairs_to_dict(response):
"Create a dict given a list of key/value pairs"
it = iter(response)
return dict(izip(it, it))
一個明顯的字典轉換。
HGETALL 的文檔描述及源碼
再來看 HGETALL 的文檔——
Returns all fields and values of the hash stored at key. In the returned value, every field name is followed by its value, so the length of the reply is twice the size of the hash.
沒有提到任何和排序有關的描述,也即,不保證返回結果的排序。
那,上邊看到的順序輸出是什么意思?Redis 官方客戶端畫蛇添足了?再者說,一個嚴肅的 Hash,key 怎么可能是順序存儲的?
我們看下 HGETALL 的源碼:
https://github.com/antirez/redis/blob/4.0/src/t_hash.c#L37
/* Check the length of a number of objects to see if we need to convert a
* ziplist to a real hash. Note that we only check string encoded objects
* as their string length can be queried in constant time. */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
int i;
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
...
從源碼可以看到,默認情況下,Redis 的 Hash 是使用 ziplist 進行存儲的,當超出一定限制后,再改為一個正統的 hash 進行存儲。ziplist 是一個雙向鏈表,所以是順序的,這就是上邊我們看到的。下面我們看看在什么情況下會進行轉換。
redis Hash 的 ziplist 和 hash 的轉換條件
# 哈希對象只包含一個鍵和值都不超過 64 個字節的鍵值對
127.0.0.1:6379> HSET test_hash test_key "value's length less than 64 bytes"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING test_hash
"ziplist"
# 向哈希對象添加一個新的鍵值對,鍵的長度為 66 字節
127.0.0.1:6379> HSET test_hash long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1
# 編碼改變
127.0.0.1:6379> OBJECT ENCODING test_hash
"hashtable"
# 創建一個包含 512 個鍵值對的哈希對象
127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "numbers"
(nil)
127.0.0.1:6379> HLEN numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"ziplist"
# 再向哈希對象添加一個新的鍵值對,使得鍵值對的數量變成 513 個
127.0.0.1:6379> HMSET numbers "key" "value"
OK
127.0.0.1:6379> HLEN numbers
(integer) 513
# 編碼改變
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"
在兩種情況下,Hash 會使用 ziplist 進行存儲:
哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于 64 字節;
哈希對象保存的鍵值對數量小于 512 個;
其中,64 字節和 512 分別可以通過配置文件中的 server.hash_max_ziplist_value、server.hash_max_ziplist_entries 進行配置。
結語
通過閱讀文檔和源碼,我們可以知道,乍看 Redis Hash 是順序存儲這個假象可以很好地被解釋。redis 的文檔寫的也沒什么毛病。Redis 默認使用 ziplist 來存儲 Hash、List、ZSet 這些數據結構,目的只有一個:節約內存。
而在本例中,Redis 的這種做法引發了一個錯覺,這是完全合理的,一個 Cache,當然要追求性能。
感謝 Redis 作者及開源貢獻者!
References
總結
以上是生活随笔為你收集整理的java lambda 画蛇添足_技术史上的画蛇添足: Redis HGETALL 排序问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魔兽怀旧服服务器怎么修改,魔兽世界怀旧服
- 下一篇: 乒乓球单循环赛_乒乓球单循环赛积分表(比