关键字联想词优化方案 - 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);
    }
}

4.4.6 测试

posted @ 2021-04-03 10:05  60kmph  阅读(1053)  评论(0编辑  收藏  举报