基于logstash6.3.2的同步MySQL数据到ElasticSearch
背景
为了满足业务查询的需求,会把MySQL中的数据导入到elasticsearch中
基本方案是用canal来监听MySQL的binlog,然后把数据写入到kafka中
通过订阅kafka-topic来把插入or变更的数据写入到es中,用status字段来辨别逻辑删除
为了保证数据的完整性,怕出现异常情况导致数据丢失,丢失后不可感知且无法补偿
所以引用了一个MySQL同步elasticsearch的开源组件elasticsearch-jdbc
这个组件的调用一直是通过在服务器上添加crontab任务来调度的
领导给我的需求是想做成一个应用服务,能够加载多个任务(同步不同的表到索引),数据增量同步,并且能够实现scheduled定时调度
于是从github上下载了源码,观察学习了之后,重写了main方法,加载自己定义的配置文件,解析后定时的执行同步任务
刚开始我也以为可以完成需求任务,通过观察后发现应用占用的内存越来越大,经过两天的运行后日志显示程序一直在gc
再加上这个开源框架最近一次提交是三年前了,最高支持的版本也是2.X,所以此时决定寻找其它替代方案
经过各种调研后发现logstash是一个不错的工具,支持读取和输出的数据源非常多,很适合做为新的同步工具
执行同步任务时,每五分钟执行一次,同步最近10分钟更新的数据
环境搭建
logstash依赖jdk,下载安装jdk,配置JAVA_HOME到Path中
下载logstash 解压 logstash
解压后logstash目录结构
logstash 属于开箱即用型 直接执行测试 语句,启动logstash,输入 hello world
logstash -e 'input { stdin { } } output { stdout {} }'
hello world
{
"@version" => "1",
"host" => "localhost",
"@timestamp" => 2018-09-18T12:39:38.514Z,
"message" => "hello world"
}
同步到es配置文件示例
logstash至少需要一个input、output两个配置,filter是对input读取到的文件加工处理
本例中input的数据源是jdbc
input {
jdbc {
jdbc_connection_string => "jdbc:mysql://ip:port/db_name" 数据库的连接地址
jdbc_user => "root" 数据库用户名
jdbc_password => "root" 数据库密码
jdbc_driver_library => "/usr/local/lib/mysql-connector-java-5.1.21.jar" 数据库驱动jar包,可以用相对路径也可以用绝对路径,为了与原组件解耦尽量放在外部
jdbc_driver_class => "com.mysql.jdbc.Driver" 数据库驱动类
jdbc_paging_enabled => "true" 是否开启分页,这个是需要打开的,防止同步一个非常大的表导致查询和导出数据量太大
jdbc_page_size => "1000" 分页每页的大小
use_column_value => true 使用追踪字段的值,用来记录
tracking_column => "ts" 追踪哪个字段
tracking_column_type => "timestamp" 被追踪的字段的类型
record_last_run => true 记录最后一次运行的值
last_run_metadata_path => "/usr/local/data/ts_last_value" 记录的值保存在哪里
statement => "select id,DATE_FORMAT(ts, '%Y-%m-%d %H:%i:%s') as ts from user WHERE ts > TIMESTAMPADD(MINUTE,-490,:sql_last_value)" 因为被记录的值用的UTC+8,所以把取到的时间做减法
schedule => "*/5 * * * *" 此任务的执行频率
type => "user" 相当于给这个任务起一个名字
}
}
filter {
if[type] == "user" { 对type=user的数据进行过滤
mutate {
remove_field => [ "@timestamp" ] 移除字段,jdbc会加上@timestamp字段
remove_field => [ "@version" ] 移除字段,jdbc会加上@version字段
}
}
}
output {
if[type] == "user" { 对指定类型进行处理
elasticsearch { 写入到elasticsearch中
hosts => "ip:9200" ip port
index => "user" 写入到的索引,建议先手动创建好
document_type => "_doc" es6后一个索引一个type,es7后要移除type
document_id => "%{id}" id的生成规则
routing => "%{id}" 路由值用id
parent => "%{id}" es6前有父子类型,需要一个parent的id
}
}
}
干净配置文件如下,建议表字段中用一个时间戳来记录,最后一次更新的时间,这样方便与增量更新,type字段可能会写入到es中
input {
jdbc {
jdbc_connection_string => "jdbc:mysql://ip:pror/db_name"
jdbc_user => "user"
jdbc_password => "user"
jdbc_driver_library => "/usr/lib/mysql-connector-java-5.1.21.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "1000"
use_column_value => true
tracking_column => "ts"
tracking_column_type => "timestamp"
record_last_run => true
last_run_metadata_path => "/usr/data/db_name_last_value"
statement => "select id,DATE_FORMAT(ts, '%Y-%m-%d %H:%i:%s') as ts from user WHERE ts > TIMESTAMPADD(MINUTE,-490,:sql_last_value)"
schedule => "*/5 * * * *"
type => "user"
}
}
filter {
if[type] == "user" {
mutate {
remove_field => [ "@timestamp" ]
remove_field => [ "@version" ]
}
}
}
output {
if[type] == "user" {
elasticsearch {
hosts => "localhost:9200"
index => "user"
document_type => "_doc"
document_id => "%{id}"
routing => "%{id}"
parent => "%{id}"
}
}
}
多节点部署配置一致性解决方案
实际环境中,会有很多个logstash部署在不同的机器,每个logstash跑着很多个任务
所以如何管理logstash就成为一个问题,所有的配置必须统一保存起来,如何统一部署
多台机器间如何保证配置文件一样,经过思考后于是有了这个方案
- 创建一个git仓库,项目中有2个目录,bin目录存放启动脚本,conf下根据主机名放不同的配置文件
bin start.sh conf host1 cron888.conf host2 cron999.conf
- 每次有任务or新增机器,修改start文件,在对应的host文件夹下增加conf
- Jenkins上配置项目,每个logstash可以当做一个项目,用同一个git地址,输出到不同的机器位置,这样解决单点启动不用更新所有logstash
- 在shell中取到本机hostname,然后得到conf路径,启动logstash指定 -f conf/hostname/
- 每台机器logstash安装位置统一,可以在start中写死,启动脚本指定配置文件,data文件,log文件 在外部文件加 不破坏logstash原生结构
- 以上可以解决一个项目一个logstash的问题,如果两台机器同时跑同一个任务,那么每次修改还要修改2个文件
这时候可以用另一个方式来解决,只写一份文件,然后在bin脚本中,根据hostname把配置文件复制到一个新的文件夹
然后指定新的文件夹启动