需求:对“昵称”进行“全文检索查询”,对“账号”进行“精确查询”。
认识 Elasticsearch
1. ES 的倒排索引
- 正向索引
- 对 id 进行检索速度很快。
- 对其他字段即使加了索引,只能满足精确查询。
- 模糊查询时,逐条数据扫描,判断是否符合条件。速度很慢。
- 倒排索引
- 词条(Term)+包含词条的所有文档(Document)的id,这种存储形式。
2. ES 与 mysql 的区别
3. ES 的数据结构与表结构
注:id不为long型,而是keyword。即,不参与分词。
4. ES 分词器的种类:
- standard
- 中文是逐字分词
- ik_smart
- 粗粒度。
- “程序员” = “程序员”
- ik_max_word
- 细粒度。
- “程序员” = “程序员”、“程序”、“员”
5. ES 增删改查:
- 指定索引库,对这个索引库进行
- 增:PUT
- 删:DELETE
- 改:PUT,只能新增字段,不能修改旧字段
- 查:GET
- 指定 id,对这个 id 的文档进行:
- 增:POST
- 删:DELETE
- 改:PUT:替换旧文档,可以实现增+改;POST:指定修改某些字段
- 查:GET
6. ES 查询方式:
- 全文检索查询
- 数据结构:text
- 利用分词器对用户输入的内容进行分词,并在倒排查询库中匹配。
- match_query:支持一个字段
- multi_match_query:支持多个字段,性能不如 match_query。
- 精确查询
- 数据结构:keyword、数值、日期、boolean
- term:精确查询,即等于。
- range:只适用于数值、日期。
- 其他:地理查询、符合查询等。
配置 Elasticsearch
- 下载 Elasticsearch
- Windows10环境下安装Es7_windows安装es7-CSDN博客
- 7.x 和 8.x 差距比较大(8.x 版本默认有ssl 认证、用户密码登录,且在 Spring 中的操作差别有点大)。采用版本是7.12.1。
- 有高、低版本。采用高版本。
- 已经不支持 java 访问 ES,而是 java request 请求的方式访问 ES。
- 在Spring boot 配置 ES
- 在 xml 中引入依赖。且需要在 properties 强制指定 ES 版本为 7.12.1。
- 在Spring boot 配置 FastJson
- 在 xml 中引入依赖。且需要指定版本为 1.2.68。1.1.x 不支持 LocalDateTime。
- 在Spring boot 配置 RabbitMQ
- 在 xml 中引入依赖。
- 在 yml 中配置 RabbitMQ。
实现 Elasticsearch
1. 增删改:数据同步
- 如果是单体式项目:对数据库进行增删改查时,对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)。
2. 查询 + 分页
示例,对“昵称”进行全文检索查询:
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<User> 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<User> 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);
}