lucene和elasticsearch笔记
目录
简介
全文检索是海量文本数据的快速查询技术;从海量数据中迅速、准确的定位到需要的信息。
倒排索引:索引文件是全文检索的核心,其实现步骤为:
- 将源数据按照结构封装成文本对象;
- 对文本数据进行分词计算,获取对应字段/属性的分词结果;分词,即将文本字符串按照一定规则切分成具有最小意义的词语。
- 将文本对象输出到索引文件的数据部分;
- 将分词结果合并输出到索引文件,形成索引指向文本对象的结构。
lucene
lucene是基于java开发的全文检索引擎工具。具有稳定、高性能、内存要求小、支持多种搜索功能的优点。分词器是处理数据文本中,字符串分词计算的实现代码,要根据不同的语言、不同的分词逻辑、不同的环境而使用不同的分词器。lucene自带一些分词器可以使用,但是无法实现满足所有情况,故放开了一个分词的接口Analyzer;实现了该接口就可以在lucene中使用,完成分词。
代码使用:
依赖
<dependency> <!-- 查询相关jar包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- lucene自带智能中文分词器 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- 默认的英文lucene分词器 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- lucene核心包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>6.0.0</version>
</dependency>
测试分词器
编写分词器方法,完成根据分词器实现类和字符串分词并打印分词结果
public void printAnalyzer(Analyzer analyzer,String msg) throws Exception{
//分词器最终都是处理流数据,先将msg转化成字符串流
StringReader reader=new StringReader(msg);
//从分词器实现类中,获取分词的流数据,获取过程就是对字符串
//已经完成了分词计算,lucene每计算一次字符串,都对应field
TokenStream tokenStream = analyzer.tokenStream("test", reader);
//重置对象reset
tokenStream.reset();
//获取token流中的字符属性对象
CharTermAttribute attribute = tokenStream.getAttribute(CharTermAttribute.class);
//遍历token
while(tokenStream.incrementToken()){
System.out.println(attribute.toString());
}
}
/*
//供测试的几种分词器
Analyzer a1 = new StandardAnalyzer();//标准
Analyzer a2 = new SmartChineseAnalyzer();//智能中文
Analyzer a3 = new SimpleAnalyzer();//简单
Analyzer a4 = new WhitespaceAnalyzer();//空格
*/
测试创建索引
索引的创建,lucene的索引文件由document对象和携带多种属性的分词结果两部分组成。lucene创建索引的步骤是:(索引创建索引后可以通过luke软件查看)
- ①指定索引文件目录(Directory);
- ②创建分词器;
- ③创建输出对象IndexWriter;
- ④封装Document对象;
- ⑥将document输出到索引文件;
示例代码如下:
@Test
public void createIndex() throws IOException {
FSDirectory dir = FSDirectory.open(Paths.get("./index"));
Analyzer analyzer = new IKAnalyzer6x();
IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(analyzer));
Document doc1 = new Document();
Document doc2 = new Document();
doc1.add(new TextField("title", "娱乐扒呀扒", Store.YES));
doc1.add(new TextField("content","巴黎圣母院烧了,很遗憾,没有爬上去过", Store.NO));
doc2.add(new TextField("name","三星固态硬盘华为", Store.YES));
doc2.add(new DoublePoint("price",2200));
doc2.add(new StringField("price", "2200", Store.YES));
doc2.add(new StringField("img","http://image.jt.com/1.jpg", Store.YES));
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.commit();
}
测试搜索索引数据:
lucene可以提供多种的查询功能,每种查询都有对应的实现类,主要有:
- 词项查询:提供关键字,从某个域中匹配;
- 多域查询:提供关键字或一句话,先解析为一批词项,在进行匹配;
- 布尔查询:在多个查询条件之间使用逻辑关系;
- 范围查询:对具有数字特征的point类型进行查询。
- 还有前缀查询、模糊查询、通配符查询等等。
实现查询的步骤如下:
- ①指定索引所在目录(Directory);
- ②根据索引位置创建查询对象:IndexSearcher;
- ③创建查询条件:Query;(需要指定查询关键字和选择查询方式)
- ④调用查询方法;(查询对象的search方法)
- ⑤遍历评分结果,从每一个评分结果中获取document的id值;
- ⑥根据id获取document并返回。
示例代码(使用了词项查询):
@Test
public void termQuery1() throws Exception {
Path path = Paths.get("./index");
FSDirectory dir = FSDirectory.open(path);
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher search = new IndexSearcher(reader);
Term term = new Term("name", "三星");
Query query = new TermQuery(term);
TopDocs topDocs = search.search(query, 10);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document doc = search.doc(docId);
System.out.println("id为:" + docId + "的评分是:" + scoreDoc.score);
System.out.println("name:" + doc.get("name"));
System.out.println("price:" + doc.get("price"));
System.out.println("img:" + doc.get("img"));
System.out.println("title:" + doc.get("title"));
System.out.println("content:" + doc.get("content"));
}
}
elasticsearch
简介
elasticsearch是一个分布式可扩展的实时搜索和分析引擎,是基于lucene开发的,具有以下特点:
- 分布式实时文件存储;
- 实时分析的分布式搜索引擎;
- 可扩展,能处理PB级别的结构化或非结构化数据;
es是面向文档型的数据库,一条数据就是一个文档,用json格式存储;
安装配置
安装:官网下载tar包,解压即可。(注意:需要jdk环境,且es不能以root用户和分组启动,所以需要新建一个用户,并将解压的整个目录拥有者改为新建的用户)
需要修改的配置(elasticsearch.yml):
# 集群名称
cluster.name: my-application
# 节点名称
node.name: node-1
# 设置es会被写入交换空间
bootstrap.memory_lock: false
# 停用bootstrap检测
bootstrap.system_call_filter: false
# ip和端口
network.host: 192.168.0.1
http.port: 9200
# 在7.0版本中,还需要如下配置
cluster.initial_master_nodes: ["node-1"]
# 开启http插件访问权限,若不用插件则不需要
http.cors.enabled: true
http.cors.allow-origin: "*"
另外,启动时可能的报错及处理:
线程数太少:修改/etc/security/limits.d/90-nproc.conf文件,以星号开头的一行的数字改为报错提示的值;
虚拟内存太小:修改或追加文件/etc/sysctl.conf,vm.max_map_count = 655360;保存退出后执行sysctl -p。
启动方式:执行./bin/elasticsearch
可视化插件安装(kibana):解压后只需要修改./conf/kibana.yml即可
header插件:
//允许远程访问
server.host: "0.0.0.0"
elasticsearch.hosts: ["es的访问地址"]
使用概念说明
首先了解4个概念:
- 索引(index):是多个分片组在一起的逻辑文件,但对于我们使用而言,索引是elasticsearch存放数据的地方,可以理解为关系型数据库的一个数据库;索引名称必须全小写,不能以下划线开头;
- 类型(type):索引下不同的数据类型,可以理解为数据库中的表;一般每个类型都有自己的映射或者结构定义;
- 文档(document):存储的数据实体,可以理解为数据库中的一行数据;
- 字段(field):字段,组成了文档,可以理解为数据库中的列;
索引的创建原则:类似的数据,类似的文档放在一个索引,即一个索引中的文档应该是很多字段都相同。且索引名称必须全小写,不能以下划线开头。
另外,在7.0中,尽量不在使用类型,否则容易出错。
校验语句
GET /index01/type01/_validate/query?explain
{
"query": {
"match": {
"field01": "关键字"
}
}
}
增删改
//创建/替换文档(也可以建立索引)(当索引为提前手动创建的时,不能使用该方式)
PUT /index01/type01/1
{
"field01" : "内容1",
"field02" : "内容2"
}
//创建文档
curl -XPOST /index01/_create/1
或者:curl -XPUT /index01/_doc/1
或者:curl -XPUT /index01/_doc/
//修改文档部分内容(其实实现原理是查询出来进行全量替换,然后更新版本)
POST /index01/type01/1/_update
{
"doc":{
"field01":"新内容"
}
}
//删除文档(不会立即删除,而是有版本号控制,当有足够的版本数是,会删除最老的)
DELETE /index01/type01/1
GET /_cat/indices?v //查看索引
get /index01 //查看索引
DELETE /index01 //删除索引
查询示例
简单查询
GET /index01/type01/_search
{
"query": { ## 查询字段
"match": {
"field01": "关键字"
}
## "match_all": {} ##查询所有字段
},
"_source": [ ## 结果要展示的字段
"field01",
"field02"
]
"sort": [ ## 排序
{
"field02": "desc"
}
]
"from": 0, ## 分页,开始是0
"size": 1 ## 分页大小
"highlight": { ## 在结果中高亮显示匹配的关键字
"fields": {
"field01": {}
}
}
}
match查询
//会使用分词器拆分关键字,根据要求查询,然后会按相关性排序
GET /index01/type01/_search
{
"query": {
"match": {
## "field01": "关键字1 关键字2" ## 默认or查询
"field01": {
"query": "关键字1 关键字2",
"operator": "and" ## 改为必须全部匹配
## "minimum_should_match": "50%" ## 设置只要有一般匹配即可
}
}
}
}
multi_match查询
//在多个字段中查询,只要有一个字段有匹配就返回
GET /index01/type01/_search
{
"query": {
"multi_match": {
"query": "关键字",
# "type": "cross_fields",
# "operator": "and"
"fields": [
"field01",
"field02"
]
}
}
}
match_phrase查询
term查询与match的区别是term不分词,类似于 =,terms类似于 in。具体用法类似。
//会使用分词器拆分关键字,但必须全部包含才会出现
GET /index01/type01/_search
{
"query": {
"match_phrase": {
## "field01": "关键字1 关键字2" ## 默认必须全部匹配
"field01": {
"query": "关键字1 关键字2",
"slop": 1 ## 设置匹配的关键词个数
}
}
}
}
组合与范围查询
在query下用bool,bool下包括must(=),must_not(!=),should(or),filter(过滤);
GET /index01/type01/_search
{
"query": {
"bool": {
"must": {
"match": {
"field01": "关键字"
}
},
"filter": {
"range": { #用于查询数值、时间区间
"field02": {
"gt": 400, #说明:gt、get、lt、lte
"lt": 700 #分别代表:>、>=、<、<=
}
}
}
}
}
}
其他查询
filter查询:是过滤精确查询,会缓存结果,不会进行相关性排序;
boost :设置字段权重,默认是1,(可以在创建索引时指定,也可以在组合查询时设置),例如:
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "编程思想",
"boost": 2
}
}
},
{
"match": {
"content": {
"query": "编程思想",
"boost": 1
}
}
}
]
}
}
}
prefix前缀查询:性能差,扫描所有倒排索引
通配符查询:使用星号,性能差,扫描所有倒排索引
正则搜索:性能差
fuzziness: 纠错查询
字段数据类型
字符型:string;
数字型:long、integer、short、byte、double、float;
日期型:date;
布尔型:boolean;
二进制型:binary;
对象类型:object,适用于单个json对象;
嵌套类型:nested,适用于json数组;
数组类型:不需要指定,只要用类似于[“xxx1”,“xxx2”]的形式使用即可;
经纬度类型:geo_point(使用示例:{ “lat”: 41.12,“lon”: -71.34});
ipv4类型:ip;
//添加数据类型示例:
PUT index01
{"mappings": {"properties": {"field01": {"type": "geo_point"}}}}
//使用该数据类型:
PUT index01/_doc/1
{"location": "41.12,-71.34"}
IK分词器
将IK分词器压缩包解压到elasticsearch/plugins下并重命名为analysis-ik(名称在插件包根目录的properties文件中有),然后重启ES即可。
//使用分词器分词并返回分词结果
POST /_analyze
{
# "analyzer": "ik_max_word", # 指定使用ik分词器,若不指定则使用默认的
"text": "*"
}
//使用ik分词器
//创建索引
curl -XPUT /index
//创建映射,并在建索引和查询时使用不同的分词器
curl -XPOST /index/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
//使用ik分词器(只指定索引,没有type)
curl -XPOST /index/_create/1
{"content": "java编程思想"}
集群
集群的结构:
- 主节点:维护当前集群所有的元数据,例如索引分片数量,副本数量,分片大小,分片存储位置,副本存储位置。客户端无论哪种方式连接,可以获取主节点的元数据信息,从而才能操作集群,使用索引、文档、搜索等功能。
- 数据节点:只对当前的集群中索引的分片进行管理,听从于主节点的命令,只负责数据的读写工作;
- 负载均衡节点:只做当前集群连接的高并发客户端的分流工作;
- 协调器节点:将不同角色不同功能的节点整合到一起,形成集群
搭建集群:
只需要修改配置文件elasticsearch.yml的配置文件即可;该配置文件中默认具有master、data、负载均衡这三个角色,协调器则需要手动配置;要搭建集群,在上面单个节点的配置的基础上,要保证同一个集群中的集群名字一致,另外,还需要以下配置:
# 设置节点间交互的tcp端口,默认是9300,可以不设置
# transport.tcp.port: 9300
# node.master: true # 主节点角色
# node.data: true # 数据节点角色
# node.ingest: true # 负载均衡器角色
# 配置协调器节点列表, 7.0没有了
discovery.zen.ping.unicast.hosts: ["ip", "ip:port"]
# 配置最小master个数避免脑裂, 7.0没有了
discovery.zen.minimum_master_nodes: 2
# 在7.0版本中集群的配置改为以下两条:(启动后会自动计算最小的master个数,不会有脑裂)
# 配置尝试发现已有的集群列表,不配置则不发现
discovery.seed_hosts: ["host1", "host2"]
# 刚启动时需要的最小的主节点数量
cluster.initial_master_nodes: ["node-1, node-2"]
# 设置索引的分片数,默认为5
# index.number_of_shards: 5
# 设置索引的副本数,默认为1:
# index.number_of_replicas: 1
脑裂:ES集群中,多个master间若发生延时断开,会造成不同master同时管理的情况;所以需要配置过半的最小master个数(n/2 + 1),使得集群中最终只有一个master集体有效管理集群。
ES启动的选举逻辑:节点启动->连接协调器->获取整个集群在线节点->将现役master放到本节点activeMaster的list中->若离list为空则继续获取,否则下一步->若list大小小于最小master个数则继续获取,否则将id最小的节点暂时定为master->若依然没有选举成功,则重新执行一遍。
索引数据的分片和副本分片:默认是5个分片,一个副本。同时,在集群数据节点足够的情况下,ES会自动根据数量计算分片的分布,以尽可能保证合理的位置。所以在数据节点足够的情况下要高性能则是扩大分片数量,要高可用则提高副本数量。另外,对于已经生成的索引数据是无法修改分片和副本的,但是修改之后生成的索引则会按新的配置计算。