思路
把elastic search当作mysql去学习,就比较清晰了。
创建表、增删改查、客户端调用es等就是最基本的功能了。
索引
正向索引
拿着书籍的目录去找页码,这样的场景就是正向索引。
倒排索引
elastic会将输入的文字进行分词,例如:
你好,大熊猫 => 你好|大熊猫
你好,java => 你好|java
早上好,大熊猫 => 早上好|大熊猫
根据分词结果构建索引表,记录文本信息。
| 索引 | 文章 |
|---|---|
| 你好 | A、B |
| 大熊猫 | A、C |
| java | B |
| 早上好 | C |
在进行搜索的时候,先分词以后,然后去查询索引表,查出文章的信息。
调用方式
http请求
简单的请求可以直接使用curl的方式进行调用
kibana
直接使用kibana的devtools进行调用,在生产环境一般不使用。
客户端
主要有三种:
- elastic search官网的java client
- elastic search8舍弃的Transport Client
- spring-data系列的spring-data-elasticsearch
建议使用spring-data-elasticsearch,具体移步java里集成章节。
使用(DSL)
插入数据并创建文档
这种带有@timestamp和event字段的数据是ecs的数据类型,可以更好的记录事件(如日志),它的查询和普通的查询不相同。
POST logs-my_app-default/_doc
{
"name": "zzy"
"@timestamp": "2099-05-06T16:21:15.000Z",
"event": {
"original": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] \"GET /images/bg.jpg HTTP/1.0\" 200 24736"
}
}
查询文档
GET logs-my_app-default/_search
{
"query": {
"match_all": { }
},
"sort": [
{
"@timestamp": "desc"
}
]
}
查询部分字段数据
GET logs-my_app-default/_search
{
"query": {
"match_all": { }
},
"fields": [
"@timestamp"
],
"_source": false,
"sort": [
{
"@timestamp": "desc"
}
]
}
删除文档
删除文档及索引
DELETE _data_stream/logs-my_app-default
删除文档
DELETE {document}
查询单条数据
GET post/_doc/{id}
修改单条数据
POST post/_doc/v4In15IBQ7S0DDlRGJi0
{
"name": "tyt"
}
查询表结构
GET post/_mapping
EQL
elastic search 的 EQL(Event Query Language)查询是一种用于分析日志和时间序列数据的查询语言。
eql查询主要针对ecs文档结构。
SQL
elastic search还支持像sql一样的查询语句,sql查询
可能需要额外插件支持,解析sql可能会带来性能问题。
post /_sql?format=txt
{
"query": "select * from post where name like '%鱼%'"
}
painless
编程式取值,比较灵活,但是学习成本高。
mapping
mapping类似mysql里的表,在elastic里,mapping描述了怎么组合一个文档。
动态mapping
mapping是动态结构,可以改变,添加一个包含新字段的数据进去,mapping结构发生改变了。
显式mapping
在创建文档的时候,也可以指定mapping。
PUT /post
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
分词器
空格分词器
得到['The', 'quick', 'brown', 'fox']词组。
POST _analyze
{
"analyzer": "whitespace",
"text": "The quick brown fox."
}
标准分词规则
指定了一些过滤器,将大写转小写,韵母折叠。最后得到的结果是这样的。 ['is', 'this', 'deja', 'vu']
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase", "asciifolding" ],
"text": "Is this déja vu?"
}
给特定字段指定分词器
下面的语句创建了一个文档,并且给字段指定了分词器。
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"std_folded": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
},
"mappings": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "std_folded"
}
}
}
}
GET my-index-000001/_analyze
{
"analyzer": "std_folded",
"text": "Is this déjà vu?"
}
ik分词器(中文)
analysis-ik, ik_smart分词规则是智能分词,将句子分成最像词语的情况,ik_max_word是按最大粒度去分词,尽可能多的分出词语来。
打分机制
假如有三条内容:
- 小王是狗
- 小王是长颈鹿
- 你是长颈鹿
当搜索小王的时候,1会胜出,因为第一条不仅匹配了关键词而且句子更短。
当搜索小王,长颈鹿时,2 > 3 > 1。
如何计算文档相关性得分并排序,参考相关性排序算法。
java里集成
ElasticsearchRepository方式
直接集成ElasticsearchRepository这个接口,调用现成的方法或者按它的命名规则创建的方法,即可查询到数据。
public interface PostRepository extends ElasticsearchRepository<Post, Long> {
List<Post> findByTitle(String title);
}
测试例子:
@SpringBootTest
class LearnElasticsearchApplicationTests {
@Resource
PostRepository postRepository;
@Test
void testSave() {
Post post = new Post();
post.setId(2L);
post.setTitle("今天要学习");
post.setContent("今天学习的是数学");
post.setUserId(2L);
post.setCreateTime(new Date());
post.setUpdateTime(new Date());
Post post1 = postRepository.save(post);
System.out.println(post1);
}
@Test
void testFindAll() {
Page<Post> list = postRepository.findAll(PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createTime")));
list.forEach(System.out::println);
}
@Test
void findByTitle() {
List<Post> list = postRepository.findByTitle("学习");
System.out.println(list);
}
}
EleasticsearchTemplate方式
elasticsearchtemplate提供了更加灵活的查询,它的编写是基于dsl语法来构建的。
比如我有以下的dsl语句:
GET post/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "今天"
}
},
{
"match": {
"content": "数学"
}
}
]
}
},
"from": 0,
"size": 5,
"_source": ["id", "title", "userId"],
"sort": [
{
"id": {
"order": "desc"
}
},
{
"_score": {
"order": "desc"
}
}
]
}
以elasticsearchtemplate的写法构建:
@Service
public class PostSearch {
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
public Page<Post> search(Post post) {
String title = post.getTitle();
String content = post.getContent();
// 根据dsl的json参数来构建代码
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// bool里构建must filter等
if (title != null) {
boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
}
if (content != null) {
boolQueryBuilder.must(QueryBuilders.matchQuery("content", content));
}
// 排序
ScoreSortBuilder scoreSortBuilder = SortBuilders.scoreSort().order(SortOrder.DESC);
FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
// 筛选字段
FetchSourceFilter fetchSourceFilter = new FetchSourceFilter(new String[]{"id", "title", "userId"}, null);
// 分页
PageRequest pageRequest = PageRequest.of(0, 10);
// 构建查询
NativeSearchQuery build = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).withPageable(pageRequest).withSourceFilter(fetchSourceFilter)
.withSort(scoreSortBuilder).withSort(fieldSortBuilder).build();
SearchHits<Post> search = elasticsearchTemplate.search(build, Post.class);
return null;
}
}
数据同步
一般情况下,如果做查询搜索功能,使用es来模糊搜索,但是数据是存放在数据库里的,所以需要将数据库里的数据与es进行同步,保持数据一致性(数据库为主)。
首次安装完es,进行全量同步,系统运行时采用增量同步。以下是四种实现方式
定时任务
比如每分钟查询前三分钟内修改的数据,然后更新到es里。
优点:简单易用,无需额外引入中间件。
缺点:有时间差。
应用场景:数据短时间内不同步影响不大。
双写
写数据的时候,将数据写入es,更新、删除数据同理,要开启事务(先保证数据库的成功,再去操作es,如果es更新失败,通过定时任务+日志+告警进行检测和修复)
LogStash
LogStach是一个收集和处理数据的管道。
采用LogStash数据同步管道(一般要配合kafka消息队列+beats采集器),这种数据同步的方式的实现移步logstash章节。
订阅数据库流水的同步方式-Canal
优点:实时同步、实时性非常强。
原理:数据库每次修改时,会修改binlog文件,只要监听该文件的修改,就能第一时间得到消息并处理。
canal伪装成了mysql的从节点,获取主节点给的binlog。

tips
介绍一些elasticsearch使用的知识点。
关键子句must和filter
- must子句的查询会影响文档的得分,elasticsearch会计算每个文档与查询的匹配度,根据相似性算法来给文档打分。
- filter不会影响文档的得分,它关心的是文档是否匹配,通常filter在查询性能上更高效,会缓存经常使用的过滤器结果。
标签高亮
在dsl语法里设置highlight属性,这样查出来的数据里匹配的词组就包上了一层高亮的标签。
GET /_search
{
"query": {
"match": { "content": "kimchy" }
},
"highlight": {
"fields": {
"content": {}
}
}
}