├── .gitignore
├── pom.xml
├── readme.md
└── src
├── main
├── java
│ └── cn
│ │ └── zhengyk
│ │ └── sync
│ │ ├── Application.java
│ │ ├── config
│ │ ├── CanalConfig.java
│ │ ├── RedisConfig.java
│ │ └── ThreadPoolConfig.java
│ │ ├── handler
│ │ ├── AbstractHandler.java
│ │ ├── DeleteHandler.java
│ │ ├── InsertHandler.java
│ │ └── UpdateHandler.java
│ │ ├── schedul
│ │ └── CanalSchedul.java
│ │ └── utils
│ │ └── RedisUtil.java
└── resources
│ ├── application-dev.yml
│ ├── application.yml
│ ├── logback-spring.xml
│ └── sql
│ └── blog.sql
└── test
└── java
└── cn
└── zhengyk
└── sync
└── ApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | .idea/*
3 | /.mvn
4 | .mvn/*
5 | mvnw
6 | mvnw.*
7 | /test
8 | /target
9 | /out
10 | /logs
11 | */target/*
12 | *.iml
13 | *.class
14 | .mymetadata
15 | .checkstyle
16 | .classpath
17 | .project
18 | .class
19 | .war
20 | .zip
21 | .rar
22 | *.jar
23 | *.pdb
24 | .pdb
25 | *.log
26 | .log.*
27 | rebel.xml
28 | .DS_Store
29 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.0.3.RELEASE
9 |
10 |
11 | cn.zhengyk
12 | cannal-sync-mysql-redis
13 | 0.0.1-SNAPSHOT
14 | cannal-sync-mysql-redis
15 | 利用 Canal 同步 mysql 和 redis
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-test
26 | test
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-data-redis
37 |
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-configuration-processor
42 | true
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | org.projectlombok
58 | lombok
59 | true
60 |
61 |
62 |
63 |
64 | com.alibaba
65 | fastjson
66 | 1.2.51
67 |
68 |
69 |
70 |
71 | com.alibaba.otter
72 | canal.protocol
73 | 1.1.2
74 |
75 |
76 | com.alibaba.otter
77 | canal.client
78 | 1.1.2
79 |
80 |
81 |
82 |
83 |
84 |
85 | org.springframework.boot
86 | spring-boot-maven-plugin
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 利用Canal中间件使MySQL与Redis保持数据同步
2 | 采用责任链模式,压测过,线程安全。
3 |
4 | 此项目不光可以同步Redis,还可同步 ES、MongoDB、Solr.....套路都是一样的。
5 |
6 | ###安装 Canal Server
7 |
8 | 下载地址:https://github.com/alibaba/canal/releases
9 |
10 | ```linux
11 | wget https://github.com/alibaba/canal/releases/download/canal-1.1.2/canal.deployer-1.1.2.tar.gz
12 | ```
13 |
14 | 下载后解压缩
15 |
16 | 编辑conf/example/instance.properties
17 | ```properties
18 | #Canal 伪装成子节点的 id,与 master id 不一样
19 | canal.instance.mysql.slaveId = 99
20 | #要订阅的数据库地址
21 | canal.instance.master.address = 127.0.0.1:3306
22 | canal.instance.master.journal.name =
23 | canal.instance.master.position =
24 | canal.instance.master.timestamp =
25 |
26 | #Canal 账户,数据库会专门开通一个账户给 Canal 连接
27 | canal.instance.dbUsername = canal
28 | canal.instance.dbPassword = canal
29 |
30 | ```
31 |
32 | #### 启动、停止 Canal 服务
33 |
34 | ```linux
35 | sh bin/start.sh
36 | sh bin/restart.sh
37 | sh bin/stop.sh
38 | ```
39 |
40 | ### 安装 Redis
41 |
42 | [http://zhengyk.cn/2017/09/01/redis/Redis01/](http://zhengyk.cn/2017/09/01/redis/Redis01/)
43 |
44 | ### 项目配置信息
45 |
46 | ```yml
47 | spring:
48 | #Redis配置
49 | redis:
50 | host: localhost
51 | port: 7000
52 | password: Yakai2018@
53 | database: 0
54 | jedis:
55 | pool:
56 | #最大连接数(负值表示没有限制)
57 | max-active: 100
58 | #最大空闲链接
59 | max-idle: 10
60 | #最小空闲链接
61 | min-idle: 5
62 | #最大阻塞时间 负值表示不限制
63 | max-wait: -1ms
64 |
65 | # canal相关配置
66 | canal:
67 | host: localhost
68 | port: 11111
69 | destination: example
70 | username:
71 | password:
72 | subscribe: test.blog
73 | batchSize: 1000
74 | # subscribe 过滤规则
75 | # 1) 所有:.* or .*\\..*
76 | # 2) "test"库下所有表: test\\..*
77 | # 3) "test"下的以"sys"打头的表:test\\.sys.*
78 | # 4) "test"下的具体一张表:test.blog blog表
79 | # 5) 多个规则组合使用:test\\..*,test.sys_user,test.sys_role (逗号分隔)
80 | ```
81 |
82 | 运行启动项目即可,当我们对数据库进行增、删、改的操作时,Redis 也会相应变化。
83 |
84 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/Application.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync;
2 |
3 | //import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 | import org.springframework.transaction.annotation.EnableTransactionManagement;
8 |
9 |
10 | @EnableScheduling //开启定时任务支持
11 | @SpringBootApplication
12 | @EnableTransactionManagement //开启事务支持
13 | //@MapperScan("cn.zhengyk.sync.dao")
14 | public class Application {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(Application.class, args);
18 | }
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/config/CanalConfig.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.config;
2 |
3 | import com.alibaba.otter.canal.client.CanalConnector;
4 | import com.alibaba.otter.canal.client.CanalConnectors;
5 | import com.google.common.collect.Lists;
6 | import lombok.Setter;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.boot.context.properties.ConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.net.InetSocketAddress;
13 |
14 | /**
15 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
16 | * @date: Created on 2018/12/16
17 | * @description:
18 | * @version: 1.0
19 | */
20 | @Slf4j
21 | @Setter
22 | @Component
23 | @ConfigurationProperties("canal")
24 | public class CanalConfig {
25 |
26 | private String host;
27 | private Integer port;
28 | private String destination;
29 | private String username;
30 | private String password;
31 | private String subscribe;
32 |
33 | @Bean
34 | public CanalConnector getCanalConnector() {
35 | CanalConnector canalConnector = CanalConnectors.newClusterConnector(Lists.newArrayList(new InetSocketAddress(host, port)), destination, username, password);
36 | canalConnector.connect();
37 | // 指定要订阅的数据库和表
38 | canalConnector.subscribe(subscribe);
39 | // 回滚到上次中断的位置
40 | canalConnector.rollback();
41 | log.info("canal客户端启动......");
42 | return canalConnector;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.config;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.data.redis.connection.RedisConnectionFactory;
7 | import org.springframework.data.redis.core.RedisTemplate;
8 | import org.springframework.data.redis.serializer.StringRedisSerializer;
9 | /**
10 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
11 | * @date: Created on 2018/12/16
12 | * @description:
13 | * @version: 1.0
14 | */
15 | @Configuration
16 | public class RedisConfig {
17 | @Autowired
18 | private RedisConnectionFactory factory;
19 |
20 | @Bean
21 | public RedisTemplate redisTemplate() {
22 | RedisTemplate redisTemplate = new RedisTemplate<>();
23 | redisTemplate.setKeySerializer(new StringRedisSerializer());
24 | redisTemplate.setHashKeySerializer(new StringRedisSerializer());
25 | redisTemplate.setHashValueSerializer(new StringRedisSerializer());
26 | redisTemplate.setValueSerializer(new StringRedisSerializer());
27 | redisTemplate.setConnectionFactory(factory);
28 | return redisTemplate;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/config/ThreadPoolConfig.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.scheduling.annotation.EnableAsync;
6 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
7 |
8 | import java.util.concurrent.Executor;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 |
11 | /**
12 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
13 | * @date: Created on 2018/12/18
14 | * @description:
15 | * @version: 1.0
16 | */
17 | @EnableAsync
18 | @Configuration
19 | public class ThreadPoolConfig {
20 |
21 | @Bean("canal")
22 | public Executor taskExecutor() {
23 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
24 | executor.setCorePoolSize(10);
25 | executor.setMaxPoolSize(20);
26 | executor.setQueueCapacity(200);
27 | executor.setKeepAliveSeconds(60);
28 | executor.setThreadNamePrefix("canal-");
29 | // CallerRunsPolicy 重试添加当前的任务,他会自动重复调用execute()方法
30 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
31 | return executor;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/handler/AbstractHandler.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.handler;
2 |
3 | import cn.zhengyk.sync.utils.RedisUtil;
4 | import com.alibaba.otter.canal.protocol.CanalEntry.Column;
5 | import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
6 | import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
7 | import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
8 | import com.google.protobuf.InvalidProtocolBufferException;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 |
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.Optional;
15 | import java.util.stream.Collectors;
16 |
17 | /**
18 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
19 | * @date: Created on 2018/12/18
20 | * @description: 使用责任链模式 链顺序:增->删->改
21 | * @version: 1.0
22 | */
23 | @Slf4j
24 | public abstract class AbstractHandler {
25 |
26 | /**
27 | * 下一个执行者
28 | */
29 | protected AbstractHandler nextHandler;
30 |
31 | /**
32 | * 事件类型 UPDATE、DELETE、INSERT
33 | */
34 | protected EventType eventType;
35 |
36 | @Autowired
37 | protected RedisUtil redisUtil;
38 |
39 |
40 | /**
41 | * 传递处理的事件
42 | */
43 | public void handleMessage(Entry entry){
44 | if(this.eventType == entry.getHeader().getEventType()){
45 | //发生写入操作的库名
46 | String database = entry.getHeader().getSchemaName();
47 | //发生写入操作的表名
48 | String table = entry.getHeader().getTableName();
49 | log.info("监听到数据库:{},表:{} 的 {} 事件",database,table, eventType.toString());
50 | //如果 rowChange 不为空,则执行 handleRowChange()
51 | Optional.ofNullable(this.getRowChange(entry))
52 | .ifPresent(this::handleRowChange);
53 | }else{
54 | if(nextHandler != null){
55 | nextHandler.handleMessage(entry);
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * 处理数据库 UPDATE、DELETE、INSERT 的数据
62 | */
63 | public abstract void handleRowChange(RowChange rowChange);
64 |
65 | /**
66 | * 获得数据库 UPDATE、DELETE、INSERT 的数据
67 | */
68 | private RowChange getRowChange(Entry entry){
69 | RowChange rowChange = null;
70 | try {
71 | rowChange = RowChange.parseFrom(entry.getStoreValue());
72 | } catch (InvalidProtocolBufferException e) {
73 | log.error("根据CanalEntry获取RowChange异常:", e);
74 | }
75 | return rowChange;
76 | }
77 |
78 | /**
79 | * columns 转 map
80 | */
81 | protected Map columnsToMap(List columns) {
82 | return columns.stream().collect(Collectors.toMap(Column::getName, Column::getValue));
83 | }
84 |
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/handler/DeleteHandler.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.handler;
2 |
3 | import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
4 | import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
11 | * @date: Created on 2018/12/18
12 | * @description: 监听到数据库删除操作后执行的逻辑
13 | * @version: 1.0
14 | */
15 | @Slf4j
16 | @Component
17 | public class DeleteHandler extends AbstractHandler {
18 |
19 | public DeleteHandler(){
20 | eventType = EventType.DELETE;
21 | }
22 |
23 | @Autowired
24 | public void setNextHandler(UpdateHandler updateHandler) {
25 | this.nextHandler = updateHandler;
26 | }
27 |
28 | @Override
29 | public void handleRowChange(RowChange rowChange) {
30 | rowChange.getRowDatasList().forEach(rowData -> {
31 | rowData.getBeforeColumnsList().forEach(column -> {
32 | if("id".equals(column.getName())){
33 | //清除 redis 缓存
34 | log.info("清除 Redis 缓存 key={} 成功!\r\n","blog:"+column.getValue());
35 | redisUtil.del("blog:"+column.getValue());
36 | }
37 | });
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/handler/InsertHandler.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.handler;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.alibaba.otter.canal.protocol.CanalEntry.Column;
5 | import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
6 | import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Component;
10 |
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | /**
15 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
16 | * @date: Created on 2018/12/18
17 | * @description: 监听到数据库插入操作后执行的逻辑
18 | * @version: 1.0
19 | */
20 | @Slf4j
21 | @Component
22 | public class InsertHandler extends AbstractHandler{
23 |
24 | public InsertHandler(){
25 | this.eventType = EventType.INSERT;
26 | }
27 |
28 | @Autowired
29 | public void setNextHandler(DeleteHandler deleteHandler) {
30 | this.nextHandler = deleteHandler;
31 | }
32 |
33 | @Override
34 | public void handleRowChange(RowChange rowChange) {
35 | rowChange.getRowDatasList().forEach(rowData -> {
36 | //每一行的每列数据 字段名->值
37 | List afterColumnsList = rowData.getAfterColumnsList();
38 | Map map = super.columnsToMap(afterColumnsList);
39 | String id = map.get("id");
40 | String jsonStr = JSONObject.toJSONString(map);
41 | log.info("新增的数据:{}\r\n",jsonStr);
42 | redisUtil.setDefault("blog:"+id, jsonStr);
43 | });
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/handler/UpdateHandler.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.handler;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.alibaba.otter.canal.protocol.CanalEntry.Column;
5 | import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
6 | import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.stereotype.Component;
9 |
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | /**
14 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
15 | * @date: Created on 2018/12/18
16 | * @description: 监听到数据库更新操作后执行的逻辑
17 | * @version: 1.0
18 | */
19 | @Slf4j
20 | @Component
21 | public class UpdateHandler extends AbstractHandler {
22 |
23 | public UpdateHandler(){
24 | this.eventType = EventType.UPDATE;
25 | }
26 |
27 |
28 | @Override
29 | public void handleRowChange(RowChange rowChange) {
30 | rowChange.getRowDatasList().forEach(rowData -> {
31 | //每一行的每列数据 字段名->值
32 | List beforeColumnsList = rowData.getBeforeColumnsList();
33 | Map beforeMap = super.columnsToMap(beforeColumnsList);
34 | log.info("更新前数据:{}",JSONObject.toJSONString(beforeMap));
35 |
36 | List afterColumnsList = rowData.getAfterColumnsList();
37 | Map afterMap = super.columnsToMap(afterColumnsList);
38 | String id = afterMap.get("id");
39 | String afterJsonStr = JSONObject.toJSONString(afterMap);
40 | log.info("更新后数据:{}\r\n",afterJsonStr);
41 | /**
42 | * 高并发下,为保证数据一致性,当数据库更新后,不建议去更新缓存,
43 | * 而是建议直接删除缓存,由查询时再设置到缓存。
44 | * 这里为了演示,对缓存作更新操作,具体看业务需求。
45 | */
46 | redisUtil.setDefault("blog:"+id,afterJsonStr);
47 |
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/schedul/CanalSchedul.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.schedul;
2 |
3 | import cn.zhengyk.sync.handler.InsertHandler;
4 | import com.alibaba.otter.canal.client.CanalConnector;
5 | import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
6 | import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
7 | import com.alibaba.otter.canal.protocol.Message;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.scheduling.annotation.Async;
12 | import org.springframework.scheduling.annotation.Scheduled;
13 | import org.springframework.stereotype.Component;
14 |
15 | import java.util.List;
16 |
17 | /**
18 | * @author: Yakai Zheng(zhengyk@cloud-young.com)
19 | * @date: Created on 2018/12/16
20 | * @description:
21 | * @version: 1.0
22 | */
23 | @Slf4j
24 | @Component
25 | public class CanalSchedul{
26 |
27 | @Autowired
28 | private CanalConnector canalConnector;
29 |
30 | @Autowired
31 | private InsertHandler insertHandler;
32 |
33 | @Value("${canal.batchSize}")
34 | private int batchSize;
35 |
36 | @Async("canal")
37 | @Scheduled(fixedDelay = 200) //每200毫秒拉取一次数据
38 | public void fetch() {
39 | try {
40 | Message message = canalConnector.getWithoutAck(batchSize);
41 | long batchId = message.getId();
42 | log.debug("batchId={}" , batchId);
43 | try {
44 | List entries = message.getEntries();
45 | if (batchId != -1 && entries.size() > 0) {
46 | entries.forEach(entry -> {
47 | if (entry.getEntryType() == EntryType.ROWDATA) {
48 | insertHandler.handleMessage(entry);
49 | }
50 | });
51 | }
52 | canalConnector.ack(batchId);
53 | } catch (Exception e) {
54 | log.error("批量获取 mysql 同步信息失败,batchId回滚,batchId=" + batchId, e);
55 | canalConnector.rollback(batchId);
56 | }
57 | } catch (Exception e) {
58 | log.error("Canal定时任务异常!", e);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/cn/zhengyk/sync/utils/RedisUtil.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync.utils;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.core.RedisTemplate;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.util.CollectionUtils;
7 |
8 | import java.util.concurrent.TimeUnit;
9 |
10 | /**
11 | * Redis 工具类
12 | * @author Yakai Zheng
13 | */
14 | @Component
15 | public final class RedisUtil {
16 |
17 | /**
18 | * 默认过期时长,单位:秒
19 | */
20 | public final static long DEFAULT_EXPIRE = 1800;
21 |
22 | @Autowired
23 | private RedisTemplate redisTemplate;
24 |
25 | // =============================common============================
26 |
27 | /**
28 | * 指定 key 失效时间
29 | * @param key 键
30 | * @param seconds 时间(秒)
31 | * @return
32 | */
33 | public void expire(String key, long seconds) {
34 | if (seconds > 0) {
35 | redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
36 | }
37 | }
38 |
39 | /**
40 | * 指定 key 默认失效时间
41 | * @param key 键
42 | * @return
43 | */
44 | public void expireDefault(String key) {
45 | this.expire(key, DEFAULT_EXPIRE);
46 | }
47 |
48 | /**
49 | * 根据key 获取过期时间
50 | * @param key 键 不能为null
51 | * @return 时间(秒) 返回0代表为永久有效
52 | */
53 | public long getExpire(String key) {
54 | return redisTemplate.getExpire(key, TimeUnit.SECONDS);
55 | }
56 | /**
57 | * 判断key是否存在
58 | * @param key 键
59 | * @return true 存在 false不存在
60 | */
61 | public boolean hasKey(String key) {
62 | return redisTemplate.hasKey(key);
63 | }
64 | /**
65 | * 删除key
66 | * @param key 可以传一个值 或多个
67 | */
68 | public void del(String... key) {
69 | if (key != null && key.length > 0) {
70 | if (key.length == 1) {
71 | redisTemplate.delete(key[0]);
72 | } else {
73 | redisTemplate.delete(CollectionUtils.arrayToList(key));
74 | }
75 | }
76 | }
77 | // ============================String=============================
78 | /**
79 | * 获取 key 的 value
80 | * @param key 键
81 | * @return 值
82 | */
83 | public Object get(String key) {
84 | return key == null ? null : redisTemplate.opsForValue().get(key);
85 | }
86 | /**
87 | * 永久 set
88 | * @param key 键
89 | * @param value 值
90 | * @return true成功 false失败
91 | */
92 | public void set(String key, Object value) {
93 | redisTemplate.opsForValue().set(key, value);
94 | }
95 |
96 | /**
97 | * 默认set 默认实效时间 DEFAULT_EXPIRE
98 | * @param key 键
99 | * @param value 值
100 | * @return true成功 false失败
101 | */
102 | public void setDefault(String key, Object value) {
103 | this.set(key, value, DEFAULT_EXPIRE);
104 | }
105 |
106 | /**
107 | * 普通缓存放入并设置时间
108 | * @param key 键
109 | * @param value 值
110 | * @param seconds 时间(秒) time要大于0 如果time小于等于0 将设置无限期
111 | * @return true成功 false 失败
112 | */
113 | public void set(String key, Object value, long seconds) {
114 | if (seconds > 0) {
115 | redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
116 | } else {
117 | set(key, value);
118 | }
119 | }
120 | /**
121 | * 递增
122 | * @param key 键
123 | * @param delta 要增加几(大于0)
124 | * @return
125 | */
126 | public long incr(String key, long delta) {
127 | if (delta <= 0) {
128 | throw new RuntimeException("递增因子必须大于0");
129 | }
130 | return redisTemplate.opsForValue().increment(key, delta);
131 | }
132 | /**
133 | * 递减
134 | * @param key 键
135 | * @param delta 要减少几(小于0)
136 | * @return
137 | */
138 | public long decr(String key, long delta) {
139 | if (delta <= 0) {
140 | throw new RuntimeException("递减因子必须大于0");
141 | }
142 | return redisTemplate.opsForValue().increment(key, -delta);
143 | }
144 | }
--------------------------------------------------------------------------------
/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | # datasource:
3 | # #使用springboot2.0后内置的HikariCP
4 | # driver-class-name: com.mysql.jdbc.Driver
5 | # url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
6 | # username: yakai
7 | # password: Yakai2018@
8 | # type: com.zaxxer.hikari.HikariDataSource
9 | # hikari:
10 | # minimum-idle: 5
11 | # maximum-pool-size: 15
12 | # auto-commit: true
13 | # idle-timeout: 30000
14 | # pool-name: HikariCP
15 | # max-lifetime: 1800000
16 | # connection-timeout: 30000
17 | # connection-test-query: SELECT 1
18 | #Redis配置
19 | redis:
20 | host: localhost
21 | port: 7000
22 | password: Yakai2018@
23 | database: 0
24 | jedis:
25 | pool:
26 | #最大连接数(负值表示没有限制)
27 | max-active: 100
28 | #最大空闲链接
29 | max-idle: 10
30 | #最小空闲链接
31 | min-idle: 5
32 | #最大阻塞时间 负值表示不限制
33 | max-wait: -1ms
34 |
35 |
36 |
37 | #mybatis:
38 | # mapper-locations: classpath:sqlmap/*.xml
39 | # type-aliases-package: cn.zhengyk.sync.model
40 |
41 | # canal相关配置
42 | canal:
43 | host: localhost
44 | port: 11111
45 | destination: example
46 | username:
47 | password:
48 | subscribe: test.blog
49 | batchSize: 1000
50 | # subscribe 过滤规则
51 | # 1) 所有:.* or .*\\..*
52 | # 2) "test"库下所有表: test\\..*
53 | # 3) "test"下的以"sys"打头的表:test\\.sys.*
54 | # 4) "test"下的具体一张表:test.blog blog表
55 | # 5) 多个规则组合使用:test\\..*,test.sys_user,test.sys_role (逗号分隔)
56 |
57 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | application:
3 | name: cannal-sync-redis
4 | profiles:
5 | active: dev
6 | server:
7 | port: 22222
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) %L - %msg %n
11 | UTF-8
12 |
13 |
14 |
15 |
16 |
17 |
18 | ${LOG_HOME}/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
19 |
20 | 5
21 |
22 |
23 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %L - %msg%n
24 | UTF-8
25 |
26 |
27 |
28 | 1GB
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/main/resources/sql/blog.sql:
--------------------------------------------------------------------------------
1 |
2 | SET NAMES utf8mb4;
3 | SET FOREIGN_KEY_CHECKS = 0;
4 | DROP TABLE IF EXISTS `blog`;
5 | CREATE TABLE `blog` (
6 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
7 | `author` varchar(20) NOT NULL DEFAULT '',
8 | `title` varchar(100) NOT NULL,
9 | `subtitle` varchar(100) NOT NULL,
10 | `content` longtext NOT NULL,
11 | `createTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
12 | `updateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
13 | PRIMARY KEY (`id`)
14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
15 |
16 | SET FOREIGN_KEY_CHECKS = 1;
17 |
--------------------------------------------------------------------------------
/src/test/java/cn/zhengyk/sync/ApplicationTests.java:
--------------------------------------------------------------------------------
1 | package cn.zhengyk.sync;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class ApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
18 |
--------------------------------------------------------------------------------