Flutter开发中使用WIFI真机调试

InterviewCoder

# Flutter 开发中使用 WIFI 真机调试

# 使用 vscode 开发 flutter,用 wifi 或者 手机热点进行 无线调试过程

# 1. 手机连接 wifi 后在设置中找到 ip 和端口

# 2. 在 vscode 终端输入 adb connect 手机 IP: 手机端口

# 3. 作者在 CSDN 这个烂环境中没找到有用的信息,全是粘贴的,所以希望这篇文章帮助到你,你可以把我的地址列出来,粘贴可以,抄袭可耻!

# 4. 完成以上步骤,手机端应有开启无线调试的提示,记得提前打开开发者模式。

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Vue】JSON编辑器Bin-Coder

InterviewCoder

# 【Vue】JSON 编辑器 Bin-Coder

Bin Code Editor

# 1 安装

# 1.1 CDN 安装

1
2
3
4
5
6
<!-- import Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- import stylesheet -->
<link rel="stylesheet" href="https://unpkg.com/bin-code-editor@0.1.0/lib/styles/index.css">
<!-- import bin-code-editor -->
<script src="https://unpkg.com/bin-code-editor@0.1.0/lib/bin-code-editor.min.js"></script>

@0.1.0 表示版本号,我们建议锁定版本号来保证代码的稳定性

# 1.2 npm 安装

推荐使用 npm 安装,它能更好地和 webpack 打包工具配合使用。而且可以更好的和 es6 配合使用。并且支持按需引入

1
2
3
npm i bin-code-editor -S
# or
yarn add bin-code-editor

# 2 引入

在 main.js 中写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue';
import CodeEditor from 'bin-code-editor';
import 'bin-code-editor/lib/style/index.css';
import App from './App.vue';

Vue.use(CodeEditor);

new Vue({
el: '#app',
render: h => h(App)
});

# 3 用法

注意,初始化如果有数据,则会默认格式化一次,格式化快捷键默认为 F7, 使用时可以进行格式化结构!

1
2
JSON.stringify(JSON.parse(jsonData),null,2)`可以将默认json进行预格式化,也可以手动触发formatCode()来格式化
`JSON.stringify(value[, replacer[, space]])

参数说明:

  • value: 必需, 要转换的 JavaScript 值(通常为对象或数组)。
  • replacer: 可选。用于转换结果的函数或数组。如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:“”。如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。
  • space: 可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<b-code-editor v-model="jsonStr" :indent-unit="4" height="auto"/>
</div>
</template>
<script>
const jsonData = `{"title":"测试json数据","children":[{"name":"子项名称", "desc":"子项说明" },{"name":"子项名称1", "desc":"子项说明1" }]}`

export default {
data() {
return {
jsonStr: jsonData
}
}
}
</script>

# 4 其他配置项

参数 说明 类型 可选值 默认值
value 绑定数据,可用 v-model String 0
show-number 显示行号 Boolean true
mode 模式 String ‘application/json’,‘text/javascript’ ‘application/json’
theme 提供若干个默认比较好看的皮肤 String 可选值参考其他配置项中列出 idea
lint 是否进行 lint 检查 Boolean 暂时只支持 json true
readonly 只读模式 Boolean - false
auto-format 是否自动格式化 Boolean - true
indent-unit 缩进字符 Number - 2
smart-indent 是否自动缩进 Boolean - true
line-wrap 代码换行 Boolean - true
gutter 代码折叠 Boolean - true
height 默认编辑器高度 String 300px

# 5 Events 事件

1
2
3
4
5
6
<template>
<div>
<b-code-editor v-model="jsonStr" :indent-unit="4" height="auto"/>
<p><b-button @click="$refs['editor'].formatCode()">手动触发格式化</b-button></p>
</div>
</template>
事件名 说明 返回值
on-change 输入项改变事件 value
formatCode 手动触发格式化方法 -
refresh 手动刷新方法 -
getValue 自行获取值 -

# 6 theme 皮肤属性

theme 属性可选值

  • idea
    在这里插入图片描述
  • eclipse
    在这里插入图片描述
  • duotone-light
    在这里插入图片描述
  • mdn-like
    在这里插入图片描述
  • xq-light
    在这里插入图片描述
  • dracula
    在这里插入图片描述
  • rubyblue
    在这里插入图片描述
  • monokai
    在这里插入图片描述
  • material
    在这里插入图片描述
  • material-darker
    在这里插入图片描述

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Mysql主从集群搭建文档,实现读写分离

InterviewCoder

# Mysql 主从集群搭建文档,实现读写分离

​ 最近在自己写的项目中需要应对大量的用户查询读写操作,一台服务器当然是不够的,所以在边学边敲的背景下,记录这篇笔记,从 0 开始搭建主从集群。

# 下面👇开始操作:

1. 分别在两台服务器搭建 mysql 服务

​ 两台服务器的 IP 地址分别为主服务器(192.168.20.1)和从服务器(192.168.20.2)。
2. 配置文件 my.cnf 的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#根据上一篇文章,编辑my.cnf文件
[root@localhost mysql]# vim /etc/my.cnf

#在[mysqld]中添加:
server-id=1
log_bin=master-bin
log_bin_index=master-bin.index
binlog_do_db=test
#备注:
#server-id 服务器唯一标识。
#log_bin 启动MySQL二进制日志,即数据同步语句,从数据库会一条一条的执行这些语句。
#binlog_do_db 指定记录二进制日志的数据库,即需要复制的数据库名,如果复制多个数据库,重复设置这个选项即可。
#binlog_ignore_db 指定不记录二进制日志的数据库,即不需要复制的数据库名,如果有多个数据库,重复设置这个选项即可。
#其中需要注意的是,binlog_do_db和binlog_ignore_db为互斥选项,一般只需要一个即可。

3. 创建从服务器的用户和权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#进入mysql数据库
[root@localhost mysql]# mysql -uroot -p
Enter password:

#创建从数据库的root用户和权限
mysql> grant replication slave on *.* to root@'192.168.20.%' identified by 'Lgq081538';

grant replication slave on *.* to '123456'
#备注
#192.168.20.%通配符,表示0-255的IP都可访问主服务器,正式环境请配置指定从服务器IP
#若将 192.168.20.% 改为 %,则任何ip均可作为其从数据库来访问主服务器

#退出mysql
mysql> exit;

4. 重启 mysql 服务

1
2
3
[root@localhost mysql]# service mysql restart
Shutting down MySQL.... SUCCESS!
Starting MySQL. SUCCESS!

5. 查看主服务器状态

1
2
3
4
5
6
7
8
9
10
11
12
#进入mysql数据库
[root@localhost mysql]# mysql -uroot -p
Enter password:

#查看主服务器状态
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 154 | test | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

6.slave 从服务器的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
配置文件my.cnf的修改
#根据上一篇文章,编辑my.cnf文件
[root@localhost mysql]# vim /etc/my.cnf

#在[mysqld]中添加:
server-id=2
relay-log=slave-relay-bin
relay-log-index=slave-relay-bin.index
#replicate-do-db=test
#备注:
#server-id 服务器唯一标识,如果有多个从服务器,每个服务器的server-id不能重复,跟IP一样是唯一标识,如果你没设置server-id或者设置为0,则从服务器不会连接到主服务器。
#relay-log 启动MySQL二进制日志,可以用来做数据备份和崩溃恢复,或主服务器挂掉了,将此从服务器作为其他从服务器的主服务器。
#replicate-do-db 指定同步的数据库,如果复制多个数据库,重复设置这个选项即可。若在master端不指定binlog-do-db,则在slave端可用replication-do-db来过滤。
#replicate-ignore-db 不需要同步的数据库,如果有多个数据库,重复设置这个选项即可。
#其中需要注意的是,replicate-do-db和replicate-ignore-db为互斥选项,一般只需要一个即可。

7. 重启 mysql 服务

1
2
3
[root@localhost mysql]# service mysql restart
Shutting down MySQL.... SUCCESS!
Starting MySQL. SUCCESS!

8. 连接 master 主服务器

1
2
3
4
5
6
7
8
9
10
11
12
#进入mysql数据库
[root@localhost mysql]# mysql -uroot -p
Enter password:

#连接master主服务器
mysql> change master to master_host='192.168.20.1',master_port=3306,master_user='root',master_password='123456',master_log_file='master-bin.000009',master_log_pos=473127;

#备注:
#master_host对应主服务器的IP地址。
#master_port对应主服务器的端口。
#master_log_file对应show master status显示的File列:master-bin.000001。
#master_log_pos对应show master status显示的Position列:154。

9. 启动 slave 数据同步

1
2
3
4
5
6
7
#启动slave数据同步
mysql> start slave;
#停止slave数据同步(若有需要)
mysql> stop slave;
3.5 查看slave信息
mysql> show slave status\G;
Slave_IO_Running和Slave_SQL_Running都为yes,则表示同步成功。

10. 测试

1
2
3
4
5
6
7
8
9
10
(1)在主服务器上登陆mysql,且进入test数据库,创建test表,且插入一条数据
提示:这里最好用数据库管理工具(如nacicat)来操作。
#创建tb_test表
create table tb_test(ID varchar(36) primary key comment '主键ID',MEMO varchar(500) not null comment '信息');
#插入一条数据
insert into tb_test(ID,MEMO) values('1','one test');
#提交
commit;
(2)在从服务器上登陆mysql,且进入test数据库
你会发现从数据库中,也出现了tb_test表,且表中还有one test数据存在,证明同步数据成功。

# 至此 Mysql 主从读写分离搭建完成

下面开始搭建 Spring Boot 项目中的相关配置以及实现👇

​ 读写分离要做的事情就是对于一条 SQL 该选择哪个数据库去执行,至于谁来做选择数据库这件事,主要有两种实现方式,分别为:
1. 使用中间件,比如 Atlas,cobar,TDDL,mycat,heisenberg,Oceanus,vitess,OneProxy 等
2. 使用程序自己实现,利用 Spring Boot 提供的路由数据源以及 AOP,实现起来简单快捷

​ 我们使用第二种方式 Spring Boot 数据源路由 + AOP ,这样能更好的控制流程,也便于后期提升性能;

代码实现
1. 首先配置下 pom.xml 因为我们使用 aop 实现,所以需要 aop 依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 数据源路由类功能 RoutingDataSource.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 数据源路由类功能
*/
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContext.get();
}
}

3. 数据源上下文类 DBContext.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 数据源上下文类
*/
@Slf4j
public class DBContext {
private static final ThreadLocal<DBTypeEnum> dbContext = new ThreadLocal<>();

private static final AtomicInteger counter = new AtomicInteger(-1);

public static void set(DBTypeEnum dbType) {
dbContext.set(dbType);
}

public static DBTypeEnum get() {
return dbContext.get();
}

public static void master() {
set(DBTypeEnum.MASTER);
log.info("切换到master库");
}

public static void slave() {
// 读库负载均衡(轮询方式)
int index = counter.getAndIncrement() % 2;
log.info("slave库访问线程数==>{}", counter.get());
if (index == 0) {
set(DBTypeEnum.SLAVE1);
log.info("切换到slave1库");
} else {
set(DBTypeEnum.SLAVE2);
log.info("切换到slave2库");
}
}
}

4. 数据库枚举类 DBTypeEnum.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.demo.databases;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 数据库枚举类
*/
public enum DBTypeEnum {
MASTER, //主库
SLAVE1, //从库1
SLAVE2 //从库2
}

这里我们配置三个库,分别是一个写库 Master,2 个读库 slave1,slave2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 数据库配置类
*/
@Configuration
public class DataSourceConfigs {

@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties("spring.datasource.slave1")
public DataSource slave1DataSource() {
return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties("spring.datasource.slave2")
public DataSource slave2DataSource() {
return DataSourceBuilder.create().build();
}

@Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slave1DataSource") DataSource slave1DataSource,
@Qualifier("slave2DataSource") DataSource slave2DataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(masterDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}

6. 切面类 DataSourceAop.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 切面类DataSourceAop
*/
@Aspect
@Component
public class DataSourceAop {
@Pointcut("@annotation(com.example.demo.databases.Master) " +
"|| execution(* com.example.demo.*.service..*.insert*(..)) " +
"|| execution(* com.example.demo.*.service..*.create*(..)) " +
"|| execution(* com.example.demo.*.service..*.save*(..)) " +
"|| execution(* com.example.demo.*.service..*.add*(..)) " +
"|| execution(* com.example.demo.*.service..*.update*(..)) " +
"|| execution(* com.example.demo.*.service..*.edit*(..)) " +
"|| execution(* com.example.demo.*.service..*.delete*(..)) " +
"|| execution(* com.example.demo.*.service..*.remove*(..))")
public void writePointcut() {

}

@Pointcut("!@annotation(com.example.demo.databases.Master) " +
"&& (execution(* com.example.demo.*.service..*.select*(..)) " +
"|| execution(* com.example.demo.*.service..*.list*(..))" +
"|| execution(* com.example.demo.*.service..*.count*(..))" +
"|| execution(* com.example.demo.*.service..*.get*(..)))"

)
public void readPointcut() {

}

@Before("writePointcut()")
public void write() {
DBContext.master();
}

@Before("readPointcut()")
public void read() {
DBContext.slave();
}
}

7. 注解类 Master.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.demo.databases;

/**
* @Auther: Brath
* Create By Administrator on 2022/6/24 12:05
* Strive to create higher performance code
* @My wechat: 17604868415
* @My QQ: 2634490675
* @My email 1: email_ guoqing@163.com
* 注解类Master 主库,可读写
*/
public @interface Master {
}

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Nginx以及Keepalived安装以及部署

InterviewCoder

​ 反向代理:指的是用户要访问 youtube, 但是 youtube 悄悄地把这个请求交给后台 N 台服务器中的其中一台来做,这种方式就是反向代理了。

​ 负载均衡:

1) 使用硬件负载均衡策略,如使用 F5,Array 等负载均衡器.

2) 使用软件进行负载均衡
3) 如使用阿里云服务器均衡 SLB
4) 使用我们今天所学习的 Nginx+Keepalived
5) 其他软件负载均衡如 LVS (Linux Virtual Server),haproxy 等技术

# 环境搭建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
步骤:
1.进行安装:tar -zxvf /root/software/nginx-1.6.2.tar.gz -C /usr/local/
2.下载所需要的依赖库文件:
yum install pcre -y
yum install pcre-devel -y
yum install zlib -y
yum install zlib-devel -y
3.进行configure配置,查看是否报错
cd nginx-1.6.2
./configure --prefix=/usr/local/nginx
4.编译安装:make && make install
5.在 /usr/local/nginx目录下,可以看到如下4个目录
conf配置文件,html网页文件,logs日志文件,sbin主要二进制程序
6.启动命令:/usr/local/nginx/sbin/nginx
关闭命令:/usr/local/nginx/sbin/nginx -s stop
重启命令:/usr/local/nginx/sbin/nginx -s reload
7.访问浏览器:http://192.168.122.133(看到欢迎页面说明没问题)

注意:如果出现这个错误:./configure: error: C compiler cc is not found
执行这个命令:yum -y install gcc gcc-c++ autoconf automake make

Keepalived:

首先介绍一下 Keepalived, 它是一个高性能的服务器高可用或热备解决方案,Keepalived 主要防止服务器单点故障的问题,可以通过其与 Nginx 的配合实现 web 服务器端的高可用.
Keepalived 以 VRRP 协议为实现基础,使用 VRRP 协议来实现高可用性 (HA).VRRP (Virtual Router Redundacy Protocol) 协议用于实现路由器冗余的协议,VRRP 协议将两台或多台路由器设备虚拟成一个设备,向外提供虚拟路由 IP (一个或多个)。

安装以及部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
第一步:安装keepalived依赖的包
yum install -y gcc
yum install -y openssl-devel
yum install -y libnl3-devel
yum install -y popt-devel
yum install -y iptables-devel
yum install -y libnfnetlink-devel
yum install -y psmisc
第二步:编译安装keepalived
将keepalived的安装包 上传到/usr/local/software 目录下
cd /usr/local/software
tar -zxvf keepalived-1.2.19.tar.gz -C /usr/local
cd /usr/local/keepalived-1.2.19
./configure --prefix=/usr/local/keepalived
make && make install
第三步:将 keepalived 安装成 Linux 系统服务
安装完成之后, 需要做一些工作复制默认配置文件到 默认路径
mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/
cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
cd /usr/local/keepalived-1.2.19
cp ./keepalived/etc/init.d/keepalived.init /etc/init.d/
chmod 755 /etc/init.d/keepalived.init
第四步:编写nginx检测脚本:
vi /etc/keepalived/nginx_check.sh
内容如下:
#!/bin/bash
A=`ps -C nginx –no-header |wc -l`
if [ $A -eq 0 ];then
/usr/local/nginx/sbin/nginx
sleep 2
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
killall keepalived
fi
fi
赋予执行权限
chmod +x /etc/keepalived/nginx_check.sh


启动命令:
keepalived

修改 Master 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
修改keepalived的Master配置文件:vi /etc/keepalived/keepalived.conf 
! Configuration File for keepalived
global_defs {
router_id wolfcode ##路由器标志
}
# 集群资源监控,组合track_script进行
vrrp_script check_haproxy {
script "/etc/keepalived/nginx_check.sh" #检测 nginx 状态的脚本路径
interval 2 #检测时间间隔
weight -20 #条件成立 权重减20
}
vrrp_instance PROXY {
# 设置当前主机为主节点,如果是备用节点,则设置为BACKUP
state MASTER
# 指定HA监测网络接口,可以用ifconfig查看来决定设置哪一个
interface ens32
# 虚拟路由标识,同一个VRRP实例要使用同一个标识,主备机
virtual_router_id 80
# 因为当前环境中VRRP组播有问题,改为使用单播发送VRRP报文 如果VRRP组播没问题,以下这块的内容可以注释掉。
# 这个地方需要关注,之前未做此设置,结果主备节点互相不能发现,因此主备节点都升级成了MASTER,并且绑定了VIP
# 主节点时,内容为:
unicast_src_ip 192.168.122.133
# 设置优先级,确保主节点的优先级高过备用节点
priority 100
# 用于设定主备节点间同步检查时间间隔
advert_int 2
# 设置主备节点间的通信验证类型及密码,同一个VRRP实例中需一致
authentication {
auth_type PASS
auth_pass wolfcode
}
# 集群资源监控,组合vrrp_script进行
track_script {
check_haproxy
}
# 设置虚拟IP地址,当keepalived状态切换为MASTER时,此IP会自动添加到系统中
# 当状态切换到BACKUP时,此IP会自动从系统中删除
# 可以通过命令ip add查看切换后的状态
virtual_ipaddress {
192.168.122.110 #虚拟ip配置完之后就用它访问
}
}

修改 Slave 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
修改keepalived的Slave配置文件:vi /etc/keepalived/keepalived.conf 
! Configuration File for keepalived
global_defs {
router_id wolfcode ##路由器标志
}
# 集群资源监控,组合track_script进行
vrrp_script check_haproxy {
script "/etc/keepalived/nginx_check.sh" #检测 nginx 状态的脚本路径
interval 2 #检测时间间隔
weight -20 #条件成立 权重减20
}
vrrp_instance PROXY {
# 设置当前主机为主节点,如果是备用节点,则设置为BACKUP
state BACKUP
# 指定HA监测网络接口,可以用ifconfig查看来决定设置哪一个
interface ens32
# 虚拟路由标识,同一个VRRP实例要使用同一个标识,主备机
virtual_router_id 80
# 因为当前环境中VRRP组播有问题,改为使用单播发送VRRP报文 如果VRRP组播没问题,以下这块的内容可以注释掉。
# 这个地方需要关注,之前未做此设置,结果主备节点互相不能发现,因此主备节点都升级成了MASTER,并且绑定了VIP
# 主节点时,内容为:
unicast_src_ip 192.168.122.134
# 设置优先级,确保主节点的优先级高过备用节点
priority 90
# 用于设定主备节点间同步检查时间间隔
advert_int 2
# 设置主备节点间的通信验证类型及密码,同一个VRRP实例中需一致
authentication {
auth_type PASS
auth_pass wolfcode
}
# 集群资源监控,组合vrrp_script进行
track_script {
check_haproxy
}
# 设置虚拟IP地址,当keepalived状态切换为MASTER时,此IP会自动添加到系统中
# 当状态切换到BACKUP时,此IP会自动从系统中删除
# 可以通过命令ip add查看切换后的状态
virtual_ipaddress {
192.168.122.110 #虚拟ip配置完之后就用它访问
}
}

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Docker环境下部署RocketMQ以及Console

InterviewCoder

# Docker 环境下部署 RocketMQ 以及 Console

# 1.docker 安装 rocketmq 镜像

1
2
3
#拉取镜像 
docker pull foxiswho/rocketmq:server-4.7.0
docker pull foxiswho/rocketmq:broker-4.7.0

# 2. 创建 server 和 broker 目录,并在目录 /opt 下创建 broker.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建目录
mkdir /opt/rocketmq-server
mkdir /opt/rocketmq-broker/conf -p
[root@localhost opt]# cat /opt/rocketmq-broker/conf/broker.conf
namesrvAddr=【你的IP地址】:9876
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 【你的IP地址】
listenPort=10911

# 3. 启动容器并在防火墙放行端口 9876 、10911 、11011

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
RocketMQ默认使用3个端口:9876 、10911 、11011
如果防火墙没有关闭的话,那么防火墙就必须开放这些端口:
nameserver 默认使用 9876 端口
master 默认使用 10911 端口
slave 默认使用11011 端口

#启动rocketmq-server
docker run -d \
--restart=always \
--name rmqnamesrv \
-p 9876:9876 \
-v /opt/rocketmq-server/logs:/root/logs \
-v /opt/rocketmq-server/store:/root/store \
-e "MAX_POSSIBLE_HEAP=100000000" \
foxiswho/rocketmq:4.7.0 \
sh mqnamesrv

#启动rocketmq-broker
docker run -d \
--restart=always \
--name rmqbroker \
--link rmqnamesrv:namesrv \
-p 10911:10911 \
-p 10909:10909 \
-v /opt/rocketmq-broker/logs:/root/logs \
-v /opt/rocketmq-broker/store:/root/store \
-v /opt/rocketmq-broker/conf/broker.conf:/opt/rocketmq-broker/conf/broker.conf \
-e "NAMESRV_ADDR=【你的IP地址】:9876" \
-e "MAX_POSSIBLE_HEAP=200000000" \
-e "autoCreateTopicEnable=true" \
foxiswho/rocketmq:4.7.0 \
sh mqbroker -c /opt/rocketmq-broker/conf/broker.conf


#启动RocketMQ的管理工具rocketmq-console
docker run -itd -e "JAVA_OPTS=-Drocketmq.namesrv.addr=【你的IP地址】:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 9877:8080 -t styletang/rocketmq-console-ng:latest

# 4. 测试访问 console 控制台

浏览器输入:192.168.1.200:9877

image-20230104082814121

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Java】Elastic canal数据同步到ES配置常见报错

InterviewCoder

# 【Java】Elastic canal 数据同步到 ES 配置常见报错

# 0. 引言

所有报错均为博主在实操过程中遇到的错误和解决办法,如果有其他报错或者不同的解决办法,请留言告诉我

安装 canal 过程中遇到问题,先在本文中查询是否有相同报错,将会为你节约大量排错时间

# 环境

1
2
3
4
jdk1.8
canal 1.1.5
mysql8.0
es7.13.0

# 1. Unknown system variable ‘query_cache_size’

这是因为 mysql 驱动包的版本过低导致的,query cache 在 MySQL5.7.20 就已经过时了,而在 MySQL8.0 之后就已经被移除了

1、只需要将 lib 中的驱动器替换成 mysql-connector-java-8.0.22.jar

2、修改驱动器权限

1
2
chmod 777 lib/mysql-connector-java-8.0.22.jar
chmod +st lib/mysql-connector-java-8.0.22.jar

查看权限如图所示

1
ll lib

在这里插入图片描述

# 2. Reason: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, java.lang.String>]

启动 canal-adapter 报错:

1
2
3
Failed to bind properties under 'canal.conf.canal-adapters[0].groups[0].outer-adapters[1].properties' to java.util.Map<java.lang.String, java.lang.String>:

Reason: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, java.lang.String>]

解决:
观察报错信息可以得知是配置文件中的 outer2(0 基,所以 outer-adapter [1] 实际指的是 2)的 properties 配置有问题,我们观察配置文件,发现是 properties 下的 mode,cluster.name 等属性与 properties 同级了,将其如下图所示后退两字符即可。
配置

# 3. RuntimeException: java.lang.RuntimeException: No data source found: xxxx

这个因为在 conf/es/xxx.yml 中配置的 dataSourceKey 并没有在 conf/application.yml 中的 srcDataSources 中维护

如下图所示,es 中的 dataSourceKey 需要在 applicaiton.yml 中设置,默认是 defaultDS
在这里插入图片描述
在这里插入图片描述

# 4. Reason: Unable to set value for property src-data-sources

1
2
3
Failed to bind properties under 'canal.conf' to com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig:

Reason: Unable to set value for property src-data-sources

原因一:
mysql 驱动器导致的问题,使用的数据库是 8.x。驱动器是 5.x 的,将 mysql 驱动替换为 8.0.x 版本的。如上所示报错 1

原因二:
检查该报错前的日志,是否有其他相关报错信息,比如无相关数据库,如下所示,根据其报错内容来检查配置项并且调整即可
在这里插入图片描述

# 5. java.sql.SQLException: null, message from server: “Host ‘172.16.188.2’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’”

同一个 ip 在短时间内产生太多中断的数据库连接而导致的阻塞

登录对应的 mysql,执行如下指令

1
flush hosts;

# 6. IllegalStateException: Extension instance(name: es7, class: interface com.alibaba.otter.canal.client.adapter.OuterAdapter) could not be instantiated: class could not be found

一般 could not be instantiated: class could not be found 这样的报错是配置文件的问题,如上的报错可以看到是 name: es7 中的错误,在官方的示例文档中使用的是 name: es6 # or es7

在 canal1.1.5 + 版本中设置的是 name: es6 # 或者 es7

但在 1.1.4 版本中直接使用 name: es 即可

# 7. IllegalArgumentException: Not found the mapping info of index: user

1、这个报错是 ES 的 mapping 设置的问题,确保 es 中有该索引,并且确认是否有部分字段没有在 es 中设置 mapping, 这个要对应之前设置的 sql,以及 es 中的 mappings 来解决

2、使用了 elasticsearch 7.x,但 adapter1.1.4 默认支持 es6.x
解决方案:
(1)修改 adapter 源码,将 es 依赖调整为 7.x;参考博客 adapter1.1.4 修改源码支持 es7.x
(2)换成 adapter1.1.5

# 8. IllegalArgumentException: Illegal character in scheme name at index 0: 172.16.188.7:9200

如果连接 es 使用的是 rest 方式,那么 hosts 中的 ip 前要添加 http:// ,如

1
hosts: http://172.16.188.7:9200

# 9. com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource

1
2
3
4
5
6
7
8
9
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource
at com.alibaba.otter.canal.client.adapter.es7x.ES7xAdapter.init(ES7xAdapter.java:54) ~[client-adapter.es7x-1.1.5-jar-with-dependencies.jar:na]
at com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterLoader.loadAdapter(CanalAdapterLoader.java:225) [client-adapter.launcher-1.1.5.jar:na]
at com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterLoader.init(CanalAdapterLoader.java:56) [client-adapter.launcher-1.1.5.jar:na]
at com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterService.init(CanalAdapterService.java:60) [client-adapter.launcher-1.1.5.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_271]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_271]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_271]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_271]

原因:
druid 包冲突
解决:
1、修改 client-adapter/escore/pom.xml

1
2
3
4
5
6
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<!--add by whx 20220112-->
<scope>provided</scope>
</dependency>

2、重新打包
在这里插入图片描述
3、将 client-adapter/es7x/target/client-adapter.es7x-1.1.5-jar-with-dependencies.jar 上传到服务器,替换 adataper/plugin 下的同名 jar 文件
在这里插入图片描述

1
scp client-adapter.es7x-1.1.5-jar-with-dependencies.jar root@172.16.188.2:/var/local

4、给该文件赋权

1
chmod 777 /var/local/client-adapter.es7x-1.1.5-jar-with-dependencies.jar 

5、重启服务

# 10 CanalParseException: java.io.IOException: EOF encountered

将 lib 目录下的 mysql 驱动器替换为 mysql8.0,并附权。参考上述

# 11. CanalClientException: java.io.IOException: Broken pipe Error sync but ACK

服务连接断开了,将 deployer 和 adapter 都关闭,先启动 deployer 再启动 adapter

# 12. DocumentMissingException[[_doc][1413298413755211778]: document missing]

1、es 集群出现问题,导致 doc 无法分配。常见的是分片数的问题,可能是副本分片过多,导致集群报黄
解决:
因为我的是 es 单节点,所以将主分片数设置为 1,副本分片设置为 0。不申明的话默认创建副本分片数为 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
PUT user
{
"mappings": {
"properties": {
"code": {
"type": "keyword"
},
"email": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"realName": {
"type": "text",
"analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"roleId": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"postId": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"deptId": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
},
"settings": {
"number_of_replicas": 0,
"number_of_shards": 1
}
}

2、修改的 mysql 数据库数据,在 es 中不存在。先进行全量同步,再进行增量同步

在 conf/example/instance.properties 中修改

1
2
3
4
5
# 全量同步
canal.instance.master.journal.name=mysql-bin.000001
canal.instance.master.position=0
#2019-01-01 00:00:00 上一次更新的时间
canal.instance.master.timestamp=1546272000000

# 13. ERROR c.a.otter.canal.server.netty.handler.SessionHandler - something goes wrong with channel:[id: 0x23d9cad9, /127.0.0.1:46472 :> /127.0.0.1:11111], exception=java.nio.channels.ClosedChannelException

这是由于 deployer 中的 conf/example/meta.dat 与 instance.properties 文件中的 journalName,position,timestamp 不一致导致的

1
2
3
4
5
6
7
# 查询bin log位置
show master status;
# 如果显示的binlog不为000001可以执行以下语句重置binlog(轻易别操作,最好让专业的运维人员操作)
# 该操作会重新生成binlog,之前的binlog就会清空,之前的数据就不会再同步
reset master;
# 刷新log日志,自此刻开始产生一个新编号的binlog日志文件
flush logs;

在这里插入图片描述
将 meta.dat 删除或者修改一致即可。删除后将会按照 instance.properties 中设置的起点同步,生产环境考虑好需要后再删除。

如果想要将之前的数据也同步的话,可以将数据库先导出,再重新导入一遍,即可重新生成 binlog,实现数据的全量同步

# 14. Received error packet: errno = 1236, sqlstate = HY000 errmsg = Could not find first log file name in binary log index file

mysql bin log 数据不同步,刷新一下即可

1
flush logs;

# 15. binlog 也设置为 000001 了,timestamp 也设置了,但就是无法实现全量同步

1、删除 conf/example/meta.dat
2、调整 conf/example/instance.properties

1
2
3
4
canal.instance.master.journal.name=mysql-bin.000001
canal.instance.master.position=0
#2019-01-01 00:00:00 上一次更新的时间
canal.instance.master.timestamp=1546272000000

3、重启 deployer

另外需要注意的是如果 bin log 是只会记录增量操作的,也就是说开启 bin log 之前的历史数据是不会记录的,如果需要同步者之前的数据,解决这个问题有三个办法:
(1)通过 logstash-input-jdbc 来实现
(2)通过业务代码来实现(后续会详细讲解这两种方式,可以关注我后续的博客)
(3)复制原数据库数据到开启了 binlog 的从数据库,然后从从数据库同步

# 16. adapter 启动报错:something goes wrong when starting up the canal client adapters: java.lang.NullPointerException: null

这个报错是空指针报错,很明显是哪里获取为空的,这种错误没有固定的原因,但大概率上可以锁定配置文件的问题

1、adapter 的配置文件中是有包含了 mysql、es、mq、zk 等配置,如果不需要的配置项,就将其注释掉,不要打开

比如我这里的报错原因就是因为打开了 zookeeperHosts,但是没有配置具体值,所以导致了空指针,因为我不需要 zk,将其注释掉即可

1
2
# flatMessage: true
# zookeeperHosts:

2、某些必要的配置没有设置,快速排查的方式就是根据官方文档中给出的配置文件对比排错
可以参考如下配置文件

3、配置文件中配置项排版错位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
server:
port: 8081
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null

canal.conf:
mode: tcp #tcp kafka rocketMQ rabbitMQ
flatMessage: true
zookeeperHosts:
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
consumerProperties:
# canal tcp consumer
canal.tcp.server.host: 127.0.0.1:11111
canal.tcp.zookeeper.hosts:
canal.tcp.batch.size: 500
canal.tcp.username:
canal.tcp.password:
# kafka consumer
kafka.bootstrap.servers: 127.0.0.1:9092
kafka.enable.auto.commit: false
kafka.auto.commit.interval.ms: 1000
kafka.auto.offset.reset: latest
kafka.request.timeout.ms: 40000
kafka.session.timeout.ms: 30000
kafka.isolation.level: read_committed
kafka.max.poll.records: 1000
# rocketMQ consumer
rocketmq.namespace:
rocketmq.namesrv.addr: 127.0.0.1:9876
rocketmq.batch.size: 1000
rocketmq.enable.message.trace: false
rocketmq.customized.trace.topic:
rocketmq.access.channel:
rocketmq.subscribe.filter:
# rabbitMQ consumer
rabbitmq.host:
rabbitmq.virtual.host:
rabbitmq.username:
rabbitmq.password:
rabbitmq.resource.ownerId:
srcDataSources:
defaultDS:
url: jdbc:mysql://172.16.188.1:3306/bladex?useUnicode=true
#driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: 123456
canalAdapters:
- instance: example # canal instance Name or mq topic name
groups:
- groupId: g1
outerAdapters:
- name: logger
-
key: esKey
name: es7 # es6 or es7
hosts: http://172.16.188.7:9200 # 集群地址,逗号隔开. 127.0.0.1:9200 for rest mode or 127.0.0.1:9300 for transport mode
properties:
mode: rest # rest or transport
# security.auth: test:123456 # only used for rest mode
cluster.name: cluster1

# 17 Field error in object ‘target’ on field ‘esMapping’: rejected value [];

1
2
3
4
5
6
7
Field error in object 'target' on field 'esMapping': rejected value []; codes 
[typeMismatch.target.esMapping,typeMismatch.esMapping,typeMismatch.com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig$ESMapping,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.esMapping,esMapping]; arguments []; default message [esMapping]]; default message [Failed to convert property value of type 'java.lang.String' to required type
'com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig$ESMapping' for property 'esMapping'; nested
exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type
'com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig$ESMapping' for property 'esMapping': no
matching editors or conversion strategy found]

这是配置文件问题,检查 es 下的配置 yml 文件,特别是 sql 语句的语法是否有问题

# 18 java.util.NoSuchElementException

没有找到对应字段导致

检查下 canal 配置文件中的字段是否在 es mapping 中有对应的,大小写是否一致,是否有遗漏

因为我的操作是 mysql 同步至 es,所以这里说明几项容易出错的地方:
1、canal 配置文件中的 sql 中是否大小写一致,canal 是区分大小写的
2、sql 中设置的别名是否与 es mappings 中的名称一致,允许 es 中的部分字段为空,但是不允许 sql 中查询出来的字段在 es mappings 中找不到对应的字段
3、canal 配置文件中的 dataSourceKey 是否正确,其对应到 canal application.yml 配置文件中的数据库是否正确

1
dataSourceKey: aaa

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
srcDataSources:
aaa: # 与之对应
url: jdbc:mysql://192.168.244.1:3306/aaa?useUnicode=true
#driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: 123456
xxx:
url: jdbc:mysql://192.168.244.1:3306/xxx?useUnicode=true
username: root
password: 123456
yyy:
url: jdbc:mysql://192.168.244.1:3306/yyy?useUnicode=true
username: root
password: 123456

4、canal 配置文件中的排版是否正确,特别注意_index,_type 等属性要放在 esMappings 下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dataSourceKey: aaa # 这里的key与上述application.yml中配置的数据源保持一致
outerAdapterKey: esKey # 与上述application.yml中配置的outerAdapters.key一直
destination: example # 默认为example,与application.yml中配置的instance保持一致
groupId:
esMapping:
_index: dept
_type: _doc
_id: _id
sql: "select
t.id,
t.id as _id,
t.dept_name as deptName,
t.dept_category as deptCategory,
t.parent_id as parentId,
t.ancestors as ancestors,
t.third_party_id as thirdPartyId,
t.phone as phone,
t.address as address,
t.is_deleted as isDeleted
from
dept t"
#etlCondition: "where t.update_time>='{0}'"
commitBatch: 3000

5、sql 查询出来的字段类型与 es mappings 中的字段数据类型是否一致

6、多表同步到同一个索引时,如果都有同一个常量列且值不同时会报错。
如下所示的两个配置文件都有 userSource 常量列用于区分不同的数据来源,但是如下的配置会报错。
xxx.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dataSourceKey: aaa # 这里的key与上述application.yml中配置的数据源保持一致
outerAdapterKey: esKey # 与上述application.yml中配置的outerAdapters.key一直
destination: example # 默认为example,与application.yml中配置的instance保持一致
groupId:
esMapping:
_index: user
_type: _doc
_id: _id
sql: "select
t.id,
0 as userSource,
from
user t"
commitBatch: 3000

yyy.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dataSourceKey: aaa # 这里的key与上述application.yml中配置的数据源保持一致
outerAdapterKey: esKey # 与上述application.yml中配置的outerAdapters.key一直
destination: example # 默认为example,与application.yml中配置的instance保持一致
groupId:
esMapping:
_index: user
_type: _doc
_id: _id
sql: "select
t.id,
1 as userSource,
from
user_wx t"
commitBatch: 3000

# 19. adapter 日志中没有报错,但是没有读取 binlog | Could not find first log file name in binary log index file

adapter 日志中没有报错信息,于是去查看 deployer 日志,这里的 example 是你配置的实例

1
cat logs/example/example.log

会发现报错:

1
Could not find first log file name in binary log index file

解决:
1、既然问题是没有找到数据库的 binglog 文件位置,那么就查看一下现在的 binlog 文件位置,登陆 mysql 执行

2.1 如果你是做增量同步,那么查询当前 binlog 位置

1
show master status;

在这里插入图片描述
修改 conf/example/instance.properties 文件

1
2
3
4
5
6
canal.instance.master.address=192.168.244.17:3306
# 这里的文件名要与上面的保持一致,我这里就是文件名不一致,写成了mysql-bin.000001
canal.instance.master.journal.name=binlog.000003
canal.instance.master.position=5921
canal.instance.master.timestamp=
canal.instance.master.gtid=

2.2 如果你要做全量同步,查询 binlog 文件

1
show binary logs;

在这里插入图片描述

1
2
3
4
5
6
7
canal.instance.master.address=192.168.244.17:3306
# 这里的文件名要与上面的保持一致,我这里就是文件名不一致,写成了mysql-bin.000001
canal.instance.master.journal.name=binlog.000001
# 位置从0开始
canal.instance.master.position=0
canal.instance.master.timestamp=
canal.instance.master.gtid=

3、重启 deployer 和 adapter

# 20. ERROR c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - java.lang.NullPointerException

启动 adapter 报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2022-05-21 06:28:44.444 [pool-2-thread-1] ERROR c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - java.lang.NullPointerException
java.lang.RuntimeException: java.lang.NullPointerException
at com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService.sync(ESSyncService.java:116) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService.sync(ESSyncService.java:64) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.ESAdapter.sync(ESAdapter.java:115) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.ESAdapter.sync(ESAdapter.java:94) ~[na:na]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.batchSync(AdapterProcessor.java:139) ~[client-adapter.launcher-1.1.5.jar:na]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.lambda$null$1(AdapterProcessor.java:97) ~[client-adapter.launcher-1.1.5.jar:na]
at java.util.concurrent.CopyOnWriteArrayList.forEach(CopyOnWriteArrayList.java:895) ~[na:1.8.0_312]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.lambda$null$2(AdapterProcessor.java:94) ~[client-adapter.launcher-1.1.5.jar:na]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_312]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_312]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_312]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_312]
Caused by: java.lang.NullPointerException: null
at com.alibaba.otter.canal.client.adapter.es7x.support.ES7xTemplate.insert(ES7xTemplate.java:79) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService.singleTableSimpleFiledInsert(ESSyncService.java:448) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService.insert(ESSyncService.java:139) ~[na:na]
at com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService.sync(ESSyncService.java:99) ~[na:na]
... 11 common frames omitted
2022-05-21 06:28:44.449 [Thread-4] ERROR c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - Outer adapter sync failed! Error sync but ACK!

解决:
1、修改 adapter/application.yml,给 outerAdapters 配置一个 key,注意这里如果有多个 adapter 实例,那么就配置不同的 key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
canalAdapters:
- instance: test # canal instance Name or mq topic name
groups:
- groupId: g2
outerAdapters:
# - name: logger
-
key: esKey3 # 配置key
name: es7 # es6 or es7
#hosts: http://192.168.101.11:9200 # 集群地址,逗号隔开. 127.0.0.1:9200 for rest mode or 127.0.0.1:9300 for transport mode
hosts: http://192.168.244.11:9200 # 集群地址,逗号隔开. 127.0.0.1:9200 for rest mode or 127.0.0.1:9300 for transport mode
properties:
mode: rest # rest or transport
security.auth: elastic:elastic # only used for rest mode
cluster.name: blade-cluster

# 21. CanalParseException: parse row data failed. | column size is not match for table

deployser 日志报错:

1
2
3
4
5
2022-05-21 07:45:15.651 [MultiStageCoprocessor-Parser-fleet-0] ERROR com.alibaba.otter.canal.common.utils.NamedThreadFactory - from MultiStageCoprocessor-Parser-fleet-0
com.alibaba.otter.canal.parse.exception.CanalParseException: com.alibaba.otter.canal.parse.exception.CanalParseException: com.alibaba.otter.canal.parse.exception.CanalParseException: parse row data failed.
Caused by: com.alibaba.otter.canal.parse.exception.CanalParseException: com.alibaba.otter.canal.parse.exception.CanalParseException: parse row data failed.
Caused by: com.alibaba.otter.canal.parse.exception.CanalParseException: parse row data failed.
Caused by: com.alibaba.otter.canal.parse.exception.CanalParseException: column size is not match for table:fleet.source_project_cargo,9 vs 8

解决:
1、可以看到报错中已经给出明确提示了

1
column size is not match for table:fleet.source_project_cargo,9 vs 8

2、该错误官方中有解释
官方文档 TableMetaTSDB
在 instance.properties 中设置

1
2
3
4
5
6
7
8
9
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
# table meta tsdb info
canal.instance.tsdb.enable=true
# 以下配置不用开启,因为在canal.properties中已经设置过了,只要没有手动关闭过就不用再配置了
#canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
#canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal

3、一般将这个开启就解决了,但是我这里即时将其开启还是报错,查阅相关资料有说将 canal.instance.tsdb.enable 设置为 false 后重启解决的,但是我这里将其设置为 false 后依旧没有解决

实在没有其他办法了,查阅官方 github,导致这个问题发生的原因是因为表结构发生过变化,但是 binlog 中读取到的与现在的表结构不一致导致。

于是直接跳过该 binlog checkpoint,也就是将 binlog 的读取位置设置为当前的最新 binlog 位置

(1)查阅当前 binlog 最新位置,mysql 中执行

1
show master status;

在这里插入图片描述
(2)将读取位置该为最新,修改 deployer conf/example/instance.properties

1
2
3
4
5
6
canal.instance.master.address=162.14.99.4:3306
canal.instance.master.journal.name=mysql-bin.000002
canal.instance.master.position=226586328
# 当前时间的时间戳形式
canal.instance.master.timestamp=1653140932
canal.instance.master.gtid=

(3)重启 deployer , adapter
(4)因为读取的是最新的 binlog。为了把当前的数据同步进来,将需要同步的表或库导出,然后再导入一遍。问题解决(注意:这里的解决方案要谨慎,生产环境因为时时刻刻在产生数据,可行性很低,所以看要么设置一个停机维护来进行实操)

# 22. use gtid and TableMeta TSDB should be config timestamp > 0

在 instance.properties 中设置时间戳

1
canal.instance.master.timestamp=1546272000000

# 23. RuntimeException: com.alibaba.fastjson.JSONException: unclosed string

该错误是因为 sql 中使用了 group_concat 函数,但是该函数默认长度是 1024,超过的会被截取,导致出现了 json 格式的数据格式不正确,没有正确的关闭 json

解决:
1、修改 my.cnf,扩大 group_concat_max_len

1
group_concat_max_len = 102400

2、重启 mysql

# 24. MySQLSyntaxErrorException: Unknown column ‘_v._id’ in ‘where clause’

sql 中没有 _id 字段导致,使用 as 将 id 命名别名: select id as _id

# 25. adapter 中有同步日志打印,但 es 中数据未同步

我这里出现这个问题是在 canal1.1.6 版本中,原因是 es7 文件夹中的 .yml 文件中书写的 sql 里使用了 `` 将表名括起来,导致未识别,如下所示

1
select id,name,age from `user`

解决:
将 `` 去掉即可

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【MYSQL】水平分表、分库和垂直分表、分库和公共表的代码实现和讲解

InterviewCoder

# 文章目录

# 一、教学讲解视频

教学讲解视频地址:视频地址

# 二、环境准备

  • 操作系统:Win10

  • 数据库:MySQL5.7

  • JDK:64 位 jdk1.8.0_202

  • 应用框架:spring-boot-2.1.3.RELEASE

  • Sharding-JDBC:sharding-jdbc-spring-boot-starter-4.0.0-RC1

对应的 pom.xml 依赖代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>


<groupId>com.yjq.programmer</groupId>
<artifactId>ShardingJDBC</artifactId>
<version>1.0-SNAPSHOT</version>

<name>ShardingJDBC</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<!-- 引入mysql连接依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入sharding-jdbc连接依赖 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!--引入阿里巴巴druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<!-- 引入测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 集成mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 集成junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

</plugins>
</pluginManagement>
</build>
</project>

# 三、水平分表

# 1. 概念

水平分表是在 同一个 数据库内,把同一个表的数据 按一定规则 拆分到多个 中。
因此,目前我在一个数据库中准备了两个表, t_user_1t_user_2 ,如下图。
在这里插入图片描述
表结构:

1
2
3
4
5
6
CREATE TABLE `t_user_1` (
`id` bigint(20) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`sex` int(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 2. 代码

①我们先来看下我们的 SpringBoot 的 配置文件 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server.port=8081
#1800s
server.servlet.session.timeout=1800
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

#定义数据源
spring.shardingsphere.datasource.names=m1

spring.shardingsphere.datasource.m1.url=jdbc:mysql://127.0.0.1:3306/db_user1?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
spring.shardingsphere.datasource.m1.driver‐class‐name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource

# 指定t_user表的数据分布情况,配置数据节点
spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes=m1.t_user_$->{1..2}

# 指定t_user表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_user.key‐generator.column=id
spring.shardingsphere.sharding.tables.t_user.key‐generator.type=SNOWFLAKE

# 指定t_user表的分表策略,分表策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.sharding‐column=id
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.algorithm‐expression=t_user_$->{id % 2 + 1}


# 控制台日志配置
logging.level.root=info
logging.level.com.yjq.programmer.dao=debug

# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

#mapper文件扫描路径
mybatis.mapper-locations=classpath*:mappers/**/*.xml
  • 首先定义数据源 m1,并对 m1 进行实际的参数配置。
  • 指定 t_user 表的数据分布情况,他分布在 m1.t_user_1,m1.t_user_2。
  • 指定 t_user 表的主键生成策略为 SNOWFLAKE,SNOWFLAKE 是一种分布式自增算法,保证 id 全局唯一。
  • 定义 t_user 分表策略,id 为偶数的数据落在 t_user_1,为奇数的落在 t_user_2,所以分表策略的表达式为 t_user_$->{id% 2 + 1}。

踩坑注意! 如果启动项目有如下报错,可能是配置文件中的 -> 没有用英文类型的。
在这里插入图片描述
在这里插入图片描述

②然后接下来就写我们的 dao 层、 mapper 层和 单元测试 的代码,去测试我们的水平分表情况下 插入查询 的结果。

dao

1
2
3
4
5
6
public interface UserDao {

int insertUser(User user);

List<User> selectUser();
}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yjq.programmer.dao.UserDao">

<insert id="insertUser" parameterType="com.yjq.programmer.entity.User">
insert into t_user(name) values (#{name})
</insert>


<select id="selectUser" resultType="com.yjq.programmer.entity.User">
select * from t_user
</select>
</mapper>
12345678910111213
单元测试
@Test
public void testShardingJDBCInsert() {
User user = new User();
for(int i=0; i<10; i++) {
user.setName("小明" + i);
if(userDao.insertUser(user) == 1) {
logger.info("插入成功!");
} else {
logger.info("插入失败!");
}
}
}


@Test
public void testShardingJDBCSelect() {
List<User> userList = userDao.selectUser();
logger.info("查询结果:{}", JSONObject.toJSONString(userList));
}

③结果说明
插入
id 为奇数的被插入到 t_user_2 表,为偶数的被插入到 t_user_1 表,达到预期目标。
查询
sharding-jdbc 分别去不同的表检索数据,达到预期目标;如果有传入 id 进行查询,sharding-jdbc 也会根据 t_user 的分表策略去不同的表检索数据

# 四、水平分库

# 1. 概念

水平分库是把同一个表的数据按 一定规则 拆分到不同的 数据库 中,每个库可以放在不同的服务器上。
现在,我在 水平分表 的基础上多加了一个 db_user2 的数据库。
在这里插入图片描述
然后两个数据库中的表结构是一致的,表结构和上面水平分表用的保持一样。
在这里插入图片描述

# 2. 代码

①我们先来看下我们的 SpringBoot 的 配置文件 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
server.port=8081
#1800s
server.servlet.session.timeout=1800
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

#定义数据源
spring.shardingsphere.datasource.names=m1,m2

spring.shardingsphere.datasource.m1.url=jdbc:mysql://127.0.0.1:3306/db_user1?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
spring.shardingsphere.datasource.m1.driver‐class‐name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource

spring.shardingsphere.datasource.m2.url=jdbc:mysql://127.0.0.1:3306/db_user2?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=
spring.shardingsphere.datasource.m2.driver‐class‐name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource

# 分库策略
spring.shardingsphere.sharding.tables.t_user.database‐strategy.inline.sharding‐column=sex
spring.shardingsphere.sharding.tables.t_user.database‐strategy.inline.algorithm‐expression=m$->{sex % 2 + 1}

# 指定t_user表的数据分布情况,配置数据节点
spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes=m$->{1..2}.t_user_$->{1..2}

# 指定t_user表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_user.key‐generator.column=id
spring.shardingsphere.sharding.tables.t_user.key‐generator.type=SNOWFLAKE

# 指定t_user表的分表策略,分表策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.sharding‐column=id
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.algorithm‐expression=t_user_$->{id % 2 + 1}


# 控制台日志配置
logging.level.root=info
logging.level.com.yjq.programmer.dao=debug

# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

#mapper文件扫描路径
mybatis.mapper-locations=classpath*:mappers/**/*.xml
  • 配置了两个数据源,分配指向两个不同的数据库 db_user1 和 db_user2。
  • 配置分库策略,sex 字段为偶数的数据落在 m1 数据源,为奇数的落在 m2 数据源,所以分库策略的表达式为 m$->{sex % 2 + 1}。
  • 配置分表策略,分表策略和上面水平分表保持一致。
  • 也就是当有数据来时,先根据 sex 字段判断落入哪个数据源,然后再根据 id 字段来判断落入哪个表中。

②然后接下来就写我们的 dao 层、 mapper 层和 单元测试 的代码,去测试我们的水平分表情况下 插入查询 的结果。

dao

1
2
3
4
5
6
public interface UserDao {

int insertUser(User user);

List<User> selectUser(User user);
}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yjq.programmer.dao.UserDao">

<insert id="insertUser" parameterType="com.yjq.programmer.entity.User">
insert into t_user(name, sex) values (#{name}, #{sex})
</insert>


<select id="selectUser" parameterType="com.yjq.programmer.entity.User" resultType="com.yjq.programmer.entity.User">
select * from t_user t where t.sex = #{sex} and t.id = #{id}
</select>
</mapper>
12345678910111213
单元测试
@Test
public void testShardingJDBCInsert() {
User user = new User();
for(int i=0; i<10; i++) {
user.setName("小明" + i);
user.setSex(1);
if(userDao.insertUser(user) == 1) {
logger.info("插入成功!");
} else {
logger.info("插入失败!");
}
}
}


@Test
public void testShardingJDBCSelect() {
User user = new User();
user.setSex(1);
user.setId(821357967667363840L);
List<User> userList = userDao.selectUser(user);
logger.info("查询结果:{}", JSONObject.toJSONString(userList));
}

③结果说明
插入
sex 字段为奇数的数据落入 m2 数据源,为偶数的落入 m1 数据源。同时 id 字段值为奇数的,插入 t_user_2 表中,为偶数的插入 t_user_1 表中,达到预期目标。
查询
sharding-jdbc 分别去不同的表检索数据,达到预期目标;如果有传入 sex 进行查询,sharding-jdbc 会根据 t_user 的分库策略去锁定查哪个库,如果有传入 id 进行查询,sharding-jdbc 会根据 t_user 的分表策略去锁定查哪个表

# 五、垂直分表

# 1. 概念

垂直分表一般就是把表的结构进行改造,关于如何改造,可以浏览我的另一篇博客:
分库分表:垂直分库、垂直分表、水平分库、水平分表四个概念
大致的思路就是:将一个表按照字段分成多表,每个表存储其中一部分字段。

# 2. 代码

无代码,垂直分表属于表结构设计层面。

# 六、垂直分库

# 1. 概念

垂直分库就是在 垂直分表 把表进行分类后,放到 不同的数据库 中。每个库可以放在不同的服务器上,它的核心理念是 专库专用 。关于如何改造,同样可以浏览我的另一篇博客:
分库分表:垂直分库、垂直分表、水平分库、水平分表四个概念

# 2. 代码

无代码,垂直分库属于数据库设计层面。

# 七、公共表

# 1. 概念

公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。 可以将这类表在每个数据库都保存一份 ,所有更新操作都同时发送到所有分库执行。

# 2. 代码

①只需要在 SpringBoot 的 配置文件 中加入下面这行来指明公共表就行。
如果有多个公共表,用逗号拼接就行

1
2
3
#公共表设置
spring.shardingsphere.sharding.broadcast‐tables=t_dict
12

②然后接下来就写我们的 dao 层、 mapper 层和 单元测试 的代码,去测试我们的公共表的 插入 的结果。
dao

1
2
3
4
public interface DictDao {

int insertDict(Dict dict);
}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yjq.programmer.dao.DictDao">

<insert id="insertDict" parameterType="com.yjq.programmer.entity.Dict">
insert into t_dict(id, name) values (#{id}, #{name})
</insert>
</mapper>

单元测试
@Test
public void testShardingJDBCInsertDict() {
Dict dict = new Dict();
dict.setId(1);
dict.setName("字典名称");
if(dictDao.insertDict(dict) == 1) {
logger.info("插入成功!");
} else {
logger.info("插入失败!");
}
}

③结果说明
插入
插入的数据在 每个库中的对应的公共表 中都能看到,达到预期目标。

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!