Elasticsearch實戰(zhàn)指南:讓你的業(yè)務(wù)搜索飛起來
當(dāng)你的MySQL數(shù)據(jù)庫查詢突然從0.5秒飆升到15秒,當(dāng)你的產(chǎn)品經(jīng)理第20次提出"模糊搜索要支持同義詞聯(lián)想"時——是時候重新認(rèn)識這個改變搜索游戲規(guī)則的分布式搜索引擎了。
1.為什么Elasticsearch是新時代的“數(shù)據(jù)引擎”?
傳統(tǒng)數(shù)據(jù)庫的三大死穴
- 模糊查詢性能差(LIKE耗時隨數(shù)據(jù)量指數(shù)級上升)
- 缺乏智能排序(無法根據(jù)用戶行為動態(tài)加權(quán))
- 擴(kuò)展性弱(分庫分表成本高)
ES的破局武器
- 分布式架構(gòu):線性擴(kuò)展支撐 PB 級數(shù)據(jù)
- 倒排索引:毫秒級響應(yīng)關(guān)鍵詞搜索
- 分詞引擎:中文/拼音/同義詞精準(zhǔn)匹配
2.五大場景+完整代碼:從入門到實戰(zhàn)
電商搜索——讓用戶“一搜即中”
痛點
- 用戶搜索“蘋果手機(jī)”時,無法智能匹配“iPhone”
- 搜索結(jié)果排序僵化(無法綜合銷量/評分/價格動態(tài)排序)
代碼實現(xiàn)
// 商品實體類
@Document(indexName = "products")
public class Product {
@Id
private String id;
// 使用ik_max_word分詞器
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
private Double price;
private Long sales;
// getters/setters
}
// 搜索服務(wù)
@Service
public class ProductService {
@Autowired
private ElasticsearchOperations esOperations;
public List<Product> search(String keyword) {
Query query = NativeQuery.builder()
.withQuery(q -> q
.match(m -> m // 多字段匹配
.field("title")
.field("description")
.query(keyword)
)
)
.withSort(s -> s // 綜合排序:銷量倒序 > 價格升序
.field(f -> f.field("sales").order(SortOrder.Desc))
.field(f -> f.field("price").order(SortOrder.Asc))
)
.build();
return esOperations.search(query, Product.class)
.stream().map(SearchHit::getContent).collect(Collectors.toList());
}
}
推薦系統(tǒng)——讓用戶“欲罷不能”
痛點
- 用戶興趣變化快,推薦結(jié)果更新延遲高
- 無法實時結(jié)合用戶位置/行為調(diào)整策略
代碼實現(xiàn)
// 用戶畫像實體
@Document(indexName = "user_profiles")
public class UserProfile {
@Field(type = FieldType.Keyword)
private String userId;
// 用戶興趣標(biāo)簽(可動態(tài)更新)
@Field(type = FieldType.Keyword)
private List<String> tags;
@GeoPointField
private GeoPoint lastLocation;
}
// 附近相似用戶推薦
public List<UserProfile> recommendUsers(String userId, int radiusKm) {
UserProfile current = getUserById(userId); // 獲取當(dāng)前用戶
Query query = NativeQuery.builder()
.withQuery(q -> q
.bool(b -> b
.must(m -> m.geoDistance(g -> g // 地理過濾
.field("lastLocation")
.distance(radiusKm + "km")
.location(l -> l.latlon(ll ->
ll.lat(current.getLastLocation().lat())
.lon(current.getLastLocation().lon())
))
))
.must(m -> m.terms(t -> t // 標(biāo)簽匹配
.field("tags")
.terms(t2 -> t2.value(current.getTags()))
)
)
)
.build();
return esOperations.search(query, UserProfile.class)
.stream().map(SearchHit::getContent)
.collect(Collectors.toList());
}
地理搜索——讓“附近的人”觸手可及
痛點
- MySQL地理計算性能差(ST_Distance函數(shù)消耗大)
- 無法支持復(fù)雜地理圍欄
完整實現(xiàn)
// 商家實體(含地理位置)
@Document(indexName = "shops")
public class Shop {
@Id
private String id;
@GeoPointField // 關(guān)鍵注解!
private GeoPoint location;
@Field(type = FieldType.Text)
private String name;
}
// 附近商家服務(wù)
@Service
public class ShopService {
public List<Shop> findNearby(double lat, double lon, double radiusKm) {
Query query = NativeQuery.builder()
.withQuery(q -> q
.geoDistance(g -> g
.field("location")
.distance(radiusKm + "km")
.location(gl -> gl.latlon(l -> l.lat(lat).lon(lon)))
)
.withSort(s -> s // 按距離排序
.geoDistance(g -> g
.field("location")
.location(l -> l.latlon(ll -> ll.lat(lat).lon(lon))
.order(SortOrder.Asc))
)
.build();
return esOperations.search(query, Shop.class)
.stream().map(SearchHit::getContent)
.collect(Collectors.toList());
}
}
// 接口調(diào)用示例
@RestController
@RequestMapping("/shops")
public class ShopController {
@GetMapping("/nearby")
public List<Shop> getNearby(
@RequestParam double lat,
@RequestParam double lon,
@RequestParam(defaultValue = "3") double radius) {
return shopService.findNearby(lat, lon, radius);
}
}
3.性能對比:ES如何碾壓傳統(tǒng)方案
場景 | 數(shù)據(jù)量 | MySQL耗時 | ES耗時 | 優(yōu)勢倍數(shù) |
商品搜索 | 1000萬 | 4.2s | 28ms | 150x |
附近商家查詢 | 50萬 | 920ms | 15ms | 61x |
4.避坑指南:ES不是銀彈
事務(wù)場景:訂單支付等需強(qiáng)一致性時,仍需結(jié)合MySQL
冷數(shù)據(jù)存儲:歷史歸檔數(shù)據(jù)建議轉(zhuǎn)存至OSS
精確統(tǒng)計:UV去重請用HyperLogLog
5.小結(jié)
從電商搜索到地理圍欄,Elasticsearch 正在重新定義數(shù)據(jù)處理的邊界。當(dāng)你的業(yè)務(wù)面臨以下挑戰(zhàn)時,就是時候考慮 ES 了
- 數(shù)據(jù)量超過千萬級
- 需要復(fù)雜搜索/聚合
- 對實時性要求高