IK 字段级别词典的升级之路
INFINI Labs 小助手 发表了文章 • 0 个评论 • 413 次浏览 • 1 天前
背景知识:词库的作用
IK 分词器是一款基于词典匹配的中文分词器,其准确性和召回率与 IK 使用的词库也有不小的关系。
这里我们先了解一下词典匹配法的作用流程:
- 预先准备一个大规模的词典,用算法在文本中寻找词典里的最长匹配项。这种方法实现简单且速度快。
- 但面临歧义切分和未登录词挑战:同一序列可能有不同切分方式(例如“北京大学生”可以切成“北京大学/生”或“北京/大学生”),需要规则或算法消除歧义;
- 而词典中没有的新词(如网络流行语、人名等)无法正确切分。
可以看到词库是词元产生的比对基础,一个完善的中文词库能大大提高分词器的准确性和召回率。
IK 使用的词库是中文中常见词汇的合集,完善且丰富,ik_smart 和 ik_max_word 也能满足大部分中文分词的场景需求。但是针对一些专业的场景,比如医药这样的行业词库、电商搜索词、新闻热点词等,IK 是很难覆盖到的。这时候就需要使用者自己去维护自定义的词库了。
IK 的自定义词库加载方式
IK 本身也支持自定义词库的加载和更新的,但是只支持一个集群使用一个词库。
这里主要的制约因素是,词库对象与 ik 的中文分词器执行对象是一一对应的关系。
{{% load-img "/img/blog/2025/ik-field-level-dictionarys-3/ik-3-1" "" %}}
这导致了 IK 的词库面对不同中文分词场景时较低的灵活性,使用者并不能做到字段级别的词库加载。并且基于文件或者 http 协议的词库加载方式也需要不小的维护成本。
字段级别词库的加载
鉴于上述的背景问题,INFINI lab 加强了 IK 的词库加载逻辑,做到了字段级别的词库加载。同时将自定义词库的加载方式由外部文件/远程访问改成了内部索引查询。
主要逻辑如图:
{{% load-img "/img/blog/2025/ik-field-level-dictionarys-3/ik-3-2" "" %}}
这里 IK 多中文词库的加载优化主要基于 IK 可以加载多词类对象(即下面这段代码)的灵活性,将原来遍历一个 CJK 词类对象修改成遍历多个 CJK 词类对象,各个自定义词库可以附着在 CJK 词库对象上实现不同词库的分词。
<br /> do{<br /> //遍历子分词器<br /> for(ISegmenter segmenter : segmenters){<br /> segmenter.analyze(context);<br /> }<br /> //字符缓冲区接近读完,需要读入新的字符<br /> if(context.needRefillBuffer()){<br /> break;<br /> }<br /> }<br />
对默认词库的新增支持
对于默认词库的修改,新版 IK 也可以通过写入词库索引方式支持,只要将 dict_key 设置为 default 即可。
<br /> POST .analysis_ik/_doc<br /> {<br /> "dict_key": "default",<br /> "dict_type": "main_dicts",<br /> "dict_content":"杨树林"<br /> }<br />
效率测试
测试方案 1:单条测试
测试方法:写入一条数据到默认 ik_max_word 和自定义词库,查看是否有明显的效率差距
- 创建测试索引,自定义一个包括默认词库的 IK 分词器
<br /> PUT my-index-000001<br /> {<br /> "settings": {<br /> "number_of_shards": 3,<br /> "analysis": {<br /> "analyzer": {<br /> "my_custom_analyzer": {<br /> "type": "custom",<br /> "tokenizer": "my_tokenizer"<br /> }<br /> },<br /> "tokenizer": {<br /> "my_tokenizer": {<br /> <br /> "type": "ik_max_word",<br /> "custom_dict_enable": true,<br /> "load_default_dicts":true,<br /> "lowcase_enable": true,<br /> "dict_key": "test_dic"<br /> }<br /> }<br /> }<br /> },<br /> "mappings": {<br /> "properties": {<br /> "test_ik": {<br /> "type": "text",<br /> "analyzer": "my_custom_analyzer"<br /> }<br /> }<br /> }<br /> }<br />
- 将该词库重复默认词库的内容
```
POST .analysis_ik/_doc
{
"dict_key": "test_dic",
"dict_type": "main_dicts",
"dict_content":"""xxxx #词库内容
"""
}
debug 日志
[2025-07-09T16:37:43,112][INFO ][o.w.a.d.Dictionary ] [ik-1] Loaded 275909 words from main_dicts dictionary for dict_key: test_dic
```
- 测试默认词库和自定义词库的分词效率
<br /> GET my-index-000001/_analyze<br /> {<br /> "analyzer": "my_custom_analyzer",<br /> "text":"自强不息,杨树林"<br /> }<br /> <br /> GET my-index-000001/_analyze<br /> {<br /> "analyzer": "ik_max_word",<br /> "text":"自强不息,杨树林"<br /> }<br /> <br />
{{% load-img "/img/blog/2025/ik-field-level-dictionarys-3/ik-3-3" "" %}}
{{% load-img "/img/blog/2025/ik-field-level-dictionarys-3/ik-3-4" "" %}}
打开 debug 日志,可以看到自定义分词器在不同的词库找到了 2 次“自强不息”
<br /> ...<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CN_QuantifierSegmenter] [ik-1] 当前扫描词元[息]不需要启动量词扫描<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [自强不息] from dict [default]<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [不息] from dict [default]<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [自强不息] from dict [test_dic]<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [不息] from dict [test_dic]<br /> [2025-07-09T16:52:22,937][INFO ][o.w.a.c.CN_QuantifierSegmenter] [ik-1] 当前扫描词元[,]不需要启动量词扫描<br /> ...<br />
而默认词库只有一次
<br /> ...<br /> [2025-07-09T16:54:22,618][INFO ][o.w.a.c.CN_QuantifierSegmenter] [ik-1] 当前扫描词元[息]不需要启动量词扫描<br /> [2025-07-09T16:54:22,618][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [自强不息] from dict [default]<br /> [2025-07-09T16:54:22,618][INFO ][o.w.a.c.CJKSegmenter ] [ik-1] >>> WORD FOUND [不息] from dict [default]<br /> [2025-07-09T16:54:22,618][INFO ][o.w.a.c.CN_QuantifierSegmenter] [ik-1] 当前扫描词元[,]不需要启动量词扫描<br /> ...<br />
测试方案 2:持续写入测试
测试方法:在 ik_max_word 和自定义词库的索引里,分别持续 bulk 写入,查看总体写入延迟。
测试索引:
```ik_max_word索引
PUT ik_max_test
{
"mappings": {
"properties": {
"chapter": {
"type": "keyword"
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
},
"paragraph_id": {
"type": "keyword"
},
"random_field": {
"type": "text"
},
"timestamp": {
"type": "keyword"
},
"word_count": {
"type": "integer"
}
}
},
"settings": {
"index": {
"number_of_shards": "1",
"number_of_replicas": "0"
}
}
}
自定义词库索引
PUT ik_custom_test
{
"mappings": {
"properties": {
"chapter": {
"type": "keyword"
},
"content": {
"type": "text",
"analyzer": "my_custom_analyzer"
},
"paragraph_id": {
"type": "keyword"
},
"random_field": {
"type": "text"
},
"timestamp": {
"type": "keyword"
},
"word_count": {
"type": "integer"
}
}
},
"settings": {
"index": {
"number_of_shards": "1",
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"load_default_dicts": "true",
"type": "ik_max_word",
"dict_key": "test_dic",
"lowcase_enable": "true",
"custom_dict_enable": "true"
}
}
},
"number_of_replicas": "0"
}
}
}
<br /> <br /> 这里利用脚本循环写入了一段《四世同堂》的文本,比较相同次数下,两次写入的总体延迟。<br /> <br /> 测试脚本内容如下:<br /> <br />
python!/usr/bin/env python3
-- coding: utf-8 --
"""
四世同堂中文内容随机循环写入 Elasticsearch 脚本
目标:生成指定 bulk 次数的索引内容
"""
import random
import time
import json
from datetime import datetime
import requests
import logging
import os
import argparse
import urllib3
配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(name)
class ESDataGenerator:
def init(self, es_host='localhost', es_port=9200, index_name='sisitontang_content',
target_bulk_count=10000, batch_size=1000, use_https=False, username=None, password=None, verify_ssl=True):
"""
初始化 ES 连接和配置
"""
protocol = 'https' if use_https else 'http'
self.es_url = f'{protocol}://{es_host}:{es_port}'
self.index_name = index_name
self.target_bulk_count = target_bulk_count # 目标 bulk 次数
self.batch_size = batch_size
self.check_interval = 1000 # 每 1000 次 bulk 检查一次进度
设置认证信息
self.auth = None<br /> if username and password:<br /> self.auth = (username, password)<br /> logger.info(f"使用用户名认证: {username}")<br />
设置请求会话
self.session = requests.Session()<br /> if self.auth:<br /> self.session.auth = self.auth<br />
处理HTTPS和SSL证书验证
if use_https:<br /> self.session.verify = False # 始终禁用SSL验证以避免证书问题<br /> logger.info("警告:已禁用SSL证书验证(适合开发测试环境)")<br /> urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)<br />
设置SSL适配器以处理连接问题
from requests.adapters import HTTPAdapter<br /> from urllib3.util.retry import Retry<br />
配置重试策略
retry_strategy = Retry(<br /> total=3,<br /> backoff_factor=1,<br /> status_forcelist=[429, 500, 502, 503, 504],<br /> )<br />
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("<a href="https://"" rel="nofollow" target="_blank">https://", adapter)
设置更宽松的SSL上下文
self.session.verify = False<br />
logger.info(f"ES连接地址: {self.es_url}")
创建索引映射
self.create_index()<br />
def create_index(self):
"""创建索引和映射"""
mapping = {
"mappings": {
"properties": {
"chapter": {"type": "keyword"},
"content": {"type": "text", "analyzer": "ik_max_word"},
"timestamp": {"type": "date"},
"word_count": {"type": "integer"},
"paragraph_id": {"type": "keyword"},
"random_field": {"type": "text"}
}
}
}
try:检查索引是否存在
response = self.session.head(f"{self.es_url}/{self.index_name}")<br /> if response.status_code == 200:<br /> logger.info(f"索引 {self.index_name} 已存在")<br /> else:<br /> # 创建索引<br /> response = self.session.put(<br /> f"{self.es_url}/{self.index_name}",<br /> headers={'Content-Type': 'application/json'},<br /> json=mapping<br /> )<br /> if response.status_code in [200, 201]:<br /> logger.info(f"创建索引 {self.index_name} 成功")<br /> else:<br /> logger.error(f"创建索引失败: {response.status_code} - {response.text}")<br /> except Exception as e:<br /> logger.error(f"创建索引失败: {e}")<br />
def load_text_content(self, file_path='sisitontang.txt'):
"""
从文件加载《四世同堂》的完整文本内容
如果文件不存在,则返回扩展的示例内容
"""
if os.path.exists(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
logger.info(f"从文件 {file_path} 加载了 {len(content)} 个字符的文本内容")
return content
except Exception as e:
logger.error(f"读取文件失败: {e}")
如果文件不存在,返回扩展的示例内容
logger.info("使用内置的扩展示例内容")<br /> return self.get_extended_sample_content()<br />
def get_extended_sample_content(self):
"""
获取扩展的《四世同堂》示例内容
"""
content = """
小羊圈胡同是北平城里的一个小胡同。它不宽,可是很长,从东到西有一里多路。在这条胡同里,从东边数起,有个小茶馆,几个小门脸,和一群小房屋。小茶馆的斜对面是个较大的四合院,院子里有几棵大槐树。这个院子就是祁家的住所,四世同堂的大家庭就在这里度过了最困难的岁月。
祁老人是个善良的老头儿,虽然年纪大了,可是还很有精神。他的一生见证了太多的变迁,从清朝的衰落到民国的建立,再到现在的战乱,他都以一种达观的态度面对着。他的儿子祁天佑是个教书先生,为人正直,在胡同里很有威望。祁家的儿媳妇韵梅是个贤惠的女人,把家里打理得井井有条,即使在最困难的时候,也要维持着家庭的尊严。
钱默吟先生是个有学问的人,他的诗写得很好,可是性格有些古怪。他住在胡同深处的一个小院子里,平时很少出门,只是偶尔到祁家坐坐,和祁天佑聊聊古今。他对时局有着自己独特的见解,但更多的时候,他选择在自己的小天地里寻找精神的慰藉。战争的残酷现实让这个文人感到深深的无力,但他依然坚持着自己的文人气节。
小顺子是个活泼的孩子,他每天都在胡同里跑来跑去,和其他的孩子们一起玩耍。他的笑声总是能感染到周围的人,让这个古老的胡同充满了生机。即使在战争的阴霾下,孩子们依然保持着他们的天真和快乐,这或许就是生活的希望所在。小顺子不懂得大人们的烦恼,他只是简单地享受着童年的快乐。
李四大爷是个老实人,他在胡同里开了个小杂货铺。虽然生意不大,但是童叟无欺,街坊邻居们都愿意到他这里买东西。他的妻子是个能干的女人,把小铺子管理得很好。在那个物资匮乏的年代,能够维持一个小铺子的经营已经很不容易了。李四大爷经常帮助邻居们,即使自己的生活也不宽裕。
胡同里的生活是平静的,每天清晨,人们就开始忙碌起来。有的人挑着水桶去井边打水,有的人牵着羊去街上卖奶,有的人挑着菜担子去菜市场。这种平静的生活在战争来临之前是那么的珍贵,人们都珍惜着这样的日子。邻里之间相互照顾,孩子们在院子里玩耍,老人们在门口晒太阳聊天。
冠晓荷是个复杂的人物,他有文化,也有野心。在日本人占领北平的时候,他选择了与敌人合作,这让胡同里的人们都看不起他。但是他的妻子还是个好人,只是被丈夫连累了。冠晓荷的选择代表了那个时代一部分知识分子的软弱和妥协,他们在民族大义和个人利益之间选择了后者。
春天来了,胡同里的槐树发芽了,小鸟们在枝头歌唱。孩子们在院子里玩耍,老人们在门口晒太阳。这样的日子让人感到温暖和希望。即使在最黑暗的时期,生活依然要继续,人们依然要保持对美好未来的希望。春天的到来总是能够给人们带来新的希望和力量。
战争的阴云笼罩着整个城市,胡同里的人们也感受到了压力。有的人选择了抗争,有的人选择了妥协,有的人选择了逃避。每个人都在用自己的方式应对这个艰难的时代。祁瑞宣面临着痛苦的选择,他既不愿意与日本人合作,也不敢公开反抗,这种内心的煎熬让他备受折磨。
老舍先生用他细腻的笔触描绘了胡同里的众生相,每个人物都有自己的特点和命运。他们的喜怒哀乐构成了这部伟大作品的丰富内涵。从祁老爷子的达观,到祁瑞宣的痛苦,从韵梅的坚强,到冠晓荷的堕落,每个人物都是那个时代的缩影。
在那个动荡的年代,普通人的生活是不容易的。他们要面对战争的威胁,要面对生活的困难,要面对道德的选择。但是他们依然坚强地活着,为了家人,为了希望。即使在最困难的时候,人们依然保持着对美好生活的向往。
胡同里的邻里关系是复杂的,有友好的,也有矛盾的。但是在大的困难面前,大家还是会相互帮助。这种邻里之间的温情是中华民族传统文化的重要组成部分。在那个特殊的年代,这种人与人之间的温情显得更加珍贵。
祁瑞宣是个有理想的青年,他受过良好的教育,有自己的抱负。但是在日本人占领期间,他的理想和现实之间产生了尖锐的矛盾。他不愿意做汉奸,但是也不能完全抵抗。这种内心的矛盾和痛苦是那个时代很多知识分子的真实写照。
小妞子是个可爱的孩子,她的天真无邪给这个沉重的故事增添了一丝亮色。她不懂得大人们的复杂心理,只是简单地生活着,快乐着。孩子们的天真和快乐在那个黑暗的年代显得格外珍贵,它们代表着生活的希望和未来。
程长顺是个朴实的人,他没有什么文化,但是有自己的原则和底线。他不愿意向日本人低头,宁愿过艰苦的生活也要保持自己的尊严。他的坚持代表了中国人民不屈不挠的精神,即使在最困难的时候也不愿意妥协。
胡同里的生活节奏是缓慢的,人们有时间去观察周围的变化,去思考生活的意义。这种慢节奏的生活在今天看来是珍贵的,它让人们有机会去体验生活的细节。在那个年代,即使生活艰难,人们依然能够从平凡的日常中找到乐趣。
老二是个有个性的人,他不愿意受约束,喜欢自由自在的生活。但是在战争年代,这种个性给他带来了麻烦,也给家人带来了担忧。他的反叛精神在某种程度上代表着年轻一代对传统束缚的反抗,但在那个特殊的时代,这种反抗往往会带来意想不到的后果。
胡同里的四合院是北京传统建筑的代表,它们见证了一代又一代人的生活。每个院子里都有自己的故事,每个房间里都有自己的记忆。这些古老的建筑承载着深厚的历史文化底蕴,即使在战争的破坏下,依然坚强地屹立着。
在《四世同堂》这部作品中,老舍先生不仅描绘了个人的命运,也反映了整个民族的命运。小胡同里的故事其实就是大中国的缩影。每个人物的遭遇都代表着那个时代某一类人的命运,他们的选择和结局反映了整个民族在那个特殊历史时期的精神状态。
战争结束了,但是人们心中的创伤需要时间来愈合。胡同里的人们重新开始了正常的生活,但是那段艰难的经历永远不会被忘记。历史的教训提醒着人们珍惜和平,珍惜现在的美好生活。四世同堂的故事将永远流传下去,成为后人了解那个时代的重要窗口。
"""
return content.strip()
def split_text_randomly(self, text, min_length=100, max_length=200):
"""
将文本按100-200字的随机长度进行分割
"""清理文本,移除多余的空白字符
text = ''.join(text.split())<br />
segments = []
start = 0
while start < len(text):随机选择段落长度
segment_length = random.randint(min_length, max_length)<br /> end = min(start + segment_length, len(text))<br />
segment = text[start:end]
if segment.strip(): # 确保段落不为空
segments.append(segment.strip())
start = end
return segments
def generate_random_content(self, base_content):
"""
基于基础内容生成随机变化的内容
"""随机选择一个基础段落
base_paragraph = random.choice(base_content)<br />
随机添加一些变化
variations = [<br /> "在那个年代,",<br /> "据说,",<br /> "人们常常说,",<br /> "老一辈人总是提到,",<br /> "历史记录显示,",<br /> "根据回忆,",<br /> "有人说,",<br /> "大家都知道,",<br /> "传说中,",<br /> "众所周知,"<br /> ]<br />
endings = [
"这就是当时的情况。",
"这样的事情在那个年代很常见。",
"这个故事至今还在流传。",
"这是一个值得回忆的故事。",
"这样的经历让人难以忘怀。",
"这就是老北京的生活。",
"这种精神值得我们学习。",
"这个时代已经过去了。",
"这样的生活现在已经很难看到了。",
"这是历史的见证。"
]
随机组合内容
if random.random() < 0.3:<br /> content = random.choice(variations) + base_paragraph<br /> else:<br /> content = base_paragraph<br />
if random.random() < 0.3:
content += random.choice(endings)
return content
def generate_document(self, text_segments, doc_id):
"""基于文本段落生成一个文档"""随机选择一个文本段落
content = random.choice(text_segments)<br />
生成随机的额外字段以增加文档大小
random_field = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=random.randint(100, 500)))<br />
doc = {
"chapter": f"第{random.randint(1, 100)}章",
"content": content,
"timestamp": datetime.now(),
"word_count": len(content),
"paragraphid": f"para{doc_id}",
"random_field": random_field
}
return doc
def get_index_size_gb(self):
"""获取索引大小(GB)"""
try:
response = self.session.get(f"{self.es_url}/_cat/indices/{self.index_name}?bytes=b&h=store.size&format=json")
if response.status_code == 200:
data = response.json()
if data and len(data) > 0:
size_bytes = int(data[0]['store.size'])
size_gb = size_bytes / (1024 1024 1024)
return size_gb
return 0
except Exception as e:
logger.error(f"获取索引大小失败: {e}")
return 0
def bulk_insert(self, documents):
"""批量插入文档使用HTTP bulk API"""构建bulk请求体
bulk_data = []<br /> for doc in documents:<br /> # 添加action行<br /> action = {"index": {"_index": self.index_name}}<br /> bulk_data.append(json.dumps(action))<br /> # 添加文档行<br /> bulk_data.append(json.dumps(doc, ensure_ascii=False, default=str))<br />
每行以换行符结束,最后也要有换行符
bulk_body = '\n'.join(bulk_data) + '\n'<br />
try:
response = self.session.post(
f"{self.es_url}/_bulk",
headers={'Content-Type': 'application/x-ndjson'},
data=bulk_body.encode('utf-8'),
timeout=30 # 添加超时设置
)
if response.status_code == 200:
result = response.json()检查是否有错误
if result.get('errors'):<br /> error_count = 0<br /> error_details = []<br /> for item in result['items']:<br /> if 'error' in item.get('index', {}):<br /> error_count += 1<br /> error_info = item['index']['error']<br /> error_details.append(f"类型: {error_info.get('type')}, 原因: {error_info.get('reason')}")<br />
if error_count > 0:
logger.warning(f"批量插入有 {error_count} 个错误")打印前5个错误的详细信息
for i, error in enumerate(error_details[:5]):<br /> logger.error(f"错误 {i+1}: {error}")<br /> if len(error_details) > 5:<br /> logger.error(f"... 还有 {len(error_details)-5} 个类似错误")<br /> return True<br /> else:<br /> logger.error(f"批量插入失败: HTTP {response.status_code} - {response.text}")<br /> return False<br /> except requests.exceptions.SSLError as e:<br /> logger.error(f"SSL连接错误: {e}")<br /> logger.error("建议检查ES集群的SSL配置或使用 --no-verify-ssl 参数")<br /> return False<br /> except requests.exceptions.ConnectionError as e:<br /> logger.error(f"连接错误: {e}")<br /> logger.error("请检查ES集群地址和端口是否正确")<br /> return False<br /> except requests.exceptions.Timeout as e:<br /> logger.error(f"请求超时: {e}")<br /> logger.error("ES集群响应超时,可能负载过高")<br /> return False<br /> except Exception as e:<br /> logger.error(f"批量插入失败: {e}")<br /> logger.error(f"错误类型: {type(e).__name__}")<br /> return False<br />
def run(self):
"""运行数据生成器"""
start_time = time.time()
start_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
logger.info(f"开始生成数据,开始时间: {start_datetime},目标bulk次数: {self.target_bulk_count}")
加载文本内容
text_content = self.load_text_content()<br />
将文本分割成100-200字的段落
text_segments = self.split_text_randomly(text_content, min_length=100, max_length=200)<br /> logger.info(f"分割出 {len(text_segments)} 个文本段落")<br />
doc_count = 0
bulk_count = 0
bulk_times = [] # 记录每次bulk的耗时
while bulk_count < self.target_bulk_count:生成批量文档
documents = []<br /> for i in range(self.batch_size):<br /> doc = self.generate_document(text_segments, doc_count + i)<br /> documents.append(doc)<br />
记录单次bulk开始时间
bulk_start = time.time()<br />
批量插入
if self.bulk_insert(documents):<br /> bulk_end = time.time()<br /> bulk_duration = bulk_end - bulk_start<br /> bulk_times.append(bulk_duration)<br />
doc_count += self.batch_size
bulk_count += 1
定期检查和报告进度
if bulk_count % self.check_interval == 0:<br /> current_size = self.get_index_size_gb()<br /> avg_bulk_time = sum(bulk_times[-self.check_interval:]) / len(bulk_times[-self.check_interval:])<br /> logger.info(f"已完成 {bulk_count} 次bulk操作,插入 {doc_count} 条文档,当前索引大小: {current_size:.2f}GB,最近{self.check_interval}次bulk平均耗时: {avg_bulk_time:.3f}秒")<br />
避免过于频繁的插入
#time.sleep(0.01) # 减少延迟,提高测试速度<br />
end_time = time.time()
end_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
total_duration = end_time - start_time
计算统计信息
final_size = self.get_index_size_gb()<br /> avg_bulk_time = sum(bulk_times) / len(bulk_times) if bulk_times else 0<br /> total_docs_per_sec = doc_count / total_duration if total_duration > 0 else 0<br /> bulk_per_sec = bulk_count / total_duration if total_duration > 0 else 0<br />
logger.info(f"数据生成完成!")
logger.info(f"开始时间: {start_datetime}")
logger.info(f"结束时间: {end_datetime}")
logger.info(f"总耗时: {total_duration:.2f}秒 ({total_duration/60:.2f}分钟)")
logger.info(f"总计完成: {bulk_count} 次bulk操作")
logger.info(f"总计插入: {doc_count} 条文档")
logger.info(f"最终索引大小: {final_size:.2f}GB")
logger.info(f"平均每次bulk耗时: {avg_bulk_time:.3f}秒")
logger.info(f"平均bulk速率: {bulk_per_sec:.2f}次/秒")
logger.info(f"平均文档写入速率: {total_docs_per_sec:.0f}条/秒")
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='四世同堂中文内容写入 Elasticsearch 脚本')
parser.add_argument('--host', default='localhost', help='ES 主机地址 (默认: localhost)')
parser.add_argument('--port', type=int, default=9200, help='ES 端口 (默认: 9200)')
parser.add_argument('--index', required=True, help='索引名称 (必填)')
parser.add_argument('--bulk-count', type=int, default=1000, help='目标 bulk 次数 (默认: 10000)')
parser.add_argument('--batch-size', type=int, default=1000, help='每次 bulk 的文档数量 (默认: 1000)')
parser.add_argument('--https', action='store_true', help='使用 HTTPS 协议')
parser.add_argument('--username', help='ES 用户名')
parser.add_argument('--password', help='ES 密码')
parser.add_argument('--no-verify-ssl', action='store_true', help='禁用 SSL 证书验证(默认已禁用)')
args = parser.parse_args()
protocol = "HTTPS" if args.https else "HTTP"
auth_info = f"认证: {args.username}" if args.username else "无认证"
ssl_info = "禁用SSL验证" if args.https else ""
logger.info(f"开始运行脚本,参数: {protocol}://{args.host}:{args.port}, 索引={args.index}, bulk次数={args.bulk_count}, {auth_info} {ssl_info}")
try:
generator = ESDataGenerator(
args.host,
args.port,
args.index,
args.bulk_count,
args.batch_size,
args.https,
args.username,
args.password,
not args.no_verify_ssl # 传入verify_ssl参数,但实际上总是False
)
generator.run()
except KeyboardInterrupt:
logger.info("用户中断了程序")
except Exception as e:
logger.error(f"程序运行出错: {e}")
logger.error(f"错误类型: {type(e).name}")
if name == "main":
main()
<br /> <br /> 根据脚本中的测试文本添加的词库如下:<br /> <br />
POST .analysis_ik/_doc
{
"dict_type": "main_dicts",
"dict_key": "test_dic",
"dict_content": """祁老人
祁天佑
韵梅
祁瑞宣
老二
钱默吟
小顺子
李四大爷
冠晓荷
小妞子
程长顺
老舍
李四大爷
小羊圈胡同
北平城
胡同
小茶馆
小门脸
小房屋
四合院
院子
祁家
小院子
杂货铺
小铺子
井边
街上
菜市场
门口
枝头
城市
房间
北京
清朝
民国
战乱
战争
日本人
抗战
大槐树
槐树
小鸟
羊
门脸
房屋
水桶
菜担子
铺子
老头儿
儿子
教书先生
儿媳妇
女人
大家庭
孩子
孩子们
街坊邻居
妻子
老人
文人
知识分子
青年
汉奸
岁月
一生
变迁
衰落
建立
态度
威望
尊严
学问
诗
性格
时局
见解
小天地
精神
慰藉
现实
无力
气节
笑声
生机
阴霾
天真
快乐
希望
烦恼
童年
生意
生活
物资
年代
经营
日子
邻里
文化
野心
敌人
选择
软弱
妥协
民族大义
个人利益
温暖
时期
未来
力量
压力
抗争
逃避
方式
时代
煎熬
折磨
笔触
众生相
人物
特点
命运
喜怒哀乐
内涵
达观
痛苦
坚强
堕落
缩影
威胁
困难
道德
家人
向往
关系
矛盾
温情
传统文化
组成部分
理想
教育
抱负
占领
写照
亮色
心理
原则
底线
节奏
意义
细节
乐趣
个性
约束
麻烦
担忧
反叛精神
束缚
反抗
后果
建筑
代表
故事
记忆
历史文化底蕴
破坏
作品
创伤
经历
教训
和平
窗口
清晨
春天
内心
玩耍
聊天
晒太阳
歌唱
合作
打水
卖奶
帮助
"""
}
<br /> <br /> 进行 2 次集中写入的记录如下:<br /> <br />
ik_max_test
2025-07-13 20:15:33,294 - INFO - 开始时间: 2025-07-13 19:45:07
2025-07-13 20:15:33,294 - INFO - 结束时间: 2025-07-13 20:15:33
2025-07-13 20:15:33,294 - INFO - 总耗时: 1825.31秒 (30.42分钟)
2025-07-13 20:15:33,294 - INFO - 总计完成: 1000 次bulk操作
2025-07-13 20:15:33,294 - INFO - 总计插入: 1000000 条文档
2025-07-13 20:15:33,294 - INFO - 最终索引大小: 0.92GB
2025-07-13 20:15:33,294 - INFO - 平均每次bulk耗时: 1.790秒
2025-07-13 20:15:33,294 - INFO - 平均bulk速率: 0.55次/秒
2025-07-13 20:15:33,294 - INFO - 平均文档写入速率: 548条/秒
ik_custom_test
2025-07-13 21:17:47,309 - INFO - 开始时间: 2025-07-13 20:44:03
2025-07-13 21:17:47,309 - INFO - 结束时间: 2025-07-13 21:17:47
2025-07-13 21:17:47,309 - INFO - 总耗时: 2023.53秒 (33.73分钟)
2025-07-13 21:17:47,309 - INFO - 总计完成: 1000 次bulk操作
2025-07-13 21:17:47,309 - INFO - 总计插入: 1000000 条文档
2025-07-13 21:17:47,309 - INFO - 最终索引大小: 0.92GB
2025-07-13 21:17:47,309 - INFO - 平均每次bulk耗时: 1.986秒
2025-07-13 21:17:47,309 - INFO - 平均bulk速率: 0.49次/秒
2025-07-13 21:17:47,309 - INFO - 平均文档写入速率: 494条/秒
```
可以看到,有一定损耗,自定义词库词典的效率是之前的 90%。
相关阅读
- [IK 字段级别词典升级:IK reload API
](https://infinilabs.cn/blog/202 ... rys-2/) - [Easysearch 新功能: IK 字段级别词典
](https://infinilabs.cn/blog/202 ... narys/)
关于 IK Analysis

IK Analysis 插件集成了 Lucene IK 分析器,并支持自定义词典。它支持 Easysearch\Elasticsearch\OpenSearch 的主要版本。由 INFINI Labs 维护并提供支持。
该插件包含分析器:ik_smart 和 ik_max_word,以及分词器:ik_smart 和 ik_max_word
开源地址:<https://github.com/infinilabs/analysis-ik>
作者:金多安,极限科技(INFINI Labs)搜索运维专家,Elastic 认证专家,搜索客社区日报责任编辑。一直从事与搜索运维相关的工作,日常会去挖掘 ES / Lucene 方向的搜索技术原理,保持搜索相关技术发展的关注。
原文:https://infinilabs.cn/blog/202 ... ys-3/
- [IK 字段级别词典升级:IK reload API
IK 字段级别词典升级:IK reload API
INFINI Labs 小助手 发表了文章 • 0 个评论 • 400 次浏览 • 1 天前
之前介绍 [IK 字段级别字典](https://infinilabs.cn/blog/202 ... narys/) 使用的时候,对于字典的更新只是支持词典库的新增,并不支持对存量词典库的修改或者删除。经过这段时间的开发,已经可以兼容词典库的更新,主要通过 IK reload API 来实现。
IK reload API
IK reload API 通过对词典库的全量重新加载来实现词典库的更新或者删除。用户可以通过下面的命令实现:
```
测试索引准备
PUT my-index-000001
{
"settings": {
"number_of_shards": 3,
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "ik_smart",
"custom_dict_enable": true,
"load_default_dicts":false, # 这里不包含默认词库
"lowcase_enable": true,
"dict_key": "test_dic"
}
}
}
},
"mappings": {
"properties": {
"test_ik": {
"type": "text",
"analyzer": "my_custom_analyzer"
}
}
}
}
原来词库分词效果,只预置了分词“自强不息”
GET my-index-000001/_analyze
{
"analyzer": "my_custom_analyzer",
"text":"自强不息,杨树林"
}
{
"tokens": [
{
"token": "自强不息",
"start_offset": 0,
"end_offset": 4,
"type": "CN_WORD",
"position": 0
},
{
"token": "杨",
"start_offset": 5,
"end_offset": 6,
"type": "CN_CHAR",
"position": 1
},
{
"token": "树",
"start_offset": 6,
"end_offset": 7,
"type": "CN_CHAR",
"position": 2
},
{
"token": "林",
"start_offset": 7,
"end_offset": 8,
"type": "CN_CHAR",
"position": 3
}
]
}
更新词库
POST .analysis_ik/_doc
{
"dict_key": "test_dic",
"dict_type": "main_dicts",
"dict_content":"杨树林"
}
删除词库,词库文档的id为coayoJcBFHNnLYAKfTML
DELETE .analysis_ik/_doc/coayoJcBFHNnLYAKfTML?refresh=true
重载词库
POST _ik/_reload
{}
更新后的词库效果
GET my-index-000001/_analyze
{
"analyzer": "my_custom_analyzer",
"text":"自强不息,杨树林"
}
{
"tokens": [
{
"token": "自",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "强",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "不",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
},
{
"token": "息",
"start_offset": 3,
"end_offset": 4,
"type": "CN_CHAR",
"position": 3
},
{
"token": "杨树林",
"start_offset": 5,
"end_offset": 8,
"type": "CN_WORD",
"position": 4
}
]
}
<br /> <br /> 这里是实现索引里全部的词库更新。<br /> <br /> 也可以实现单独的词典库更新<br /> <br />
POST _ik/_reload
{"dict_key":"test_dic”}
debug 日志
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.ReloadIK ] [ik-1] 收到重载IK词典的请求,将在所有节点上执行。dict_key: test_dic, dict_index: .analysis_ik
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: test_dic, dict_index: .analysis_ik
<br /> <br /> 这里传入的 dict_key 对应的词库 id。<br /> <br /> 对于自定义的词库存储索引,也可以指定词库索引的名称,如果不指定则默认使用 .analysis_ik<br /> <br />
POST _ik/_reload
{"dict_index":"ik_index"}
debug 日志
[2025-07-09T15:32:59,196][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: null, dict_index: test_ik
[2025-07-09T15:32:59,196][INFO ][o.w.a.d.ReloadDict ] [ik-1] Reloading all dictionaries
```
注:
- 更新或者删除词库重载后只是对后续写入的文档生效,对已索引的文档无效;
- 因为用户无法直接更改 IK 内置的词库(即默认配置路径下的词库文件),因此 reload API 不会影响内置词库的信息。
相关阅读
- [IK 字段级别词典的升级之路
](https://infinilabs.cn/blog/202 ... rys-3/) - [Easysearch 新功能: IK 字段级别词典
](https://infinilabs.cn/blog/202 ... narys/)
关于 IK Analysis

IK Analysis 插件集成了 Lucene IK 分析器,并支持自定义词典。它支持 Easysearch\Elasticsearch\OpenSearch 的主要版本。由 INFINI Labs 维护并提供支持。
该插件包含分析器:ik_smart 和 ik_max_word,以及分词器:ik_smart 和 ik_max_word
开源地址:<https://github.com/infinilabs/analysis-ik>
作者:金多安,极限科技(INFINI Labs)搜索运维专家,Elastic 认证专家,搜索客社区日报责任编辑。一直从事与搜索运维相关的工作,日常会去挖掘 ES / Lucene 方向的搜索技术原理,保持搜索相关技术发展的关注。
原文:https://infinilabs.cn/blog/202 ... ys-2/
- [IK 字段级别词典的升级之路
Easysearch 集成阿里云与 Ollama Embedding API,构建端到端的语义搜索系统
INFINI Labs 小助手 发表了文章 • 0 个评论 • 706 次浏览 • 2 天前
背景
在当前 AI 与搜索深度融合的时代,语义搜索已成为企业级应用的核心能力之一。作为 Elasticsearch 的国产化替代方案,Easysearch 不仅具备高性能、高可用、弹性伸缩等企业级特性,更通过灵活的插件化架构,支持多种主流 Embedding 模型服务,包括 阿里云通义千问(DashScope) 和 本地化 Ollama 服务,实现对 OpenAI 接口规范的完美兼容。
本文将详细介绍如何在 Easysearch 中集成阿里云和 Ollama 的 Embedding API,构建端到端的语义搜索系统,并提供完整的配置示例与流程图解析。
---
一、为什么选择 Easysearch?
Easysearch 是由极限科技(INFINI Labs)自主研发的分布式近实时搜索型数据库,具备以下核心优势:
- ✅ 完全兼容 Elasticsearch 7.x API 及 8.x 常用操作
- ✅ 原生支持向量检索(kNN)、语义搜索、混合检索
- ✅ 内置数据摄入管道与搜索管道,支持 AI 模型集成
- ✅ 支持国产化部署、数据安全可控
- ✅ 高性能、低延迟、可扩展性强
尤其在 AI 增强搜索场景中,Easysearch 提供了强大的text_embedding
和semantic_query_enricher
处理器,允许无缝接入外部 Embedding 模型服务。
---
二、支持的 Embedding 服务
Easysearch 通过标准 OpenAI 兼容接口无缝集成各类第三方 Embedding 模型服务,理论上支持所有符合 OpenAI Embedding API 规范的模型。以下是已验证的典型服务示例:
| 服务类型 | 模型示例 | 接口协议 | 部署方式 | 特点 |
| ------------- | ----------------------------- | ----------- | ----------- | ------------------ |
| 云端 SaaS | 阿里云 DashScope | OpenAI 兼容 | 云端 | 开箱即用,高可用性 |
| | OpenAItext-embedding-3
| OpenAI 原生 | 云端 | |
| | 其他兼容 OpenAI 的云服务 | OpenAI 兼容 | 云端 | |
| 本地部署 | Ollama (nomic-embed-text
等) | 自定义 API | 本地/私有化 | 数据隐私可控 |
| | 自建开源模型(如 BGE、M3E) | OpenAI 兼容 | 本地/私有化 | 灵活定制 |
核心优势:
- 广泛兼容性
支持任意实现 OpenAI Embedding API 格式(/v1/embeddings
)的服务,包括:
- 请求格式:
{ "input": "text", "model": "model_name" }
- 响应格式:
{ "data": [{ "embedding": [...] }] }
- 请求格式:
- 即插即用
仅需配置服务端点的base_url
和api_key
即可快速接入新模型。
- 混合部署
可同时配置多个云端或本地模型,根据业务需求灵活切换。
---
三、结合 AI 服务流程图

说明:
- 索引阶段:通过 Ingest Pipeline 调用 Embedding API,将文本转为向量并存储。
- 搜索阶段:通过 Search Pipeline 动态生成查询向量,执行语义相似度匹配。
- 所有 API 调用均兼容 OpenAI 接口格式,降低集成成本。
---
四、集成阿里云 DashScope(通义千问)
阿里云 DashScope 提供高性能文本嵌入模型text-embedding-v4
,支持 256 维向量输出,适用于中文语义理解任务。
1. 创建 Ingest Pipeline(索引时生成向量)
auto<br /> PUT _ingest/pipeline/text-embedding-aliyun<br /> {<br /> "description": "阿里云用于生成文本嵌入向量的管道",<br /> "processors": [<br /> {<br /> "text_embedding": {<br /> "url": "<a href="https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings"" rel="nofollow" target="_blank">https://dashscope.aliyuncs.com ... ot%3B</a>,<br /> "vendor": "openai",<br /> "api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",<br /> "text_field": "input_text",<br /> "vector_field": "text_vector",<br /> "model_id": "text-embedding-v4",<br /> "dims": 256,<br /> "batch_size": 5<br /> }<br /> }<br /> ]<br /> }<br />
2. 创建索引并定义向量字段
auto<br /> PUT /my-index<br /> {<br /> "mappings": {<br /> "properties": {<br /> "input_text": {<br /> "type": "text",<br /> "fields": {<br /> "keyword": {<br /> "type": "keyword",<br /> "ignore_above": 256<br /> }<br /> }<br /> },<br /> "text_vector": {<br /> "type": "knn_dense_float_vector",<br /> "knn": {<br /> "dims": 256,<br /> "model": "lsh",<br /> "similarity": "cosine",<br /> "L": 99,<br /> "k": 1<br /> }<br /> }<br /> }<br /> }<br /> }<br />
3. 使用 Pipeline 批量写入数据
auto<br /> POST /_bulk?pipeline=text-embedding-aliyun&refresh=wait_for<br /> { "index": { "_index": "my-index", "_id": "1" } }<br /> { "input_text": "风急天高猿啸哀,渚清沙白鸟飞回..." }<br /> { "index": { "_index": "my-index", "_id": "2" } }<br /> { "input_text": "月落乌啼霜满天,江枫渔火对愁眠..." }<br /> ...<br />
4. 配置 Search Pipeline(搜索时动态生成向量)
auto<br /> PUT /_search/pipeline/search_model_aliyun<br /> {<br /> "request_processors": [<br /> {<br /> "semantic_query_enricher": {<br /> "tag": "tag1",<br /> "description": "阿里云 search embedding model",<br /> "url": "<a href="https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings"" rel="nofollow" target="_blank">https://dashscope.aliyuncs.com ... ot%3B</a>,<br /> "vendor": "openai",<br /> "api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",<br /> "default_model_id": "text-embedding-v4",<br /> "vector_field_model_id": {<br /> "text_vector": "text-embedding-v4"<br /> }<br /> }<br /> }<br /> ]<br /> }<br />
5. 设置索引默认搜索管道
auto<br /> PUT /my-index/_settings<br /> {<br /> "index.search.default_pipeline": "search_model_aliyun"<br /> }<br />
6. 执行语义搜索
auto<br /> GET /my-index/_search<br /> {<br /> "_source": "input_text",<br /> "query": {<br /> "semantic": {<br /> "text_vector": {<br /> "query_text": "风急天高猿啸哀,渚清沙白鸟飞回...",<br /> "candidates": 10,<br /> "query_strategy": "LSH_COSINE"<br /> }<br /> }<br /> }<br /> }<br />
搜索结果示例:
auto<br /> "hits": [<br /> {<br /> "_id": "1",<br /> "_score": 2.0,<br /> "_source": { "input_text": "风急天高猿啸哀..." }<br /> },<br /> {<br /> "_id": "4",<br /> "_score": 1.75,<br /> "_source": { "input_text": "白日依山尽..." }<br /> },<br /> ...<br /> ]<br />
结果显示:相同诗句匹配得分最高,其他古诗按语义相似度排序,效果理想。
---
五、集成本地 Ollama 服务
Ollama 支持在本地运行开源 Embedding 模型(如nomic-embed-text
),适合对数据隐私要求高的场景。
1. 启动 Ollama 服务
bash<br /> ollama serve<br /> ollama pull nomic-embed-text:latest<br />
2. 创建 Ingest Pipeline(使用 Ollama)
auto<br /> PUT _ingest/pipeline/ollama-embedding-pipeline<br /> {<br /> "description": "Ollama embedding 示例",<br /> "processors": [<br /> {<br /> "text_embedding": {<br /> "url": "<a href="http://localhost:11434/api/embed"" rel="nofollow" target="_blank">http://localhost:11434/api/embed"</a>,<br /> "vendor": "ollama",<br /> "text_field": "input_text",<br /> "vector_field": "text_vector",<br /> "model_id": "nomic-embed-text:latest"<br /> }<br /> }<br /> ]<br /> }<br />
3. 创建 Search Pipeline(搜索时使用 Ollama)
auto<br /> PUT /_search/pipeline/ollama_model_pipeline<br /> {<br /> "request_processors": [<br /> {<br /> "semantic_query_enricher": {<br /> "tag": "tag1",<br /> "description": "Sets the ollama model",<br /> "url": "<a href="http://localhost:11434/api/embed"" rel="nofollow" target="_blank">http://localhost:11434/api/embed"</a>,<br /> "vendor": "ollama",<br /> "default_model_id": "nomic-embed-text:latest",<br /> "vector_field_model_id": {<br /> "text_vector": "nomic-embed-text:latest"<br /> }<br /> }<br /> }<br /> ]<br /> }<br />
后续步骤与阿里云一致:创建索引 → 写入数据 → 搜索查询。
---
六、安全性说明
Easysearch 在处理 API Key 时采取以下安全措施:
- 索引阶段:通过 Ingest Pipeline 调用 Embedding API,将文本转为向量并存储。
- 广泛兼容性
- 🔐 所有
api_key
在返回时自动加密脱敏(如TfUmLjPg...infinilabs
) - 🔒 支持密钥管理插件(如 Hashicorp Vault 集成)
- 🛡️ 支持 HTTPS、RBAC、审计日志等企业级安全功能
确保敏感信息不被泄露,满足合规要求。
---
七、总结
通过 Easysearch 的 Ingest Pipeline 与 Search Pipeline,我们可以轻松集成:
- ✅ 阿里云 DashScope(云端高性能)
- ✅ Ollama(本地私有化部署)
- ✅ 其他支持 OpenAI 接口的 Embedding 服务
无论是追求性能还是数据安全,Easysearch 都能提供灵活、高效的语义搜索解决方案。
---
八、下一步建议
- 尝试混合检索:结合关键词匹配与语义搜索
- 使用 Rerank 模型提升排序精度
- 部署多节点集群提升吞吐量
- 接入 INFINI Gateway 实现统一 API 网关管理
---
参考链接
- [Easysearch 官网文档](https://docs.infinilabs.com/easysearch)
- [阿里云 DashScope 文档](https://help.aliyun.com/zh/mod ... dding/)
- [Ollama 官方网站](https://ollama.com)
- [INFINI Labs GitHub](https://github.com/infinilabs)
---
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:张磊,极限科技(INFINI Labs)搜索引擎研发负责人,对 Elasticsearch 和 Lucene 源码比较熟悉,目前主要负责公司的 Easysearch 产品的研发以及客户服务工作。
原文:https://infinilabs.cn/blog/202 ... -API/
一键启动:使用 start-local 脚本轻松管理 INFINI Console 与 Easysearch 本地环境
INFINI Labs 小助手 发表了文章 • 0 个评论 • 3615 次浏览 • 2025-07-01 19:15
系列回顾与引言
在我们的 INFINI 本地环境搭建系列博客中:
- 第一篇《[搭建持久化的 INFINI Console 与 Easysearch 容器环境](https://infinilabs.cn/blog/202 ... docker)》,我们深入探讨了如何使用基础的
docker run
命令,一步步构建起 Console 和 Easysearch 服务,并重点解决了数据持久化的问题。 - 第二篇《[使用 Docker Compose 简化 INFINI Console 与 Easysearch 环境搭建](https://infinilabs.cn/blog/202 ... ompose)》,我们学习了如何利用 Docker Compose 的声明式配置,将多容器应用的定义和管理变得更加简洁高效。
虽然 Docker Compose 已经极大地提升了便利性,但在实际的开发和测试流程中,我们可能还需要处理版本选择、初始配置复制、多节点配置、指标采集开启等更细致的需求。为了进一步封装这些复杂性,提供真正的一键式体验,我们精心打造了一个强大的 Shell 脚本 [start-local](https://github.com/infinilabs/ ... cal.sh) 。
本篇文章将带你领略start-local
的魅力,看看它是如何将 Console 和 Easysearch (本文仍以 Console 1.29.6 和 Easysearch 1.13.0 为例) 的本地环境搭建与管理提升到一个全新的便捷高度——只需一行命令,即可拥有一个功能完备、数据持久的本地 INFINI Console 运行环境。
start-local
:您的 INFINI Console 本地环境瑞士军刀
start-local
脚本(灵感来源于 [elastic/start-local](https://github.com/elastic/start-local))集成了环境搭建的诸多最佳实践,旨在提供极致的易用性。它在后台仍然依赖 Docker 和 Docker Compose,但为用户屏蔽了底层的复杂配置细节。
核心功能:
- 智能版本管理:自动获取 INFINI Console 和 Easysearch 的最新稳定版(或你指定的版本)作为默认镜像标签。
- 动态配置生成:根据用户提供的命令行选项(如节点数、密码、版本等)自动生成
.env
和docker-compose.yml
文件。 - 初始配置自动处理:在首次启动或本地配置目录不存在时,自动从 Docker 镜像中提取并设置初始配置文件。
- 一键式生命周期管理:通过简单的命令 (
up
,down
,logs
,clean
) 管理整个应用的启动、停止、日志查看和彻底清理。 - 持久化内置:默认将所有关键数据(配置、索引数据、日志)持久化到本地的
./startlocal
目录(可配置)。 - 集成 Agent 指标采集:通过
--metrics-agent
选项,轻松启用 Easysearch 的指标收集并自动配置其指向 INFINI Console。 - 跨平台设计:主要针对 Linux 和 macOS 环境。
如何获取和使用
start-local
获取和执行start-local
最便捷的方式是通过curl
将脚本内容直接通过管道传递给sh
执行:
```bash启动默认配置 (Console + 1 个 Easysearch 节点)
curl -fsSL http://get.infini.cloud/start-local | sh -s
想要更丰富的体验?试试这个:
启动 3 个 Easysearch 节点,设置密码,并开启 Agent 指标采集
curl -fsSL http://get.infini.cloud/start-local | sh -s -- up --nodes 3 --password "MyDevPass123." --metrics-agent
``<br /> <br /> _(请将
http://get.infini.cloud/start-local替换为脚本的实际官方获取地址)_<br /> <br />
sh -s --部分确保脚本从标准输入读取,并且后续参数能正确传递给脚本。<br /> <br /> 脚本执行后,所有操作文件和持久化数据都会在当前目录下的
./startlocal` (默认) 子目录中创建和管理。
start-local
命令和选项概览
通过help
命令可以查看所有支持的功能:
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s -- help<br />
以下是一些最常用的命令和选项:
命令 (COMMAND
):
up
: 核心命令。创建并启动定义的服务。自动处理初始配置。down
: 停止服务,移除容器、网络和相关匿名卷。本地持久化数据不受影响。logs [服务名...]
: 实时查看指定服务或所有服务的日志。clean
: 彻底清理。执行down
后,删除整个工作目录 (./startlocal
及其所有内容)。help
: 显示帮助信息。
常用选项 (OPTIONS
) (主要用于up
命令):
-cv TAG
,--console-version TAG
: 指定 Console 镜像版本 (例如1.29.6
)。-ev TAG
,--easysearch-version TAG
: 指定 Easysearch 镜像版本 (例如1.13.0
)。-n N
,--nodes N
: Easysearch 节点数量 (默认 1)。-p PASSWORD
,--password PASSWORD
: Easysearchadmin
用户初始密码 (默认ShouldChangeme123.
)。--services s1[,s2,...]
: 指定要启动的服务 (可选值:console
,easysearch
)。如果未指定,默认启动两者。--metrics-agent
: 启用 Easysearch 指标收集代理。-wd PATH
,--work-dir PATH
: 自定义工作目录,替代默认的./startlocal
。
实际操作示例
让我们通过几个示例来感受start-local
的便捷:
1. 启动一个标准的开发环境 (Console + 1 个 Easysearch 节点,开启指标)
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s<br />
脚本会自动完成所有后台工作:检查依赖、确定版本、创建工作目录、生成配置文件、复制初始配置、生成docker-compose.yml
,最后启动服务并打印访问地址。
2. 启动一个 3 节点的 Easysearch 集群,并指定版本和密码
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s -- up \<br /> --nodes 3 \<br /> --password "ComplexP@ssw0rd." \<br /> --console-version 1.29.6 \<br /> --easysearch-version 1.13.0 \<br /> --services easysearch,console<br />
脚本会智能处理多节点配置和持久化目录结构。
3. 查看所有服务的日志
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s -- logs<br />
4. 停止运行环境(慎重操作)
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s -- down<br />
这将停止运行的所有容器。
4. 删除运行环境(慎重操作)
bash<br /> curl -fsSL <a href="http://get.infini.cloud/start-local" rel="nofollow" target="_blank">http://get.infini.cloud/start-local</a> | sh -s -- clean<br />
这将移除所有相关的 Docker 资源以及本地的./startlocal
目录。
持久化:数据安全无忧
start-local
脚本的核心设计之一就是确保数据的持久化。所有重要的配置、数据和日志都会映射到宿主机的./startlocal
(或你通过-wd
指定的) 目录下的结构化子目录中:
- Console:
./startlocal/console/{config,data,logs}/
- Easysearch (单节点):
./startlocal/easysearch/{config,data,logs}/
- Easysearch (多节点):
./startlocal/easysearch/node-X/{config,data,logs}/
这意味着你可以随时down
和up
你的环境,而不用担心丢失任何工作。
访问服务
启动成功后,脚本会打印出访问地址:
- INFINI Console:
<a href="http://localhost:9000" rel="nofollow" target="_blank">http://localhost:9000</a>
(默认主机端口) - INFINI Easysearch:
<a href="https://localhost:9200" rel="nofollow" target="_blank">https://localhost:9200</a>
(默认主机端口,用户admin
,密码为你设置的或默认值)
总结:从复杂到简单,专注核心价值
从繁琐的docker run
命令,到结构化的docker-compose.yml
,再到如今便捷的start-local
脚本,我们一步步简化了 INFINI 本地环境的搭建和管理过程。start-local
将所有底层的复杂性封装起来,让你能够通过一行命令就拥有一个功能齐全、数据持久的本地环境,从而更专注于应用本身的功能测试、开发和学习。
这正是良好工具的价值所在——让复杂的事情变简单,让我们能更高效地创造。
希望这个 [start-local](https://github.com/infinilabs/ ... cal.sh) 脚本能成为你日常工作中得力的助手!如果你有任何建议或发现问题,欢迎通过项目仓库反馈。
关于 INFINI Console

INFINI Console 是一款开源的非常轻量级的多集群、跨版本的搜索基础设施统一管控平台。通过对流行的搜索引擎基础设施进行跨版本、多集群的集中纳管,企业可以快速方便的统一管理企业内部的不同版本的多套搜索集群。INFINI Console 还可以对集群内的索引及数据进行操作管理,可以配置灵活的告警规则,可以指定统一的安全策略,可以查看各个维度的日志和审计信息,真正实现企业级的搜索服务平台化建设和运营。
官网文档:<https://docs.infinilabs.com/console/main/>
开源地址:<https://github.com/infinilabs/console>
作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
原文:http://localhost:1313/blog/202 ... ocal/
- 智能版本管理:自动获取 INFINI Console 和 Easysearch 的最新稳定版(或你指定的版本)作为默认镜像标签。
使用 Docker Compose 简化 INFINI Console 与 Easysearch 环境搭建
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2935 次浏览 • 2025-07-01 19:14
前言回顾
在上一篇文章《[搭建持久化的 INFINI Console 与 Easysearch 容器环境](https://infinilabs.cn/blog/202 ... ocker/)》中,我们详细介绍了如何使用基础的 docker run
命令,手动启动和配置 INFINI Console (1.29.6) 和 INFINI Easysearch (1.13.0) 容器,并实现了关键数据的持久化,解决了重启后配置丢失的问题。
手动操作虽然能让我们深入理解 Docker 的核心机制,但在管理多个容器、网络和卷时,命令会变得冗长且容易出错。这时,Docker Compose 就派上了用场。它允许我们使用一个 YAML 文件来定义和运行多容器 Docker 应用程序。
本篇文章将演示如何将上一篇的手动步骤转换为使用 Docker Compose,让你更轻松地管理和维护这套本地开发测试环境。
Docker Compose 的优势
使用 Docker Compose 带来了诸多好处:
- 声明式配置:在一个
docker-compose.yml
文件中定义所有服务、网络和卷,清晰明了。 - 一键式管理:使用简单的命令(如
docker compose up
,docker compose down
)即可启动、停止和重建整个应用环境。 - 简化网络和服务连接:Compose 会自动处理服务间的网络设置和依赖关系。
- 易于共享和版本控制:
docker-compose.yml
文件可以轻松地与团队共享并通过版本控制系统(如 Git)进行管理。
准备工作
与上一篇类似,你需要:
- 操作系统: macOS (本文示例)
- Docker 环境: OrbStack ([https://orbstack.dev/](https://orbstack.dev/)) 或 Docker Desktop for Mac。
- 确保 Docker Compose V2 (
docker compose
) 或 V1 (docker-compose
) 已安装并可用。
查看
docker compose
版本
bash<br /> docker compose version<br /> Docker Compose version v2.24.5<br />
步骤一:项目目录结构
我们将继续使用上一篇文章中创建的目录结构。如果你还没有创建,或者想重新开始,可以在你的项目根目录(例如~/infini_compose_lab
)下创建如下结构:
```bash1. 创建项目根目录
mkdir -p ~/infini_compose_lab
cd ~/infini_compose_lab
2. 为 Console 和 Easysearch 创建持久化子目录
这些目录将用于存储配置、数据和日志
mkdir -p console/config console/data console/logs
mkdir -p easysearch/config easysearch/data easysearch/logs
```
步骤二:提取初始配置文件
这一步与上一篇完全相同。你在首次启动时使用从镜像中提取的默认配置,请执行以下操作。如果这些目录中已存在配置文件(例如从上一篇博客的操作中保留下来的),Docker Compose 在挂载时会直接使用它们。
1. INFINI Console (1.29.6) 初始配置
(容器内配置路径:/config
)
```bash确保在 ~/infini_compose_lab 目录下
docker pull infinilabs/console:1.29.6
docker run --rm \
-v $PWD/console/config:/temp_host_config \
infinilabs/console:1.29.6 \
sh -c "cp -a /config/. /temp_host_config/ && chmod -R ugo+rw /temp_host_config/"
<br /> <br /> **2. INFINI Easysearch (1.13.0) 初始配置**<br /> (容器内配置路径: `/app/easysearch/config`,初始密码: `INFINILabs01`)<br /> <br /> **重要提示:请务必为 Easysearch 设置安全的密码。**<br /> <br />
bash确保在 ~/infini_compose_lab 目录下
docker pull infinilabs/easysearch:1.13.0
docker run --rm \
-e EASYSEARCH_INITIAL_ADMIN_PASSWORD="INFINILabs01" \
-v $PWD/easysearch/config:/temp_host_config \
infinilabs/easysearch:1.13.0 \
sh -c "cp -a /app/easysearch/config/. /temp_host_config/ && chmod -R ugo+rw /temp_host_config/"
```
步骤三:创建
docker-compose.yml
文件
这是核心步骤。在你的项目根目录~/infini_compose_lab
下,创建一个名为docker-compose.yml
的文件,并填入以下内容。这个文件定义了我们的服务、它们如何运行以及它们如何交互。
```bash
cat <docker-compose.yml
services:
easysearch:
image: infinilabs/easysearch:1.13.0
container_name: infini-easysearch
environment:- cluster.name=infini_compose_cluster
- node.name=node-01
- cluster.initial_master_nodes=node-01
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- EASYSEARCH_INITIAL_ADMIN_PASSWORD=INFINILabs01
ports: - "9200:9200"
- "9300:9300"
volumes: - ./easysearch/config:/app/easysearch/config
- ./easysearch/data:/app/easysearch/data
- ./easysearch/logs:/app/easysearch/logs
ulimits:
memlock: {soft: -1, hard: -1}
nofile: {soft: 65536, hard: 65536}
networks: - infini_app_net
console:
image: infinilabs/console:1.29.6
container_name: infini-console
ports: - "9000:9000"
volumes: - ./console/config:/config
- ./console/data:/data
- ./console/logs:/log
networks: - infini_app_net
networks:
infini_app_net:
driver: bridge
EOF
``<br /> <br /> **
docker-compose.yml` 文件关键点:**
- cluster.name=infini_compose_cluster
services
: 定义了easysearch
和console
两个服务。image
: 指定了每个服务使用的 Docker 镜像和版本。container_name
: 为容器指定一个易于识别的名称。environment
: 设置容器的环境变量。- Easysearch 单节点配置: 注意
cluster.initial_master_nodes
设置为节点自身的名称。 ports
: 将容器的端口映射到宿主机的端口。volumes
: 实现持久化的核心。将宿主机当前目录 (./
) 下的console/*
和easysearch/*
子目录分别映射到容器内对应的路径。networks
: 将两个服务都连接到我们定义的infini_app_net
网络。这使得console
服务可以通过服务名easysearch
(例如<a href="https://easysearch:9200" rel="nofollow" target="_blank">https://easysearch:9200</a>
) 来访问easysearch
服务。
步骤四:使用 Docker Compose 启动环境
现在,所有配置都在docker-compose.yml
文件中了。启动整个环境只需要一条命令。
在~/infini_compose_lab
目录下(包含docker-compose.yml
文件),执行:
bash<br /> docker compose up -d<br />
docker compose
(V2) 或docker-compose
(V1)。up
: 创建并启动在docker-compose.yml
中定义的所有服务。-d
: 后台模式运行。
首次运行时,如果本地没有对应的镜像,Docker Compose 会自动拉取。
常用 Docker Compose 命令:
- 查看服务状态:
bash<br /> docker compose ps<br />
- 查看所有服务的实时日志:
bash<br /> docker compose logs -f<br />
- 查看特定服务的日志:
bash<br /> docker compose logs -f console<br /> docker compose logs -f easysearch<br />
- 停止所有服务(保留数据):
bash<br /> docker compose stop<br />
- 停止并移除所有容器、网络和匿名卷(保留通过
volumes
映射的本地数据):
bash<br /> docker compose down<br />
步骤五:验证和使用
- 访问 Console: 浏览器打开
<a href="http://localhost:9000" rel="nofollow" target="_blank">http://localhost:9000</a>
。 - 进行配置: 在 Console 中连接 Easysearch (
<a href="https://easysearch:9200" rel="nofollow" target="_blank">https://easysearch:9200</a>
,因为它们在同一个 Docker 网络中,可以直接使用服务名),创建用户,查看监控等。 - 测试持久化:
```bash
docker compose down # 停止并移除容器
稍等片刻
docker compose up -d # 重新启动
``<br /> <br /> 再次访问
http://localhost:9000`,你会发现之前的配置都还在!
操作截图




彻底清理,包括删除命名卷(如果使用了的话)和本地数据(可选)
```bash-v 移除命名卷
docker compose down -v
然后手动删除本地持久化目录
rm -rf ~/infini_compose_lab/console
rm -rf ~/infini_compose_lab/easysearch
```
总结
通过 Docker Compose,我们用一个简洁的docker-compose.yml
文件取代了之前冗长的docker run
命令,极大地简化了 INFINI Console 和 Easysearch 本地环境的搭建和管理过程。同时,通过正确的卷挂载配置,我们依然确保了数据的持久化,解决了重启后配置丢失的问题。
对于开发、测试和快速原型验证,Docker Compose 无疑是一个强大而高效的工具。希望本教程能帮助你更轻松地使用 INFINI Console 进行本地实验和开发!
关于 INFINI Console

INFINI Console 是一款开源的非常轻量级的多集群、跨版本的搜索基础设施统一管控平台。通过对流行的搜索引擎基础设施进行跨版本、多集群的集中纳管,企业可以快速方便的统一管理企业内部的不同版本的多套搜索集群。INFINI Console 还可以对集群内的索引及数据进行操作管理,可以配置灵活的告警规则,可以指定统一的安全策略,可以查看各个维度的日志和审计信息,真正实现企业级的搜索服务平台化建设和运营。
官网文档:<https://docs.infinilabs.com/console/main/>
开源地址:<https://github.com/infinilabs/console>
作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
原文:https://infinilabs.cn/blog/202 ... pose/
- 访问 Console: 浏览器打开
如何搭建持久化的 INFINI Console 与 Easysearch 容器环境
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2825 次浏览 • 2025-07-01 19:13
背景介绍
许多用户在使用 Docker 部署 INFINI Console(本文使用 1.29.6 版本)时,可能会遇到一个常见问题:重启容器后,之前在 INFINI Console 中所连接的系统集群配置会丢失。这个问题通常源于未能正确配置 Docker 的数据持久化。原本通过 Docker 运行 INFINI Console 只是一个简单的测试示例,并未考虑多次重启使用,现官方文档也进行了更新,参考:[容器部署](https://docs.infinilabs.com/co ... ocker/)
接下来我们本地测试一下。
理解核心问题:Docker 容器与数据持久化
默认情况下,Docker 容器的文件系统是临时的。当容器被停止并删除后,容器内部所做的任何未被持久化的更改都会丢失。INFINI Console 的配置存储在其容器内部的特定目录中。为了在容器重启或重建后保留这些信息,我们必须将这些关键目录映射到宿主机(你的电脑)上的持久化存储位置。
准备工作
- 操作系统: macOS (本文示例)
- Docker 环境: OrbStack ([https://orbstack.dev/](https://orbstack.dev/)) 或 Docker Desktop for Mac。
请确保 Docker 服务已启动并正常运行。你可以通过在终端执行docker --version
来验证。
bash<br /> docker --version<br /> Docker version 25.0.5, build 5dc9bcc<br />
步骤一:创建本地持久化目录和自定义 Docker 网络
首先,在宿主机上为 Console 和 Easysearch 创建用于存储配置、数据和日志的目录。同时,创建一个自定义 Docker 网络,以便容器之间可以通过名称进行通信。
```bash1. 创建项目根目录和各个服务的持久化子目录
mkdir -p ~/infini_manual_setup/console/config ~/infini_manual_setup/console/data ~/infini_manual_setup/console/logs
mkdir -p ~/infini_manual_setup/easysearch/config ~/infini_manual_setup/easysearch/data ~/infini_manual_setup/easysearch/logs
cd ~/infini_manual_setup
2. 创建一个自定义的 Docker 桥接网络
docker network create infini_app_net
```
infini_app_net
是我们为这两个容器创建的自定义网络名称。
步骤二:提取初始配置文件
为了方便首次启动和后续自定义,我们需要从官方 Docker 镜像中提取默认的配置文件到我们本地创建的持久化目录中。
1. INFINI Console (1.29.6) 初始配置
根据 INFINI Console [官方 Docker 文档](https://docs.infinilabs.com/co ... docker),其容器内配置文件位于/config
。
bash<br /> docker pull infinilabs/console:1.29.6<br /> docker run --rm \<br /> -v $PWD/console/config:/temp_host_config \<br /> infinilabs/console:1.29.6 \<br /> sh -c "cp -a /config/. /temp_host_config/ && chmod -R ugo+rw /temp_host_config/"<br />
2. INFINI Easysearch (1.13.0) 初始配置
INFINI Easysearch 镜像内部的配置文件位于/app/easysearch/config
,并且需要初始管理员密码INFINILabs01
。
重要提示:请务必为 Easysearch 设置安全的密码。
bash<br /> docker pull infinilabs/easysearch:1.13.0<br /> docker run --rm \<br /> -e EASYSEARCH_INITIAL_ADMIN_PASSWORD="INFINILabs01" \<br /> -v $PWD/easysearch/config:/temp_host_config \<br /> infinilabs/easysearch:1.13.0 \<br /> sh -c "cp -a /app/easysearch/config/. /temp_host_config/ && chmod -R ugo+rw /temp_host_config/"<br />
现在,你的本地console/config
和easysearch/config
目录应该包含了初始配置文件。
检查目录如下
bash<br /> tree -L 3 .<br /> .<br /> ├── console<br /> │ ├── config<br /> │ │ ├── install_agent.tpl<br /> │ │ ├── permission.json<br /> │ │ ├── setup<br /> │ │ └── system_config.tpl<br /> │ ├── data<br /> │ └── logs<br /> └── easysearch<br /> ├── config<br /> │ ├── admin.crt<br /> │ ├── admin.key<br /> │ ├── analysis-ik<br /> │ ├── ca.crt<br /> │ ├── ca.key<br /> │ ├── easysearch.yml<br /> │ ├── easysearch.yml.example<br /> │ ├── instance.crt<br /> │ ├── instance.key<br /> │ ├── jvm.options<br /> │ ├── jvm.options.d<br /> │ ├── log4j2.properties<br /> │ └── security<br /> ├── data<br /> └── logs<br />
步骤三:手动运行 INFINI Easysearch 容器
使用docker run
命令启动 Easysearch,并配置端口映射、环境变量和最重要的——卷挂载。
bash<br /> docker run -d \<br /> --name easysearch01 \<br /> --network infini_app_net \<br /> -p 9200:9200 \<br /> -p 9300:9300 \<br /> -e cluster.name="infini_local_cluster" \<br /> -e node.name="easysearch-node01" \<br /> -e cluster.initial_master_nodes="easysearch-node01" \<br /> -e "ES_JAVA_OPTS=-Xms1g -Xmx1g" \<br /> -e EASYSEARCH_INITIAL_ADMIN_PASSWORD="INFINILabs01" \<br /> -v $PWD/easysearch/config:/app/easysearch/config \<br /> -v $PWD/easysearch/data:/app/easysearch/data \<br /> -v $PWD/easysearch/logs:/app/easysearch/logs \<br /> --ulimit memlock=-1:-1 \<br /> --ulimit nofile=65536:65536 \<br /> infinilabs/easysearch:1.13.0<br />
关键参数解释:
--name easysearch01
: 为容器指定一个名称。--network infini_app_net
: 连接到自定义网络。-p HOST_PORT:CONTAINER_PORT
: 端口映射。-e VARIABLE=VALUE
: 设置环境变量。-v $PWD/host/path:/container/path
: 实现持久化的核心。将宿主机当前工作目录 ($PWD
) 下的子目录映射到容器内的指定路径。
步骤四:手动运行 INFINI Console 容器
现在启动 Console 容器,同样配置网络、端口、环境变量和卷挂载。
bash<br /> docker run -d \<br /> --name console01 \<br /> --network infini_app_net \<br /> -p 9000:9000 \<br /> -v $PWD/console/config:/config \<br /> -v $PWD/console/data:/data \<br /> -v $PWD/console/logs:/log \<br /> infinilabs/console:1.29.6<br />
查看日志
bash<br /> docker logs -f easysearch01<br /> docker logs -f console01<br />
步骤五:验证服务和持久化
- 检查容器状态:
docker ps
(应能看到easysearch01
和console01
)。- 访问 Console: 浏览器打开
<a href="http://localhost:9000" rel="nofollow" target="_blank">http://localhost:9000</a>
。- 在 Console 中进行初始化配置
- 测试持久化 (重启 Console 容器):
```bash
docker stop console01
docker rm console01
重新运行步骤四中启动 Console 的 docker run 命令 (确保所有参数一致)
``<br /> <br /> 操作截图<br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> **再次访问 Console**: 打开
http://localhost:9000`。如果一切正常,证明持久化成功。
步骤六:停止和清理(可选)
- 停止容器:
docker stop console01 easysearch01
- 移除容器:
docker rm console01 easysearch01
- 移除网络:
docker network rm infini_app_net
- 移除本地持久化数据 (如果不再需要):
bash<br /> rm -rf ~/infini_manual_setup/console<br /> rm -rf ~/infini_manual_setup/easysearch<br />
总结
通过docker run
命令并仔细配置卷挂载,我们成功地为 INFINI Console 和 Easysearch 构建了一个具有持久化能力的本地容器环境,有效解决了重启后配置丢失的问题。虽然手动操作参数较多,但它能让你更清晰地理解 Docker 的核心机制。
在后续的文章中,我们将探讨如何使用 Docker Compose 来简化这一过程。
关于 INFINI Console

INFINI Console 是一款开源的非常轻量级的多集群、跨版本的搜索基础设施统一管控平台。通过对流行的搜索引擎基础设施进行跨版本、多集群的集中纳管,企业可以快速方便的统一管理企业内部的不同版本的多套搜索集群。INFINI Console 还可以对集群内的索引及数据进行操作管理,可以配置灵活的告警规则,可以指定统一的安全策略,可以查看各个维度的日志和审计信息,真正实现企业级的搜索服务平台化建设和运营。
官网文档:<https://docs.infinilabs.com/console/main/>
开源地址:<https://github.com/infinilabs/console>
作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
原文:https://infinilabs.cn/blog/202 ... cker/
Easysearch 索引备份之 Clone API
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2139 次浏览 • 2025-06-17 19:00
在日常运维 Easysearch 的过程中,备份数据是一项重要工作。为了确保数据安全和业务连续性,我们可能需要了解并掌握多种备份索引的方法,以便应对不同的场景。我们先梳理下常用的备份方法有哪些。
Snapshot
Easysearch 的 Snapshot(快照) 是一种官方支持的集群数据备份与恢复机制,通过将索引数据、集群状态(如设置、模板)和分片分配信息保存到外部存储仓库(如本地文件系统、AWS S3、华为云 OBS 等)实现全量或增量备份。其核心原理是复制索引的 Lucene 分片文件,并利用段文件(Segment)的不可变性实现增量存储优化。
快照的优点包括:
- 高效性:增量备份仅存储新增或修改的段文件,显著节省存储空间和网络传输成本;
- 可靠性:支持跨集群恢复和灾难性故障修复,避免直接拷贝数据目录导致的数据不一致风险;
- 灵活性:可指定备份特定索引,并支持版本兼容性恢复(需遵循版本匹配规则);
- 自动化:通过策略(Snapshot Policy)实现定时备份管理。
缺点则包括:
- 时效性限制:无法实现实时备份,是一种 PIT (Point in Time) 备份;
- 需预先配置:需预先注册仓库并确保存储系统可用性;
- 恢复约束:恢复时需关闭或删除目标索引,或恢复时修改索引名称;
- 依赖主分片状态:若主分片不可用(如节点故障),快照任务会失败。
总体而言,Snapshot 是生产环境首选的备份方案,尤其适合大规模数据归档和跨环境迁移,但需权衡备份频率与存储成本。详情可以参考[文档](https://infinilabs.cn/blog/202 ... ackup/)。
Reindex
Easysearch 的 Reindex 是一种通过 API 将数据从一个索引复制到另一个索引的备份方法,适用于同集群或跨集群的数据迁移与重建。其核心操作是使用POST _reindex
命令将源索引的文档批量读取并写入目标索引。备份时需确保目标索引的 Mapping 与源索引兼容(字段类型一致),并通过size
参数控制批量处理量(如"size": 2000
)以优化性能。对于跨集群备份,需在目标集群配置文件中添加源集群 IP 白名单(reindex.remote.whitelist
)并提供认证信息,详情可以参考[文档](https://infinilabs.cn/blog/202 ... emote/)。
优点:
- 灵活性:支持通过
query
参数筛选特定数据备份(如仅迁移某字段值符合条件的数据); - 无缝整合:可在目标索引中修改索引结构(如分片数、字段类型);
- 并发及限流:支持设置并发度和限流阈值,适应不同的场景;
- 操作便捷:无需额外存储配置,适合临时备份或小规模迁移。
缺点:
- 资源消耗大:reindex 本质是数据写入,要占用 CPU、内存和磁盘 IO,可能影响集群性能;
- 网络依赖:跨集群备份依赖网络带宽,高延迟或带宽不足会显著拖慢速度;
- 中断风险:reindex 一旦中途报错,无法继续重试,只能从头再来;
- 时效性局限:备份完成后新增数据需手动触发二次迁移,无法实现实时同步。
建议在低峰期执行 Reindex,并优先采用快照(Snapshot)进行生产环境长期备份,Reindex 更适合索引结构调整或小规模数据迁移场景。
工具备份
还有些工具支持将 Easyearch 的索引数据备份成一个文件,比如 elasticsearch-dump、Logstash 等。数据量较大的情况下,这些工具可能会有效率问题,一般在特定场景下有用,在此不展开介绍。
Clone API
Easysearch 的 Clone API 并不是传统意义上的备份工具,其核心设计目标是通过复制索引的底层段文件(Segment)快速生成一个与原索引数据一致的新索引,包括源索引是 Mapping 和 Setting 也一起复制。
具体操作步骤如下:
- 设置源索引为只读状态
bash<br /> PUT /.infini_metrics-000004/_settings<br /> {<br /> "settings": {<br /> "index.blocks.write": true<br /> }<br /> }<br />

- 执行 Clone 操作
bash<br /> POST .infini_metrics-000004/_clone/backup_infini_metrics-000004<br />

- 设置源索引和新索引为可读写状态
复制是新索引也会是只可读状态,大家根据需要选择是否都改成可读写状态。
bash<br /> PUT /.infini_metrics-000004,backup_infini_metrics-000004/_settings<br /> {<br /> "settings": {<br /> "index.blocks.write": null<br /> }<br /> }<br />

优点:
- 极速复制:直接复用底层段文件,无需重写数据,适用于大数据量快速复制。
- 保留定义: 直接使用源索引的 Setting 和 Mapping。
- 存储优化:可调整目标索引的副本数,节省资源。
缺点:
- 业务影响:克隆前需修改源索引为只可读,导致写入中断,影响服务可用性。
- 不够灵活:沿用源索引 Setting 和 Mapping 无法修改(副本数可修改)。
- 扩展性不足:不能跨集群,目标索引只能在本集群。
Clone API 有自己鲜明的特点,对比 Snapshot,它不用恢复过程,目标索引直接在集群中了。对比 Reindex,它无需重写数据和先创建索引,更加高效。在特定场景下非常有用,也可以搭配其他备份方法一起使用。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
原文:https://infinilabs.cn/blog/202 ... -api/
- 极速复制:直接复用底层段文件,无需重写数据,适用于大数据量快速复制。
Easysearch 迁移数据之 Reindex From Remote
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2388 次浏览 • 2025-06-12 15:24
在之前的博客《[从 Elasticsearch 迁移到 Easysearch 指引](https://infinilabs.cn/blog/202 ... earch/)》中介绍过如何把索引从 Elasticsearch 迁移到 Easysearch。有时候想临时从 Elasticsearch 迁移点儿数据做测试,数据量不大,也可尝试使用 Reindex From Remote 的方法。
测试环境介绍
本次主要测试从远程集群索引数据,reindex 还有很多其他使用方式,详情请参考[官方文档](https://docs.infinilabs.com/ea ... -data/)。
- [Easysearch](https://infinilabs.cn/products/easysearch/) 版本:1.10.0,监听 localhost:9200
- Elasticsearch 版本:6.8.23,监听 localhost:9201
- [INFINI Console](https://infinilabs.cn/products/console/) 版本:1.25.1(运行 reindex 命令用)
Reindex API
Reindex 可以从本地或远程集群将源索引数据写入本地目标索引。使用简单,有以下注意点:
- 源索引启用 _source ,这个默认都是启用的
- 在调用 _reindex 之前,应该先创建、配置目标索引
- 如果源索引在远程集群,必须在 easysearch.yml 中配置 reindex.remote.whitelist 设置
- 使用 POST 调用
测试过程
我们先不设置白名单,直接从远程集群 reindex 看看会怎样。

报错提示 localhost:9201 不在 reindex.remote.whitelist 中。
正常操作步骤
- 编辑 Easysearch 配置文件 easysearch.yml,添加白名单,重启生效。
plain<br /> reindex.remote.whitelist: [localhost:9201]<br />
- 建立目标索引,指定 setting 和 mapping
reindex 不会复制源索引的 setting 和 mapping,需要提前创建目标索引,否则会使用默认设置。
- 执行 reindex 命令

执行成功。需要注意的是,如果数据量比较大,reindex 命令会超时,这个没关系,任务会继续在后台执行。也可以在执行 reindex 的时候添加参数 wait_for_completion=false 不等待执行完成,直接返回任务 id。
plain<br /> POST _reindex?wait_for_completion=false<br />

针对有认证的集群,reindex 可以指定以下选项:

总结
针对临时数据量不大的场景可尝试使用 reindex 迁移数据。如果数据量大了,reindex 迁移速度不是很高效,而且如果中途出现错误迁移中断了,需要重新 reindex 不方便,建议使用 [INFINI Console 进行数据迁移](https://docs.infinilabs.com/co ... ation/)。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
原文:https://infinilabs.cn/blog/202 ... mote/
- 编辑 Easysearch 配置文件 easysearch.yml,添加白名单,重启生效。
Easysearch 时序数据的基于时间范围的合并策略
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2391 次浏览 • 2025-05-07 16:44
如果你正在使用 [Easysearch](https://docs.infinilabs.com/easysearch/main/) 处理日志、监控指标、事件流或其他任何具有时间顺序的数据,那么你一定知道索引的性能和效率至关重要。Easysearch 底层的 Lucene Segment 合并是保持搜索和索引性能的关键后台任务。然而,你是否意识到,默认的合并策略可能并不是处理时序数据的最佳选择?

今天,我们就来介绍 Easysearch 1.12.1 版本起引入的一个重要优化:基于时间范围的合并策略 (TimeRangeMergePolicy) ,它专门为优化时序数据的 Segment 合并而生。
时序数据的合并挑战:默认策略的局限性
Easysearch 默认使用的合并策略(如 TieredMergePolicy)非常智能,它会根据 Segment 的大小、文档删除比例等因素来决定合并哪些 Segment,以平衡查询性能和资源使用。
但在时序数据场景下,这种通用策略可能会遇到一些问题:
- 冷热数据混合: 想象一下,几个月前的旧日志数据(冷数据)可能因为大小合适而被选中,与最近几小时内产生的新数据(热数据)进行合并。这会带来不必要的 I/O 和 CPU 开销,因为冷数据通常访问很少,合并它们对查询性能的提升有限,反而消耗了宝贵的资源。
- 查询性能影响: 合并可能产生覆盖时间跨度非常大的 Segment。当你执行按时间范围过滤的查询时(这在时序场景中非常常见),查询可能需要扫描这些巨大的 Segment,即使其中大部分数据都不在你的目标时间范围内,从而降低查询效率。
解决方案:TimeRangeMergePolicy 登场!
为了解决上述痛点,Easysearch 引入了TimeRangeMergePolicy
。顾名思义,这种策略在做合并决策时,将时间维度纳入了核心考量。
它的核心思想很简单,但非常有效:
- 时间优先: 倾向于合并那些时间上相邻或接近的 Segment。比如,属于同一天或同一小时的 Segment 更有可能被一起合并。
- 保留时间分区: 尽量避免将时间跨度极大的 Segment 合并在一起。这有助于保持数据的“时间局部性”,使得按时间范围查询时能更快地排除不相关的 Segment。
- 优先合并新数据: 通常,新产生的数据(热数据)更新和删除操作更频繁。优先合并包含较新数据的 Segment,有助于更快地回收被删除文档占用的空间,并优化对最新数据的查询性能。
如何为你的时序索引启用 TimeRangeMergePolicy?
启用这个功能非常简单,只需要两步:
- 时间优先: 倾向于合并那些时间上相邻或接近的 Segment。比如,属于同一天或同一小时的 Segment 更有可能被一起合并。
- 确认日期字段: 首先,确保你的索引 Mapping 中有一个能准确代表数据时间的字段,通常是日期(
date
)或时间戳(date_nanos
)类型,例如@timestamp
、event_time
等。这个字段的值应该反映数据产生的实际时间。 - 更新索引设置: 使用 Index Settings API,为你的索引指定
index.merge.policy.time_range_field
参数,并将其值设置为你的时间字段名。
示例:
假设你的时间字段是timestamp
,索引名称是my-timeseries-index
,你可以执行以下请求:
auto<br /> PUT /my-timeseries-index/_settings<br /> {<br /> "index": {<br /> "merge.policy.time_range_field": "timestamp"<br /> }<br /> }<br />
搞定!设置之后,my-timeseries-index
后续的 Segment 合并就会自动采用TimeRangeMergePolicy
了。
专家提示: 如果你想让所有新创建的时序索引默认就使用这个策略,可以将这个设置添加到你的索引模板 (Index Template) 中。
TimeRangeMergePolicy 的优势
启用时间范围合并策略能带来哪些好处呢?
- 降低合并开销: 显著减少冷热数据的无效合并,节省 I/O 和 CPU 资源。
- 提高资源效率: 更智能的合并有助于更快地回收已删除文档的空间,并可能降低整体计算资源的使用。
- 优化查询性能: 保持 Segment 的时间局部性,对于按时间范围过滤的查询(例如,“查询过去一小时的日志”)可能会有明显的性能提升。
- 对时序数据更友好: 该策略的设计初衷就是为了更好地服务于日志、指标这类严格按时间增长的数据模式。
注意事项
在使用TimeRangeMergePolicy
时,有几点需要注意:
- 时间字段是关键: 策略的效果高度依赖于你所指定的
time_range_field
。如果该字段不存在,或者字段中的时间值混乱、不准确,策略可能无法发挥预期效果,甚至适得其反。 - 并非万能丹: 这个策略最适合具有明确时间序列特征的数据。对于非时序数据(例如,商品信息、用户信息索引),默认的
TieredMergePolicy
可能仍然是更好的选择。 - 版本要求: 请确保你的 Easysearch 集群版本至少为 1.12.1。
总结
对于处理大量时序数据的 Easysearch 用户来说,TimeRangeMergePolicy
是一个非常有价值的优化工具。通过感知数据的时间属性,它可以让 Segment 合并操作更加智能和高效,从而降低资源消耗、提升查询性能。如果你的索引符合时序数据的特征,并且正在运行 Easysearch 1.12.1 或更高版本,不妨尝试启用这个策略,看看它能否为你的集群带来改善!
关于 Easysearch
INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:张磊,极限科技(INFINI Labs)搜索引擎研发负责人,对 Elasticsearch 和 Lucene 源码比较熟悉,目前主要负责公司的 Easysearch 产品的研发以及客户服务工作。
原文:https://infinilabs.cn/blog/202 ... arch/
- 降低合并开销: 显著减少冷热数据的无效合并,节省 I/O 和 CPU 资源。
Easysearch Rollup 相比 OpenSearch Rollup 的优势分析
INFINI Labs 小助手 发表了文章 • 0 个评论 • 3716 次浏览 • 2025-04-16 11:56
背景
在处理时序数据时,Rollup 功能通过数据聚合显著降低存储成本,并提升查询性能。Easysearch 与 OpenSearch 均提供了 Rollup 能力,但在多个关键维度上,[Easysearch Rollup](https://infinilabs.cn/blog/202 ... ollup/) 展现出更优的表现。本文将从查询体验、索引管理、聚合能力、性能优化和任务管理五个方面,分析 Easysearch Rollup 相较于 OpenSearch Rollup 的优势。
---
Easysearch vs OpenSearch
1. 查询体验:标准接口与无缝集成
Easysearch Rollup 支持通过标准的 _search
API 查询原始索引,系统自动融合 Rollup 数据,用户无需变更现有代码或使用专用查询端点。相比之下,OpenSearch Rollup 虽然使用标准查询语法,但需要用户显式指定 Rollup 索引,无法自动结合原始数据,在需要同时访问原始与聚合数据的场景下显得更为繁琐。
- 差异:Easysearch 支持自动融合原始与 Rollup 数据,OpenSearch 需手动指定索引。
- 影响:Easysearch 显著降低了查询逻辑的复杂性和开发维护成本。
---
2. 索引管理:自动化与扩展能力
Easysearch Rollup 提供自动索引滚动功能,可通过rollup.index_max_docs.
配置项为不同的目标 Rollup 索引设置文档数上限,触发新索引的动态创建,显著简化管理流程。此外,配置中支持使用变量(如{{ctx.source_index}}
)动态生成目标索引名,便于多个任务复用同一模板,批量扩展 Rollup 任务时更加高效和灵活。
相比之下,OpenSearch Rollup 依赖 Index State Management(ISM)策略或手动操作进行索引切换,配置复杂、监控成本高,且在大规模任务部署时缺乏灵活的模板化机制。
- 差异:Easysearch 提供内建的自动滚动机制,OpenSearch 依赖外部策略或手动配置。
- 影响:Easysearch 更易于统一管理和大规模扩展,运维成本更低。
---
3. 聚合能力:更广泛的聚合类型支持
Easysearch Rollup 支持丰富的聚合类型,包括数值字段的avg
、sum
、max
、min
、percentiles
,关键词字段的terms
,日期字段的date_histogram
与date_range
,还支持filter
聚合与special_metrics
(可自定义聚合字段和方式)等高级功能。OpenSearch Rollup 支持的聚合类型相对有限,不支持date_range
、filter
等复杂聚合表达式。
- 差异:Easysearch 提供更全面的聚合能力,OpenSearch 仅支持基础聚合。
- 影响:Easysearch 更适合构建复杂的时序分析任务,满足更广泛的业务需求。
---
4. 性能优化:精细化配置与资源控制
Easysearch Rollup 提供多种性能优化选项,例如ROLLUP_SEARCH_MAX_COUNT
控制并发查询数,rollup.hours_before
限制回溯时间范围,write_optimization
和field_abbr
用于优化写入效率与运行时的内存占用。而 OpenSearch Rollup 缺乏类似的专用配置项,主要依赖通用的集群参数,灵活性与精细度较低。
- 差异:Easysearch 提供针对 Rollup 场景的专属优化选项,OpenSearch 优化能力较通用。
- 影响:Easysearch 在资源使用效率、查询性能和成本控制方面更具优势。
---
5. 任务管理:批量控制与更高灵活性
Easysearch Rollup 支持使用通配符进行任务批量管理,且新建任务默认处于非激活状态,用户可按需启用。而 OpenSearch Rollup 中,任务默认立即启用,管理粒度较粗,仅支持单个任务的启停与修改,缺乏批量操作能力。
- 差异:Easysearch 支持批量任务管理与按需启用,OpenSearch 功能较为基础。
- 影响:Easysearch 在多任务环境下提供更高的管理效率和控制能力。
---
实战示例:节点统计 Rollup 配置
以下是一个 Easysearch Rollup 任务的配置示例:
json<br /> {<br /> "rollup": {<br /> "source_index": ".infini_metrics",<br /> "target_index": "rollup_node_stats_{{ctx.source_index}}",<br /> "timestamp": "timestamp",<br /> "continuous": true,<br /> "page_size": 200,<br /> "cron": "*/5 1-23 * * *",<br /> "interval": "1m",<br /> "identity": ["metadata.labels.cluster_id", "metadata.labels.node_id"],<br /> "stats": [{ "max": {} }, { "min": {} }, { "value_count": {} }],<br /> "special_metrics": [<br /> {<br /> "source_field": "payload.elasticsearch.node_stats.jvm.mem.heap_used_in_bytes",<br /> "metrics": [{ "avg": {} }, { "max": {} }, { "min": {} }]<br /> }<br /> ],<br /> "write_optimization": true<br /> }<br /> }<br />
- 亮点:支持自动索引滚动、标准 API 查询、
special_metrics
高级聚合与写入优化等特性。
---
总结
综合来看,Easysearch Rollup 在以下方面优于 OpenSearch Rollup:
- 查询接口的兼容性与无感知集成
- 自动化的索引管理与扩展能力
- 更丰富的聚合类型与表达能力
- 针对性更强的性能优化参数
- 灵活高效的任务批量管理机制
这些优势使 Easysearch Rollup 更加适用于复杂、多样化的时序数据处理场景,特别是在对性能、扩展性与运维效率有较高要求的系统中表现出色。如果你正在寻找一款功能全面、易于管理的 Rollup 解决方案,Easysearch 是一个值得重点考虑的选择。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:张磊,极限科技(INFINI Labs)搜索引擎研发负责人,对 Elasticsearch 和 Lucene 源码比较熟悉,目前主要负责公司的 Easysearch 产品的研发以及客户服务工作。
原文:https://infinilabs.cn/blog/202 ... llup/
Easysearch 自动备份:快照生命周期管理
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2739 次浏览 • 2025-04-09 18:28
之前介绍了 Easysearch 如何[使用 S3 进行快照备份](http://infinilabs.cn/blog/2025 ... ackup/),毕竟那是手工操作。Easysearch 还提供了[快照生命周期管理](https://docs.infinilabs.com/ea ... m_api/),能够按照策略自动创建、删除快照,极大地方便了用户的日常管理。
快照生命周期管理计划由创建计划、删除计划以及快照配置组成。
- 创建计划和删除计划包含一个 cron 表达式,指定任务的频率和时间。
- 删除计划可以指定快照保留策略,以保留过去 30 天的快照或仅保留最近的 10 个快照。
- 快照配置包括快照的索引和存储库,并支持所有通过 API 创建快照时的参数。此外,还可以指定快照名称中使用的日期的格式和时区。
快照生命周期创建的快照名称格式为<policy _ name>-<date>-<Random number>
。
比如, 计划每 2 分钟对索引 .infini_metrics-00001 创建一个快照,并且只保留最近的 2 个快照。
plain<br /> curl -XPOST -uadmin:admin -H 'Content-Type: application/json' 'https://localhost:9200/_slm/policies/daily-policy' -d '<br /> {<br /> "description": "测试快照策略",<br /> "creation": {<br /> "schedule": {<br /> "cron": {<br /> "expression": "*/2 * * * *",<br /> "timezone": "Asia/Shanghai"<br /> }<br /> },<br /> "time_limit": "1h"<br /> },<br /> "deletion": {<br /> "schedule": {<br /> "cron": {<br /> "expression": "*/1 * * * *",<br /> "timezone": "Asia/Shanghai"<br /> }<br /> },<br /> "condition": {<br /> "max_count": 2<br /> },<br /> "time_limit": "1h"<br /> },<br /> "snapshot_config": {<br /> "date_format": "yyyy-MM-dd-HH:mm",<br /> "date_format_timezone": "Asia/Shanghai",<br /> "indices": ".infini_metrics-00001",<br /> "repository": "easysearch_s3_repo",<br /> "ignore_unavailable": "true",<br /> "include_global_state": "false"<br /> }<br /> }'<br />
自动创建的快照如下图,一个 16 点 34 分创建的,另一个 16 点 36 分创建的。

⚠️ 注意:虽然指定只保留最近两个快照,但因为创建和删除其实是两个独立的任务,所以会短暂出现存在 3 个快照的现象,等删除任务调度一次就会删除多余的快照了。
如果遇到维护需要停止自动备份,也有相应的 API 来启停快照策略。
停止策略
plain<br /> curl -XPOST -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy/_start'<br />
启动策略
plain<br /> curl -XPOST -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy/_stop'<br />
查看策略
plain<br /> curl -XGET -uadmin:admin 'https://localhost:9200/_slm/policies'<br />
删除策略
plain<br /> curl -XDELETE -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy?pretty'<br />
更多详细信息请参考[官方文档](https://docs.infinilabs.com/ea ... m_api/)。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch/main/>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
Easysearch S3 备份实战
INFINI Labs 小助手 发表了文章 • 0 个评论 • 2597 次浏览 • 2025-04-09 18:28
Easysearch 内置了 S3 插件,这意味着用户可以直接使用该功能而无需额外安装任何插件。通过这一内置支持,用户能够方便快捷地执行 Amazon S3 上的数据快照操作。这种设计不仅简化了配置流程,也提高了工作效率,使得数据备份或迁移等任务变得更加简单易行。对于需要频繁与 S3 存储服务交互的应用场景来说,这是一个非常实用且高效的功能特性。
Minio
MinIO 是一款高性能的开源对象存储系统,专为存储大量的非结构化数据而设计。它提供了与 Amazon S3 兼容的 API,本次测试我们使用 MinIO 作为存储仓库。
建立 Bucket
进入 MinIO 管理界面,创建测试用的 bucket。


创建 Access key
测试的 Access Key 设置的比较简单。

Easysearch
为了能够使用 S3 存储,Easysearch 要进行必要的配置。
easyearch.yml
修改 easysearch.yml 配置 S3 信息。
plain<br /> s3.client.default.endpoint: 172.17.0.4:9000<br /> s3.client.default.protocol: http<br />
⚠️ 注意:修改了 easysearch.yml 需要重启生效。
keystore
为了安全,我们把 S3 的 Access key 信息加入 keystore 中。
plain<br /> bin/easysearch-keystore add s3.client.default.access_key #输入easysearch<br /> bin/easysearch-keystore add s3.client.default.secret_key #输入easysearch<br /> bin/easysearch-keystore list<br />
注册存储库
在 INFINI Console 的开发工具中,使用命令注册 s3 存储库。
plain<br /> PUT /_snapshot/easysearch_s3_repo?verify=true&pretty<br /> {<br /> "type": "s3",<br /> "settings": {<br /> "bucket": "easysearch-bucket",<br /> "compress": true<br /> }<br /> }<br />
更多参数请查看[文档](https://infinilabs.cn/docs/lat ... zon-s3)。
创建快照
在 [INFINI Console](https://infinilabs.cn/products/console/) 的开发工具中,使用命令创建快照。


备份执行完成。
S3 查看快照
我们在 INFINI Console 中通过命令创建了快照,可以在 MinIO 的 bucket 中进行进一步确认是否有相关文件。


快照还原测试
删除以备份索引 .infini_metrics-0001,删除前先查看下索引情况,文档数 557953。

删除 .infini_metrics-0001 索引。

确认 .infini_metrics-0001 索引已被删除。

进行快照还原。

验证恢复索引。

索引 .infini_metrics-0001 已经还原了,文档数也一致。
小结
Easysearch 使用 S3 存储备份的步骤如下:
- S3 服务建立 Bucket、Access Key。
- Easysearch 编辑 easysearch.yml 添加 S3 服务 endpoint 信息。
- easysearch-keystore 添加 S3 的 Access key 信息,加密保存。
- Easysearch 注册 S3 存储仓库。
- 执行快照备份。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch/main/>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
Easysearch 节点磁盘不足应对方法
yangmf2040 发表了文章 • 0 个评论 • 3424 次浏览 • 2025-03-17 14:57
[Easyearch](http://localhost:1313/products/easysearch/) 为了防止索引将磁盘空间完全占满,使用磁盘水位线进行磁盘空间控制。之前有[文章](https://infinilabs.cn/blog/202 ... -tips/)提过不同水位线的作用,以及如何使用 INFINI Console 提前进行告警,提前进行处理。本篇主要探讨提前处理的情况。
一、增加资源
如果资源充裕,可考虑为 Easysearch 集群扩充资源:
- 添加新的数据节点
扩充节点后,集群会自动进行数据平衡,可用下面的命令查看进度
plain<br /> GET /_cat/shards?v&h=state,node&s=state<br />
如果响应中分片的状态是 RELOCATING ,则表示分片仍在移动。
- 扩充现有数据节点磁盘容量
扩充完后可查看磁盘利用率下降情况
plain<br /> GET _cat/allocation?v&s=disk.avail&h=node,disk.percent,disk.avail,disk.total,disk.used,disk.indices,shards<br />
二、释放磁盘空间
如果无资源可添加,则考虑减少磁盘消耗:
- 删除无用索引
建议使用[索引生命周期](https://infinilabs.cn/docs/lat ... m_api/)进行管理,自动删除过期索引。
- 删除多余副本
有些业务索引可能会有多分副本,可酌情缩减副本数,降低磁盘消耗。以下命令按副本数量和主存储大小的降序排列索引。
plain<br /> GET _cat/indices?v&s=rep:desc,pri.store.size:desc&h=health,index,pri,rep,store.size,pri.store.size<br />
- 可搜索快照
对于有些数据平时不常用,但需要长期保留的,建议使用[可搜索快照](https://infinilabs.cn/blog/202 ... ticle/)功能降低磁盘消耗。
三、索引空间优化
- 启用 ZSTD 压缩及 source_reuse 功能
Easysearch 支持 ZSTD 和 source_reuse 功能,对比默认的压缩算法,可大幅降低磁盘消耗。
可在创建索引时启用 ZSTD 和 source_reuse 功能,也可通过索引模板来进行设置,参考[文档](https://infinilabs.cn/docs/lat ... c%25a9)。
plain<br /> PUT test-index<br /> {<br /> "settings": {<br /> "index.codec": "ZSTD",<br /> "index.source_reuse": "true"<br /> }<br /> }<br />
⚠️ 注意:当索引里包含 nested 类型映射,或插件额外提供的数据类型时,不能启用 source_reuse,例如 knn 索引。
- 索引优化
- mapping 优化
避免使用默认的 mapping 类型,因为字符串类型的数据将得到 text 和 keyword 两个类型的 mapping。 - 字段优化
统计指定索引每个字段的访问次数。
plain<br /> GET metrics/_field_usage_stats<br />
分析指定索引各个字段占用磁盘的大小。
plain<br /> POST metrics/_disk_usage?run_expensive_tasks=true<br />
结合以上信息进一步优化各个字段,如关闭不用的功能等
- mapping 优化
- 使用 rollup 功能
对于时序场景类的数据,往往会有大量的非常详细的聚合指标,随着时间的图推移,存储将持续增长。汇总功能可以将旧的、细粒度的数据汇总为粗粒度格式以进行长期存储。通过将数据汇总到一个单一的文档中,可以大大降低历史数据的存储成本。
Easysearch 的 [rollup](https://infinilabs.cn/docs/lat ... p_api/) 具备一些独特的优势,可以自动对 rollup 索引进行滚动而不用依赖其他 API 去单独设置,并且在进行聚合查询时支持直接搜索原始索引,做到了对业务端的搜索代码完全兼容,从而对用户无感知。
如果有问题,欢迎加我微信沟通。

关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
引爆知识革命!Easysearch+携手+DeepSeek+打造下一代智能问答系统
yangmf2040 发表了文章 • 0 个评论 • 2812 次浏览 • 2025-03-13 11:31
去年我们尝试过使用 Easysearch + 千问 2 大模型打造一个[企业内部知识问答系统](https://infinilabs.cn/blog/202 ... earch/),今年又有更加给力的大模型出现了--DeepSeek,性能对标 OpenAI o1 正式版。而且 Easysearch 对比去年也有了不少进步,是时候让我们升级下问答系统了。
DeepSeek
2025 年 1 月 20 日,人工智能领域迎来里程碑式突破!深度求索(DeepSeek)正式发布新一代推理大模型 DeepSeek-R1,不仅实现与 OpenAI 最新 o1 正式版的性能对标,更以全栈开放的生态布局引发行业震动。DeepSeek-R1 是首个遵循 MIT License 开源协议的高性能推理模型,完全开源,不限制商用,无需申请,极大地推动了 AI 技术的开放与共享。

下载模型
我们使用 ollama 下载运行 DeepSeek-R1,根据本地资源情况选择一个大小合适的版本:8b。

- 8b 蒸馏模型源自 Llama3.1-8B-Base
- 7b 蒸馏模型源自 Qwen-2.5 系列
这两个可能是个人用户使用最多的选择,大家资源充足的可以都下载下来对比下效果。

由于是升级,我们只需在原有程序基础上替换新版本的 Easysearch 和集成 DeepSeek 即可,[Easysearch](https://infinilabs.cn/download/) 升级成新版本 1.10.1,程序框架和 embedding 模型 (mxbai-embed-large:latest) 仍然保持不变。

数据准备
跟上次一样,使用 "INFINI 产品安装手册.PDF" 作为知识内容,通过程序将文档内容切片、转换成向量后写入 Easysearch 存储,然后结合大模型对其中的内容进行提问。
程序调整
程序代码需要调整 LLM 为 deepseek-r1:8b。另外本地主机资源有限,为节约时间,取消上个版本的用户问题改写功能(注释部分)。定义新的 retriever 和 qa_chain 直接将用户问题和 context 信息发送给大模型。
```python实例化一个大模型工具
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="deepseek-r1:8b")
from langchain.prompts import PromptTemplate
my_template = PromptTemplate(
input_variables=["question"],
template="""You are an AI language model assistant. Your task is
to generate 3 different versions of the given user
question in Chinese to retrieve relevant documents from a vector database.
By generating multiple perspectives on the user question,
your goal is to help the user overcome some of the limitations
of distance-based similarity search. Provide these alternative
questions separated by newlines. Original question: {question}""",
)
实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=docsearch.as_retriever(),
llm=llm,
prompt=my_template,
include_original=True)
retriever = docsearch.as_retriever()实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever)
```
至此程序修改已经完成,原程序算上注释也不过 100 来行,大家感兴趣的可以去查看[原博客](https://infinilabs.cn/blog/202 ... earch/)。
效果测试
模拟用户提问:网关运行后监听哪个端口。

系统回答如下。

在回答中,可以看到 DeepSeek 的"思考"过程,另外回答结果也非常正确,文档中原文还是用的英语 INFINI Gateway 表示网关。

模拟用户提问:LOGGING_ES_ENDPOINT 有什么用。

系统回答如下。

文档原文内容如下。

好了,我对 DeepSeek 的表现很满意,至此知识问答系统就升级完了。
如有任何问题,请随时联系我,期待与您交流!
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://infinilabs.cn/docs/latest/easysearch>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
Easysearch 磁盘水位线注意事项
yangmf2040 发表了文章 • 0 个评论 • 3272 次浏览 • 2025-03-13 11:31
[Easyearch](https://infinilabs.cn/products/easysearch/) 为了防止索引将磁盘空间完全占满,使用磁盘水位线进行磁盘空间控制。具体来说有三条磁盘水位线:low、high、flood。
低水位线
通过参数 cluster.routing.allocation.disk.watermark.low
进行设置,默认值 85%。也可设置成一个具体值,比如:400mb,代表须保留 400mb 空闲磁盘空间,否则就算超水位线。
一旦节点磁盘使用率超过了低水位线,Easysearch 集群不会将分片分配至该节点,但是不影响新建索引的主分片分配到该节点,新建索引的副本分配不能分配到该节点。
如果所有节点都超过高水位线,此时创建新索引会导致集群状态变成 yellow。
高水位线
通过参数 cluster.routing.allocation.disk.watermark.high
进行设置,默认值 90%。也可设置成一个具体值,比如:300mb,代表须保留 300mb 空闲磁盘空间,否则就算超水位线。
一旦节点磁盘使用率超过了高水位线,Easysearch 集群会尝试将分片移动到其他节点,不允许任何分片分配到该节点。
如果所有节点都超过高水位线,此时创建新索引会导致集群状态变成 red。

洪水位线
通过参数 cluster.routing.allocation.disk.watermark.flood_stage
进行设置,默认值 95%。也可设置成一个具体值,比如:200mb,代表须保留 200mb 空闲磁盘空间,否则就算超水位线。
一旦节点磁盘使用率超过了洪水位线,Easysearch 集群会为该节点上的所有索引添加只读锁,包括系统索引。只读锁会阻止新数据写入,当磁盘利用率低于高水位线时,只读锁会自动释放。
针对节点磁盘使用率,我们可以使用 [INFINI Console 进行节点磁盘使用率告警](https://docs.infinilabs.com/co ... usage/),便于我们及时发现问题苗头,提前进行处理。有任何问题,欢迎加我微信沟通。
关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://infinilabs.cn/docs/latest/easysearch>
作者:杨帆,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。