商城-Nginx限流、Canal实现广告缓存数据同步

nginx限流

一般情况下,首页的并发量是比较大的,即使 有了多级缓存,当用户不停的刷新页面的时候,也是没有必要的,另外如果有恶意的请求 大量达到,也会对系统造成影响。

而限流就是保护措施之一。

生活中限流对比

  • 水坝泄洪,通过闸口限制洪水流量(控制流量速度)。

  • 办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)

  • 火车站排队买票安检,通过排队 的方式依次放入。(缓存带处理任务)

nginx的限流

nginx提供两种限流的方式:

  • 一是控制速率

  • 二是控制并发连接数

控制速率

控制速率的方式之一就是采用漏桶算法。

(1)漏桶算法实现控制速率限流

漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),

然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:

(2)nginx的配置

配置示意图如下:

配置说明:

binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
zone:定义共享内存区来存储访问信息, contentRateLimit:10m 表示一个大小为10M,名字为contentRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求.我们这里设置成2 方便测试。

是nginx,不要写错了

user  root root;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #定义Nginx缓存模块,模块名字叫dis_cache,容量大小128M
    lua_shared_dict dis_cache 128m;

    #限流设置
    limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;

    #根据IP地址来限制,存储内存大小10M
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    #个人IP显示
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    #针对整个服务所有的并发量控制
    limit_conn_zone $server_name zone=perserver:10m;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        #监听的域名
        server_name  localhost;

        #192.168.211.1
        location /brand {
            limit_conn perip 3;      #单个客户端ip与服务器的连接数.
            limit_conn perserver 5;  #限制与服务器的总连接数
            #同一个IP只允许有2个并发连接
            #limit_conn addr 2;
            #所有以/brand的请求,都将交给  192.168.211.1服务器的18081程序处理.
            proxy_pass http://192.168.2.1:18081;
        }

        #表示所有以 localhost/read_content的请求都由该配置处理
        location /read_content {
            #使用指定限流配置,burst=4表示允许同时有4个并发连接,如果不能同时处理,则会放入队列,等请求处理完成后,再从队列中拿请求
            #nodelay 并行处理所有请求
            limit_req zone=contentRateLimit burst=4 nodelay;
            #content_by_lua_file:所有请求都交给指定的lua脚本处理(/root/lua/read_content.lua)
            content_by_lua_file /root/lua/read_content.lua;
        }

        #表示所有以 localhost/update_content的请求都由该配置处理
        location /update_content {
            #content_by_lua_file:所有请求都交给指定的lua脚本处理(/root/lua/update_content.lua)
            content_by_lua_file /root/lua/update_content.lua;
        }
    }
}

测试:

重新加载配置文件

cd /usr/local/openresty/nginx/sbin

./nginx -s reload

 

(3)处理突发流量

上面例子限制 2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。

例如,如下配置表示:

上图代码如下:

server {
    listen       80;
    server_name  localhost;
    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
    location /read_content {
        limit_req zone=contentRateLimit burst=4;
        content_by_lua_file /root/lua/read_content.lua;
    }
}

burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。

此处,**burst=4 **,若同时有4个请求到达,Nginx 会处理第一个请求,剩余3个请求将放入队列,然后每隔500ms从队列中获取一个请求进行处理。若请求数大于4,将拒绝处理多余的请求,直接返回503.

不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。

因此,burst 往往结合 nodelay 一起使用。

例如:如下配置:

server {
    listen       80;
    server_name  localhost;
    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
    location /read_content {
        limit_req zone=contentRateLimit burst=4 nodelay;
        content_by_lua_file /root/lua/read_content.lua;
    }
}

如上表示:

平均每秒允许不超过2个请求,突发不超过4个请求,并且处理突发4个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。

如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。完整配置代码如下:

user  root root;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #cache
    lua_shared_dict dis_cache 128m;

    #限流设置
    limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location /update_content {
            content_by_lua_file /root/lua/update_content.lua;
        }

        location /read_content {
            limit_req zone=contentRateLimit burst=4 nodelay;
            content_by_lua_file /root/lua/read_content.lua;
        }
    }
}

测试:如下图 在1秒钟之内可以刷新4次,正常处理。 

但是超过之后,连续刷新5次,抛出异常。

控制并发量(连接数)

ngx_http_limit_conn_module 提供了限制连接数的能力。主要是利用limit_conn_zone和limit_conn两个指令。

利用连接数限制 某一个用户的ip连接的数量来控制流量。

注意:并非所有连接都被计算在内 只有当服务器正在处理请求并且已经读取了整个请求头时,才会计算有效连接。此处忽略测试。

(1)配置限制固定连接数

limit_conn_zone $binary_remote_addr zone=addr:10m;  表示限制根据用户的IP地址来显示,设置存储地址为的内存大小10M

limit_conn addr 2;   表示 同一个地址只允许连接2次。

测试:

此时开3个线程,测试的时候会发生异常,开2个就不会有异常

 (2)限制每个客户端IP与服务器的连接数,同时限制与虚拟服务器的连接总数。(了解)

如下配置:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m; 
server {  
    listen       80;
    server_name  localhost;
    charset utf-8;
    location / {
        limit_conn perip 10;#单个客户端ip与服务器的连接数.
        limit_conn perserver 100; #限制与服务器的总连接数
        root   html;
        index  index.html index.htm;
    }
}

修改本地IP,192.168.2.173

修改好ip,保存,退出

记得重新加载:

cd ../sbin

./nginx -s reload

注册中心启动:

jmeter测试:

连接数达到3次后就访问不了

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Canal同步广告(canal只适用于MySQL数据库,不适应于别的)

canal可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据。

canal是应阿里巴巴存在杭州和美国的双机房部署,存在跨机房同步的业务需求而提出的。

阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。

Canal工作原理

原理相对比较简单:

  1. canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
  2. mysql master收到dump请求,开始推送binary log给slave(也就是canal)
  3. canal解析binary log对象(原始为byte流)

canal需要使用到mysql,我们需要先安装mysql,给大家发的虚拟机中已经安装了mysql容器,但canal是基于mysql的主从模式实现的,所以必须先开启binlog.

MySQL开启binlog模式

先使用docker 创建mysql容器,此处不再演示.

(1) 连接到mysql中,并修改/etc/mysql/mysql.conf.d/mysqld.cnf 需要开启主 从模式,开启binlog模式。

执行如下命令,编辑mysql配置文件

docker exec -it mysql /bin/bash
cd /etc/mysql/mysql.conf.d
vi mysqld.cnf

接上面:

cd mysql.conf.d

vi mysqld.cnf

修改mysqld.cnf配置文件,添加如下配置:

log-bin/var/lib/mysql/mysql-bin
server-id=12345

可以看到已经帮我们配好了

(2) 创建账号 用于测试使用,

使用root账号创建用户并授予权限

账号:canal

密码:canal

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

(3)重启mysql容器

docker restart mysql

canal容器安装

下载镜像:

docker pull docker.io/canal/canal-server

容器安装

docker run -p 11111:11111 --name canal -d docker.io/canal/canal-server

进入容器,修改核心配置canal.properties 和instance.properties,canal.properties 是canal自身的配置,instance.properties是需要同步数据的数据库连接配置。

执行代码如下:

docker exec -it canal /bin/bash
cd canal-server/conf/
vi canal.properties
cd example/
vi instance.properties

修改canal.properties的id,不能和mysql的server-id重复,如下图:

修改instance.properties,配置数据库连接地址:192.168.2.132

这里的canal.instance.filter.regex有多种配置,如下:

可以参考地址如下:

https://github.com/alibaba/canal/wiki/AdminGuide
mysql 数据解析关注的表,Perl正则表达式.
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\) 
常见例子:
1.  所有表:.*   or  .*\\..*
2.  canal schema下所有表: canal\\..*
3.  canal下的以canal打头的表:canal\\.canal.*
4.  canal schema下的一张表:canal.test1
5.  多个规则组合使用:canal\\..*,mysql.test1,mysql.test2 (逗号分隔)
注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤)

配置完成后,设置开机启动,并记得重启canal

docker update --restart=always canal
docker restart canal

canal微服务搭建

当用户执行 数据库的操作的时候,binlog 日志会被canal捕获到,并解析出数据。我们就可以将解析出来的数据进行同步到redis中即可。

思路:创建一个独立的程序,并监控canal服务器,获取binlog日志,解析数据,将数据更新到redis中。这样广告的数据就更新了。

在changgou-service下创建changgou-service-canal工程,并引入相关配置。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!--canal依赖-->
    <dependency>
        <groupId>com.xpand</groupId>
        <artifactId>starter-canal</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

canal不能导入maven依赖,中央仓库及阿里仓库没有相关的包,需要自己再本地进行安装

前往https://github.com/chenqian56131/spring-boot-starter-canal

下载完成进入项目根目录starter-canal 中 ,在文件管理器地址栏输入cmd,进入到cmd窗口,输入命令行如下:

已经没有红叉了

application.yml配置

server:
  port: 18083
spring:
  application:
    name: canal
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
        #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
#canal配置
canal:
  client:
    instances:
      example:
        host: 192.168.2.132
        port: 11111

创建启动类,代码如下:

package com.changgou;

import com.xpand.starter.canal.annotation.EnableCanalClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableCanalClient
public class CanalApplication {

    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class,args);
    }
}

创建一个CanalDataEventListener类,实现对表增删改操作的监听,代码如下:

注意java8的写法

package com.changgou.canal;

import com.alibaba.otter.canal.protocol.CanalEntry;
import com.xpand.starter.canal.annotation.*;

@CanalEventListener
public class CanalDataEventListener {

    /***
     * 增加数据监听
     * @param eventType
     * @param rowData
     */
    @InsertListenPoint
    public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }

    /***
     * 修改数据监听
     * @param rowData
     */
    @UpdateListenPoint
    public void onEventUpdate(CanalEntry.RowData rowData) {
        System.out.println("UpdateListenPoint");
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }

    /***
     * 删除数据监听
     * @param eventType
     */
    @DeleteListenPoint
    public void onEventDelete(CanalEntry.EventType eventType) {
        System.out.println("DeleteListenPoint");
    }

    /***
     * 自定义数据修改监听
     * @param eventType
     * @param rowData
     */
    @ListenPoint(destination = "example", schema = "changgou_content", table = {"tb_content_category", "tb_content"}, eventType = CanalEntry.EventType.UPDATE)
    public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        System.err.println("DeleteListenPoint");
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }

}

测试:启动注册中心EurekaApplication,CanalApplication

可以看到控制台上的数据:

我在数据库中新增一条记录,控制台马上就可以监听到:

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

广告同步

如上图,每次执行广告操作的时候,MySQL会记录操作日志,然后将操作日志发送给canal,canal将操作记录发送给canal微服务,

canal微服务根据修改的分类ID调用content微服务查询分类对应的所有广告,canal微服务再将所有广告存入到Redis缓存。

content-api搭建

创建changgou-service-content-api,将代码生成器生成的pojo拷贝到API工程中

content微服务搭建

对应的dao、service、controller由代码生成器生成,拷贝过去的

pom.xml

<?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">
    <parent>
        <artifactId>changgou-service</artifactId>
        <groupId>com.changgou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>changgou-service-content</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <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>
    </dependencies>



</project>

application.yml配置:

server:
  port: 18084
spring:
  application:
    name: content
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.2.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

 启动类ContentApplication创建

package com.changgou.content;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.changgou.content.dao"})
public class ContentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ContentApplication.class);
    }
}

在content微服务中,添加根据分类查询广告的方法

/***
 * 根据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);
}


/***
 * 根据categoryId查询广告集合
 * @param id
 * @return
 */
List<Content> findByCategory(Long id);


/***
 * 根据分类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);
}

feign配置

在changgou-service-content-api工程中添加feign,代码如下:

package com.changgou.content.feign;

import com.changgou.content.pojo.Content;
import entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@FeignClient(name="content")
@RequestMapping(value = "/content")
public interface ContentFeign {

    /*
    根据分类的ID 获取到广告列表
     */
    @GetMapping(value = "/list/category/{id}")
    Result<List<Content>> findByCategory(@PathVariable(name="id") Long id);

}

同步实现:

在canal微服务中修改如下:

(1)配置redis

修改application.yml配置文件,添加redis配置,如下代码:

server:
  port: 18082
spring:
  application:
    name: canal
  redis:
    host: 192.168.2.132
    port: 6379
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
#canal配置
canal:
  client:
    instances:
      example:
        host: 192.168.2.132
        port: 11111

(2)启动类中开启feign

CanalApplication,添加@EnableFeignClients注解

(3)同步实现

修改监听类CanalDataEventListener,实现监听广告的增删改,并根据增删改的数据使用feign查询对应分类的所有广告,将广告存入到Redis中,代码如下:

package com.changgou.canal;

import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.changgou.content.feign.ContentFeign;
import com.changgou.content.pojo.Content;
import com.xpand.starter.canal.annotation.CanalEventListener;
import com.xpand.starter.canal.annotation.ListenPoint;
import entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.List;

@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));
        //经测试,boundValueOps和opsForValue这两个方法都可以
        //stringRedisTemplate.opsForValue().set("test", "100",60*10, TimeUnit.SECONDS);
        //stringRedisTemplate.opsForValue().set("content_" + categoryId, 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;
    }


    //--------------------------------------------------------------------------------
    /***
     * 增加数据监听
     * @param eventType
     * @param rowData
     */
    /*@InsertListenPoint
    public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }*/

    /***
     * 修改数据监听
     * @param rowData
     */
    /*@UpdateListenPoint
    public void onEventUpdate(CanalEntry.RowData rowData) {
        System.out.println("UpdateListenPoint");
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }*/

    /***
     * 删除数据监听
     * @param eventType
     */
    /*@DeleteListenPoint
    public void onEventDelete(CanalEntry.EventType eventType) {
        System.out.println("DeleteListenPoint");
    }*/

    /***
     * 自定义数据修改监听
     * @param eventType
     * @param rowData
     */
    /*@ListenPoint(destination = "example", schema = "changgou_content", table = {"tb_content_category", "tb_content"}, eventType = CanalEntry.EventType.UPDATE)
    public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        System.err.println("DeleteListenPoint");
        stringRedisTemplate.opsForValue().set("test", "100",60*10, TimeUnit.SECONDS);
        rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
    }*/

}

测试:

原本表中的记录:

原本redis记录,清空后没有任何值:

启动注册中心EurekaApplication,广告微服务ContentApplication,canal微服务CanalApplication

http://localhost:7001/

在广告表中新增一条记录:

查看redis:

新增一条分类id为2的数据:

查看redis:

测试OK!

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

实际开发过程中遇到的问题,报错:

连不上redis、方法onEventCustomUpdate有问题

开始以为是boundValueOps方法不对,所以存不进redis:

//3.使用redisTemplate存储到redis中
stringRedisTemplate.boundValueOps("content_" + categoryId).set(JSON.toJSONString(data));
//经测试,boundValueOps和opsForValue这两个方法都可以
//stringRedisTemplate.opsForValue().set("test", "100",60*10, TimeUnit.SECONDS);
//stringRedisTemplate.opsForValue().set("content_" + categoryId, JSON.toJSONString(data));

最后发现这两个方法都可以。

继续排查,是不是密码问题,连不上redis 

通过命令config get requirepass,发现redis默认没有设置密码:

所以password: 123456是错误的。

去掉密码,然后还是连不上,哎,最后才发现是yml配置中redis格式不对,redis要和application对齐,它们是同一级

改成这样即可:

测试ok,可以同步数据

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHOU_VIP

您的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值