黑马程序员Redis教程学习笔记(第5部分):缓存同步与批处理优化

2026-04-02
2
-
- 分钟
|

黑马程序员Redis教程学习笔记(第4部分):缓存同步与批处理优化

课程概述

本篇笔记涵盖黑马程序员Redis教程中关于缓存同步与批处理优化的重要内容,主要包括Canal缓存同步、Redis键值设计最佳实践以及批处理优化策略。

1. Canal缓存同步实现

Canal简介

Canal是阿里巴巴开源的MySQL数据库增量日志解析中间件,它伪装成MySQL的Slave节点,监听MySQL的binlog变化,实现数据的实时同步。

Canal工作原理

  • MySQL主从同步:MySQL主节点记录binlog文件,从节点读取并重放操作
  • Canal伪装:Canal伪装成MySQL从节点,监听binlog变化
  • 数据同步:当数据库发生变更时,Canal立即感知并通知缓存服务更新

Canal安装配置

1. 开启MySQL主从同步

修改MySQL配置文件:

# binlog文件位置和名称
log-bin=mysql-bin
# 监控的数据库
binlog-do-db=hmdp

2. 创建Canal用户并授权

-- 创建Canal用户
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';
-- 授权
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 刷新权限
FLUSH PRIVILEGES;

3. 创建Docker网络

# 创建网络
docker network create hmdp

# 将MySQL容器加入网络
docker network connect hmdp mysql

4. 运行Canal容器

docker run -p 11111:11111 --name canal \
  -e canal.destinations=hmdp \
  -e canal.instance.master.address=mysql: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 \
  --network=hmdp \
  -d canal/canal-server:v1.1.5

Canal客户端集成

1. 引入依赖

<dependency>
    <groupId>top.javatool</groupId>
    <artifactId>canal-spring-boot-starter</artifactId>
    <version>1.2.1-RELEASE</version>
</dependency>

2. 配置文件

canal:
  destination: hmdp  # 与Canal服务器配置保持一致
  server: 192.168.150.101:11111  # Canal服务器地址

3. 实体类注解

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_item")
public class Item {
    @TableId(type = IdType.ASSIGN_ID)
    @Column
    private Long id;  // 主键映射
    
    private String name;
    private String title;
    private Long price;
    private String image;
    private String category;
    private String brand;
    
    @TableField(exist = false)
    @Transient
    private Integer stock;
    
    @TableField(exist = false)
    @Transient
    private Integer sold;
}

4. Canal处理器实现

@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {

    @Autowired
    private RedisHandler redisHandler;

    @Override
    public void insert(Item item) {
        // 新增数据到缓存
        redisHandler.saveItem(item);
    }

    @Override
    public void update(Item before, Item after) {
        // 更新缓存数据
        redisHandler.saveItem(after);
    }

    @Override
    public void delete(Item item) {
        // 删除缓存数据
        redisHandler.deleteItemById(item.getId());
    }
}

5. Redis缓存操作封装

@Component
public class RedisHandler {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    public void saveItem(Item item) {
        try {
            String json = objectMapper.writeValueAsString(item);
            redisTemplate.opsForValue().set("item:" + item.getId(), json);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public void deleteItemById(Long id) {
        redisTemplate.delete("item:" + id);
    }
}

2. Redis键值设计最佳实践

Key设计规范

  • 格式:使用冒号分隔的多段格式,如业务名:数据名:ID
  • 示例login:user:1001表示登录业务的用户ID为1001的用户信息
  • 长度:不超过44个字节,避免使用特殊字符
  • 优势
    • 可读性强,便于理解
    • 避免key冲突
    • 便于管理,支持层级结构

Value设计考虑

1. BigKey问题识别

  • 字符串类型:大小不超过10KB
  • 集合类型:元素数量不超过1000个
  • 检测命令
    • MEMORY USAGE key:查看key占用内存
    • STRLEN key:查看字符串长度
    • LLEN key:查看列表长度
    • HLEN key:查看哈希长度
    • SCARD key:查看集合大小

2. BigKey问题危害

  • 网络阻塞:大key占用带宽,影响其他请求
  • 数据倾斜:导致Redis节点内存使用不均
  • Redis阻塞:单线程处理大key操作时阻塞其他请求
  • CPU占用:序列化/反序列化消耗大量CPU

3. BigKey检测方案

方案一:使用redis-cli –bigkeys
redis-cli -a 密码 --bigkeys
方案二:使用SCAN命令遍历
@Test
void testScan(){
    // 定义BigKey的阈值
    int strMaxLen = 10 * 1024; // 10KB
    int hashMaxLen = 500;       // 500个field
    
    long cursor = 0; // 游标
    do {
        // 扫描一批数据
        ScanResult<String> result = stringRedisTemplate.execute(RedisServerCommands::scan,
                new ScanOptions.ScanOptionsBuilder()
                        .match("*")
                        .count(100)
                        .build());
        cursor = Long.parseLong(result.getCursor());

        // 判断是否为BigKey
        List<String> keys = result.getResult();
        for (String key : keys) {
            // 获取数据类型
            DataType type = stringRedisTemplate.type(key);
            switch (type) {
                case STRING:
                    String value = stringRedisTemplate.opsForValue().get(key);
                    if (value != null && value.length() > strMaxLen) {
                        System.out.printf("发现BigKey: %s, 类型: %s, 长度: %d%n", key, type, value.length());
                    }
                    break;
                case HASH:
                    Long hashLen = stringRedisTemplate.opsForHash().size(key);
                    if (hashLen > hashMaxLen) {
                        System.out.printf("发现BigKey: %s, 类型: %s, 长度: %d%n", key, type, hashLen);
                    }
                    break;
                // 其他类型类似处理
            }
        }
    } while (cursor > 0);
}

4. BigKey解决方案

  • 删除策略
    • Redis 4.0+:使用UNLINK命令异步删除
    • Redis 4.0以下:使用SCAN遍历逐个删除集合元素,最后删除key
  • 拆分策略
    • 将大集合拆分为多个小集合
    • 使用合适的分片策略,如按ID范围或哈希分片

3. 批处理优化

单个命令执行的问题

在Redis中执行单个命令的总时长包括: - 一次网络往返传输时间 - Redis执行命令时间

其中网络传输时间通常是主要的耗时部分,特别是当网络延迟较高时。

批处理优化策略

1. MSET命令优化

MSET命令可以一次性设置多个键值对:

// 批量设置键值对
String[] keysAndValues = new String[2000]; // 1000个键值对
for(int i = 0; i < 100000; i++){
    int idx = (i % 1000) << 1; // 计算数组索引
    keysAndValues[idx] = "test:" + i;
    keysAndValues[idx + 1] = "value:" + i;
    
    if(idx == 0){ // 每1000个键值对批量插入一次
        jedis.mset(keysAndValues);
    }
}

2. Pipeline优化

Pipeline允许将多个命令一次性发送到Redis服务器,减少网络往返次数:

// 使用Pipeline进行批量操作
Pipeline pipeline = jedis.pipelined();
for(int i = 0; i < 100000; i++){
    pipeline.set("test:" + i, "value:" + i);
    if(i % 1000 == 0){
        pipeline.sync(); // 每1000个命令同步一次
    }
}
pipeline.sync(); // 最后同步剩余命令

MSET vs Pipeline对比

特性 MSET Pipeline
原子性 具备原子性,命令一次性执行 不具备原子性,命令按顺序执行
适用场景 相同数据类型,批量设置 任意命令组合,灵活度高
性能 非常高,内置原子操作 高,但略低于MSET
限制 仅支持部分数据类型 支持所有Redis命令

批处理注意事项

  • 不要一次传输太多命令:避免网络拥塞
  • 合理控制批处理大小:通常建议1000-5000个命令为一批
  • 选择合适的批处理方式:根据数据类型和业务需求选择MSET或Pipeline

4. 多级缓存架构总结

完整架构流程

浏览器/客户端 -> Nginx静态资源服务器 -> OpenResty负载均衡 -> Tomcat集群
                                    -> OpenResty本地缓存
                                    -> Redis缓存
                                    -> Tomcat进程缓存
                                    -> MySQL数据库

各层缓存策略

  1. 浏览器客户端缓存:静态资源缓存,通过304状态码优化
  2. Nginx本地缓存:基于URL哈希的负载均衡,确保相同商品请求路由到同一服务器
  3. OpenResty本地缓存:使用shared_dict实现进程间共享缓存
  4. Redis缓存:分布式缓存,支持集群部署
  5. Tomcat进程缓存:JVM进程内缓存,使用Caffeine实现

缓存同步策略

  • OpenResty本地缓存:使用过期时间策略,适合更新频率较低的数据
  • Redis缓存:通过Canal监听MySQL binlog实现数据同步
  • Tomcat进程缓存:同样通过Canal实现同步

5. Redis最佳实践总结

Key设计原则

  • 使用冒号分隔的多段格式:业务名:数据名:ID
  • 长度不超过44字节
  • 避免特殊字符
  • 便于管理和避免冲突

缓存同步策略

  • 有效期控制:适用于更新频率低、实时性要求不高的数据
  • 同步双写:适用于对一致性要求极高的数据
  • 异步通知:通过Canal实现,适用于多服务同步场景

批处理优化

  • 根据网络延迟和数据量选择合适的批处理大小
  • 使用MSET进行相同数据类型的批量操作
  • 使用Pipeline进行复杂命令的批量执行

这些最佳实践能够帮助我们在实际项目中更好地使用Redis,提高系统性能和稳定性。

评论交流

文章目录