<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>

          面試官:請(qǐng)你談?wù)動(dòng)肦edis實(shí)現(xiàn)一個(gè)輕量級(jí)的搜索引擎!

          共 3122字,需瀏覽 7分鐘

           ·

          2021-06-02 09:24






          大家如果是做后端開發(fā)的,想必都實(shí)現(xiàn)過列表查詢的接口,當(dāng)然有的查詢條件很簡單,一條 SQL 就搞定了。   


          但有的查詢條件極其復(fù)雜,再加上庫表中設(shè)計(jì)的各種不合理,導(dǎo)致查詢接口特別難寫,然后加班什么的就不用說了(不知各位有沒有這種感受呢~)。


          下面以一個(gè)例子開始,這是某購物網(wǎng)站的搜索條件,如果讓你實(shí)現(xiàn)這樣的一個(gè)搜索接口,你會(huì)如何實(shí)現(xiàn)?


          當(dāng)然你說借助搜索引擎,像 Elasticsearch 之類的,你完全可以實(shí)現(xiàn)。但我這里想說的是,如果要你自己實(shí)現(xiàn)呢?



          從上圖中可以看出,搜索總共分為 6 大類,每大類中又分了各個(gè)子類。


          這中間,各大類條件之間是取的交集,各子類中有單選、多選、以及自定義的情況,最終輸出符合條件的結(jié)果集。


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


          1. 實(shí)現(xiàn)方案 1


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


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


          select ... from table_1left join table_2left join table_3left join (select ... from table_x where ...) tmp_1...where ...order by ...limit m,n


          代碼在測試環(huán)境跑了一把,結(jié)果好像都匹配上了,于是準(zhǔn)備上預(yù)發(fā)。這一上預(yù)發(fā),問題就開始暴露出來。


          預(yù)發(fā)為了盡可能的逼真線上環(huán)境,所以數(shù)據(jù)量自然而然要比測試大的多。所以這么一個(gè)復(fù)雜的 SQL,它的執(zhí)行效率可想而知。測試同學(xué)果斷把小 A 的代碼給打了回來。


          2. 實(shí)現(xiàn)方案 2


          總結(jié)了小 A 失敗的教訓(xùn),小 B 開始對(duì) SQL 進(jìn)行了優(yōu)化,先是通過了 explain 關(guān)鍵字進(jìn)行 SQL 性能分析,對(duì)該加索引的地方都加上了索引。


          同時(shí)將一條復(fù)雜 SQL 拆分成了多條 SQL,計(jì)算結(jié)果在程序內(nèi)存中進(jìn)行計(jì)算。


          偽代碼如下:

          $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, ...);

          這種方案從性能上明顯比第一種要好很多,可是在功能驗(yàn)收的時(shí)候,產(chǎn)品經(jīng)理還是覺得查詢速度不夠快。


          小 B 自己也知道,每次查詢都會(huì)向數(shù)據(jù)庫查詢多次,而且有些歷史原因,部分條件是做不到單表查詢的,所以查詢等待的時(shí)間是避免不了的。


          3. 實(shí)現(xiàn)方案 3


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


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


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


          具體方案,如圖所示:



          這里每個(gè)條件都事先將計(jì)算好的結(jié)果集 ID 存入對(duì)應(yīng)的 Key 中,選用的數(shù)據(jù)結(jié)構(gòu)是集合(Set)。


          查詢操作包括:


          • 子類單選:直接根據(jù)條件 Key,獲取對(duì)應(yīng)結(jié)果集。

          • 子類多選:根據(jù)多個(gè)條件 Key,進(jìn)行并集操作,獲取對(duì)應(yīng)結(jié)果集。

          • 最終結(jié)果:將獲取的所有子類結(jié)果集進(jìn)行交集操作,得到最終結(jié)果。


          這其實(shí)就是所謂的反向索引。這里會(huì)發(fā)現(xiàn),漏了一個(gè)價(jià)格的條件。從需求中可知,價(jià)格條件是個(gè)區(qū)間,并且是無窮舉的。


          所以上述的這種窮舉條件的 Key-Value 方式是做不到的。這里我們采用 Redis 的另一種數(shù)據(jù)結(jié)構(gòu)進(jìn)行實(shí)現(xiàn),有序集合(Sorted Set):



          將所有商品加入 Key 為價(jià)格的有序集合中,值為商品 ID,每個(gè)值對(duì)應(yīng)的分?jǐn)?shù)為商品價(jià)格的數(shù)值。


          這樣在 Redis 的有序集合中就可以通過 ZRANGEBYSCORE 命令,根據(jù)分?jǐn)?shù)(價(jià)格)區(qū)間,獲取相應(yīng)結(jié)果集。


          至此,方案三的優(yōu)化已全部結(jié)束,將數(shù)據(jù)的查詢與計(jì)算通過緩存的手段,進(jìn)行了分離。


          在每次查找時(shí),只需要簡單的查找 Redis 幾次就能得出結(jié)果。查詢速度上符合了驗(yàn)收的要求。


          4 擴(kuò)展


          4.1 分頁


          這里你或許發(fā)現(xiàn)了一個(gè)嚴(yán)重的功能缺陷,列表查詢?cè)趺茨軟]有分頁。是的,我們馬上來看 Redis 是如何實(shí)現(xiàn)分頁的。


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



          圖中藍(lán)色部分是以創(chuàng)建時(shí)間為分值的商品有序集合,藍(lán)色下方的結(jié)果集即為條件計(jì)算而得的結(jié)果,通過 ZINTERSTORE 命令,賦結(jié)果集權(quán)重為 0,商品時(shí)間結(jié)果為 1,取交集而得的結(jié)果集賦予創(chuàng)建時(shí)間分值的新有序集合。


          對(duì)新結(jié)果集的操作即能得到分頁所需的各個(gè)數(shù)據(jù):


          • 頁面總數(shù)為:ZCOUNT 命令。

          • 當(dāng)前頁內(nèi)容:ZRANGE 命令。

          • 若以倒序排列:ZREVRANGE命令。


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


          關(guān)于索引數(shù)據(jù)更新的問題,有兩種方式來進(jìn)行。一種是通過商品數(shù)據(jù)的修改,來即時(shí)觸發(fā)更新操作,一種是通過定時(shí)腳本來進(jìn)行批量更新。


          這里要注意的是,關(guān)于索引內(nèi)容的更新,如果暴力的刪除 Key,再重新設(shè)置 Key。


          因?yàn)?Redis 中兩個(gè)操作不會(huì)是原子性進(jìn)行的,所以中間可能存在空白間隙,建議采用僅移除集合中失效元素,添加新元素的方式進(jìn)行。


          4.3 性能優(yōu)化


          Redis 是內(nèi)存級(jí)操作,所以單次的查詢會(huì)很快。但是如果我們的實(shí)現(xiàn)中會(huì)進(jìn)行多次的 Redis 操作,Redis 的多次連接時(shí)間可能是不必要時(shí)間消耗。


          通過使用 MULTI 命令,開啟一個(gè)事務(wù),將 Redis 的多次操作放在一個(gè)事務(wù)中,最后通過 EXEC 來進(jìn)行原子性執(zhí)行。


          注意:這里所謂的事務(wù),只是將多個(gè)操作在一次連接中執(zhí)行,如果執(zhí)行過程中遇到失敗,是不會(huì)回滾的。


          5. 總結(jié)


          這里只是一個(gè)采用 Redis 優(yōu)化查詢搜索的一個(gè)簡單 Demo,和現(xiàn)有的開源搜索引擎相比,它更輕量,學(xué)習(xí)成本也相應(yīng)低些。


          其次,它的一些思想與開源搜索引擎是類似的,如果再加上詞語解析,也可以實(shí)現(xiàn)類似全文檢索的功能。



          轉(zhuǎn)自:jasonGeng88

          鏈接:github.com/jasonGeng88/blog

          1、Intellij IDEA這樣 配置注釋模板,讓你瞬間高出一個(gè)逼格!
          2、吊炸天的 Docker 圖形化工具 Portainer,必須推薦給你!
          3、最牛逼的 Java 日志框架,性能無敵,橫掃所有對(duì)手!
          4、把Redis當(dāng)作隊(duì)列來用,真的合適嗎?
          5、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
          6、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會(huì)?
          7、Spring中毒太深,離開Spring我居然連最基本的接口都不會(huì)寫了

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 23
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日韩欧美高清 | 操熟女网站 | 天堂一区 | 日本免费黄色小视频 | 青青草天天爽 |