数据接口同步
1、业务场景:一般系统,多数会与第三方系统的数据打交道,而第三方的生产库,并不允许我们直接操作。在企业里面,一般都是通过中间表进行同步,即第三方系统将生产数据放入一张与其生产环境隔离的另一个独立的库中的独立的表,再根据接口协议,增加相应的字段。而我方需要读取该中间表中的数据,并对数据进行同步操作。此时就需要编写相应的程序进行数据同步。
2、数据同步一般分两种情况:
全量同步:每天定时将当天的生产数据全部同步过来(优点:实现简单 缺点:数据同步不及时)
增量同步:每新增一条,便将该数据同步过来(优点:数据近实时同步 缺点:实现相对困难)
3、我方需要做的事情:读取中间表的数据,并同步到业务系统中(可能需要调用我方相应的业务逻辑)
4、模型抽离
生产者消费者模型
生产者:读取中间表的数据
消费者:消费生产者生产的数据
5、接口协议的制定
1.取我方业务所需要的字段
2.需要有字段记录数据什么时候进入中间表
3.增加相应的数据标志位,用于标志数据的同步状态
4.记录数据的同步时间
6、技术选型:mybatis、单一生产者多消费者、多线程并发操作
中间表设计
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`sex` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`birth` datetime(0) NULL DEFAULT NULL,
`department` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`add_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`data_status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '10I' COMMENT '10I 第三方系统把数据入库 10D 处理中 10F 处理完成 10E处理失败',
`deal_time` datetime(0) NULL DEFAULT NULL COMMENT '处理时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`sex` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`birth` datetime(0) NULL DEFAULT NULL,
`department` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
基础环境搭建
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
生产者代码实现
1、分批读取中间表(10I),并将读取到的数据状态修改为10D(处理中)
2、将相应的数据交付给消费者进行消费
方式1:把生产完的数据,直接放到队列里,由消费者去进行消费
方式2:把消费者放到队列里面,生产完数据,直接从队列里拿出消费者进行消费
3、第2步使用方式2,将(第三方系统)中间表同步到(业务端)学生表
# 生产者
public class Producer implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
// 用于操作中间表数据
private QryBusi qryBusi;
// 队列
private LinkedBlockingQueue<Runnable> consumers;
// 线程池
private ThreadPoolExecutor executor;
// 注入
public Producer(QryBusi qryBusi,LinkedBlockingQueue<Runnable> consumers,ThreadPoolExecutor executor) {
this.qryBusi = qryBusi;
this.consumers = consumers;
this.executor = executor;
}
@Override
public void run() {
while (true) {
// 从中间表查询10条状态为10I数据
List list = qryBusi.queryList(10);
try {
if (list != null && list.size() > 0) { // 如果查询到数据
// 将中间表中查询到的数据,状态修改为处理中
qryBusi.modifyListStatus(list,DataStutusConst.DEALING);
Consumer consumer = (Consumer) consumers.take(); // 从队列移除元素,在队列为空的时候,会进入等待的状态
consumer.setData(list); // 将数据注入消费者的list集合
executor.execute(consumer); // 提交到线程池
} else {
try {
Thread.sleep(5000L); // 否则休眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
LOGGER.error("生产者发生异常=======》",e);
qryBusi.modifyListStatus(list, DataStutusConst.ERROR); // 修改中间表的状态为错误
}
}
}
}
# 消费者
public class Consumer implements Runnable {
// 存储数据的list集合
private List data;
// 操作数据库
private DealBusi dealBusi;
// 存放消费者的队列
private LinkedBlockingQueue<Runnable> consumers;
// 往集合中添加数据
public void setData(List data) {
this.data = data;
}
// 构造器注入
public Consumer(DealBusi dealBusi, LinkedBlockingQueue<Runnable> consumers) {
this.dealBusi = dealBusi;
this.consumers = consumers;
}
@Override
public void run() {
try {
dealBusi.deal(data); // 往学生表中添加数据
} finally {
try {
consumers.put(this); // 往队列中添加数据,在队列满的时候,会进入阻塞的状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
# 测试类
public class Main {
public static void main(String[] args) {
QryBusi qryBusi = new QryBusiImpl();
DealBusi dealBusi = new DealBusiImpl();
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
// 创建对列
LinkedBlockingQueue<Runnable> runnables = new LinkedBlockingQueue<>(10);
for (int i = 0; i < 10; i++) {
try {
runnables.put(new Consumer(dealBusi,runnables)); // 放入消费者到队列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 创建生产者
Producer producer = new Producer(qryBusi,runnables,threadPoolExecutor);
// 创建生产者线程
new Thread(producer).start();
}
}