黑马程序员Redis教程学习笔记(第4部分):缓存同步与最佳实践
课程概述
本篇笔记涵盖黑马程序员Redis教程中关于缓存同步策略与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配置文件my.cnf:
# binlog文件位置和名称
log-bin=mysql-bin
# 监控的数据库
binlog-do-db=hmdp2. 创建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 mysql4. 运行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.5Canal客户端集成
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)
private Long id; // 主键映射
private String name;
private String title;
private Long price;
private String image;
private String category;
private String brand;
@TableField(exist = false) // 非表字段
private Integer stock;
@TableField(exist = false) // 非表字段
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. 多级缓存架构总结
整体架构流程
浏览器/客户端 -> Nginx静态资源服务器 -> OpenResty负载均衡 -> Tomcat集群
-> OpenResty本地缓存
-> Redis缓存
-> Tomcat进程缓存
-> MySQL数据库各层缓存策略
- 浏览器客户端缓存:静态资源缓存,通过304状态码优化
- Nginx本地缓存:基于URL哈希的负载均衡,确保相同商品请求路由到同一服务器
- OpenResty本地缓存:使用shared_dict实现进程间共享缓存
- Redis缓存:分布式缓存,支持集群部署
- Tomcat进程缓存:JVM进程内缓存,使用Caffeine实现
缓存同步策略
- OpenResty本地缓存:使用过期时间策略,适合更新频率较低的数据
- Redis缓存:通过Canal监听MySQL binlog实现数据同步
- Tomcat进程缓存:同样通过Canal实现同步
3. Redis键值设计最佳实践
Key设计规范
- 格式:使用冒号分隔的多段格式,如
业务名:数据名:ID - 示例:
login:user:1001表示登录业务的用户ID为1001的用户信息 - 长度:不超过150字节,避免使用特殊字符
- 优势:
- 可读性强,便于理解
- 避免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
- Redis 4.0+:使用
- 拆分策略:
- 将大集合拆分为多个小集合
- 使用合适的分片策略,如按ID范围或哈希分片
4. 缓存同步策略总结
常见同步策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 有效期控制 | 简单方便,无代码入侵 | 实时性差,存在数据不一致期 | 更新频率低、实时性要求低的业务 |
| 同步双写 | 实时性强,数据一致性高 | 代码侵入度高,耦合度高 | 对一致性要求极高的业务 |
| 异步通知 | 低耦合,代码侵入少 | 实时性相对较差 | 对实时性要求不高、多服务同步场景 |
Canal同步优势
- 零代码入侵:无需修改业务代码
- 高实时性:基于binlog监听,延迟极低
- 低耦合:业务与缓存同步完全分离
- 高效能:避免了消息队列的额外开销
总结
本篇笔记详细介绍了多级缓存架构中的缓存同步机制和Redis最佳实践:
- Canal缓存同步:通过监听MySQL binlog实现数据的实时同步,具有零代码入侵、高实时性等优势
- 多级缓存架构:从浏览器到数据库的多层缓存体系,极大提升了系统性能
- 键值设计规范:合理的Key命名规范和BigKey问题处理,确保Redis的高性能运行
- 缓存同步策略:不同场景下选择合适的同步策略,平衡性能与一致性
这些最佳实践对于构建高性能、高可用的Redis缓存系统具有重要意义,有助于在实际项目中避免常见问题并优化系统性能。