思考:
- 考虑ES如何与数据库实现同步?
- ES如何查询多个字段?
- 如何构建商品服务(包含搜索功能)?
- ES肯定是集群的,如何集群?
- 一个项目当它做大做当后都可能会需要将数据从传统的数据库同步到另一种数据集合中,一般用于提高查询效率或将数据进行备份的目的。其中比较常见的一种同步方式是从关系型数据库同步到es
- MQ与logstash实现ES与数据库同步区别
- 基于Docker方式实现Elasticsearch集群
- 整合Elasticsearch IK分词器
一、理论基础
1.1、es与数据库是如何保持一致的呢?
原理:
1.2、MySQL与ES实时同步常用插件
参考:https://www.cnblogs.com/jpfss/p/10832918.html
1.3、logstash-input-jdbc同步插件原理:
作用:使用 logstash-input-jdbc 插件读取 mysql 的数据
如何安装logstash-input-jdbc插件:https://blog.csdn.net/yeyuma/article/details/50240595#quote
原理:
定时执行一个 sql,然后将 sql 执行的结果写入到流中,增量获取的方式没有通过 binlog 方式同步,而是用一个递增字段作为条件去查询,每次都记录当前查询的位置,由于递增的特性,只需要查询比当前大的记录即可获取这段时间内的全部增量,一般的递增字段有两种,AUTO_INCREMENT 的主键 id 和 ON UPDATE CURRENT_TIMESTAMP 的 update_time 字段,id 字段只适用于那种只有插入没有更新的表,update_time 更加通用一些,建议在 mysql 表设计的时候都增加一个 update_time 字段
1.4、什么是全量同步和增量同步
(1)全量同步
什么是全量同步:将一个mysql的整个表的所有数据都同步到es中
常用插件是logstash-input-jdbc,logstash通过sql语句分区间对数据进行查询,然后输出到es进行实现。
(2)增量同步(canal)
什么是增量同步:业务场景需要实时性较高并对要求对数据库的压力比较小。
logstash不再适合增量同步,logstash是基于sql来完成的,并且是通过cron表达式来调用。
阿里在做大做强的过程中也遇到过类似的问题,为了达到性能的最优,他们自己通过JAVA代码实现了mysql的数据同步功能,通过解析mysql 的日志进行实现的,并且把这个项目开源——canal
canal地址:https://github.com/alibaba/canal
原理:
简单看了下代码,它里面是通过一个server和一个client来进行实现的,及server负责mysql的日志监听与收集,然后再传给client端,server与client端使用的netty进行数据的tcp通信,现在也有了kafka和rocketMQ作为通信渠道的版本。同时canal也有专门的可视化监控界面,方便进行查看,同时也有HA(hadoop集群)的实现方式,通过zookeeper来进行实现的。
使用:
想将数据从mysql同步到es通过canal的一个adapter就可以直接实现了
参考:https://github.com/alibaba/canal/tree/master/client-adapter
canal优点:
(1)canal实现数据的增量同步性能高,有可视化界面可监控
(2)看canal esAdapter的源码可知,canal esAdater中的etl方法对全量同步的功能也已经做了实现,可增量可全量同步了
1.5、MQ与logstash实现ES与数据库同步区别
Logstash实现ES与数据库同步:使用定时器方式 、实现简单
MQ实现ES与数据库同步:实时性、复杂性更高、一致性强
1.6、拼音分词器elasticsearch-analysis-pinyin
略
二、实战
2.1、商品服务搜索如何实现
商品服务搜索采用ElasticSearch实现
需要考虑什么问题?
(1)如何实现ES与mysql数据库同步
A、使用logstash 把mysql 同步到 elasticsearch
什么是logstash?
Logstash是一个开源数据收集引擎,具有实时管道功能。Logstash可以动态地将来自不同数据源的数据统一起来,并将数据标准化到你所选择的目的地
1.上传logstash-6.4.3.tar.gz到服务中
2.tar –zxvf logstash-6.4.3.tar.gz
3.cd logstash-6.4.3
4. bin/logstash-plugin install logstash-input-jdbc
5. bin/logstash-plugin install logstash-output-elasticsearch
相关配置文件说明:
jdbc_driver_library: jdbc mysql 驱动的路径,在上一步中已经下载
jdbc_driver_class: 驱动类的名字,mysql 填 com.mysql.jdbc.Driver 就好了
jdbc_connection_string: mysql 地址
jdbc_user: mysql 用户
jdbc_password: mysql 密码
schedule: 执行 sql 时机,类似 crontab 的调度
statement: 要执行的 sql,以 “:” 开头是定义的变量,可以通过 parameters 来设置变量,这里的 sql_last_value 是内置的变量,表示上一次 sql 执行中 update_time 的值,这里 update_time 条件是 >= 因为时间有可能相等,没有等号可能会漏掉一些增量
use_column_value: 使用递增列的值
tracking_column_type: 递增字段的类型,numeric 表示数值类型, timestamp 表示时间戳类型
tracking_column: 递增字段的名称,这里使用 update_time 这一列,这列的类型是 timestamp
last_run_metadata_path: 同步点文件,这个文件记录了上次的同步点,重启时会读取这个文件,这个文件可以手动
B、多文件方式同步ES数据
一个 logstash 实例可以借助 pipelines 机制同步多个表,只需要写多个配置文件就可以了
案例:
假设我们有两个表 table1 和 table2,对应两个配置文件 sync_table1.cfg 和 sync_table2.cfg
在 config/pipelines.yml 中配置:
- pipeline.id: table1
path.config: "config/sync_table1.cfg"
- pipeline.id: table2
path.config: "config/sync_table2.cfg"
./bin/logstash
(2)构建商品服务信息接口
商品搜索服务接口:
@GetMapping("/search")
public BaseResponse<List<ProductDto>> search(String productName);
@Autowired
private ProductReposiory productReposiory;
@Override
public BaseResponse<List<ProductDto>> search(String productName) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
// 设置模糊搜索
builder.must(QueryBuilders.fuzzyQuery("name", productName));
List<ProductEntity> search = (List<ProductEntity>) productReposiory.search(builder);
List<ProductDto> listReuslt = new ArrayList<>();
BeanUtils.copyProperties(search, listReuslt);
return setResultSuccess(listReuslt);
}
public interface ProductReposiory extends ElasticsearchRepository<ProductEntity, Long> {
}
@SpringBootApplication
@EnableElasticsearchRepositories(basePackages = { "com.mayikt.product.es" })
public class AppProduct {
public static void main(String[] args) {
SpringApplication.run(AppProduct.class, args);
}
}
配置文件:
###服务启动端口号
server:
port: 8500
###服务名称(服务注册到eureka名称)
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
spring:
application:
name: app-mayikt-goods
redis:
host: 188.131.155.46
port: 6379
password: 123456
pool:
max-idle: 100
min-idle: 1
max-active: 1000
max-wait: -1
###数据库相关连接
datasource:
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/goods?useUnicode=true&characterEncoding=UTF-
data:
elasticsearch:
####集群名称
cluster-name: myes
####地址
cluster-nodes: 192.168.212.247:9300
2.2、基于Docker方式实现Elasticsearch集群
注意:spring-boot-starter-data-elasticsearch必须为集群方式连接,否则情况下会报一下错误
None of the configured nodes are available
(1)
1.mkdir -p es/config
2.cd es
3.mkdir data1
4.mkdir data2
5.mkdir data3
firewall-cmd --add-port=9300/tcp
firewall-cmd --add-port=9301/tcp
6. mkdir plugins1
7. mkdir plugins2
(2)在es/config分别放入es1.yml、es2.yml:
Es1:
cluster.name: elasticsearch-cluster
node.name: es-node1
network.bind_host: 0.0.0.0
network.publish_host: 192.168.212.252
http.port: 9200
transport.tcp.port: 9300
http.cors.enabled: true
http.cors.allow-origin: "*"
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["192.168.212.252:9300","192.168.212.252:9301"]
discovery.zen.minimum_master_nodes: 1
Es2:
cluster.name: elasticsearch-cluster
node.name: es-node2
network.bind_host: 0.0.0.0
network.publish_host: 192.168.212.252
http.port: 9201
transport.tcp.port: 9301
http.cors.enabled: true
http.cors.allow-origin: "*"
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["192.168.212.252:9300","192.168.212.252:9301"]
discovery.zen.minimum_master_nodes: 1
(3)启动俩容器
启动容器1:
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 -p 5601:5601 -v /usr/local/es/config/es1.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /usr/local/es/plugins1:/usr/share/elasticsearch/plugins -v /usr/local/es/data1:/usr/share/elasticsearch/data --name ES01 elasticsearch
启动容器2:
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9201 -p 9301:9301 -v /usr/local/es/config/es2.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /usr/local/es/plugins2:/usr/share/elasticsearch/plugins -v /usr/local/es/data2:/usr/share/elasticsearch/data --name ES02 elasticsearch
(4)测试集群效果
http://你的IP地址:9200/_cat/nodes?pretty
2.3、SpringBoot整合Elasticsearch
(1)创建服务项目shop-service-goods服务接口
(2)创建商品搜索服务接口
public interface ProductSearchService {
@GetMapping("/search")
public BaseResponse<List<ProductDto>> search(String name);
}
(3)dto实体类
@Data
public class ProductDto {
/** 主键ID */
private Integer id;
/** 类型ID */
private Integer categoryId;
/** 名称 */
private String name;
/** 小标题 */
private String subtitle;
/** 主图像 */
private String mainImage;
/** 小标题图像 */
private String subImages;
/** 描述 */
private String detail;
/** 商品规格 */
private String attributeList;
/** 价格 */
private Double price;
/** 库存 */
private Integer stock;
/** 状态 */
private Integer status;
/** 乐观锁 */
private Integer revision;
/** 创建人 */
private String createdBy;
/** 创建时间 */
private Date createdTime;
/** 更新人 */
private String updatedBy;
/** 更新时间 */
private Timestamp updatedTime;
}
(4)maven依赖
<!-- springboot 整合ES -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.2</version>
</dependency>
(5)业务实现
@RestController
public class ProductSearchServiceImpl extends BaseApiService<List<ProductDto>> implements ProductSearchService {
@Autowired
private ProductReposiory productReposiory;
@Override
public BaseResponse<List<ProductDto>> search(String name) {
//1.拼接查询条件
BoolQueryBuilder builder = QueryBuilders.boolQuery();
//2.模拟查询name字段
builder.must(QueryBuilders.fuzzyQuery("name", name));
Pageable pageable = new QPageRequest(0, 5);
//3.调用ES接口查询
Page<ProductEntity> page = productReposiory.search(builder, pageable);
//4.获取集合数据
List<ProductEntity> content = page.getContent();
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
//5.将entity转换为dto
List<ProductDto> mapAsList = mapperFactory.getMapperFacade().mapAsList(content, ProductDto.class);
return setResultSuccess(mapAsList);
}
}
public interface ProductReposiory extends ElasticsearchRepository<ProductEntity, Long> {
}
(6)实体
@Document(indexName = "goods", type = "goods")
@Data
public class ProductEntity {
/** 主键ID */
private Integer id;
/** 类型ID */
private Integer categoryId;
/** 名称 */
private String name;
/** 小标题 */
private String subtitle;
/** 主图像 */
private String mainImage;
/** 小标题图像 */
private String subImages;
/** 描述 */
private String detail;
/** 商品规格 */
private String attributeList;
/** 价格 */
private Double price;
/** 库存 */
private Integer stock;
/** 状态 */
private Integer status;
/** 创建人 */
private String createdBy;
/** 创建时间 */
private Date createdTime;
/** 更新时间 */
private Timestamp updatedTime;
}
(7)配置文件
###服务启动端口号
server:
port: 8500
###服务名称(服务注册到eureka名称)
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
spring:
application:
name: app-goods
redis:
host: redisIP地址
port: 6379
password: 123456
pool:
max-idle: 100
min-idle: 1
max-active: 1000
max-wait: -1
###数据库相关连接
datasource:
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/goods?useUnicode=true&characterEncoding=UTF-
data:
elasticsearch:
####集群名称
cluster-name: elasticsearch-cluster
####地址
cluster-nodes: IP地址:9300
2.4、Elasticsearch集成IK分词器
如果没有整合IK分词,es无法支持模糊查询
(1)ES文档类型映射
GET /goods/_mapping
DELETE /goods
PUT /goods
POST /goods/_mapping/goods
{
"goods": {
"properties": {
"@timestamp": {
"type": "date"
},
"@version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"attribute_list": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"category_id": {
"type": "long"
},
"created_time": {
"type": "date"
},
"detail": {
"type": "text",
"analyzer":"ik_smart",
"search_analyzer":"ik_smart"
},
"id": {
"type": "long"
},
"main_image": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"analyzer":"ik_smart",
"search_analyzer":"ik_smart"
},
"revision": {
"type": "long"
},
"status": {
"type": "long"
},
"sub_images": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"subtitle": {
"type": "text",
"analyzer":"ik_smart",
"search_analyzer":"ik_smart"
},
"updated_time": {
"type": "date"
}
}
}
}
{
"analyzer": "ik_smart",
"text": "苹果"
}
{
"analyzer": "standard",
"text": "奥迪a4l"
}