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

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

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

我们找到Monitor的runUnprivileged方法,可以找到以下内容:
先看下官方给的注释:

这里已经很清楚的说明了执行的过程,下面就是具体的实施了。

向我们配制的地址先发送head的请求,并携带当前的Last-Modify和ETags,刚开始没有及为null。

这里就是拿到返回的Last-Modify和ETags字段,如果和当前的不一样,则重新加载词库,并更新当前的Last-Modify和ETags,下面继续看Dictionary.getSingleton().reLoadMainDict()方法中。

这里注释也比较清楚,新开了一个Dictionary实例,下面进到Dictionary类中找到getRemoteWordsUnprivileged方法,这个就是获取远程词库的方法。

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


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

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

  • 首先创建一个head接口,在该接口中可以拿到es当前的modified和eTag,我们可以做写自己的判断处理,也可以直接返回最新的modified和eTag。
  • 如果返回的modified和eTag和当前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 updateList = dictDao.selectList(null);
        return String.join("n", updateList.stream().map(DictEntity::getText).collect(Collectors.toSet()));
    }
}
@Mapper
@Repository
public interface DictDao extends baseMapper {
    @Select("select max(update_time) from dict")
    String getCurrentNewModified();
}

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

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

下面启动我们的SpringBoot项目,并修改ik的配制文件ikconfigIKAnalyzer.cfg.xml:

重新启动es即可,在我们的SpringBoot 控制台就可以看到打印了:

第一次,ik中为空,所以我们接受的也为空。

下面再请求的,就是拿的我们返回的了。

四、分词测试

首先测试下小毕超这个我们自定义的词语:

可以看到并没有分词,下面我们调用添加词汇的接口,添加小毕超

然后再来测试,可能不会立即生效,上面在看源码时,就提到了,ik是每隔1分钟进行一次head请求,判断文件是否被修改,所以大概等待一分钟的时间,再次测试:

已经有了我们自定义的分词效果。

喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5694199.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存