代码见 https://github.com/betterGa/ChangGou

可以看到,需要一个广告微服务,当它执行广告操作的时候(比如 查询),会记录 操作日志 binlog 到 MySQL,然后将 操作日志 发送给 canal ,canal 将操作记录发送给 canal 微服务 ,canal 微服务根据修改的分类 ID 调用 content 微服务查询分类对应的所有广告,canal 微服务 还会将所有广告存入到 Redis 缓存。
一、搭建广告微服务
首先,在 changgou-service-api 中创建 changgou-service-content-api,将和数据库里的表映射的 pojo 拷贝到 API 工程中,和广告相关的表是 tb_content 和 tb_content_category。
接下来,在 changgou-service 中搭建 changgou-service-content 微服务,对应的 dao、service、controller、pojo 由代码生成器生成,并导入依赖:
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-service-content-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
提供启动类:
@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {
"content.dao"})
public class ContentApplication {
public static void main(String[] args) {
SpringApplication.run(ContentApplication.class);
}
}
提供 application.yml 配置文件:
server:
port: 18084
spring:
application:
name: content
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.211.132:3306/changgou_content?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
mybatis:
configuration:
map-underscore-to-camel-case: true #开启驼峰功能
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
二、广告查询
在 content 微服务中,添加 根据分类查询广告 的逻辑。
(1)业务层
修改 changgou-service-content 的com.changgou.content.service.ContentService 接口,添加根据分类 ID 查询广告数据:
/**
* 根据广告分类 ID 查询广告集合
* @param id
* @return
*/
List<Content> findByCategory(Long id);
实现:
/**
* 根据 Category ID 查询广告信息
* @param id
* @return
*/
@Override
public List<Content> findByCategory(Long id) {
Content content=new Content();
content.setCategoryId(id);
content.setStatus("1");
return contentMapper.select(content);
}
(2)控制层
/***
* 根据categoryId查询广告集合
*/
@GetMapping(value = "/list/category/{id}")
public Result<List<Content>> findByCategory(@PathVariable Long id){
//根据分类ID查询广告集合
List<Content> contents = contentService.findByCategory(id);
return new Result<List<Content>>(true,StatusCode.OK,"查询成功!",contents);
}
(3)feign 配置
在 changgou-service-content-api 工程中添加 feign,代码如下:
@FeignClient(name="content")
@RequestMapping(value = "/content")
public interface ContentFeign {
/***
* 根据分类 ID 查询所有广告
*/
@GetMapping(value = "/list/category/{id}")
Result<List<Content>> findByCategory(@PathVariable Long id);
}
三、广告同步
在 canal 微服务中
(1)启动类中开启 feign
修改CanalApplication,添加 @EnableFeignClients 注解:
(2)同步实现
修改监听类 CanalDataEventListener:
@CanalEventListener
public class CanalDataEventListener {
@Autowired
private ContentFeign contentFeign;
//字符串
@Autowired
private StringRedisTemplate stringRedisTemplate;
//自定义数据库的 操作来监听
//destination = "example"
@ListenPoint(destination = "example",
schema = "changgou_content",
table = {
"tb_content", "tb_content_category"},
eventType = {
CanalEntry.EventType.UPDATE,
CanalEntry.EventType.DELETE,
CanalEntry.EventType.INSERT})
public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
//1.获取列名 为category_id的值
String categoryId = getColumnValue(eventType, rowData);
//2.调用feign 获取该分类下的所有的广告集合
Result<List<Content>> categoryresut = contentFeign.findByCategory(Long.valueOf(categoryId));
List<Content> data = categoryresut.getData();
//3.使用redisTemplate存储到redis中
stringRedisTemplate.boundValueOps("content_" + categoryId).set(JSON.toJSONString(data));
}
private String getColumnValue(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
String categoryId = "";
//判断 如果是删除 则获取beforlist
if (eventType == CanalEntry.EventType.DELETE) {
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (column.getName().equalsIgnoreCase("category_id")) {
categoryId = column.getValue();
return categoryId;
}
}
} else {
//判断 如果是添加 或者是更新 获取afterlist
for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
if (column.getName().equalsIgnoreCase("category_id")) {
categoryId = column.getValue();
return categoryId;
}
}
}
return categoryId;
}
}
可以看到,代码实现了监听广告的增删改操作,并通过 getColumnValue 方法判断了操作类型,无论是增、删、改操作,都会使用 Feign 把 根据 操作的 分类 ID 查询的广告记录 ,存入到 Redis 中。
测试:
在数据库中新增一条 category_id = 2 的记录:
Redis 中:
可以看到 category_id=2 的记录都缓存到了 Redis 中。
这时,再从数据库中删除新增的记录,看 Redis:
四、总结
不论是 增、删 还是 改 (查操作就没必要同步了),思路是监控操作,一旦对 MySQL 数据库进行了增删改操作,就会通过遍历 进行了操作的记录 的每个属性,从中获取到 category_id,即 分类 ID,然后通过 Feign 调用,到 MySQL 中根据分类 ID 查询广告信息集,并把广告信息集存储到 Redis 中,因为是以 “category_xxx”(xxx 就是具体的 ID)为 key,广告信息集为 value 的形式,所以相同 key 的情况下,新的 value 会覆盖掉原先的 value,这样每次存储的就是和 MySQL 同步的数据,实现了俩数据库的一致性。相当于以 分类 ID 为维度,把数据从 MySQL 同步到 Redis 中。没有开篇示意图的那么复杂,不需要直接操作 binlog。