《搜索慢了?SpringBoot+Elasticsearch让查询快100倍!》
我是小坏,今天咱们聊搜索。用户搜个东西,转圈圈等半天,这体验可不行。数据库like查询是方便,但数据多了能把你卡死。今天教你用Elasticsearch(后面就叫ES),让搜索快到飞起。
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
一、数据库搜索的痛
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
场景:1000万商品,搜索"华为手机"
SELECT * FROM products
WHERE name LIKE '%华为%'
OR description LIKE '%华为%';
问题:
- 全表扫描,不走索引
- 返回慢,用户等3秒
- 不能分词("华为手机"搜不到"华为P40手机")
- 不能按相关度排序
- 不支持拼音搜索
用ES能解决啥:
- 毫秒级返回
- 自动分词
- 支持拼音、同义词
- 智能排序
- 聚合统计
二、ES快速上手
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
2.1 先明白几个词
- 索引:相当于数据库
- 文档:相当于一行数据
- 字段:相当于列
- 分词:把句子拆成词,比如"华为手机"拆成"华为"和"手机"
2.2 3步集成
第一步:加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
第二步:写配置
spring:
elasticsearch:
uris: http://localhost:9200
username: elastic
password: 123456
第三步:定义商品实体
@Document(indexName = "product")
@Data
public class Product {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name; // 商品名,用ik分词
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description; // 描述
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Keyword)
private String category; // 分类,不分词
@Field(type = FieldType.Integer)
private Integer sales; // 销量
}
三、数据同步:4种方案选哪个?
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
3.1 双写(简单直接)
@Service
public class ProductService {
public void addProduct(Product product) {
// 1. 写数据库
productRepository.save(product);
// 2. 写ES
elasticsearchRepository.save(product);
}
}
优点:实时性强
缺点:可能不一致,影响性能
3.2 定时同步(稳定可靠)
@Component
public class SyncTask {
@Scheduled(fixedDelay = 300000) // 5分钟一次
public void syncProducts() {
// 查最近5分钟修改的商品
List<Product> products = productRepository.findRecentUpdated();
elasticsearchRepository.saveAll(products);
}
}
优点:解耦,稳定
缺点:有延迟
3.3 消息队列(推荐)
商品更新发消息,消费者同步到ES。解耦,实时,但复杂。
3.4 监听binlog(高级玩法)
监听数据库变化自动同步。实时,对业务无侵入,但技术门槛高。
建议:中小项目用双写或定时同步,大项目用消息队列。
四、搜索实战
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
4.1 基础搜索
@Service
public class ProductSearchService {
public Page<Product> search(String keyword, int page, int size) {
// 构建查询
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "description"))
.withPageable(PageRequest.of(page, size))
.build();
return elasticsearchRestTemplate.search(query, Product.class);
}
}
4.2 多字段搜索+高亮
public SearchResult searchProducts(String keyword, int page, int size) {
// 1. 多字段匹配
MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyword)
.field("name", 3.0f) // name权重3倍
.field("description", 1.0f);
// 2. 高亮显示
HighlightBuilder highlightBuilder = new HighlightBuilder()
.field("name")
.preTags("<em>").postTags("</em>");
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(query)
.withHighlightBuilder(highlightBuilder)
.withPageable(PageRequest.of(page, size))
.build();
// 执行搜索
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
// 封装结果
return convertToResult(searchHits);
}
4.3 拼音搜索
步骤:
- 安装拼音分词插件
- 字段用拼音分词器
- 搜索时同时匹配中文和拼音
// 搜索时同时查中文和拼音
String[] fields = {"name", "name.pinyin", "description", "description.pinyin"};
MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyword, fields);
4.4 同义词搜索
场景:搜"笔记本"也能找到"笔记本电脑"
// 同义词配置
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": ["笔记本,笔记本电脑", "手机,移动电话"]
}
}
五、排序和筛选
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
5.1 智能排序
// 按相关度、销量、价格综合排序
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withSort(SortBuilders.scoreSort()) // 相关度
.withSort(SortBuilders.fieldSort("sales").order(SortOrder.DESC)) // 销量
.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)) // 价格
.build();
5.2 条件筛选
// 价格区间
RangeQueryBuilder priceFilter = QueryBuilders.rangeQuery("price")
.gte(minPrice).lte(maxPrice);
// 分类筛选
TermQueryBuilder categoryFilter = QueryBuilders.termQuery("category", category);
// 组合查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.multiMatchQuery(keyword, "name", "description"))
.filter(priceFilter)
.filter(categoryFilter);
六、聚合分析
场景:搜索结果的分类统计,用于生成筛选条件
public Map<String, Long> getCategoryStats(String keyword) {
// 按分类聚合
TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_category")
.field("category")
.size(10);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "description"))
.addAggregation(aggregation)
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(query, Product.class);
// 解析聚合结果
Terms terms = searchHits.getAggregations().get("by_category");
Map<String, Long> result = new HashMap<>();
for (Terms.Bucket bucket : terms.getBuckets()) {
result.put(bucket.getKeyAsString(), bucket.getDocCount());
}
return result;
}
七、性能优化
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
7.1 索引优化
@Document(indexName = "product",
createIndex = true,
shards = 3, // 分片数
replicas = 1) // 副本数
public class Product {
// 字段优化
@Field(type = FieldType.Keyword)
private String sku; // 精确查找用keyword
@Field(type = FieldType.Integer, index = false)
private Integer stock; // 不索引库存,只用于显示
}
7.2 查询优化
// 避免深度分页
SearchAfterBuilder searchAfter = new SearchAfterBuilder();
searchAfter.setSortValues(new Object[]{lastScore, lastId});
// 使用filter替代query(不计算分数,可缓存)
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", keyword))
.filter(QueryBuilders.termQuery("status", 1)); // 状态过滤用filter
7.3 缓存优化
@Service
public class ProductSearchService {
@Cacheable(value = "search", key = "#keyword + ':' + #page")
public Page<Product> search(String keyword, int page, int size) {
// 热门搜索结果缓存
return doSearch(keyword, page, size);
}
}
八、监控告警
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
8.1 健康检查
# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics
elasticsearch:
health:
enabled: true
8.2 关键指标监控
- 查询响应时间
- 索引速度
- 节点负载
- 磁盘使用率
8.3 告警规则
@Component
public class ESMetricsMonitor {
@Scheduled(fixedRate = 60000)
public void checkHealth() {
// 检查集群状态
ClusterHealth health = elasticsearchRestTemplate.cluster().health();
if (health.getStatus() == ClusterHealthStatus.RED) {
// 发送告警
alertService.send("ES集群异常,状态:" + health.getStatus());
}
// 检查查询延迟
double avgQueryTime = getAverageQueryTime();
if (avgQueryTime > 1000) { // 超过1秒
alertService.send("ES查询延迟过高:" + avgQueryTime + "ms");
}
}
}
九、实战:电商搜索完整流程
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
9.1 搜索建议
public List<String> getSuggestions(String prefix) {
CompletionSuggestionBuilder suggestion = SuggestBuilders
.completionSuggestion("suggest")
.prefix(prefix)
.size(5);
SuggestBuilder suggestBuilder = new SuggestBuilder()
.addSuggestion("product_suggest", suggestion);
SearchRequest request = new SearchRequest("product")
.source(new SearchSourceBuilder().suggest(suggestBuilder));
// 执行搜索建议
SearchResponse response = elasticsearchRestTemplate.suggest(request, RequestOptions.DEFAULT);
// 解析结果
return parseSuggestions(response);
}
9.2 搜索结果封装
@Data
public class SearchResult {
private List<ProductVO> products; // 商品列表
private List<CategoryAgg> categories; // 分类统计
private List<BrandAgg> brands; // 品牌统计
private PriceRange priceRange; // 价格区间
private long total; // 总条数
private int totalPage; // 总页数
private List<String> suggestions; // 搜索建议
}
// 搜索接口
@GetMapping("/search")
public SearchResult search(
@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String category,
@RequestParam(required = false) Double minPrice,
@RequestParam(required = false) Double maxPrice) {
return productSearchService.search(keyword, page, size, category, minPrice, maxPrice);
}
十、今日要点
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
- ES比数据库like快得多,支持分词、拼音、同义词
- 数据同步4种方案:双写简单,定时稳定,消息队列推荐
- 搜索要智能:多字段、高亮、拼音、同义词
- 排序和筛选是电商搜索的刚需
- 聚合分析用于生成筛选条件
- 性能优化:索引设计、查询优化、缓存
- 监控告警不能少,出问题早知道
十一、避坑指南
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
坑1:分词器选错
// ❌ 错误:用默认分词器分中文
@Field(type = FieldType.Text) // 默认分词器处理不好中文
// ✅ 正确:用ik分词器
@Field(type = FieldType.Text, analyzer = "ik_max_word")
坑2:字段类型用错
// ❌ 错误:分类用Text,搜索时会分词
@Field(type = FieldType.Text)
private String category;
// ✅ 正确:分类用Keyword,精确匹配
@Field(type = FieldType.Keyword)
private String category;
坑3:分页太深
// ❌ 错误:深度分页性能差
from: 10000, size: 20 // 性能差
// ✅ 正确:用search_after
search_after: [lastScore, lastId]
十二、思考题
场景:你要做一个商品搜索,要求:
- 支持中文、拼音、同义词
- 结果按综合排序(相关度+销量+价格)
- 左侧有分类、品牌、价格筛选
- 搜索框有智能提示
- 热门搜索词推荐
问题:
- 你会如何设计ES索引?
- 如何实现拼音和同义词搜索?
- 如何优化搜索性能?
评论区聊聊你的方案,明儿咱们讲MyBatis-Plus。
明天预告:《SpringBoot+MyBatis-Plus:开发效率提升10倍》
今日福利:关注后回复"ES搜索",获取电商搜索完整源码和ik分词器配置。
运营小贴士:
💡 互动:
- 你的项目搜索用的是什么方案?
- 遇到过哪些搜索性能问题?
- 留言提问,明天文章解答
👥 进群: 零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。