关键字联想词优化方案 - Redis + Trie树
关键字联想词优化方案 - Redis + Trie树
4.4.1现有问题
每次输入关键字的时候都会调用后台获取数据,频繁的发起请求查询数据库,并且是模糊查询
随着联想词的搜索越来越频繁,每次从数据库查询非常占用数据库资源,同时查询效率比较低
4.4.2 优化方案Trie树
优化方案:
- 数据能够缓存到redis
- 构造Trie树数据结构,高效查询数据
Trie树:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排
序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点
是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树
高。
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。
- 每个单词的公共前缀作为一个字符节点保存。
工具类TrieNode
package com.heima.search.model;
import java.util.HashMap;
import java.util.Map;
public class TrieNode {
public char var;
public boolean isWord;
public Map<Character,TrieNode> children = new HashMap<>();
public boolean containLongTail = false;
public TrieNode(){}
public TrieNode(char c){
TrieNode node = new TrieNode();
node.var = c;
}
}
package com.heima.search.model;
import java.util.ArrayList;
import java.util.List;
public class Trie {
private TrieNode root;
public Trie(){
root = new TrieNode();
root.var = ' ';
}
/**
* 插入trie树
* @param word
*/
public void insert(String word){
TrieNode ws = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
if(!ws.children.keySet().contains(c)){
ws.children.put(c,new TrieNode(c));
}
ws = ws.children.get(c);
}
ws.isWord = true;
}
/**
* 查询trie树
* @param prefix
* @return
*/
public List<String> startWith(String prefix){
List<String> match = new ArrayList<>();
TrieNode ws = root;
for(int i = 0; i < prefix.length(); i++){
char c = prefix.charAt(i);
if(!ws.children.keySet().contains(c)) return match;
ws = ws.children.get(c);
if(!ws.containLongTail){
for (char cc : ws.children.keySet()){
match.add(prefix+cc);
}
}else{
//包含长尾词 从map中取
}
}
return match;
}
public static void main(String[] args) {
Trie t = new Trie();
t.insert("黑马");
List<String> ret = t.startWith("黑");
System.out.println(ret);
}
}
4.4.3 搭建redis环境
(1)使用docker创建容器
拉取镜像
docker pull redis
创建容器并设置开机自启
docker run -d --name redis --restart=always -p 6379:6379 redis --requirepass "1234qwer"
(2)在common模块中集成redis
新建redis.properties文件
#redis config
spring.redis.host=192.168.200.130
spring.redis.port=6379
#spring.redis.password=123456
spring.redis.password=1234qwer
spring.redis.timeout=90000
#连接池的最大数据库连接数
spring.redis.lettuce.pool.max-active=8
#连接池的最大空闲数
spring.redis.lettuce.pool.max-idle=8
#连接池的最大建立连接等待时间 -1 为无限等待
spring.redis.lettuce.pool.max-wait=-1
#连接池的最大空闲数 0 表示不限制
spring.redis.lettuce.pool.min-idle=0
新建配置类
package com.heima.common.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:redis.properties")
@ConfigurationProperties(prefix = "spring.redis")
public class RedisConfiguration {
}
(3)在搜索微服务中集成redis
package com.heima.search.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.heima.common.redis")
public class RedisConfig {
}
4.4.4 改造现有service
在ApAssociateWordsService中新增V2版本的方法
/**
联想词 V2
@param userSearchDto
@return
*/
ResponseResult searchV2(UserSearchDto userSearchDto);
实现方法
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public ResponseResult searchV2(UserSearchDto dto) {
//1.从缓存中获取数据
String assoStr = redisTemplate.opsForValue().get("associate_list");
List<ApAssociateWords> apAssociateWords = null;
if(StringUtils.isNotEmpty(assoStr)){
//2.缓存中存在,直接拿数据
apAssociateWords = JSON.parseArray(assoStr, ApAssociateWords.class);
}else {
//3.缓存中不存在,从数据库中获取数据,存储到redis
apAssociateWords = list();
redisTemplate.opsForValue().set("associate_list", JSON.toJSONString(apAssociateWords));
}
//4.构建trie数据结构,从trie中获取数据,封装返回
Trie t = new Trie();
for (ApAssociateWords apAssociateWord : apAssociateWords) {
t.insert(apAssociateWord.getAssociateWords());
}
List<String> ret = t.startWith(dto.getSearchWords());
List<ApAssociateWords> resultList = new ArrayList<>();
for (String s : ret) {
ApAssociateWords aaw = new ApAssociateWords();
aaw.setAssociateWords(s);
resultList.add(aaw);
}
return ResponseResult.okResult(resultList);
}
4.4.5 改造现有controller
新建v2版本的控制器
package com.heima.search.controller.v2;
import com.heima.apis.search.ApAssociateWordsControllerApi;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.service.ApAssociateWordsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v2/associate")
public class ApAssociateWordsV2Controller implements ApAssociateWordsControllerApi {
@Autowired
private ApAssociateWordsService apAssociateWordsService;
@PostMapping("/search")
@Override
public ResponseResult search(@RequestBody UserSearchDto dto) {
return apAssociateWordsService.searchV2(dto);
}
}