<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 Redis 實現(xiàn)一個輕量級的搜索引擎,牛逼啊 !

          共 3246字,需瀏覽 7分鐘

           ·

          2021-05-10 01:01


          來源 | http://github.com/jasonGeng88/blog/blob/master/201706/redis-search.md


          場景

          大家如果是做后端開發(fā)的,想必都實現(xiàn)過列表查詢的接口,當然有的查詢條件很簡單,一條 SQL 就搞定了,但有的查詢條件極其復雜,再加上庫表中設(shè)計的各種不合理,導致查詢接口特別難寫,然后加班什么的就不用說了(不知各位有沒有這種感受呢~)。

          下面以一個例子開始,這是某購物網(wǎng)站的搜索條件,如果讓你實現(xiàn)這樣的一個搜索接口,你會如何實現(xiàn)?(當然你說借助搜索引擎,像 Elasticsearch 之類的,你完全可以實現(xiàn)。但我這里想說的是,如果要你自己實現(xiàn)呢?

          必備技能!聊聊二維碼掃碼登錄的原理

          從上圖中可以看出,搜索總共分為6大類,每大類中又分了各個子類。這中間,各大類條件之間是取的交集,各子類中有單選、多選、以及自定義的情況,最終輸出符合條件的結(jié)果集。

          好了,既然需求很明確了,我們就開始來實現(xiàn)。

          實現(xiàn)1

          率先登場是小A同學,他是寫 SQL 方面的“專家”。小A信心滿滿的說:“不就是一個查詢接口嗎?看著條件很多,但憑著我豐富的 SQL 經(jīng)驗,這點還是難不倒我的。”

          于是乎就寫出了下面這段代碼(這里以 MYSQL 為例):

          select ... from table_1
          left join table_2
          left join table_3
          left join (select ... from table_x where ...) tmp_1
          ...
          where ...
          order by ...
          limit m,n

          代碼在測試環(huán)境跑了一把,結(jié)果好像都匹配上了,于是準備上預發(fā)。這一上預發(fā),問題就開始暴露出來。預發(fā)為了盡可能的逼真線上環(huán)境,所以數(shù)據(jù)量自然而然要比測試大的多。所以這么一個復雜的 SQL,它的執(zhí)行效率可想而知。測試同學果斷把小A的代碼給打了回來。

          實現(xiàn)2

          總結(jié)了小A失敗的教訓,小B開始對SQL進行了優(yōu)化,先是通過了explain關(guān)鍵字進行SQL性能分析,對該加索引的地方都加上了索引。同時將一條復雜SQL拆分成了多條SQL,計算結(jié)果在程序內(nèi)存中進行計算。

          偽代碼如下:

          $result_1 = query('select ... from table_1 where ...');
          $result_2 = query('select ... from table_2 where ...');
          $result_3 = query('select ... from table_3 where ...');
          ...

          $result = array_intersect($result_1$result_2$result_3, ...);

          這種方案從性能上明顯比第一種要好很多,可是在功能驗收的時候,產(chǎn)品經(jīng)理還是覺得查詢速度不夠快。小B自己也知道,每次查詢都會向數(shù)據(jù)庫查詢多次,而且有些歷史原因,部分條件是做不到單表查詢的,所以查詢等待的時間是避免不了的。

          實現(xiàn)3

          小C從上面的方案中看到了優(yōu)化的空間。他發(fā)現(xiàn)小B在思路上是沒問題的,將復雜條件拆分,計算各個子維度的結(jié)果集,最后將所有的子結(jié)果集進行一個匯總合并,得到最終想要的結(jié)果。

          于是他突發(fā)奇想,能否事先將各個子維度的結(jié)果集給緩存起來,這要查詢的時候直接去取想要的子集,而不用每次去查庫計算。

          這里小C采用 Redis 來存儲緩存數(shù)據(jù),用它的主要原因是,它提供了多種數(shù)據(jù)結(jié)構(gòu),并且在 Redis 中進行集合的交并集操作是一件很容易的事情。

          具體方案,如圖所示:

          這里每個條件都事先將計算好的結(jié)果集ID存入對應的key中,選用的數(shù)據(jù)結(jié)構(gòu)是集合(Set)。查詢操作包括:

          • 子類單選:直接根據(jù)條件 key,獲取對應結(jié)果集;
          • 子類多選:根據(jù)多個條件 Key,進行并集操作,獲取對應結(jié)果集;
          • 最終結(jié)果:將獲取的所有子類結(jié)果集進行交集操作,得到最終結(jié)果;

          *這其實就是所謂的反向索引。*

          這里會發(fā)現(xiàn),漏了一個價格的條件。從需求中可知,價格條件是個區(qū)間,并且是無窮舉的。所以上述的這種窮舉條件的 Key-Value 方式是做不到的。這里我們采用 Redis 的另一種數(shù)據(jù)結(jié)構(gòu)進行實現(xiàn),有序集合(Sorted Set):

          說實話,DataGrip真得牛逼,只是你不會用而已~

          將所有商品加入 Key 為價格的有序集合中,值為商品ID,每個值對應的分數(shù)為商品價格的數(shù)值。這樣在 Redis 的有序集合中就可以通過ZRANGEBYSCORE命令,根據(jù)分數(shù)(價格)區(qū)間,獲取相應結(jié)果集。

          至此,方案三的優(yōu)化已全部結(jié)束,將數(shù)據(jù)的查詢與計算通過緩存的手段,進行了分離。在每次查找時,只需要簡單的查找 Redis 幾次就能得出結(jié)果。查詢速度上符合了驗收的要求。

          擴展

          分頁

          這里你或許發(fā)現(xiàn)了一個嚴重的功能缺陷,列表查詢怎么能沒有分頁。是的,我們馬上來看 Redis 是如何實現(xiàn)分頁的。

          分頁主要涉及排序,這里簡單起見,就以創(chuàng)建時間為例。

          如圖所示:

          來給IDEA換個酷炫的主題吧!這幾個都有點哇塞啊

          圖中藍色部分是以創(chuàng)建時間為分值的商品有序集合,藍色下方的結(jié)果集即為條件計算而得的結(jié)果,通過ZINTERSTORE命令,賦結(jié)果集權(quán)重為0,商品時間結(jié)果為1,取交集而得的結(jié)果集賦予創(chuàng)建時間分值的新有序集合。對新結(jié)果集的操作即能得到分頁所需的各個數(shù)據(jù):

          • 頁面總數(shù)為:ZCOUNT命令
          • 當前頁內(nèi)容:ZRANGE命令
          • 若以倒序排列:ZREVRANGE命令

          數(shù)據(jù)更新

          關(guān)于索引數(shù)據(jù)更新的問題,有兩種方式來進行。一種是通過商品數(shù)據(jù)的修改,來即時觸發(fā)更新操作,一種是通過定時腳本來進行批量更新。這里要注意的是,關(guān)于索引內(nèi)容的更新,如果暴力的刪除 Key,再重新設(shè)置 Key。因為 Redis 中兩個操作不會是原子性進行的,所以中間可能存在空白間隙,建議采用僅移除集合中失效元素,添加新元素的方式進行。

          性能優(yōu)化

          Redis 是內(nèi)存級操作,所以單次的查詢會很快。但是如果我們的實現(xiàn)中會進行多次的 Redis 操作,Redis 的多次連接時間可能是不必要時間消耗。通過使用MULTI命令,開啟一個事務(wù),將 Redis 的多次操作放在一個事務(wù)中,最后通過EXEC來進行原子性執(zhí)行(*注意:這里所謂的事務(wù),只是將多個操作在一次連接中執(zhí)行,如果執(zhí)行過程中遇到失敗,是不會回滾的 *)。

          總結(jié)

          這里只是一個采用 Redis 優(yōu)化查詢搜索的一個簡單 Demo,和現(xiàn)有的開源搜索引擎相比,它更輕量,學習成本頁相應低些。其次,它的一些思想與開源搜索引擎是類似的,如果再加上詞語解析,也可以實現(xiàn)類似全文檢索的功能。

          最后,未完,待續(xù)。。。

          1、最牛逼的 Java 日志框架,性能無敵,橫掃所有對手!
          2、把Redis當作隊列來用,真的合適嗎?
          3、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
          4、牛逼哄哄的 BitMap,到底牛逼在哪?
          5、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會?
          6、30個IDEA插件總有一款適合你
          7、Spring中毒太深,離開Spring我居然連最基本的接口都不會寫了

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一区二区三区四区五区六区七区 | 北条麻妃在线一区二区三区精品 | 日本www色 | 影音先锋婷婷 | 国产中文字幕在线观看 |