使用 Redis 實現自動補全功能

自動補全也能夠稱爲自動提示,相似於在百度搜索的輸入框中輸入一個字符,下面會提示多個關鍵詞供參考。php

假設:redis

輸入 a,會自動提示 apple、application、acfun、adobe;
輸入 ap,提示 apple、application;
輸入 ac,提示 acfun;
輸入 ad,提示 adobe。數據庫

看到這個功能需求,很簡單的方式就是在數據庫中直接使用 SQL LIKE 操做符來匹配,可是性能確定是不行的。使用 Redis 卻是一個很不錯的方案。bash

Redis 是 KV 型數據庫,查詢都是基於 key 的,key 值必須具備惟一性。app

雖然經過 key 的通配符方式也能夠實現自動提示,可是這種方法在 Redis 中數據量較大時也存在性能問題。性能

回到上面的話題,本質就是我但願經過 a 獲取 apple、application、acfun、adobe,經過 ap 獲取 apple、application,以此類推。那麼,須要查詢的值就應該做爲 key,查詢結果應該做爲 value。遵循這個思路,能夠利用 Redis 的集合存儲這些值。爲何不用列表,而使用集合?由於我但願查詢出來的詞不會重複,而集合的特性就是元素惟一,性質決定用途,因此就使用集合。通常狀況下,查詢出來的詞也會按照熱度頻率什麼的排序,須要排序的話,就要使用 Redis 的有序集合。this

以 word: 爲前綴,方便管理和區別(保證惟一性),後面跟上要查詢的值,例如:word:a(其實這樣仍是沒法確保惟一性,就假設這個 key 在 Redis 中是惟一的)。
向集合中添加 關鍵詞:spa

bashZADD word:a 0 apple 0 application 0 acfun 0 adobe
ZADD word:ap 0 apple 0 application
ZADD word:app 0 apple 0 application
ZADD word:appl 0 apple 0 application
ZADD word:apple 0 apple
ZADD word:appli 0 application
……

上面尚未添加完成,完整添加的代碼以下:code

php<?php

namespace Blog\Redis;

use \Redis;


class Suggest {

    const PREFIX = 'word:';

    protected $redis = null;


    public function __construct(Redis $redis) {
        $this->redis = $redis;
    }


    public function add($word) {
        $len = mb_strlen($word, 'UTF-8');
        for ($i = 1; $i <= $len; $i++) {
            $sub = mb_substr($word, 0, $i, 'UTF-8');
            $this->redis->zAdd(self::PREFIX . $sub, 0, $word);
        }
    }

}

看以上代碼,應該發現我給有序集合每一個元素的 score 爲 0,這個意義何在?不該該給每一個詞不一樣的 score,以便給搜索結果排序嗎?確實如此,可是這樣會存在一個問題:某一個詞會出如今多個集合中,若是該詞的熱點要增長,那麼就須要同時更新多個集合中該詞的 score。這顯然是不合理的。排序

那麼到底應該怎麼辦哩?再建立一個有序集合,專門用於存放這些詞和它們的 score。最後,將查詢結合和這個記錄熱度的集合作交集,就能得出按熱度排列後的結果。

設置這些詞的熱度:

bashZADD word_scores 100 apple 80 adobe 70 application 60 acfun

交集:

bashZINTERSTORE word_result 2 word_scores word:a WEIGHTS 1 1
ZRANGE word_result 0 -1 withscores

具體 PHP 實現代碼以下,僅供參考:

php<?php

namespace Blog\Redis;

use \Redis;


class Suggest {

    const PREFIX = 'word:';
    const WORDS_PREFIX = 'word_scores';
    const RESULT_PREFIX = 'word_result';

    protected $redis = null;


    public function __construct(Redis $redis) {
        $this->redis = $redis;
    }


    public function add($word) {
        $len = mb_strlen($word, 'UTF-8');
        for ($i = 1; $i <= $len; $i++) {
            $sub = mb_substr($word, 0, $i, 'UTF-8');
            $this->redis->zAdd(self::PREFIX . $sub, 0, $word);
        }
    }


    public function incScore($word, $score = 1) {
        return $this->redis->zIncrBy(self::WORDS_PREFIX, $score, $word);
    }


    public function search($keyword, $stop = 5) {
        $this->redis->zInter(self::RESULT_PREFIX, array(self::PREFIX . $keyword, self::WORDS_PREFIX), array(1, 1));
        return $this->redis->zRevRange(self::RESULT_PREFIX, 0, $stop, true);
    }

}
相關文章
相關標籤/搜索