赞
踩
缓存数据同步的常见方式有三种:
给缓存设置有效期,到期后自动删除。再次查询时更新。
- 优点:简单,方便。
- 缺点:时效性差,缓存过期之前可能不一致。
- 场景:更新频率较低,时效性要求低的业务。
在修改数据库的同时,直接修改缓存。
- 优点:时效性强,缓存与数据库强一致。
- 缺点:有代码侵入,耦合度高。
- 场景:对一致性,时效性要求较高的缓存数据。
修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据。
- 优点:低耦合,可以同时通知多个缓存服务。
- 缺点:时效性一般,可能存在中间不一致状态。
- 状态:时效性一般,有多个服务需要同步。
Canal: 阿里巴巴旗下的一款开源项目,基于Java开发。基于数据库增量日志解析,提供增量数据订阅和消费。
Canal是基于MySQL主从同步来实现的,MySQL主从同步的原理如下:
Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。
修改my.cnf文件
如果修改不起作用则修改/etc/mysql/mysql.conf.d/mysqld.cnf 文件
# 设置binary log文件的存放地址和文件名,叫做mysql-bin
log-bin=/var/lib/mysql/mysql-bin
# 指定对哪个database记录binary log events,这里记录item这个库
binlog-do-db=item
create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;
重启mysql容器即可
docker restart mysql
测试设置是否成功:在mysql控制台,或者Navicat中,输入命令:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 154 | item | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
我们需要创建一个网络,将MySQL、Canal、MQ放到同一个Docker网络中:
docker network create binlog-network
让mysql加入这个网络:
docker network connect binlog-network mysql
下载canal
docker pull canal/canal-server:v1.1.5
创建canal容器:
docker run -p 11111:11111 --name canal \
-e canal.destinations=item\
-e canal.instance.master.address=mysql5.7:3306 \
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
-e canal.instance.filter.regex=item\\..* \
--network binlog-network \
-d canal/canal-server:v1.1.5
说明:
- `-p 11111:11111`:这是canal的默认监听端口
- `-e canal.instance.master.address=mysql:3306`:数据库地址和端口,如果不知道mysql容器地址,可以通过`docker inspect 容器id`来查看
- `-e canal.instance.dbUsername=canal`:数据库用户名
- `-e canal.instance.dbPassword=canal` :数据库密码
- `-e canal.instance.filter.regex=`:要监听的表名称
表名称监听支持的语法:
mysql 数据解析关注的表,Perl正则表达式.
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\)
常见例子:
1. 所有表:.* or .*\\..*
2. canal schema下所有表: canal\\..*
3. canal下的以canal打头的表:canal\\.canal.*
4. canal schema下的一张表:canal.test1
5. 多个规则组合使用然后以逗号隔开:canal\\..*,mysql.test1,mysql.test2
查看启动结果
[root@localhost home]# docker logs -f canal DOCKER_DEPLOY_TYPE=VM ==> INIT /alidata/init/02init-sshd.sh ==> EXIT CODE: 0 ==> INIT /alidata/init/fix-hosts.py ==> EXIT CODE: 0 ==> INIT DEFAULT Generating SSH1 RSA host key: [ OK ] Starting sshd: [ OK ] Starting crond: [ OK ] ==> INIT DONE ==> RUN /home/admin/app.sh ==> START ... start canal ... start canal successful ==> START SUCCESSFUL ...
查看canal连接数据库状态:
[root@f1c7640d4486 admin]# tail canal-server/logs/item/item.log
2021-12-05 11:07:26.012 [main] INFO c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-item
2021-12-05 11:07:26.067 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^item\..*$
2021-12-05 11:07:26.067 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter : ^mysql\.slave_.*$
2021-12-05 11:07:26.083 [main] INFO c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
2021-12-05 11:07:26.431 [destination = item , address = mysql5.7/172.18.0.2:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> begin to find start position, it will be long time for reset or first position
2021-12-05 11:07:26.431 [destination = item , address = mysql5.7/172.18.0.2:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - prepare to find start position just show master status
2021-12-05 11:07:27.510 [destination = item , address = mysql5.7/172.18.0.2:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> find start position successfully, EntryPosition[included=false,journalName=mysql-bin.000001,position=4,serverId=1000,gtid=<null>,timestamp=1638669831000] cost : 1055ms , the next step is binlog dump
增加依赖:
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
application.yml中增加canal的连接配置
canal:
destination: item
server: 192.168.25.129:11111
redis的Handler
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.List; @Component public class RedisHandler implements InitializingBean { @Autowired private StringRedisTemplate redisTemplate; @Autowired private IItemService itemService; @Autowired private IItemStockService stockService; private static final ObjectMapper MAPPER = new ObjectMapper(); @Override public void afterPropertiesSet() throws Exception { // 初始化缓存 // 1.查询商品信息 List<Item> itemList = itemService.list(); // 2.放入缓存 for (Item item : itemList) { // 2.1.item序列化为JSON String json = MAPPER.writeValueAsString(item); // 2.2.存入redis redisTemplate.opsForValue().set("item:id:" + item.getId(), json); } // 3.查询商品库存信息 List<ItemStock> stockList = stockService.list(); // 4.放入缓存 for (ItemStock stock : stockList) { // 2.1.item序列化为JSON String json = MAPPER.writeValueAsString(stock); // 2.2.存入redis redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json); } } public void saveItem(Item item) { try { String json = MAPPER.writeValueAsString(item); redisTemplate.opsForValue().set("item:id:" + item.getId(), json); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } public void deleteItemById(Long id) { redisTemplate.delete("item:id:" + id); } }
增加canal的Handler
import com.github.benmanes.caffeine.cache.Cache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import top.javatool.canal.client.annotation.CanalTable; import top.javatool.canal.client.handler.EntryHandler; @CanalTable("tb_item") @Component public class ItemHandler implements EntryHandler<Item> { @Autowired private RedisHandler redisHandler; @Autowired private Cache<Long, Item> itemCache; @Override public void insert(Item item) { // 写数据到JVM进程缓存 itemCache.put(item.getId(), item); // 写数据到redis redisHandler.saveItem(item); } @Override public void update(Item before, Item after) { // 写数据到JVM进程缓存 itemCache.put(after.getId(), after); // 写数据到redis redisHandler.saveItem(after); } @Override public void delete(Item item) { // 删除数据到JVM进程缓存 itemCache.invalidate(item.getId()); // 删除数据到redis redisHandler.deleteItemById(item.getId()); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。