需求:对“昵称”进行“全文检索查询”,对“账号”进行“精确查询”。
- 正向索引
- 对 id 进行检索速度很快。
- 对其他字段即使加了索引,只能满足精确查询。
- 模糊查询时,逐条数据扫描,判断是否符合条件。速度很慢。
- 倒排索引
- 词条(Term)+包含词条的所有文档(Document)的id,这种存储形式。
注:id不为long型,而是keyword。即,不参与分词。
- standard
- 中文是逐字分词
- ik_smart
- 粗粒度。
- “程序员” = “程序员”
- ik_max_word
- 细粒度。
- “程序员” = “程序员”、“程序”、“员”
- 指定索引库,对这个索引库进行
- 增:PUT
- 删:DELETE
- 改:PUT,只能新增字段,不能修改旧字段
- 查:GET
- 指定 id,对这个 id 的文档进行:
- 增:POST
- 删:DELETE
- 改:PUT:替换旧文档,可以实现增+改;POST:指定修改某些字段
- 查:GET
- 全文检索查询
- 数据结构:text
- 利用分词器对用户输入的内容进行分词,并在倒排查询库中匹配。
- match_query:支持一个字段
- multi_match_query:支持多个字段,性能不如 match_query。
- 精确查询
- 数据结构:keyword、数值、日期、boolean
- term:精确查询,即等于。
- range:只适用于数值、日期。
- 其他:地理查询、符合查询等。
- 如果是单体式项目:对数据库进行增删改查时,对ES也进行增删改查
- 如果是微服务项目:
- 同步调用:
- 服务层先操作数据库,再调用更新ES的接口。
- 该接口去更新ES。
- ES更新完成后,结果返回给接口。
- 接口返回给服务层。
- 缺点:业务耦合、耗时增加、性能下降。
- 异步通知:
- 服务层操作数据库,再发布消息。
- ES监听并更新数据。
- 优点:低耦合。缺点:依赖于MQ的可靠性。
- 监听binlog:
- 服务层操作数据库。
- 数据库把操作记录到binlog。
- canal这个中间件去监听binlog,通知ES。
- ES更新数据。
- 优点:完全解除耦合度。缺点:依赖于中间件canal和mysql。mysql压力增大。
异步通知的操作:
- 发送MQ:
- 采用topic交换机。
- 当进行新增和修改时,发送 id 给交换机,声明“新增”的路由键(routing key)。
- 当进行删除时,发送 id 给交换机,并声明“删除”的路由键(routing key)。
- 注:不发送整个数据,而是数据的 id,以减少信息传输的数据量。
- 监听MQ:
- 监听“新增”队列的监听器:对 ES 发送新增请求。
- 监听“删除”队列的监听器:对 ES 发送删除请求。
单体式项目:
示例,增的同步代码,在 Controller 层:
- // 增
- @PostMapping()
- public User save(@RequestBody User user) throws IOException {
- // 保存到mysql
- userService.save(user);
- // 保存到mysql后,id已经有了,可以直接插入到ES
- esService.AddDocument(user);
- return user;
- }
注:前端发来的数据 user 无 id,通过Mybatis Plus 插入到 mysql 数据库后 user 有 id,可以直接插入到 ES(不需要从 mysql 数据库查询得到 user 数据,再插入 ES)。
示例,对“昵称”进行全文检索查询:
1. 创建一个配置类,注入一个 bean 方法,把向 ES 发送请求的 client 注入 IOC。
- @Configuration
- public class EsConfig {
- @Bean
- public RestHighLevelClient clien(){
- return new RestHighLevelClient(RestClient.builder(
- HttpHost.create("http://localhost:9200")
- ));
- }
- }
2. POJO 中封装三个类:
收到前端的类 EsPageParams
- @Data
- public class EsPageParams {
- private String key;
- private Integer page;
- private Integer size;
- }
发给前端的类 EsPageResult
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class EsPageResult {
- private Long total;
- private List
users; - }
数据库的类 User
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class User {
- @TableId(type = IdType.AUTO)
- private Long id; //ID
- private String username; //用户名
- private String password; //密码
- private String niCheng; //姓名
- private Integer gender;
- private String location;
- private String txImageName;
- @TableField(fill = FieldFill.INSERT)
- private LocalDateTime createTime;
- @TableField(fill = FieldFill.INSERT_UPDATE)
- private LocalDateTime updateTime;
- }
(如果 ES 和 mysql 数据库不一致,还需要一个 ES 类)
3. Controller 层:接受请求,发送给 Service 层。
4. Service 层:对 user 索引表的 niCheng 字段进行检索,检索方式是倒排索引。最终结果返回给 Controller 层。
- @Service
- public class EsService {
-
- @Autowired
- private RestHighLevelClient client;
-
- @Autowired
- private UserService userService;
-
- public EsPageResult search(EsPageParams esPageParams) throws IOException {
- // 1.准备request
- SearchRequest request = new SearchRequest("user");
- // 2. 准备DSL
- String key = esPageParams.getKey();
- request.source().query(QueryBuilders.matchQuery("niCheng",key));
- int page = esPageParams.getPage();
- int size = esPageParams.getSize();
- request.source().from((page - 1) * size).size(size);
- // 3. 发送请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
- // 4.解析响应
- return handleResponse(response);
- }
-
- private EsPageResult handleResponse(SearchResponse response){
- SearchHits searchHits = response.getHits();
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条数据");
- SearchHit[] hits = searchHits.getHits();
- List
users = new ArrayList<>(); - for(SearchHit hit : hits){
- String json = hit.getSourceAsString();
- User user = JSON.parseObject(json, User.class);
- users.add(user);
- }
- return new EsPageResult(total, users);
- }