Skip to content

Commit 3f30768

Browse files
committed
Added support for COM_BINLOG_DUMP_GTID (resolves #41)
1 parent 633d5d2 commit 3f30768

File tree

4 files changed

+434
-6
lines changed

4 files changed

+434
-6
lines changed

src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import com.github.shyiko.mysql.binlog.event.EventHeader;
2121
import com.github.shyiko.mysql.binlog.event.EventHeaderV4;
2222
import com.github.shyiko.mysql.binlog.event.EventType;
23+
import com.github.shyiko.mysql.binlog.event.GtidEventData;
2324
import com.github.shyiko.mysql.binlog.event.RotateEventData;
2425
import com.github.shyiko.mysql.binlog.event.deserialization.ChecksumType;
2526
import com.github.shyiko.mysql.binlog.event.deserialization.EventDataDeserializationException;
2627
import com.github.shyiko.mysql.binlog.event.deserialization.EventDataDeserializer;
2728
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
29+
import com.github.shyiko.mysql.binlog.event.deserialization.GtidEventDataDeserializer;
2830
import com.github.shyiko.mysql.binlog.event.deserialization.RotateEventDataDeserializer;
2931
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
3032
import com.github.shyiko.mysql.binlog.jmx.BinaryLogClientMXBean;
@@ -35,7 +37,9 @@
3537
import com.github.shyiko.mysql.binlog.network.protocol.PacketChannel;
3638
import com.github.shyiko.mysql.binlog.network.protocol.ResultSetRowPacket;
3739
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateCommand;
40+
import com.github.shyiko.mysql.binlog.network.protocol.command.Command;
3841
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogCommand;
42+
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand;
3943
import com.github.shyiko.mysql.binlog.network.protocol.command.PingCommand;
4044
import com.github.shyiko.mysql.binlog.network.protocol.command.QueryCommand;
4145

@@ -80,6 +84,9 @@ public class BinaryLogClient implements BinaryLogClientMXBean {
8084
private volatile String binlogFilename;
8185
private volatile long binlogPosition = 4;
8286

87+
private GtidSet gtidSet;
88+
private final Object gtidSetAccessLock = new Object();
89+
8390
private EventDeserializer eventDeserializer = new EventDeserializer();
8491

8592
private final List<EventListener> eventListeners = new LinkedList<EventListener>();
@@ -200,6 +207,34 @@ public void setBinlogPosition(long binlogPosition) {
200207
this.binlogPosition = binlogPosition;
201208
}
202209

210+
/**
211+
* @return GTID set. Note that this value changes with each received GTID event (provided client is in GTID mode).
212+
* @see #setGtidSet(String)
213+
*/
214+
public String getGtidSet() {
215+
synchronized (gtidSetAccessLock) {
216+
return gtidSet != null ? gtidSet.toString() : null;
217+
}
218+
}
219+
220+
/**
221+
* @param gtidSet GTID set (can be an empty string).
222+
* <p>NOTE #1: Any value but null will switch BinaryLogClient into a GTID mode (in which case GTID set will be
223+
* updated with each incoming GTID event) as well as set binlogFilename to "" (empty string) (meaning
224+
* BinaryLogClient will request events "outside of the set" <u>starting from the oldest known binlog</u>).
225+
* <p>NOTE #2: {@link #setBinlogFilename(String)} and {@link #setBinlogPosition(long)} can be used to specify the
226+
* exact position from which MySQL server should start streaming events (taking into account GTID set).
227+
* @see #getGtidSet()
228+
*/
229+
public void setGtidSet(String gtidSet) {
230+
if (gtidSet != null && this.binlogFilename == null) {
231+
this.binlogFilename = "";
232+
}
233+
synchronized (gtidSetAccessLock) {
234+
this.gtidSet = gtidSet != null ? new GtidSet(gtidSet) : null;
235+
}
236+
}
237+
203238
/**
204239
* @return true if "keep alive" thread should be automatically started (default), false otherwise.
205240
* @see #setKeepAlive(boolean)
@@ -309,7 +344,7 @@ public void connect() throws IOException {
309344
if (checksumType != ChecksumType.NONE) {
310345
confirmSupportOfChecksum(checksumType);
311346
}
312-
channel.write(new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition));
347+
requestBinaryLogStream();
313348
} catch (IOException e) {
314349
if (channel != null && channel.isOpen()) {
315350
channel.close();
@@ -328,14 +363,42 @@ public void connect() throws IOException {
328363
if (keepAlive && !isKeepAliveThreadRunning()) {
329364
spawnKeepAliveThread();
330365
}
331-
EventDataDeserializer eventDataDeserializer = eventDeserializer.getEventDataDeserializer(EventType.ROTATE);
332-
if (eventDataDeserializer.getClass() != RotateEventDataDeserializer.class &&
366+
ensureEventDataDeserializer(EventType.ROTATE, RotateEventDataDeserializer.class);
367+
synchronized (gtidSetAccessLock) {
368+
if (gtidSet != null) {
369+
ensureEventDataDeserializer(EventType.GTID, GtidEventDataDeserializer.class);
370+
}
371+
}
372+
listenForEventPackets();
373+
}
374+
375+
private void requestBinaryLogStream() throws IOException {
376+
Command dumpBinaryLogCommand;
377+
synchronized (gtidSetAccessLock) {
378+
if (gtidSet != null) {
379+
dumpBinaryLogCommand = new DumpBinaryLogGtidCommand(serverId, binlogFilename, binlogPosition, gtidSet);
380+
} else {
381+
dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition);
382+
}
383+
}
384+
channel.write(dumpBinaryLogCommand);
385+
}
386+
387+
private void ensureEventDataDeserializer(EventType eventType,
388+
Class<? extends EventDataDeserializer> eventDataDeserializerClass) {
389+
EventDataDeserializer eventDataDeserializer = eventDeserializer.getEventDataDeserializer(eventType);
390+
if (eventDataDeserializer.getClass() != eventDataDeserializerClass &&
333391
eventDataDeserializer.getClass() != EventDeserializer.EventDataWrapper.Deserializer.class) {
334-
eventDeserializer.setEventDataDeserializer(EventType.ROTATE,
335-
new EventDeserializer.EventDataWrapper.Deserializer(new RotateEventDataDeserializer(),
392+
EventDataDeserializer internalEventDataDeserializer;
393+
try {
394+
internalEventDataDeserializer = eventDataDeserializerClass.newInstance();
395+
} catch (Exception e) {
396+
throw new RuntimeException(e);
397+
}
398+
eventDeserializer.setEventDataDeserializer(eventType,
399+
new EventDeserializer.EventDataWrapper.Deserializer(internalEventDataDeserializer,
336400
eventDataDeserializer));
337401
}
338-
listenForEventPackets();
339402
}
340403

341404
private void authenticate(String salt, int collation) throws IOException {
@@ -526,6 +589,7 @@ private void listenForEventPackets() throws IOException {
526589
if (isConnected()) {
527590
notifyEventListeners(event);
528591
updateClientBinlogFilenameAndPosition(event);
592+
updateGtidSet(event);
529593
}
530594
}
531595
} catch (Exception e) {
@@ -565,6 +629,24 @@ private void updateClientBinlogFilenameAndPosition(Event event) {
565629
}
566630
}
567631

632+
private void updateGtidSet(Event event) {
633+
EventHeader eventHeader = event.getHeader();
634+
if (eventHeader.getEventType() == EventType.GTID) {
635+
synchronized (gtidSetAccessLock) {
636+
if (gtidSet != null) {
637+
EventData eventData = event.getData();
638+
GtidEventData gtidEventData;
639+
if (eventData instanceof EventDeserializer.EventDataWrapper) {
640+
gtidEventData = (GtidEventData) ((EventDeserializer.EventDataWrapper) eventData).getInternal();
641+
} else {
642+
gtidEventData = (GtidEventData) eventData;
643+
}
644+
gtidSet.add(gtidEventData.getGtid());
645+
}
646+
}
647+
}
648+
}
649+
568650
private ResultSetRowPacket[] readResultSet() throws IOException {
569651
List<ResultSetRowPacket> resultSet = new LinkedList<ResultSetRowPacket>();
570652
while ((channel.read())[0] != (byte) 0xFE /* eof */) { /* skip */ }
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2015 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog;
17+
18+
import java.util.*;
19+
20+
/**
21+
* GTID set as described in <a href="https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html">GTID Concepts</a>
22+
* of MySQL 5.6 Reference Manual.
23+
*
24+
* <pre>
25+
* gtid_set: uuid_set[,uuid_set]...
26+
* uuid_set: uuid:interval[:interval]...
27+
* uuid: hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh, h: [0-9|A-F]
28+
* interval: n[-n], (n >= 1)
29+
* </pre>
30+
*
31+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
32+
*/
33+
public class GtidSet {
34+
35+
private final Map<String, UUIDSet> map = new LinkedHashMap<String, UUIDSet>();
36+
37+
public GtidSet(String gtidSet) {
38+
String[] uuidSets = gtidSet.isEmpty() ? new String[0] : gtidSet.split(",");
39+
for (String uuidSet : uuidSets) {
40+
int uuidSeparatorIndex = uuidSet.indexOf(":");
41+
String sourceId = uuidSet.substring(0, uuidSeparatorIndex);
42+
List<Interval> intervals = new ArrayList<Interval>();
43+
String[] rawIntervals = uuidSet.substring(uuidSeparatorIndex + 1).split(":");
44+
for (String interval : rawIntervals) {
45+
String[] is = interval.split("-");
46+
long[] split = new long[is.length];
47+
for (int i = 0, e = is.length; i < e; i++) {
48+
split[i] = Long.parseLong(is[i]);
49+
}
50+
if (split.length == 1) {
51+
split = new long[] {split[0], split[0] + 1};
52+
}
53+
intervals.add(new Interval(split[0], split[1]));
54+
}
55+
map.put(sourceId, new UUIDSet(sourceId, intervals));
56+
}
57+
}
58+
59+
public Collection<UUIDSet> getUUIDSets() {
60+
return map.values();
61+
}
62+
63+
/**
64+
* @param gtid GTID ("source_id:transaction_id")
65+
* @return whether or not gtid was added to the set (false if it was already there)
66+
*/
67+
public boolean add(String gtid) {
68+
String[] split = gtid.split(":");
69+
String sourceId = split[0];
70+
long transactionId = Long.parseLong(split[1]);
71+
UUIDSet uuidSet = map.get(sourceId);
72+
if (uuidSet == null) {
73+
map.put(sourceId, uuidSet = new UUIDSet(sourceId, new ArrayList<Interval>()));
74+
}
75+
List<Interval> intervals = (List<Interval>) uuidSet.intervals;
76+
int index = findInterval(intervals, transactionId);
77+
boolean addedToExisting = false;
78+
if (index < intervals.size()) {
79+
Interval interval = intervals.get(index);
80+
if (interval.getStart() == transactionId + 1) {
81+
interval.start = transactionId;
82+
addedToExisting = true;
83+
} else
84+
if (interval.getEnd() == transactionId) {
85+
interval.end = transactionId + 1;
86+
addedToExisting = true;
87+
} else
88+
if (interval.getStart() <= transactionId && transactionId < interval.getEnd()) {
89+
return false;
90+
}
91+
}
92+
if (!addedToExisting) {
93+
intervals.add(index, new Interval(transactionId, transactionId + 1));
94+
}
95+
if (intervals.size() > 1) {
96+
joinAdjacentIntervals(intervals, index);
97+
}
98+
return true;
99+
}
100+
101+
/**
102+
* Collapses intervals like a-b:b-c into a-c (only in index+-1 range).
103+
*/
104+
private void joinAdjacentIntervals(List<Interval> intervals, int index) {
105+
for (int i = Math.min(index + 1, intervals.size() - 1), e = Math.max(index - 1, 0); i > e; i--) {
106+
Interval a = intervals.get(i - 1), b = intervals.get(i);
107+
if (a.getEnd() == b.getStart()) {
108+
a.end = b.end;
109+
intervals.remove(i);
110+
}
111+
}
112+
}
113+
114+
@Override
115+
public String toString() {
116+
List<String> gtids = new ArrayList<String>();
117+
for (UUIDSet uuidSet : map.values()) {
118+
gtids.add(uuidSet.getUUID() + ":" + join(uuidSet.intervals, ":"));
119+
}
120+
return join(gtids, ",");
121+
}
122+
123+
/**
124+
* @return index which is either a pointer to the interval containing v or a position at which v can be added
125+
*/
126+
private static int findInterval(List<Interval> ii, long v) {
127+
int l = 0, p = 0, r = ii.size();
128+
while (l < r) {
129+
p = (l + r) / 2;
130+
Interval i = ii.get(p);
131+
if (i.getEnd() < v) {
132+
l = p + 1;
133+
} else
134+
if (v < i.getStart()) {
135+
r = p;
136+
} else {
137+
return p;
138+
}
139+
}
140+
if (!ii.isEmpty() && ii.get(p).getEnd() < v) {
141+
p++;
142+
}
143+
return p;
144+
}
145+
146+
private String join(Collection o, String delimiter) {
147+
if (o.isEmpty()) {
148+
return "";
149+
}
150+
StringBuilder sb = new StringBuilder();
151+
for (Object o1 : o) {
152+
sb.append(o1).append(delimiter);
153+
}
154+
return sb.substring(0, sb.length() - delimiter.length());
155+
}
156+
157+
public static class UUIDSet {
158+
159+
private String uuid;
160+
private Collection<Interval> intervals;
161+
162+
public UUIDSet(String uuid, Collection<Interval> intervals) {
163+
this.uuid = uuid;
164+
this.intervals = intervals;
165+
}
166+
167+
public String getUUID() {
168+
return uuid;
169+
}
170+
171+
public Collection<Interval> getIntervals() {
172+
return intervals;
173+
}
174+
}
175+
176+
public static class Interval implements Comparable<Interval> {
177+
178+
private long start;
179+
private long end;
180+
181+
public Interval(long start, long end) {
182+
this.start = start;
183+
this.end = end;
184+
}
185+
186+
public long getStart() {
187+
return start;
188+
}
189+
190+
public long getEnd() {
191+
return end;
192+
}
193+
194+
@Override
195+
public String toString() {
196+
return start + "-" + end;
197+
}
198+
199+
@Override
200+
public int compareTo(Interval o) {
201+
return saturatedCast(this.start - o.start);
202+
}
203+
204+
private static int saturatedCast(long value) {
205+
if (value > Integer.MAX_VALUE) {
206+
return Integer.MAX_VALUE;
207+
}
208+
if (value < Integer.MIN_VALUE) {
209+
return Integer.MIN_VALUE;
210+
}
211+
return (int) value;
212+
}
213+
}
214+
215+
}

0 commit comments

Comments
 (0)