自動補全也能夠稱爲自動提示,相似於在百度搜索的輸入框中輸入一個字符,下面會提示多個關鍵詞供參考。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); } }