Logstash实时同步MySQL数据到ElasticSearch的经验总结

目录

  • 项目需求
  • Logstash配置详解
    • pipeline配置文件
      • Input
      • Filter
      • Output
    • Logstash运行步骤详解
  • 遇到的问题及解决方法
    • 如何实现复杂的业务处理逻辑
    • 如何既保证数据同步速度又不影响正常MySQL使用
    • 如何保证数据实时更新
    • 如何支持多个view的数据同步
    • 如何处理删除的数据
    • ElasticSearch的Template设计
    • Schedule调度配置
    • 为什么不使用canal
  • 一点经验
  • 参考文献

ElasticSearch可以提供强大灵活的搜索功能,这是MySQL不具备的。所以当业务需要复杂的搜索功能时,一般用ElasticSearch提供搜索功能。此时ElasticSearch如何能实时同步MySQL的数据至关重要,Logstash可以通过简单的一些配置来实现MySQL数据同步到ElasticSearch。关于一些基础的配置详细讲解可以参考 官方博客。本文总结作者项目中碰到的一些实际问题以及解决方案。

项目需求

项目需要实现对MySQL数据的快速读取以及查询功能,查询包括对所有列的全文检索,单列的下拉过滤,搜索自动补全等。ElasticSearch能够满足所有的项目搜索需求。MySQL中共有6个view需要同步到ES,每个view都是join多张表,最大的view有一千多万条数据,每天有上万条的新增。每个view中均有updated_time列,每当数据更新时,updated_time会更新。以其中的project view为例,介绍如何做到MySQL与ElasticSearch之间的实时同步。

Logstash配置详解

软件版本

Logstash: 7.9.3
ElasticSearch: 7.9.3
MySQL:AWS Aurora MySQL 5.7.12

pipeline配置文件

input {
  jdbc {
    jdbc_driver_library => "/opt/mysql-connector-java-8.0.16.jar"
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://${JDBC_HOSTNAME}:${JDBC_PORT}/${DB_NAME}?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull"
    jdbc_user => "${JDBC_USER}"
    jdbc_password => "${JDBC_PASSWORD}"
    jdbc_paging_enabled => true
    tracking_column => "unix_ts_in_secs"
    use_column_value => true
    tracking_column_type => "numeric"
    schedule => "*/1 * * * *"
    record_last_run => true
    last_run_metadata_path => "/mnt/lastrun/.project_last_run"
    statement => "SELECT *, UNIX_TIMESTAMP(updated_time) AS unix_ts_in_secs FROM view_project WHERE (UNIX_TIMESTAMP(updated_time)) > :sql_last_value AND updated_time < NOW() ORDER BY updated_time asc"
  }
}

filter {
    mutate {
      copy => { "id" => "[@metadata][_id]"}
      remove_field => ["@version", "unix_ts_in_secs"]
    }
}

output {
    elasticsearch {
        index => "view_project_idx"
        document_id => "%{[@metadata][_id]}"
        doc_as_upsert => true
        action => "update"
        hosts => "http://localhost:9200"
        manage_template => true
        template => "/opt/project-index-template.json"
        template_name => "project-index-template"
        template_overwrite => true
    }
}

配置文件有三个部分组成,分别是输入,转换,输出。

Input

定义数据从哪里来,也就是告诉Logstash如何获取数据。

  • MySQL连接信息用环境变量配置,便于在不同MySQL环境中切换
  • 配置如何查询数据是否发生变化。
    • use_column_value => true,使用数据源中的字段
    • tracking_column => “unix_ts_in_secs”, unix_ts_in_secs是updated_time的unix epoch表示,Logstash通过此列来检查数据是否发生变化
    • tracking_column_type => “numeric” tracking column的数据类型是数字类型。
  • 配置sql_last_run路径,last_run_metadata_path => “/mnt/lastrun/.project_last_run”,Logstash在一个Job结束时会将结束的时间写在此文件中,下次运行时,只取updated_time>sql_last_run的数据。
  • 配置取数据的sql statement,数据按updated_time升序排列。

Filter

对数据的转换,处理等在此处配置,Logstash已有多个filter plugin。

  • 此处Muate实现用MySQL中的id作为ES中doc的_id,并且移除不需要的列。

Output

配置ElasticSearch地址,ES Index template等。

  • 配置template,template => “/opt/project-index-template.json”,定义数据在ElasticSearch中的类型,Analyzer等
  • 配置如何处理数据变化,doc_as_upsert => true,action => “update”,此两个配置的意义是当MySQL数据发生变化时,将数据变化更新到ElasticSearch中。action默认配置为index,当为index时,如果ElasticSearch侧也对数据做过update,MySQL变化时会重新Index所有MySQL数据,导致ES侧做过的update丢失。

Logstash运行步骤详解

  1. Logstash执行select count(*)获得总数据条目数。
[2021-06-04T06:29:00,520][INFO ][logstash.inputs.jdbc     ][main][cef0a724ea4fc07490bbe09f83d681572322bd71f3fbef06cacab4f1ba501378] (0.267778s) SELECT count(*) AS `count` FROM (SELECT *, UNIX_TIMESTAMP(updated_time) AS unix_ts_in_secs FROM view_project WHERE (UNIX_TIMESTAMP(updated_time)) > 0 AND updated_time < NOW() ORDER BY updated_time asc) AS `t1` LIMIT 1 
  1. 如果count大于0,通过SQL Limit语句分批从MySQL取数据,默认是每次10万条。
[2021-06-04T06:29:01,503][INFO ][logstash.inputs.jdbc     ][main][cef0a724ea4fc07490bbe09f83d681572322bd71f3fbef06cacab4f1ba501378] (0.980027s) SELECT * FROM (SELECT *, UNIX_TIMESTAMP(updated_time) AS unix_ts_in_secs FROM view_project WHERE (UNIX_TIMESTAMP(updated_time)) > 0 AND updated_time < NOW() ORDER BY updated_time asc) AS `t1` LIMIT 100000 OFFSET 0 
  1. 分批获得所有数据后,此次job执行完毕,将最后一条数据的updated_time时间(1622787508)写入到sql last run中。如果数据获取过程中下次Job调度已经到了,则会将Job放到任务执行堆栈中。
  2. Logstash会根据schedule配置来执行下一次Job运行,新的Job中updated_time > sql_last_run,sql_last_run已经从0变为了上次任务获取到最后的updated_time,所以数据不会重新获取。
SELECT * FROM (SELECT *, UNIX_TIMESTAMP(updated_time) AS unix_ts_in_secs FROM view_project WHERE (UNIX_TIMESTAMP(updated_time)) > 1622787508 AND updated_time < NOW() ORDER BY updated_time asc) AS `t1` LIMIT 100000 OFFSET 0

遇到的问题及解决方法

在实际使用过程中碰到一些问题,解决方案总结如下:

如何实现复杂的业务处理逻辑

比如需要对其中的一列或者多列基于业务逻辑做分解,预处理等,可以通过filter ruby插件,直接写Ruby来实现。Logstash是基于Ruby写的,所以并不需要额外配置运行环境。

def filter(event)
	service = event.to_hash
	project_name = service["name"]
	## your process logic
	event.set("project_name ",project_name)
	return [event]
end

如何既保证数据同步速度又不影响正常MySQL使用

数据同步会增加MySQL的负载,可能会对MySQL正常的数据读写造成影响,可以在MySQL clusters中增加一个专用的Readonly Node,这个Node的地理位置尽可能与Logstash,ElasticSearch在同一个机房,

如何保证数据实时更新

除了设置schedule提供数据更新Job运行频率外,当MySQL数据更新时,需要确保表中的updated_time列更改为当前时间。如果updated_time设置不正确,比如早于sql_last_run,则Logstash将取不到此条数据。

schedule根据业务可以容易的delay来设置。所谓实时只要能满足业务需求就是实时。绝大部分不需要秒级更新。

如何支持多个view的数据同步

在一个pipeline中配置type参数,可以在一个pipeline配置文件中支持多个view的同步。

如何处理删除的数据

Logstash通过时间戳或ID等监视MySQL中的新数据或者updated的数据,如果数据在MySQL中被删除,Logstash没有办法在ElasticSearch中将数据删除。两个办法处理:

  • 在MySQL删除数据的业务代码中同时删除ElasticSearch数据。
    • 项目中一般不能实现,MySQL是集中数据库,可能被多个业务线访问。
  • MySQL数据表中增加一个status列,删除数据是将status设为deleted。这样可以同步到ElasticSearch,然后ES查询时候增加一个条件,status != deleted.
    • 如果MySQL中存在多个status列,可以copy到ES的一个列中,通过ES查询来获取非deleted的数据。

ElasticSearch的Template设计

  • 每个field保留keyword subfield,排序使用。
  • 每个field都显示定义数据类型,如date需定义好具体格式
  • ES不允许更改数据类型,提前整理好查询需求,将Analyzer在Template中定义好。

Schedule调度配置

Logstash官方jdbc input文档中支持秒表调度,通过rufus-scheduler来支持。如每隔10s执行一次同步任务,这对同步性要求非常高的数据是非常有用的。经测试这在服务器上运行时是可以的,但当在k8s上部署时,并不支持秒级调度,在ES论坛发了帖子Logstash input plugin jdbc schedule issue on k8s但未收到回应。

为什么不使用canal

canal是阿里MySQL Binlog增量消费组件,相比Logstash执行SQL查询获得数据,canal是通过读取MySQL的binlog来获得数据更新,速度会比Logstash快,项目中没有使用canal有以下几个原因。

  • ElasticSearch不擅长join操作,MySQL中数据存在多个table中,利用MySQL的join功能直接将结果集数据同步到ElasticSearch提供检索功能。
  • 数据更新不是很heavy,每天是万条数据左右,Logstash执行SQL查询速度可以接受。
  • 数据实时性要求不是很高,2分钟以内的延时是可以接受的。
  • MySQL cluster是由其他部门管理的,只有可读账户,无法配置MySQL slave。
  • ELK技术栈在部门应该比较广泛,canal对技术团队来说比较新。

一点经验

  1. MySQL表结构设计一般一个entity一张表,通过外键关联,查询时使用Join。但ElasticSearch这种分布式系统不支持类似SQL的Join操作。所以需要在MySQL中创建view然后同步view数据到ElasticSearch中。
  2. 如果数据量比较大,可以在view中加time的where条件,让Logstash执行view的查询速度更快。如果不在view定义中加where而是Logstash将where条件加在view外面,因为view是一个temp table所以MySQL还是执行整个数据的查询,并没有利用time的where条件来加速查询。
  3. updated_time时间必须准确,不能写入时候是一个小于当前时间的值。最好设计为MySQL自动填充。
  4. 尽量避免数据重新ingest。ES update mapping可以增加新field,但不能update ES field数据类型。
  5. SQL statement必须按updated time升序排列

参考文献

  1. Logstash Reference
  2. How to keep Elasticsearch synchronized with a relational database using Logstash and JDBC
  3. Logstash JDBC tracking column value not latest timestamp
  4. Question on Use of Use_Column_Value And Timestamps

你可能感兴趣的:(ElasticSearch,Logstash,elasticsearch,mysql,数据同步)