自动补全也能够称为自动提示,相似于在百度搜索的输入框中输入一个字符,下面会提示多个关键词供参考。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); } }