Hello World!

使用Canal进行MySQL数据库全量+增量同步以及踩坑指南

作者: StoneWu  |  时间: 2022-06-14 11:24:38  |  
分类: 开发日记  |  0 条评论  |  817 次浏览

背景

最近工作中遇到一个迁移数据库的需求,需要将数据库从A服务器迁移至B服务器,为了尽量减少迁移导致的停机时间,考虑使用全量迁移+增量同步的方式,最终选择使用Canal作为迁移工具

准备工作

1. 数据库

两台服务器的数据库都需要提前准备好数据库账号,用于Canal进行数据库连接,因为Canal是伪装成从库读取源数据的,所以需要对源库进行一些配置,比如账号需要授予从库权限(如果给的是最高权限账号,那基本不用担心这个)、保证Canal部署的程序与数据库网络互通、以及最重要的!开启ROW格式的Binlog,这个非常重要!

2.Canal安装

使用Canal进行全量和增量同步需要用到两个组件,Canal adapter、Canal deployer,可自行前往github仓库下载。

Canal配置启动增量同步和全量同步

1.deployer配置

将下载的两个组件解压,首先配置deployer,下图为deployer的结构

主要关注conf目录,conf目录内文件如下:

进入example目录,编辑instance.properties文件:

重点关注这几个参数:canal.instance.master.address,canal.instance.dbUsername,canal.instance.dbPassword,canal.instance.filter.regex(重要)

#################################################
## mysql serverId , v1.0.26+ will autoGen
# canal.instance.mysql.slaveId=0

# enable gtid use true/false
canal.instance.gtidon=false

# position info
canal.instance.master.address=这里填写源数据库的ip:端口
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=

# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=

# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal

#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=

# username/password
canal.instance.dbUsername=填写源数据库用户名,例:root
canal.instance.dbPassword=填写源数据库密码,例:root
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==

# table regex
canal.instance.filter.regex=这里填写需要进行同步的数据库表名,格式:数据库名.表名,多个表用逗号分分隔,例如:testdb.user,testdb.role,testdb.menu
# table black regex
canal.instance.filter.black.regex=mysql\\.slave_.*
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch

# mq config
canal.mq.topic=example
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,topic2:mytest2\\..*,.*\\..*
canal.mq.partition=0
# hash partition config
#canal.mq.enableDynamicQueuePartition=false
#canal.mq.partitionsNum=3
#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#################################################

这样就配置好了deployer,回到deployer的bin目录,执行startup.sh即可启动deployer程序(此时还未同步)

2.adapter配置

同样,进入conf目录,内容如下:

首先编辑bootstrap.yml文件,将里面的配置全部注释,因为我们没有用上admin的服务,不需要连Canal自身的数据库

#canal:
#  manager:
#    jdbc:
#      url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
#      username: canal
#      password: canal

接着编辑application.yml文件

主要关注canal.conf.srcDataSourcescanal.conf.canalAdapters下的配置

  srcDataSources:
    defaultDS:
      url: 这里填源数据库的jdbc连接信息,例:jdbc:mysql://127.0.0.1:3306/testdb
      username: 数据库账号,例:root
      password: 数据库密码,例:root
      maxActive: 100 #额外增加这一行,默认的连接数只有3,会导致全量同步出现异常,导致全量同步数据缺失,最好改大一点
  canalAdapters:
  - instance: example #这里就保留叫example,只是个命名
    groups:
    - groupId: g1
      outerAdapters:
      - name: logger
      - name: rdb
        key: mysql1 # 这里保持默认即可
        properties:
          jdbc.driverClassName: com.mysql.jdbc.Driver #数据库驱动就保持这个驱动,不要修改为带cj的那个驱动,如果这个驱动有问题,再考虑更换驱动
          jdbc.url: 这里填写需要同步接收方的数据库链接,例:jdbc:mysql://192.168.1.2:3309/testdb?useUnicode=true
          jdbc.username: root
          jdbc.password: 123456
          druid.stat.enable: false
          druid.stat.slowSqlMillis: 1000

修改好文件后保存

由于本文场景是从mysql同步到另一个mysql,如果需要同步到es或者其他系统,请参考github内的教程

同步到mysql(或是其他关系型数据库)则接着进入到rdb目录

该文件夹内,里面已经有一个示例yml文件,一个yml文件一般配置一个同步表,如果有多个表需要同步,则复制多个yml副本将文件名修改为库名_表名.yml,方便归类,本文由于只有一个库,就直接命名为表名.yml

以本文为例,复制默认yml文件3份,分别命名为user.yml、role.yml、menu.yml,代表三个表,接下来以user.yml配置为例

dataSourceKey: defaultDS #无需修改
destination: example #这个对应application.yml文件中的
groupId: g1
outerAdapterKey: mysql1
concurrent: true
dbMapping:
  database: epidemic #需要同步的数据库名,两台服务器的数据库名最好命名成一样的,不然可能有各种意外情况发生
  table: user #需要同步的表名
  targetTable: user #同步到目标服务器对应的表名
  targetPk: 
    id: id # 主键,格式为, 源表主键名:目标表主键名
  mapAll: true # 映射所有字段,这里改为true的话,下面的targetColumns就可以不用改了,除非两边的数据结构不一样
#  targetColumns:
#    id:
#    name:
#    role_id:
#    c_time:
#    test1:
#  etlCondition: "where c_time>={}"
  commitBatch: 3000 # 批量提交的大小,保持不动即可

其他几个表的yml文件填写方式类似

修改好配置文件后,回到adapter的bin目录内,执行startup.sh,启动adapter,此时可以查看adapter/logs/adapter/adapter.log,如果里面开始有输出DML语句则代表增量同步已经开始了

3.全量同步

使用命令行执行如下命令,即可启动全量同步:

curl http://canal部署的ip:8081/etl/rdb/mysql1/rdb目录内对应表的文件名.yml -X POST
#例如:
curl http://127.0.0.1:8081/etl/rdb/mysql1/user.yml -X POST

依然查看adapter/logs/adapter/adapter.log日志即可看到全量同步的输出

 

至此,增量同步和全量同步都完成了

 

Spring Cache @Cacheable 缓存在部分Service中不生效的解决办法

作者: StoneWu  |  时间: 2021-01-22 18:24:27  |  
分类: JAVA开发  |  1 条评论  |  2520 次浏览

一、背景

最近开发的项目中,需要大量的使用到缓存以提升性能

其中,有个活动controller,需要查询所有的活动,代码如下:

    @GetMapping("/list")
    public RestResult<List<ActivityInfoDTO>> list() {
        List<ActivityInfoDTO> list = activityService.getAllActivity();
        return addRestResult(list);
    }

对应的ActivityService方法如下:

    @Override
    @Cacheable(value = "ActivityInfoDTO", key = "'getAllActivity'")
    public List<ActivityInfoDTO> getAllActivity() {
        List<ActivityInfoDTO> allActivity = activityDefMapper.getAllActivity();
        return allActivity;
    }

结构十分简单,但是奇怪的是,ActivityService里的方法完全不会走缓存

而另一个service,BannerInfoService则可以正常缓存

调试时可以看到,正常的BannerInfoService是有被cglib代理的,如下图:

而ActivityService是没有被代理的

二、解决方法

漫长的搜索过后,没能在网上找到解决方案

最终将目光瞄向了ActivityService的其他引用,于是猜测是否是循环引用导致ActivityService没有被代理

mybatis使用collection标签进行树形结构数据查询时如何携带外部参数查询

作者: StoneWu  |  时间: 2019-11-01 16:36:06  |  
分类: JAVA开发  |  0 条评论  |  854 次浏览

1、背景

最近更新博客的评论功能,想实现这么一个需求:

评论使用树形结构展示,评论提交后,需要后台审核后才展示到前台,但是用户自己可以显示自己提交的未审核的评论,效果如下:

上面的审核中的评论是当前用户发的,下面的是其他用户发的审核通过的

2、实施

最初的实现方法是想使用collection进行树形结构查询

为了实现树形查询,需要两个resultMap,一个为最外层的查询结果,另一个是集合里的查询结果,也就是对象中的children对应的List,因为是树形结构,所以外层和里层的结构基本一样,下方代码为两个resultMap代码(示例):

Java在ElasticSearch中使用LocalDatetime类型

作者: StoneWu  |  时间: 2020-09-11 11:32:05  |  
分类: JAVA开发  |  0 条评论  |  918 次浏览

问题


最近在开发一个搜索功能的需求的时候,遇到了LocalDatetime类型不能保存到ElasticSearch中的问题,报错如下:

ElasticsearchStatusException[Elasticsearch exception [type=mapper_parsing_exception, reason=failed to parse field [createTime] of type [date] in document with id '3000']
]; nested: ElasticsearchException[Elasticsearch exception [type=illegal_state_exception, reason=Can't get text on a START_OBJECT at 1:125]];

经过


从网上查找尝试第一个办法:

将以下注解加到时间字段上,依然无效

@Field(type = FieldType.Date, index = FieldIndex.not_analyzed, store = true,
        format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd HH:mm:ss.SSS")
private LocalDateTime createTime;

 

解决办法


在项目中添加以下依赖:

<dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

在字段上加上以下注解

@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Field(type = FieldType.Date, store = true, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS")
private LocalDateTime createTime;

完美解决

ajax跨域请求中设置header的一个坑

作者: StoneWu  |  时间: 2020-02-28 22:30:09  |  
分类: 开发日记  |  0 条评论  |  1451 次浏览

今天晚上有同事反馈,线上一个登记系统出现错误,具体表现如图

提示的报错内容为:

{
	"readyState": 0,
	"responseText": "",
	"status": 0,
	"statusText": "error"
}

第一眼直觉就是ajax跨域请求失败了,触发了CORS限制。

印象中自己已经做好了跨域相关的配置呀,怎么还会触发呢?

由于是在微信浏览器上才会出现,使用vconsole也没有找到详细的错误原因,又粗略检查了下代码,java后端代码如下:

// 工具类中的统一返回
httpResponse.addHeader("Access-Control-Allow-Origin", "*");
httpResponse.addHeader("Access-Control-Allow-Headers", "*");
httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpResponse.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
httpResponse.addHeader("Access-Control-Max-Age", "3600");

//继承WebMvcConfigurationSupport的configurer
registry.addMapping("/**")// 设置允许跨域的路径
        .allowedOrigins("*")// 设置允许跨域请求的域名
        .allowCredentials(true)// 是否允许证书 不再默认开启
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")// 设置允许的方法
        .allowedHeaders("*")

origin和method都已经配置上了需要配置的header,可是微信浏览器的options预校验死活不通过!

最终,将目光锁定在了Access-Control-Allow-Headers参数上

查阅资料后发现,Access-Control-Allow-Headers请求头的值设置成 “*” 是不生效的,只能设置成具体的值,比如token等等

最终代码修改如下:

// 工具类中的统一返回
httpResponse.addHeader("Access-Control-Allow-Origin", "*");
httpResponse.addHeader("Access-Control-Allow-Headers", "X-Token");
httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpResponse.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
httpResponse.addHeader("Access-Control-Max-Age", "3600");

//继承WebMvcConfigurationSupport的configurer
registry.addMapping("/**")// 设置允许跨域的路径
        .allowedOrigins("*")// 设置允许跨域请求的域名
        .allowCredentials(true)// 是否允许证书 不再默认开启
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")// 设置允许的方法
        .allowedHeaders("X-Token")

指定明确的Access-Control-Allow-Headers请求头的值即可

kibana安装配置以及授权访问

作者: StoneWu  |  时间: 2017-07-21 16:26:34  |  
分类: linux学习  |  0 条评论  |  807 次浏览

最近工作中使用了elasticsearch,每次都敲命令行实在是不方便,kibana则可以很方便的管理elasticsearch

1、下载安装包,我用的是5.4.2的elasticsearch,所以下载5.4.2版本的kibana

wget https://artifacts.elastic.co/downloads/kibana/kibana-5.4.2-linux-x86_64.tar.gz

2、解压

tar zxvf kibana-5.4.2-linux-x86_64.tar.gz

3、配置kibana配置文件config/kibana.yml

     具体配置:

#kibana启动端口
server.port: ""

#kibana启动主机
#设为127.0.0.1则为本机访问
#设为内网ip则为只能内网访问
#设为外网ip则可外网访问
server.host: ""

#elasticsearch的访问路径
elasticsearch.url: ""

一般知道这几个参数就行了

 

4、后台启动kibana

nohup bin/kibana log.txt 2>&1 &

 

5、关闭外网访问5601端口

略......

 

6、生成密码文件

htpasswd -c /usr/local/nginx/httpdwd kibana

 

7、nginx设置转发,并且加上授权

server {
        #将kibana的访问端口设置为15601,
        listen       15601;
        server_name  你的服务器ip或者域名;
		
        location / {
            proxy_set_header    X-Real-IP           $remote_addr;
            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
            proxy_set_header    Host                $http_host;
            proxy_set_header    X-NginX-Proxy       true;
            proxy_set_header    Connection          "";
            proxy_http_version  1.1;
            proxy_redirect    off;
            proxy_pass http://127.0.0.1:5601;
            auth_basic "客官,输个密码呗~";
            auth_basic_user_file /usr/local/nginx/httpdwd;
            access_log off;
        }

}

JAVA使用腾讯企业邮箱发送邮件时报错Could not connect to SMTP host

作者: StoneWu  |  时间: 2019-11-04 17:40:06  |  
分类: JAVA开发  |  0 条评论  |  1467 次浏览

最近做一个邮件发送功能的时候,发现腾讯企业邮的邮箱,用java发送邮件的时候一直报错:

Mail server connection failed; nested exception is javax.mail.MessagingException: Could not connect to SMTP host: smtp.exmail.qq.com, port: 465, response: -1. Failed messages: javax.mail.MessagingException: Could not connect to SMTP host: smtp.exmail.qq.com, port: 465, response: -1

发邮件使用的spring-boot-starter-mail,配置文件如下:

# 设置邮箱主机
spring.mail.host=smtp.exmail.qq.com
spring.mail.port=465
spring.mail.protocol=smtp

# 设置用户名
spring.mail.username=xxxxxx
# 设置密码,是客户端专用密码,非网页登录密码
spring.mail.password=xxxxxx

# true代表邮箱需要认证
spring.mail.properties.mail.smtp.auth=true

#启用SSL
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

查阅各种资料后发现,配置文件应当加入下面的配置:

spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.port=465
spring.mail.properties.mail.smtp.socketFactory.port=465

将这三行加入到配置文件里就可以正常发送邮件了

chrome浏览器获取百度bduss

作者: StoneWu  |  时间: 2018-05-14 16:23:52  |  
分类: 大杂烩  |  0 条评论  |  574 次浏览

自动签到需要百度的登录状态:BDUSS

获取方式如下:

使用chrome浏览器打开百度首页

然后点击地址栏的小绿锁标志

再点击下方的红色方块区域

在出现的弹框中如下图上方的红框中选中BDUSS

然后双击下方的红色框内容,ctrl+c复制,得到BDUSS

新版博客上线啦!!

作者: StoneWu  |  时间: 2019-09-19 23:37:22  |  
分类: 开发日记  |  0 条评论  |  374 次浏览

(mō)(yú)了一个月后,新版的博客终于做完了

新版博客把三年前用的spring mvc+spring。。等等一大堆的框架,切换成了更为优雅的spring boot,说是切换,其实就是重写了。。TAT。

原来用起来反人类的后台管理也换成了基于vue+element-ui的前端框架,用起来顺滑多了,感觉自己写文章的动力都变大了呢(并不

而且终于成功注册到了这个新域名stonewu.com,旧域名可能明年就不会再续费了吧哈哈哈哈哈哈哈

博客的前台交互也改善了很多,不过可能没有旧版博客的兼容性好,新版大概只能兼容IE10+了

不过现在还有一些小细节还需要优化下,慢慢来吧。

现在全站上了CDN,是之前阿里云学生认证免费送的20G,俩年前就送了,可惜现在才用上,年底就要到期了,果然人只有失去的时候才懂得珍惜

以后还是得养成写文章的习惯,有的时候好多想法,想分享出来的时候看到旧版的写作界面就没有欲望了。

好了就酱

dmesg命令查看java程序突然挂掉的原因

作者: StoneWu  |  时间: 2019-05-08 10:40:27  |  
分类: 开发日记  |  0 条评论  |  861 次浏览

最近做项目,总是发现项目突然挂掉,但是java的日志又没有显示任何内容。

网上搜索了一波才知道了这个命令

# 按时间格式显示
dmesg -T

# 显示跟java 有关的日志
dmesg -T | grep "(java)"

最终打印的信息如下

[Wed May  8 09:40:57 2019] Out of memory: Kill process 5348 (java) score 125 or sacrifice child
[Wed May  8 09:40:57 2019] Killed process 5348 (java) total-vm:6105560kB, anon-rss:1032792kB, file-rss:0kB, shmem-rss:0kB

看来的确是因为内存不足导致的进程被杀