Elasticsearch 7.X Ik源码解读,及自定义远程动态词库

一、ik 远程词库

上篇文章对ik进行了整体的讲解,包括远程动态词库的讲解,但是上篇文章中是基于nginx+静态txt文件实现的,利用nginx 对文件修改后自动添加Last-Modified 的属性,这种方式也是官方推荐的方式:
在这里插入图片描述
官方推荐使用另一个工具更新这个.txt文件,既然我们都写另一个工具了,不如将词典也由另一个工具来提供,将数据存放在数据库中岂不更好管理。

下面我们就可以来事件以下,对ik不了解的可以参考我的上篇文章:

https://blog.csdn.net/qq_43692950/article/details/122274613

二、Ik部分源码解读

下面是ik源码的地址,大家可以先拉取下来:

https://github.com/medcl/elasticsearch-analysis-ik

我们找到MonitorrunUnprivileged方法,可以找到以下内容:
先看下官方给的注释:
在这里插入图片描述
这里已经很清楚的说明了执行的过程,下面就是具体的实施了。
在这里插入图片描述
向我们配制的地址先发送head的请求,并携带当前的Last-ModifyETags,刚开始没有及为null。
在这里插入图片描述
这里就是拿到返回的Last-ModifyETags字段,如果和当前的不一样,则重新加载词库,并更新当前的Last-ModifyETags,下面继续看Dictionary.getSingleton().reLoadMainDict()方法中。
在这里插入图片描述
这里注释也比较清楚,新开了一个Dictionary实例,下面进到Dictionary类中找到getRemoteWordsUnprivileged方法,这个就是获取远程词库的方法。

在这里插入图片描述

从这里可以看到,同样又向我们配制的地址进行了一个Get请求。并将拿到的返回以默认UTF-8的方式进行解码,这就是上篇文章中,说明的我们最好将nginx中的扩展词典的编码设为UTF-8的原因。

在这里插入图片描述
在这里插入图片描述

这个方法返回的list就是远程词典的内容,在解析时根据一行一个词的范式进行解析,所以在上篇文章中我们是一个词站一行进行划分的。

到这里,从上面的部分源码的解析应该就可以知道我们自定义的接口应该怎么实现了:

  • 首先创建一个head接口,在该接口中可以拿到es当前的modifiedeTag,我们可以做写自己的判断处理,也可以直接返回最新的modifiedeTag
  • 如果返回的modifiedeTag和当前ik中的不一致,则ik会再次调用这个url以Get的方式获取词库,并且我们的词库要按一行一个词的形式进行返回。

就这么两点,下面我们开始实践下吧。

三、自定义远程词库

我们将词库放在了mysql 中进行管理,首先新建一个ik 数据库,并新建dict分词表:

CREATE TABLE `dict` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dict_text` varchar(255) NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

在这里插入图片描述
下面新建一个SpringBoot 项目,其中引入依赖,和连接数据库这种就直接略过了。

在写给ik的接口前,先写一个添加分词的接口,作为后面的测试:

@Service
public class DictServiceImpl implements DictService {
    @Autowired
    DictDao dictDao;

    @Override
    public boolean addDict(String text) {
        DictEntity entity = new DictEntity(null, text, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        return dictDao.insert(entity) > 0;
    }
}
@RestController
public class DictController {
    @Autowired
    DictService dictService;

    //添加词汇
    @PutMapping("/dict/{text}")
    public String addVocabulary(@PathVariable String text) {
        return dictService.addDict(text) ? "success" : "err";
    }
}

上面两段写的很简单,就是做了个向数据库中添加数据的操作,并且每次添加都会给当前添加的时间。下面就主要看下提供给ik的接口了:

@Slf4j
@RestController
public class IkController {
    @Autowired
    IkService ikService;

    @RequestMapping(value = "/extDict", method = RequestMethod.HEAD)
    public String headExtDict(HttpServletRequest request, HttpServletResponse response) throws ParseException {
        String modified = request.getHeader("If-Modified-Since");
        String eTag = request.getHeader("If-None-Match");
        log.info("head请求,接收modified:{} ,eTag:{}", modified, eTag);
        String newModified = ikService.getCurrentNewModified();
        String newTag = String.valueOf(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(newModified).getTime());
        response.setHeader("Last-Modified", newModified);
        response.setHeader("ETag", newTag);
        return "success";
    }


    @RequestMapping(value = "/extDict", method = RequestMethod.GET)
    public String getExtDict(HttpServletRequest request, HttpServletResponse response) throws ParseException {
        String newModified = ikService.getCurrentNewModified();
        String newTag = String.valueOf(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(newModified).getTime());
        response.setHeader("Last-Modified", newModified);
        response.setHeader("ETag", newTag);
        return ikService.getDict();
    }
}
@Service
public class IkServiceImpl implements IkService {
    @Autowired
    DictDao dictDao;

    @Override
    public String getCurrentNewModified() {
        return dictDao.getCurrentNewModified();
    }

    @Override
    public String getDict() {
        List<DictEntity> updateList = dictDao.selectList(null);
        return String.join("\n", updateList.stream().map(DictEntity::getText).collect(Collectors.toSet()));
    }
}
@Mapper
@Repository
public interface DictDao extends BaseMapper<DictEntity> {
    @Select("select max(update_time) from dict")
    String getCurrentNewModified();
}

两个接口,一个是验证当前是否是最新的head接口,一个是获取分词的Get接口,注意这两个路由一定要写一致的,毕竟我们只给ik配制了一个接口。

head接口中我们直接返回了当前数据库最后更新的时间作为newModifiednewTag则为该时间的时间戳,并返还给ikik端判断不一致就会访问我们的get接口,再该接口直接返回了全部的分词给了ik,并已\n为分界。

下面启动我们的SpringBoot项目,并修改ik的配制文件ik\configIKAnalyzer.cfg.xml
在这里插入图片描述
重新启动es即可,在我们的SpringBoot 控制台就可以看到打印了:
在这里插入图片描述
第一次,ik中为空,所以我们接受的也为空。
在这里插入图片描述
下面再请求的,就是拿的我们返回的了。

四、分词测试

首先测试下小毕超这个我们自定义的词语:
在这里插入图片描述
可以看到并没有分词,下面我们调用添加词汇的接口,添加小毕超
在这里插入图片描述
然后再来测试,可能不会立即生效,上面在看源码时,就提到了,ik是每隔1分钟进行一次head请求,判断文件是否被修改,所以大概等待一分钟的时间,再次测试:
在这里插入图片描述
已经有了我们自定义的分词效果。
在这里插入图片描述
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小毕超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值