├── src
├── main
│ ├── java
│ │ └── org
│ │ │ └── fh
│ │ │ └── gae
│ │ │ └── das
│ │ │ ├── template
│ │ │ ├── DasSerializable.java
│ │ │ ├── vo
│ │ │ │ ├── Column.java
│ │ │ │ ├── Template.java
│ │ │ │ └── Table.java
│ │ │ ├── level
│ │ │ │ ├── DasLevel.java
│ │ │ │ └── TextDasLevel.java
│ │ │ ├── DasTable.java
│ │ │ ├── OpType.java
│ │ │ ├── DasTemplate.java
│ │ │ └── TemplateHolder.java
│ │ │ ├── sender
│ │ │ ├── DasSender.java
│ │ │ ├── kafka
│ │ │ │ ├── KafkaSender.java
│ │ │ │ └── KafkaConfig.java
│ │ │ └── file
│ │ │ │ └── FileSender.java
│ │ │ ├── mysql
│ │ │ ├── listener
│ │ │ │ ├── BizListener.java
│ │ │ │ ├── DemoTableListener.java
│ │ │ │ └── AggregationListener.java
│ │ │ ├── binlog
│ │ │ │ ├── BinlogPositionStore.java
│ │ │ │ ├── BinlogPosition.java
│ │ │ │ └── FileBinlogPositionStore.java
│ │ │ ├── MysqlRowData.java
│ │ │ ├── MysqlBinlogConfig.java
│ │ │ └── BinlogClient.java
│ │ │ ├── exception
│ │ │ ├── DasStoreException.java
│ │ │ ├── GaeDasException.java
│ │ │ └── InvalidDasTemplateException.java
│ │ │ ├── ha
│ │ │ ├── heartbeat
│ │ │ │ ├── BeatTimeHolder.java
│ │ │ │ ├── JsonRequestDecoder.java
│ │ │ │ ├── BeatTask.java
│ │ │ │ ├── BeatMessage.java
│ │ │ │ └── BeatHandler.java
│ │ │ ├── NettyUtils.java
│ │ │ ├── HaServer.java
│ │ │ └── CoordinationService.java
│ │ │ ├── DasApp.java
│ │ │ ├── utils
│ │ │ └── GaeCollectionUtils.java
│ │ │ └── DasAppEventListener.java
│ └── resources
│ │ ├── application-peer1.yml
│ │ ├── application-peer2.yml
│ │ ├── application.yml
│ │ └── template.json
└── test
│ └── java
│ └── org
│ └── fh
│ └── gae
│ └── das
│ └── test
│ ├── TemplateHolderTest.java
│ └── TextDasLevelTest.java
├── index
└── incr.idx
├── README.md
└── pom.xml
/src/main/java/org/fh/gae/das/template/DasSerializable.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template;
2 |
3 | public interface DasSerializable {
4 | byte[] serialize();
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/resources/application-peer1.yml:
--------------------------------------------------------------------------------
1 | das:
2 | mysql:
3 | server-id: 1
4 |
5 | ha:
6 | port: 10000
7 | peer-host: 127.0.0.1
8 | peer-port: 10001
9 |
--------------------------------------------------------------------------------
/src/main/resources/application-peer2.yml:
--------------------------------------------------------------------------------
1 | das:
2 | mysql:
3 | server-id: 2
4 |
5 | ha:
6 | port: 10001
7 | peer-host: 127.0.0.1
8 | peer-port: 10000
9 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/sender/DasSender.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.sender;
2 |
3 | import org.fh.gae.das.template.DasSerializable;
4 |
5 | public interface DasSender {
6 | void send(DasSerializable data);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/mysql/listener/BizListener.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.mysql.listener;
2 |
3 | import org.fh.gae.das.mysql.MysqlRowData;
4 |
5 | public interface BizListener {
6 | void onEvent(MysqlRowData eventData);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/vo/Column.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template.vo;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 |
6 | @Data
7 | @NoArgsConstructor
8 | public class Column {
9 | private String column;
10 |
11 | private int position;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/exception/DasStoreException.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.exception;
2 |
3 | public class DasStoreException extends GaeDasException {
4 | public DasStoreException(String msg) {
5 | super(msg);
6 | }
7 |
8 | public DasStoreException() {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/exception/GaeDasException.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.exception;
2 |
3 | public class GaeDasException extends RuntimeException {
4 | public GaeDasException(String msg) {
5 | super(msg);
6 | }
7 |
8 | public GaeDasException() {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/ha/heartbeat/BeatTimeHolder.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.ha.heartbeat;
2 |
3 | import org.fh.gae.das.mysql.binlog.BinlogPosition;
4 |
5 | public class BeatTimeHolder {
6 | public static volatile long lastBeat = 0;
7 |
8 | public static volatile BinlogPosition position;
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/vo/Template.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template.vo;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 |
6 | import java.util.List;
7 |
8 | @Data
9 | @NoArgsConstructor
10 | public class Template {
11 | private String database;
12 | private List
tableList;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/exception/InvalidDasTemplateException.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.exception;
2 |
3 | public class InvalidDasTemplateException extends GaeDasException {
4 | public InvalidDasTemplateException(String msg) {
5 | super(msg);
6 | }
7 |
8 | public InvalidDasTemplateException() {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/org/fh/gae/das/test/TemplateHolderTest.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.test;
2 |
3 | import org.fh.gae.das.template.TemplateHolder;
4 | import org.junit.Test;
5 |
6 | public class TemplateHolderTest {
7 | @Test
8 | public void testLoad() {
9 | TemplateHolder holder = new TemplateHolder();
10 | holder.loadJson("template.json");
11 | int i = 0;
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/vo/Table.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template.vo;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 |
6 | import java.util.List;
7 |
8 | @Data
9 | @NoArgsConstructor
10 | public class Table {
11 | private String tableName;
12 | private Integer level;
13 |
14 | private List insert;
15 | private List update;
16 | private List delete;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/DasApp.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class DasApp {
8 | public static void main(String[] args) {
9 | SpringApplication app = new SpringApplication(DasApp.class);
10 | app.addListeners(new DasAppEventListener());
11 | app.run(args);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/mysql/binlog/BinlogPositionStore.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.mysql.binlog;
2 |
3 | public interface BinlogPositionStore {
4 | /**
5 | * 加载上次保存的binlog pos
6 | * @return
7 | */
8 | BinlogPosition load();
9 |
10 | /**
11 | * 保存当前binlog位置
12 | * @param binlogPosition
13 | * @return
14 | */
15 | int save(BinlogPosition binlogPosition);
16 |
17 | /**
18 | * 获取当前binlog位置
19 | * @return
20 | */
21 | BinlogPosition extract();
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/mysql/MysqlRowData.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.mysql;
2 |
3 | import com.github.shyiko.mysql.binlog.event.EventType;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 | import org.fh.gae.das.template.DasTable;
7 |
8 | import java.util.Map;
9 |
10 | @Data
11 | @NoArgsConstructor
12 | public class MysqlRowData {
13 | private DasTable table;
14 |
15 | private EventType eventType;
16 |
17 | private Map after;
18 |
19 | private Map before;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/level/DasLevel.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template.level;
2 |
3 | import lombok.Data;
4 | import org.fh.gae.das.template.DasSerializable;
5 | import org.fh.gae.das.template.DasTable;
6 | import org.fh.gae.das.template.OpType;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | @Data
12 | public abstract class DasLevel implements DasSerializable {
13 | protected DasTable table;
14 |
15 | protected OpType opType;
16 |
17 | protected Map fieldValueMap = new HashMap<>();
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/DasTable.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | @Data
10 | public class DasTable {
11 | private String tableName;
12 |
13 | private String level;
14 |
15 | /**
16 | * 操作类型->字段顺序
17 | */
18 | private Map> opTypeFieldSetMap = new HashMap<>();
19 |
20 | /**
21 | * 字段位置->字段名
22 | */
23 | private Map posMap = new HashMap<>();
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/utils/GaeCollectionUtils.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.utils;
2 |
3 | import java.util.Map;
4 | import java.util.function.Supplier;
5 |
6 | public class GaeCollectionUtils {
7 | private GaeCollectionUtils() {
8 |
9 | }
10 |
11 | /**
12 | * 从Map中按key取值, 如果不存在则创建,入map, 并返回创建的对象
13 | * @param key
14 | * @param map
15 | * @param factory
16 | * @return
17 | */
18 | public static R getAndCreateIfNeed(T key, Map map, Supplier factory) {
19 | R ret = map.get(key);
20 | if (null == ret) {
21 | ret = factory.get();
22 | map.put(key, ret);
23 | }
24 |
25 | return ret;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/index/incr.idx:
--------------------------------------------------------------------------------
1 | 1 1 7 22333 2333
2 | 1 1 8 cat1ss 2
3 | 1 1 9 hellokitty 50
4 | 1 1 9 hellokittys 50
5 | 1 1 9 hellokittyss 50
6 | 1 1 9 hellokittysss 50
7 | 1 1 9 hellokittyssss 50
8 | 1 1 10 darks 1
9 | 1 1 8 cat1ssa 2
10 | 1 1 10 darkss 1
11 | 1 1 10 dark 1
12 | 1 1 10 dark 1
13 | 1 1 9 hellokittyssss 50
14 | 1 1 7 22333 2333
15 | 1 1 8 cat1ssa 2
16 | 1 1 6 name223 2
17 | 1 1 5 name1 1
18 | 1 1 5 name1 2
19 | 1 1 10 dark 12
20 | 1 1 10 dark 12
21 | 1 1 12 dark souls 3
22 | 1 1 12 dark souls 4
23 | 1 0 13 cod13 13
24 | 1 1 13 cod13 14
25 | 1 0 6 apple
26 | 1 1 6 apples
27 | 2 2 5 aaa
28 | 2 0 7 pear
29 | 2 2 1 a
30 | 2 2 4 efffs
31 | 1 2 4 brucess 201
32 | 1 2 5 name1 2
33 | 1 2 3 test 30
34 | 1 2 8 cat1ssa 2
35 | 1 1 7 22333 23332
36 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/mysql/MysqlBinlogConfig.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.mysql;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | @ConfigurationProperties(prefix = "das.mysql")
10 | @Data
11 | @NoArgsConstructor
12 | public class MysqlBinlogConfig {
13 | private String host = "localhost";
14 |
15 | private Integer port = 3306;
16 |
17 | private String username = "root";
18 |
19 | private String password = "";
20 |
21 | private long serverId = 1L;
22 |
23 | /**
24 | * binlog文件名
25 | */
26 | private String binlogName = "";
27 |
28 | /**
29 | * binlog偏移量
30 | */
31 | private Long position = -1L;
32 |
33 | /**
34 | * binlog文件和偏移量保存位置
35 | */
36 | private String binlogPositionFile = "binlog.pos";
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/sender/kafka/KafkaSender.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.sender.kafka;
2 |
3 | import org.fh.gae.das.sender.DasSender;
4 | import org.fh.gae.das.template.DasSerializable;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.kafka.core.KafkaTemplate;
9 | import org.springframework.stereotype.Component;
10 |
11 | @Component
12 | @ConditionalOnProperty(prefix = "das.store.kafka", name = "enable", matchIfMissing = false, havingValue = "true")
13 | public class KafkaSender implements DasSender {
14 | @Value("${das.store.kafka.topic}")
15 | private String topic;
16 |
17 | @Autowired
18 | private KafkaTemplate kafkaTemplate;
19 |
20 | @Override
21 | public void send(DasSerializable data) {
22 | kafkaTemplate.send(topic, new String(data.serialize()));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | application:
3 | name: GAE-DAS
4 |
5 | datasource:
6 | driver-class-name: com.mysql.jdbc.Driver
7 | url: jdbc:mysql://localhost:3306/gae-das
8 | username: root
9 |
10 | profiles:
11 | active: peer1
12 |
13 |
14 | das:
15 | mysql:
16 | host: 127.0.0.1
17 | port: 3306
18 | username: root
19 | password:
20 |
21 | # binlog文件名
22 | binlog-name: ""
23 | # binlog偏移量, -1表示从当前位置开始
24 | position: -1
25 | # binlog同步点保存文件名, 下次启动时会从该点继续
26 | binlog-position-file: binlog.pos
27 |
28 |
29 | # 增量索引配置
30 | store:
31 | # 文件存储
32 | file:
33 | enable: true
34 | # 文件名
35 | path: incr.idx
36 |
37 | kafka:
38 | enable: false
39 | topic: gae-idx
40 | addr: 127.0.0.1:8092
41 |
42 | ha:
43 | beat-interval: 5000
44 |
45 | logging:
46 | level:
47 | org.fh.gae.das: info
48 | org.springframework: error
49 | pattern:
50 | console: "[%d{HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n"
51 |
52 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/OpType.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template;
2 |
3 | import com.github.shyiko.mysql.binlog.event.EventType;
4 | import org.fh.gae.das.exception.InvalidDasTemplateException;
5 |
6 | public enum OpType {
7 | ADD,
8 | UPDATE,
9 | DELETE,
10 | OTHER;
11 |
12 | public static OpType of(String str) {
13 | switch (str) {
14 | case "insert":
15 | return ADD;
16 |
17 | case "update":
18 | return UPDATE;
19 |
20 | case "delete":
21 | return DELETE;
22 | }
23 |
24 | throw new InvalidDasTemplateException("invalid type: " + str);
25 | }
26 |
27 | public static OpType of(EventType eventType) {
28 | switch (eventType) {
29 | case WRITE_ROWS:
30 | return ADD;
31 |
32 | case UPDATE_ROWS:
33 | return UPDATE;
34 |
35 | case DELETE_ROWS:
36 | return DELETE;
37 |
38 | default:
39 | return OTHER;
40 |
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/DasAppEventListener.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.fh.gae.das.ha.HaServer;
5 | import org.springframework.context.ApplicationListener;
6 | import org.springframework.context.event.ApplicationContextEvent;
7 | import org.springframework.context.event.ContextClosedEvent;
8 | import org.springframework.context.event.ContextRefreshedEvent;
9 |
10 | @Slf4j
11 | public class DasAppEventListener implements ApplicationListener {
12 | @Override
13 | public void onApplicationEvent(ApplicationContextEvent event) {
14 | // 上下文关闭时
15 | if (event instanceof ContextClosedEvent) {
16 | return;
17 | }
18 |
19 | // 上下文初始化完毕
20 | if (event instanceof ContextRefreshedEvent) {
21 | HaServer haServer = event.getApplicationContext().getBean(HaServer.class);
22 |
23 | // 启动心跳服务器
24 | haServer.start();
25 | // coordinationService.startBinlogClient();
26 | // 启动binlog client
27 | // binlogClient.connect();
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/resources/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": "gae-das",
3 | "tableList": [
4 | {
5 | "tableName": "new_table",
6 | "level": 1,
7 |
8 | "insert": [
9 | {"column": "id"},
10 | {"column": "name"},
11 | {"column": "age"}
12 | ],
13 | "update": [
14 | {"column": "id"},
15 | {"column": "name"},
16 | {"column": "age"}
17 | ],
18 | "delete": [
19 | {"column": "id"},
20 | {"column": "name"},
21 | {"column": "age"}
22 | ]
23 | },
24 | {
25 | "tableName": "acc",
26 | "level": 2,
27 |
28 | "insert": [
29 | {"column": "id"},
30 | {"column": "name"}
31 | ],
32 | "update": [
33 | {"column": "id"},
34 | {"column": "name"}
35 | ],
36 | "delete": [
37 | {"column": "id"},
38 | {"column": "name"}
39 | ]
40 | }
41 |
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/template/level/TextDasLevel.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.template.level;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.fh.gae.das.template.OpType;
5 |
6 | @Slf4j
7 | public class TextDasLevel extends DasLevel {
8 | @Override
9 | public byte[] serialize() {
10 | // 操作类型
11 | OpType opType = getOpType();
12 |
13 | int valueSize = getFieldValueMap().size();
14 |
15 | StringBuilder sb = new StringBuilder(valueSize * 10);
16 | sb.append(getTable().getLevel()).append("\t");
17 | sb.append(opType.ordinal()).append("\t");
18 |
19 | // 遍历当前层级对应的操作类型的所有字段
20 | for (String fieldName : getTable().getOpTypeFieldSetMap().get(opType)) {
21 | // 取出字段值
22 | String fieldValue = getFieldValueMap().get(fieldName);
23 | if (null == fieldValue) {
24 | log.warn("field {} have no value", fieldName);
25 | continue;
26 | }
27 |
28 | sb.append(fieldValue);
29 | sb.append("\t");
30 | }
31 |
32 | int len = sb.length();
33 | sb.replace(len - 1, len, "");
34 |
35 | return sb.toString().getBytes();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/fh/gae/das/ha/heartbeat/JsonRequestDecoder.java:
--------------------------------------------------------------------------------
1 | package org.fh.gae.das.ha.heartbeat;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import io.netty.channel.ChannelHandler;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.handler.codec.MessageToMessageDecoder;
7 | import io.netty.handler.codec.http.FullHttpRequest;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.stereotype.Component;
10 |
11 | import java.nio.charset.Charset;
12 | import java.util.List;
13 |
14 | @Component
15 | @ChannelHandler.Sharable
16 | @Slf4j
17 | public class JsonRequestDecoder extends MessageToMessageDecoder {
18 | @Override
19 | protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List