fluent-plugin-mysql-replicator 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +14 -0
- data/README.md +70 -0
- data/Rakefile +9 -0
- data/fluent-plugin-mysql-replicator.gemspec +18 -0
- data/lib/fluent/plugin/in_mysql_replicator.rb +109 -0
- data/test/helper.rb +28 -0
- data/test/plugin/test_in_mysql_replicator.rb +38 -0
- metadata +106 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (c) 2013- Kentaro Yoshida
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# fluent-plugin-mysql-replicator [](https://travis-ci.org/y-ken/fluent-plugin-mysql-replicator)
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Fluentd input plugin to track insert/update/delete event from MySQL database server.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
`````
|
10
|
+
### native gem
|
11
|
+
gem install fluent-plugin-mysql-replicator
|
12
|
+
|
13
|
+
### td-agent gem
|
14
|
+
/usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-mysql-replicator
|
15
|
+
`````
|
16
|
+
|
17
|
+
## Tutorial
|
18
|
+
|
19
|
+
#### configuration
|
20
|
+
|
21
|
+
`````
|
22
|
+
<source>
|
23
|
+
type mysql_replicator
|
24
|
+
host localhost
|
25
|
+
username your_mysql_user
|
26
|
+
password your_mysql_password
|
27
|
+
database myweb
|
28
|
+
interval 5s
|
29
|
+
tag replicator
|
30
|
+
query SELECT id, text from search_test
|
31
|
+
</source>
|
32
|
+
|
33
|
+
<match replicator.*>
|
34
|
+
type stdout
|
35
|
+
</match>
|
36
|
+
`````
|
37
|
+
|
38
|
+
#### sample query
|
39
|
+
|
40
|
+
`````
|
41
|
+
$ mysql -e "create database myweb"
|
42
|
+
$ mysql myweb -e "create table search_test(id int auto_increment, text text, PRIMARY KEY (id))"
|
43
|
+
$ sleep 10
|
44
|
+
$ mysql myweb -e "insert into search_test(text) values('aaa')"
|
45
|
+
$ sleep 10
|
46
|
+
$ mysql myweb -e "update search_test set text='bbb' where text = 'aaa'"
|
47
|
+
$ sleep 10
|
48
|
+
$ mysql myweb -e "delete from search_test where text='bbb'"
|
49
|
+
`````
|
50
|
+
|
51
|
+
#### result
|
52
|
+
|
53
|
+
`````
|
54
|
+
$ tail -f /var/log/td-agent/td-agent.log
|
55
|
+
2013-11-25 18:22:25 +0900 replicator.insert: {"id":"1","text":"aaa"}
|
56
|
+
2013-11-25 18:22:35 +0900 replicator.update: {"id":"1","text":"bbb"}
|
57
|
+
2013-11-25 18:22:45 +0900 replicator.delete: {"id":"1"}
|
58
|
+
`````
|
59
|
+
|
60
|
+
## TODO
|
61
|
+
|
62
|
+
Pull requests are very welcome!!
|
63
|
+
|
64
|
+
## Copyright
|
65
|
+
|
66
|
+
Copyright © 2013- Kentaro Yoshida ([@yoshi_ken](https://twitter.com/yoshi_ken))
|
67
|
+
|
68
|
+
## License
|
69
|
+
|
70
|
+
Apache License, Version 2.0
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = "fluent-plugin-mysql-replicator"
|
4
|
+
s.version = "0.0.1"
|
5
|
+
s.authors = ["Kentaro Yoshida"]
|
6
|
+
s.email = ["y.ken.studio@gmail.com"]
|
7
|
+
s.homepage = "https://github.com/y-ken/fluent-plugin-mysql-replicator"
|
8
|
+
s.summary = %q{Fluentd input plugin to track insert/update/delete event from MySQL database server.}
|
9
|
+
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
12
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
|
15
|
+
s.add_development_dependency "rake"
|
16
|
+
s.add_runtime_dependency "fluentd"
|
17
|
+
s.add_runtime_dependency "mysql2"
|
18
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Fluent
|
2
|
+
class MysqlReplicatorInput < Fluent::Input
|
3
|
+
Plugin.register_input('mysql_replicator', self)
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
require 'mysql2'
|
7
|
+
require 'digest/sha1'
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
config_param :host, :string, :default => 'localhost'
|
12
|
+
config_param :port, :integer, :default => 3306
|
13
|
+
config_param :username, :string, :default => 'root'
|
14
|
+
config_param :password, :string, :default => nil
|
15
|
+
config_param :database, :string, :default => nil
|
16
|
+
config_param :encoding, :string, :default => 'utf8'
|
17
|
+
config_param :interval, :string, :default => '1m'
|
18
|
+
config_param :tag, :string
|
19
|
+
config_param :query, :string
|
20
|
+
config_param :primary_key, :string, :default => 'id'
|
21
|
+
|
22
|
+
def configure(conf)
|
23
|
+
super
|
24
|
+
@interval = Config.time_value(@interval)
|
25
|
+
$log.info "adding mysql_replicator job: [#{@query}] interval: #{@interval}sec"
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
@thread = Thread.new(&method(:run))
|
30
|
+
end
|
31
|
+
|
32
|
+
def shutdown
|
33
|
+
Thread.kill(@thread)
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
begin
|
38
|
+
poll
|
39
|
+
rescue StandardError => e
|
40
|
+
$log.error "error: #{e.message}"
|
41
|
+
$log.error e.backtrace.join("\n")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def poll
|
46
|
+
table_hash = Hash.new
|
47
|
+
ids = Array.new
|
48
|
+
loop do
|
49
|
+
previous_ids = ids
|
50
|
+
current_ids = Array.new
|
51
|
+
query(@query).each do |row|
|
52
|
+
current_ids << row[@primary_key]
|
53
|
+
current_hash = Digest::SHA1.hexdigest(row.flatten.join)
|
54
|
+
if !table_hash.include?(row[@primary_key])
|
55
|
+
emit_record(:insert, row)
|
56
|
+
elsif table_hash[row[@primary_key]] != current_hash
|
57
|
+
emit_record(:update, row)
|
58
|
+
end
|
59
|
+
table_hash[row[@primary_key]] = current_hash
|
60
|
+
end
|
61
|
+
ids = current_ids
|
62
|
+
deleted_ids = previous_ids - current_ids
|
63
|
+
if deleted_ids.count > 0
|
64
|
+
hash_delete_by_list(table_hash, deleted_ids)
|
65
|
+
deleted_ids.each {|id| emit_record(:delete, {@primary_key => id})}
|
66
|
+
end
|
67
|
+
sleep @interval
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def hash_delete_by_list (hash, deleted_keys)
|
72
|
+
deleted_keys.each{|k| hash.delete(k)}
|
73
|
+
end
|
74
|
+
|
75
|
+
def emit_record(type, record)
|
76
|
+
tag = "#{@tag}.#{type.to_s}"
|
77
|
+
Engine.emit(tag, Engine.now, record)
|
78
|
+
end
|
79
|
+
|
80
|
+
def query(query)
|
81
|
+
@mysql ||= get_connection
|
82
|
+
begin
|
83
|
+
return @mysql.query(query, :cast => false, :cache_rows => false)
|
84
|
+
rescue Exception => e
|
85
|
+
$log.warn "mysql_replicator: #{e}"
|
86
|
+
sleep @interval
|
87
|
+
retry
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_connection
|
92
|
+
begin
|
93
|
+
return Mysql2::Client.new({
|
94
|
+
:host => @host,
|
95
|
+
:port => @port,
|
96
|
+
:username => @username,
|
97
|
+
:password => @password,
|
98
|
+
:database => @database,
|
99
|
+
:encoding => @encoding,
|
100
|
+
:reconnect => true
|
101
|
+
})
|
102
|
+
rescue Exception => e
|
103
|
+
$log.warn "mysql_replicator: #{e}"
|
104
|
+
sleep @interval
|
105
|
+
retry
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
|
+
require 'fluent/test'
|
15
|
+
unless ENV.has_key?('VERBOSE')
|
16
|
+
nulllogger = Object.new
|
17
|
+
nulllogger.instance_eval {|obj|
|
18
|
+
def method_missing(method, *args)
|
19
|
+
# pass
|
20
|
+
end
|
21
|
+
}
|
22
|
+
$log = nulllogger
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'fluent/plugin/in_mysql_replicator'
|
26
|
+
|
27
|
+
class Test::Unit::TestCase
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class MysqlReplicatorInputTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
Fluent::Test.setup
|
6
|
+
end
|
7
|
+
|
8
|
+
CONFIG = %[
|
9
|
+
host localhost
|
10
|
+
port 3306
|
11
|
+
interval 30
|
12
|
+
tag input.mysql
|
13
|
+
query SELECT id, text from search_text
|
14
|
+
record_hostname yes
|
15
|
+
]
|
16
|
+
|
17
|
+
def create_driver(conf=CONFIG,tag='test')
|
18
|
+
Fluent::Test::OutputTestDriver.new(Fluent::MysqlReplicatorInput, tag).configure(conf)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_configure
|
22
|
+
assert_raise(Fluent::ConfigError) {
|
23
|
+
d = create_driver('')
|
24
|
+
}
|
25
|
+
d = create_driver %[
|
26
|
+
host localhost
|
27
|
+
port 3306
|
28
|
+
interval 30
|
29
|
+
tag input.mysql
|
30
|
+
query SELECT id, text from search_text
|
31
|
+
]
|
32
|
+
d.instance.inspect
|
33
|
+
assert_equal 'localhost', d.instance.host
|
34
|
+
assert_equal 3306, d.instance.port
|
35
|
+
assert_equal 30, d.instance.interval
|
36
|
+
assert_equal 'input.mysql', d.instance.tag
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-mysql-replicator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kentaro Yoshida
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: fluentd
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mysql2
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description:
|
63
|
+
email:
|
64
|
+
- y.ken.studio@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .travis.yml
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- fluent-plugin-mysql-replicator.gemspec
|
76
|
+
- lib/fluent/plugin/in_mysql_replicator.rb
|
77
|
+
- test/helper.rb
|
78
|
+
- test/plugin/test_in_mysql_replicator.rb
|
79
|
+
homepage: https://github.com/y-ken/fluent-plugin-mysql-replicator
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.23
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Fluentd input plugin to track insert/update/delete event from MySQL database
|
103
|
+
server.
|
104
|
+
test_files:
|
105
|
+
- test/helper.rb
|
106
|
+
- test/plugin/test_in_mysql_replicator.rb
|