elasticsearch - ik 动态分词管理

elasticsearch - ik动态分词管理

场景

对于抓取的资讯,有些敏感词需要屏蔽,但有些词又是新的词,放在 es 中可能搜索不到,或者需要先中入分词字典,重新启动服务

解决方案

通过 es 的远程获取字典方式, 这里使用 ik 中文分词

ik 分词

地址: 安装ik 中文分词, 安装略

动态地址配置
xx@a:/opt/soft/lib/dc/es$ cat plugins/ik/config/IKAnalyzer.cfg.xml 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	## 填写自定义 地址
    <entry key="remote_ext_dict">http://192.168.xx.xx:8080/v1/1/sensitive/dict</entry>
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

原理

查询敏感词字典
其中 location 是指一个 url,比如 http://yoursite.com/getCustomDict,该请求只需满足以下两点即可完成分词热更新。

  1. 该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,
    只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。
  2. 该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可
  3. ik 分词会请求两次
    1. 一次校验Last-Modified和ETag,如果两者有变化,则会去抓取新的分词
    2. 真正拉取字典值
    3. es 每隔 1 分钟定时请求 字典这个接口

实现

架构
  • 使用 mongodb 作为敏感词 管理, 保存敏感词字典
  • 向 redis 保存 数据更新状态,提示已经有数据变更
  • 因为填了 远程字典地址, ES 会起定时任务 每隔 1 分钟拉取敏感词字典, 如果发现 redis 中有数据状态是有更新的,ES 则会再次请求真正的敏感词字典列表

在这里插入图片描述

java 实现
  1. 拉取字典
@GetMapping("/v1/{app}/sensitive/dict")
public void getSensitiveWordDict(@PathVariable(value = "app")Integer app, HttpServletResponse response) {
    log.info(">>>>>>>>> getSensitiveWordDict app:{}", app);
    //从 redis 获取 数据更新状态 
    SensitiveWordTag wordTag = sensitiveWordService.getSensitiveWordTag(app);
    try {

        String content = "";
        if (wordTag.getRequestTimes() == Constants.WORD_TAG_REQUEST_IS_UPDATED) {
            //第一次表示有数据有更新
            log.info(">>>>>>>>> getSensitiveWordDict app:{} 数据有更新啦", app);
        } else if (wordTag.getRequestTimes() == Constants.WORD_TAG_REQUEST_DATA_UPDATED) {
            //第二次表示更新数据输出
            content = sensitiveWordService.getSensitiveWordDict(app);
            log.info(">>>>>>>>>>> getSensitiveWordDict app:{} 数据更新 content:{}",app, content);
        } else {
            //其他代表没有更新
            log.info(">>>>>>>>> getSensitiveWordDict app:{} is 没有数据更新", app);
        }
        ServletOutputStream out = response.getOutputStream();
        response.setHeader("Last-Modified", wordTag.getLastModified());
        response.setHeader("ETag", wordTag.getETag());
        response.setContentType("text/plain;charset=utf-8");
        out.write(content.getBytes("utf-8"));
        out.flush();
    } catch (IOException e) {
        log.error("########### getSensitiveWordDict", e);
    } finally {
        wordTag.setRequestTimes(wordTag.getRequestTimes() - 1);
        if (wordTag.getRequestTimes() >= 0) {
            sensitiveWordService.sensitiveWordDataChanged(app, wordTag);
        }
    }
}
  1. 保存敏感词, 并通知数据更新
@Override
public boolean addOrUpdSensitiveWord(SensitiveWordDto sensitiveWordDto) {
    SensitiveWordDoc doc = new SensitiveWordDoc();

    if (sensitiveWordDto.getWordId() == null) {
        sensitiveWordDto.setCreateTime(LocalDateTime.now());
        sensitiveWordDto.setUpdateTime(LocalDateTime.now());
        BeanUtils.copyProperties(sensitiveWordDto, doc);
    } else {
        SensitiveWordDoc oldDoc =
            mongoTemplate.findOne(new Query(Criteria.where("wordId").is(sensitiveWordDto.getWordId())),
                                  SensitiveWordDoc.class);
        if (Objects.isNull(oldDoc)) {
            throw new ServiceException("500", "该敏感词不存在");
        }
        sensitiveWordDto.setUpdateTime(LocalDateTime.now());
        BeanUtils.copyProperties(oldDoc, doc);
    }
    SensitiveWordDoc result = mongoTemplate.save(doc);
    if (!Objects.isNull(result)) {
        SensitiveWordTag tag = new SensitiveWordTag();
        String val = String.valueOf(System.currentTimeMillis());
        tag.setLastModified(val);
        tag.setETag(val);
        //数据变化 写入 redis
        tag.setRequestTimes(Constants.WORD_TAG_REQUEST_IS_UPDATED);
        String key = MessageFormat.format(RedisKeyConsts.KEY_SENSITIVE_WORDS, sensitiveWordDto.getApp());
        log.info(">>>>>> addOrUpdSensitiveWord key:{}", key);
        redisTemplate.opsForValue().set(key, tag);
        return true;
    }
    return false;
}

结果:

# 第一次
2022-04-16 15:50:44.159  INFO 391071 --- : >>>>>>>>> getSensitiveWordDict app:1
2022-04-16 15:50:44.165  INFO 391071 --- : >>>>>>>>> getSensitiveWordDict app:1 数据有更新啦
# 第二次
2022-04-16 15:50:44.253  INFO 391071 --- : >>>>>>>>> getSensitiveWordDict app:1
2022-04-16 15:50:44.293  INFO 391071 --- : >>>>>>>>>>> getSensitiveWordDict app:1 数据更新 content:

2022-04-16 15:51:44.158  INFO 391071 --- : >>>>>>>>> getSensitiveWordDict app:1
2022-04-16 15:51:44.166  INFO 391071 --- : >>>>>>>>> getSensitiveWordDict app:1 is 没有数据更新

可以发现

  • 当数据有变化时, ES 确实拉取了两次
    • 一次用于鉴定数据有变化
    • 第二次才是真正拉取数据
  • 后面就是一直检测数据没有更新

验证分词

未动态添加敏感词
GET _analyze
{
  "analyzer": "ik_smart",
  "text":"美好的中国,大好河山"
}

{
  "tokens" : [
    {
      "token" : "美好",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "的",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "中国",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "大好河山",
      "start_offset" : 6,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 3
    }
  ]
}
加入分词
http://localhost:8080/v1/sensitive/word
{
  "word": "美好的中国",
  "type": 1,
  "app": 1,
  "level": 1,
  "description": "测试"
}

# 查询 mongo
> db.sensitive_word.find({"app":1}).pretty()
{
	"_id" : "1",
	"wordId" : NumberLong(1),
	"word" : "美好的中国",
	"type" : 1,
	"app" : 1,
	"level" : 1,
	"description" : "测试",
	"createTime" : ISODate("2022-04-16T08:41:52.267Z"),
	"updateTime" : ISODate("2022-04-16T08:41:52.267Z")
}

再次分词:

GET _analyze
{
  "analyzer": "ik_smart",
  "text":"美好的中国,大好河山"
}

{
  "tokens" : [
    {
      "token" : "美好的中国",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "大好河山",
      "start_offset" : 6,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 1
    }
  ]
}

可以发现,成功了

good luck!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值