外观
ES(Elasticsearch)学习笔记
黑马 SpringCloud 微服务课程(黑马商城)Elasticsearch 部分完整笔记。ES 是分布式搜索引擎,核心是倒排索引,用于商品搜索、日志检索等"搜索"场景(MySQL 的
LIKE在大数据量下很慢)。
1. 初识ES-介绍与倒排索引
【重点要点】
- ES 是什么:基于 Lucene 的分布式搜索引擎,对外提供 RESTful API,部署简单、扩展容易,搜索快(毫秒级)。
- 核心原理 倒排索引:
- 正排索引(MySQL 思路):文档 → 词条(查某词要遍历所有文档)。
- 倒排索引(ES 思路):词条 → 文档,先对文档内容分词建立索引,查"手机"直接找到包含它的文档(像字典的拼音索引)。
- 与 MySQL 对比:索引 Index ↔ 数据库;类型(7.x 已废弃)↔ 表;文档 Document ↔ 行;字段 Field ↔ 列。
- 安装:Docker 跑 ES(注意内存
-e ES_JAVA_OPTS=-Xms512m -Xmx512m)+ Kibana(可视化控制台,Dev Tools 里写 DSL)。 - 易错点:ES 默认占用大量内存,开发机要限制;7.x 以上没有"类型"概念,索引下直接是文档;Kibana 与 ES 版本要一致。
bash
# Docker 启动 ES + Kibana(版本一致,如 7.12.1)
docker run -d --name es -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
elasticsearch:7.12.1
docker run -d --name kibana -p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://<宿主机IP>:9200" kibana:7.12.12. IK分词器与自定义分词
【重点要点】
- 分词(analysis):ES 建立倒排索引前把文本拆成词条(token)。默认 Standard 分词器对中文按单字拆(
"黑马程序员"拆成黑/马/程/序/员,搜索体验差)。 - IK 分词器:中文分词插件,两种模式:
ik_max_word:最细粒度拆分("中华人民共和国" → 拆出所有可能的词),建立索引用。ik_smart:最粗粒度(只拆成最合理的词),搜索时用(字段配 search_analyzer)。
- 安装:下载对应 ES 版本的
ik插件 zip,放插件目录解压,重启 ES。 - 自定义词典:IK 支持扩展词典(
IKAnalyzer.cfg.xml配置ext_dict,如"黑马"、"奥力给"这些新词,不配置会被错误拆分)。 - 易错点:IK 插件版本必须与 ES 版本严格一致(否则装不上/闪退);先装插件再建索引;分词器要配在 Mapping 的
analyzer(索引用)与search_analyzer(搜索用)。
json
// 分词测试(Kibana Dev Tools)
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "黑马程序员学习Elasticsearch"
}
// 结果会把"黑马/程序员/学习/elasticsearch"拆成词条3. 索引库操作-Mapping映射
【重点要点】
- Mapping(映射):定义索引的字段类型和分词器,相当于建表语句——一旦创建不能随意改类型(只能加字段、或重建索引)。
- 常用字段类型:
- 字符串:
text(可分词,搜索用) /keyword(不分词,作精确匹配/排序/聚合用,如品牌、订单号) - 数值:
long/integer/double - 时间:
date - 布尔:
boolean - 地理:
geo_point(经纬度,用于附近搜索)
- 字符串:
- 常用属性:
index(是否索引,false 不参与搜索)、analyzer(分词器)、copy_to(把多个字段合并到一个字段,跨字段搜索)。 - 索引操作 API:
PUT /索引名(创建)、GET /索引名(查看)、PUT /索引名/_mapping(追加字段)、DELETE /索引名(删除)。 - 易错点:text 和 keyword 别混——想精确匹配/排序/聚合必须 keyword;复杂对象用
properties嵌套;copy_to字段本身要设index: false(只作拷贝源)。
json
// 创建索引 + Mapping
PUT /item
{
"mappings": {
"properties": {
"id": { "type": "keyword" },
"name": { "type": "text", "analyzer": "ik_max_word",
"search_analyzer": "ik_smart" },
"brand": { "type": "keyword" },
"price": { "type": "double" },
"image": { "type": "keyword", "index": false },
"location": { "type": "geo_point" }
}
}
}4. 文档操作-增删改查
【重点要点】
- ES 文档用 RESTful API 操作,文档 id 需要自己指定(通常用 MySQL 主键,方便数据同步对应)。
- 新增:
PUT /索引名/_doc/{id}(指定 id,重复会覆盖)或POST /索引名/_doc(自动生成 id,少用)。 - 查询:
GET /索引名/_doc/{id}按 id 查。 - 修改:
POST /索引名/_update/{id}局部修改(只改给定字段);PUT .../_doc/{id}整体覆盖。 - 删除:
DELETE /索引名/_doc/{id}。 - 批量操作:
POST /索引名/_bulk,一次多条(_index/_delete),数据同步时用(性能好)。 - 易错点:PUT 整篇覆盖会丢失未提交的字段(要用 _update 局部改);文档不存在时 PUT 变新增;批量语法每两行一组(操作行 + 数据行)。
json
// 新增文档(id = mysql 主键)
PUT /item/_doc/1001
{
"name": "华为Mate60手机",
"brand": "华为",
"price": 5999.00,
"image": "http://image.com/huawei.png"
}
// 局部修改
POST /item/_update/1001
{ "doc": { "price": 5499.00 } }
// 批量新增
POST /item/_bulk
{"index": {"_id": 1002}}
{"name": "小米14", "brand": "小米", "price": 3999.00}
{"index": {"_id": 1003}}
{"name": "苹果iPhone15", "brand": "苹果", "price": 7999.00}5. DSL查询-全文检索与精准查询
【重点要点】
- ES 查询语法叫 DSL(Domain Specific Language),
GET /索引名/_search+ body 里写查询条件。 - 全文检索类(先分词再匹配,text 字段用):
match:单字段分词匹配("华为手机"会按词匹配)。multi_match:多字段同时匹配(fields: ["name","brand"])。match_all:查所有文档(配合排序分页做"全量列表")。
- 精准查询类(不分词,keyword/数值字段用):
term:按词条精确匹配(品牌/商品编号)。range:范围查询(价格gte/lte、时间)。
- 易错点:text 字段用 term 会查不到(text 已被分词,term 找的是完整词条);ES 查询结果在
hits.hits里(_source是原文档)。
json
// 全文检索:name 字段分词匹配"手机"
GET /item/_search
{
"query": { "match": { "name": "手机" } }
}
// 精准查询:品牌(keyword)精确 = 华为
GET /item/_search
{
"query": { "term": { "brand": "华为" } }
}
// 范围查询:价格 1000-5000
GET /item/_search
{
"query": { "range": { "price": { "gte": 1000, "lte": 5000 } } }
}6. DSL查询-复合查询与排序分页
【重点要点】
- bool 复合查询:多个条件的组合(最常用):
must:必须满足(参与算分),类似 AND。should:满足其一(加分),类似 OR。filter:必须满足(不算分,更快),反例:范围过滤、品牌过滤放 filter。must_not:必须不满足。
- function_score(相关性算分):在原始算分上加权——黑马商城用"是否新品(isAD)加权"让广告/新品排前面。
- 排序:
sort按字段(数值/时间);地理按_geo_distance距离排序。 - 分页:
from+size(默认 from=0, size=10,深度分页性能差,别翻太深);大数据深分页用search_after(配合 sort 唯一值)。 - 易错点:
filter里的条件不进算分、性能更好,能放 filter 的别放 must;排序字段要是 keyword/数值(text 不能直接 sort);from 过大(>10000)会报错,要用 search_after。
json
// bool 复合:品牌 must + 价格 filter(不算分)
GET /item/_search
{
"query": {
"bool": {
"must": [{ "match": { "name": "手机" } }],
"filter": [
{ "term": { "brand": "华为" } },
{ "range": { "price": { "gte": 1000 } } }
]
}
},
"sort": [{ "price": "asc" }],
"from": 0,
"size": 20
}json
// function_score:新品(isAD=true)加权
GET /item/_search
{
"query": {
"function_score": {
"query": { "match_all": {} },
"functions": [
{ "filter": { "term": { "isAD": true } }, "weight": 5 }
],
"boost_mode": "sum"
}
}
}7. DSL查询-高亮显示
【重点要点】
- 高亮:搜索词在结果中加标记显示(前端渲染成红色/黄色),提升体验。
- 语法:
highlight+fields指定要高亮的字段,pre_tags/post_tags自定义标签(默认<em>)。 - 命中词 fragment_size:默认高亮片段长度(约 100 字),可调
fragment_size截取重点片段。 - 高亮只在参与了算分的字段生效(
match的字段);filter里过滤的词不高亮。 - 前端处理:后端把高亮字段返回,前端替换
<em>标签为高亮样式。 - 易错点:高亮字段必须是参与搜索的 text 字段(keyword 不高亮);
pre_tags前端要能解析(JSON/HTML 转义);多字段匹配分别高亮需要require_field_match: false。
json
// 查询 + 高亮
GET /item/_search
{
"query": { "match": { "name": "华为手机" } },
"highlight": {
"fields": { "name": {} },
"pre_tags": "<em style='color:red'>",
"post_tags": "</em>"
}
}
// 结果 hits.hits[].highlight.name = ["<em style='color:red'>华为</em>手机"]8. 聚合-桶聚合与度量聚合
【重点要点】
- 聚合(Aggregation):对查询结果统计分组,实现"按品牌筛选"、"价格区间统计"等,类似 SQL 的 GROUP BY + 聚合函数。
- 桶聚合(Bucket):按条件分桶(分组)——
terms(按字段值分组)、range(按数值区间分组)、date_histogram(按时间分组)。常用于筛选条件(左侧品牌列表)。 - 度量聚合(Metric):对桶内数据计算——
avg/min/max/sum、stats(一次给全部统计)、cardinality(去重计数)等。常与桶聚合嵌套(先桶后度)。 - 语法:请求体
aggs+ 聚合名(自定义) +terms: { field: "品牌字段" }+ 可选aggs嵌套;结果在aggregations里。 - 分页/排序:
terms里size控制返回桶个数、order排序;size: 0表示只统计不分页返回文档。 - 易错点:桶聚合的字段必须是 keyword(text 不能直接聚合,除非加 keyword 子字段);嵌套聚合结构是"aggs 套 aggs";聚合结果字段名是自定义的,注意对应。
json
// 按品牌聚合 + 统计每个品牌的平均价格
GET /item/_search
{
"size": 0, // 只要聚合结果,不要文档
"aggs": {
"brandAgg": { // 聚合名:品牌聚合
"terms": { "field": "brand", "size": 10 },
"aggs": {
"avgPrice": { "avg": { "field": "price" } } // 嵌套:平均价格
}
}
}
}9. 自动补全-拼音分词与completion
【重点要点】
- 自动补全(suggest):输入"hua"就提示"华为Mate60"(搜索框联想),用 ES 的 completion suggester。
- 前置条件:拼音分词器 + 自定义分词器(IK + 拼音组合:
analyzer: ik_max_word索引 + 拼音分词用于补全);拼音插件(pinyin)需安装并兼容版本。 - 字段类型
completion:专用于自动补全的字段类型,必须精确前缀匹配,创建索引时指定(不能后续改)。 - 查询语法:
suggest+prefix(输入前缀)+completion: { field: "suggestion" };配置fuzzy可容忍拼写错误。 - 数据准备:文档里要有
suggestion字段,值 = 完整可补全的字符串数组(如["华为Mate60", "华为Mate60pro"])。 - 易错点:completion 字段要用独立字段存补全数据(把品牌+标题拼起来);拼音大小写/多音字("重庆chongqing")问题;自定义分词器要在索引创建时配置(不能后改)。
json
// 创建索引:suggestion 字段用 completion 类型
PUT /item
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"suggestion": { "type": "completion" } // 自动补全专用
}
}
}json
// 自动补全查询:输入 p 前缀
GET /item/_search
{
"suggest": {
"suggestions": {
"prefix": "华",
"completion": {
"field": "suggestion",
"size": 10
}
}
}
}10. 数据同步-MQ同步方案
【重点要点】
- 问题:数据在 MySQL,搜索在 ES——数据库变了(增删改商品),ES 索引要跟着变,否则搜索到过期数据。
- 三种同步方案对比:
- 同步双写:业务代码里写 MySQL 后顺手写 ES——简单但耦合(业务侵入、双写失败难一致、代码到处改)。
- 异步通知(MQ,黑马商城用这个):业务只发一条 MQ 消息,监听器消费后写 ES——解耦、可靠(MQ 可靠性学了就能用)。
- Canal 监听 binlog:旁路监听 MySQL 日志自动同步——零侵入但要额外部署 Canal。
- MQ 方案要点(结合 MQ 笔记):声明
topic交换机(如hmall.item.topic,路由键item.insert/item.update/item.delete)+ 队列;商品服务发消息、搜索服务@RabbitListener消费后调用 ES RestClient 增/改/删文档。 - 易错点:幂等(消息可能重复,写 ES 前按 id 查或直接 upsert);顺序(先删除后新增的消息乱序要处理);监听器里 ES 操作失败要重试/死信(可靠性那套);
_bulk批量同步效率高。
java
// 商品服务:增删改后发消息
@RabbitListener(queues = "queue.item")
public void onItemMessage(ItemMsg msg) {
if ("delete".equals(msg.getType())) {
itemDocClient.delete(msg.getItemId()); // ES 删除
} else {
Item item = itemMapper.selectById(msg.getItemId());
itemDocClient.saveOrUpdate(item); // ES 新增/更新
}
}java
// 使用 RestHighLevelClient 写 ES(旧版客户端)
public void save(Item item) throws IOException {
IndexRequest request = new IndexRequest("item")
.id(item.getId().toString())
.source(JSON.toJSONString(item), XContentType.JSON);
client.index(request, RequestOptions.DEFAULT);
}11. ES集群-分片与集群部署
【重点要点】
- 分片(Shard):一个索引数据量大会撑爆单节点,ES 把索引水平切分成多个分片分散到各节点(主分片 primary + 副本分片 replica)。
- 主分片数 创建索引时定死(如
number_of_shards: 3,之后不能改);副本数可动态调(number_of_replicas)。 - 节点角色:
master(管理元数据、选主)、data(存数据)、coordinating(路由转发查询,默认所有节点兼);discovery-type: single-node是单机开发模式。 - 集群高可用:主分片挂了 → 副本顶上(故障转移);同一主分片及其副本不在同一节点(数据冗余)。
- 部署:多节点
elasticsearch.yml配cluster.name(相同才算一个集群)+node.name+ 网络(Docker 多容器 or 多机)。 - 易错点:主分片数不能改,规划不好后期要重建索引;副本数 = 节点数 - 1 是上限(副本不能和主分片同节点);脑裂问题(master 选举),生产 master 节点要配
discovery.zen.minimum_master_nodes(旧版)/ 新版 bootstrap 配置。
yaml
# elasticsearch.yml(每个节点)
cluster.name: hmall-es-cluster # 集群名必须一致
node.name: node-1 # 节点名唯一
network.host: 0.0.0.0
discovery.seed_hosts: ["node-2", "node-3"] # 集群其他节点
cluster.initial_master_nodes: ["node-1"]json
// 创建索引时指定分片与副本
PUT /item
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}