以前有兩篇文章介紹了Redis中BitMap的用途和用法,有些小夥伴說這個東西好像沒太大的用途,今天我給你們分享一個在實際場景中常常會碰到的狀況,那就是多屬性篩選
拿京東舉例,以下圖php
咱們要找一款電子琴,牌子有:雅馬哈、卡西歐,價格有各類區間,各類顏色、不一樣的音色數。mysql
現現在動不動就得整點高併發啥的,直接用mysql咱們是否是真的扛不住?在前面加一層cache?怎麼加?各類屬性的組合存到一個屬性組合成的key中?如何相對實時的更新屬性?redis
以前的文章我有介紹過redis
中setbit
和bitop
的使用方法,就是將某一位標記爲1或者0表明存在不存在,而後利用bitop
進行AND
或者OR
計算,獲得咱們想要的結果,今天咱們就從零開始打造一個「高性能」的屬性篩選器!sql
假設如今咱們有三款電子琴,一款雅馬哈、兩款卡西歐,具體的屬性表格爲:小程序
ID | 品牌 | 顏色 | 價格 | 音色 |
---|---|---|---|---|
1 | 雅馬哈 | 紅色 | 1000 | 100 |
2 | 卡西歐 | 黑色 | 2000 | 150 |
3 | 卡西歐 | 白色 | 2000 | 200 |
咱們將屬性+屬性值組合爲key,ID爲對應的某位偏移量,這樣使用下面的語句初始化數據到redis併發
//初始化品牌 $redis->setBit('brand-雅馬哈', 1, 1); $redis->setBit('brand-卡西歐', 2, 1); $redis->setBit('brand-卡西歐', 3, 1); //初始化顏色 $redis->setBit('color-紅色', 1, 1); $redis->setBit('color-黑色', 2, 1); $redis->setBit('color-白色', 3, 1); //初始化價格 $redis->setBit('price-1000', 1, 1); $redis->setBit('price-2000', 2, 1); $redis->setBit('price-2000', 3, 1); ......
我想要搜一下,2000元的白色卡西歐,只須要這樣函數
$redis->bitop('AND', 'cacheKey', 'brand-卡西歐', 'color-白色'); $redis->bitop('AND', 'cacheKey1', 'cacheKey', 'price-2000');
結果cacheKey1的二進制形式爲001
,這樣咱們就知道搜索的結果是ID爲3的商品。高併發
然而redis並無提供查詢哪些位位1的方法,咱們只能經過get方法將內容獲取出來,本身處理。提供一段參考代碼:性能
$bit = $redis->get($cacheKey); $bitLength = strlen($bit); //redis返回的數據長度可能不是8的倍數,爲了方便解包,咱們將它補齊 while($bitLength % 8 != 0) { $bitLength++; } $bit = str_pad($bit, $bitLength, pack('N', 0)); $bit = unpack('N*', $bit); $bit = array_filter($bit); $ids = []; foreach($bit as $k => $b) { $bitPos = []; while($b) { $bin = sprintf('%032s', decbin($b)); $bitPos[] = strrpos($bin, '1'); $b &= ($b - 1); } foreach($bitPos as $pos) { $ids[] = ($k - 1) * 32 + $pos; } }
我在本地試了一下,20W的數據(單個屬性-屬性值redis佔用大概24k),同時搜索4個屬性只須要不到10ms,固然現實中確定沒這麼理想,但效果必定不會太差。優化
若是商品和屬性過多,對redis的寫入壓力是至關大的(商品數屬性數屬性值數的寫入數),咱們能夠先自行組合成字符串,而後單個屬性-屬性值對寫入,具體實現細節就不寫了,就是利用pack函數打包。