摘要:Redis作为高性能的键值对数据库,凭借其高速读写、支持多种数据结构、可持久化等特性,已成为企业级项目中缓存、分布式锁、消息队列等场景的首选工具。本文基于Redis 7.x,结合电商、微服务等实战场景,详细讲解Redis核心数据结构、缓存设计模式、缓存问题解决(缓存穿透、缓存击穿、缓存雪崩)、分布式锁实现、持久化配置等核心知识点,附完整命令与代码案例,帮助后端开发者、运维工程师快速掌握Redis实战技巧,提升系统性能与稳定性,适合Redis入门与进阶学习。
一、前言:Redis的核心价值与应用场景
Redis 7.x相比之前版本,带来了诸多优化:支持多线程IO,提升读写性能;新增Redis Functions(函数),支持自定义脚本逻辑;优化持久化机制,提升数据安全性;增强集群功能,提升可扩展性。
在企业级项目中,Redis的核心应用场景包括:数据缓存(减轻数据库压力)、分布式锁(解决微服务并发问题)、消息队列(简单的异步通信)、计数器(如点赞数、访问量)、会话存储(如用户登录会话)等。本文聚焦Redis最核心的缓存设计与分布式锁,结合实战场景,解决实际开发中的常见问题。
二、核心基础:Redis 7.x安装与核心数据结构
2.1 Redis 7.x安装(CentOS 8)
# 1. 安装依赖yuminstall-ygcc gcc-c++make# 2. 下载Redis 7.x源码包wgethttps://download.redis.io/releases/redis-7.2.4.tar.gz# 3. 解压源码包tar-zxvfredis-7.2.4.tar.gz# 4. 编译安装cdredis-7.2.4makemakeinstallPREFIX=/usr/local/redis# 5. 配置Redis(复制配置文件)cpredis.conf /usr/local/redis/conf/# 6. 修改配置文件(/usr/local/redis/conf/redis.conf)# 允许远程访问(注释bind 127.0.0.1)# protected-mode no(关闭保护模式)# daemonize yes(后台运行)# requirepass 123456(设置密码,可选)# 7. 启动Redis/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf# 8. 连接Redis/usr/local/redis/bin/redis-cli-h127.0.0.1-p6379-a123456# 9. 验证启动127.0.0.1:6379>pingPONG# 启动成功2.2 Redis核心数据结构(实战必备)
Redis支持5种核心数据结构,每种结构有其特定的应用场景,掌握其用法是Redis实战的基础。
# 1. String(字符串):适用于缓存、计数器、会话存储等场景# 设置值(key=user:1001, value=JSON字符串)setuser:1001'{"id":1001,"username":"zhangsan","age":20}'# 获取值get user:1001# 设置过期时间(3600秒)setex user:10013600'{"id":1001,"username":"zhangsan","age":20}'# 自增(计数器)incr like:1001# 点赞数+1# 自减decr like:1001# 点赞数-1# 2. Hash(哈希):适用于存储对象(如用户信息、商品信息)# 设置哈希值(key=user:1001,字段=username,值=zhangsan)hset user:1001 username zhangsan age20# 获取单个字段值hget user:1001 username# 获取所有字段值hgetall user:1001# 删除字段hdel user:1001 age# 3. List(列表):适用于消息队列、最新消息列表等场景# 从左侧插入lpush message:1001"hello""world"# 从右侧插入rpush message:1001"redis"# 获取列表所有元素lrange message:10010-1# 从左侧弹出lpop message:1001# 从右侧弹出rpop message:1001# 4. Set(集合):适用于去重、交集、并集等场景(如好友列表、标签)# 添加元素sadd tag:1001javapython go# 获取所有元素smembers tag:1001# 判断元素是否存在sismember tag:1001java# 求两个集合的交集(好友共同关注)sinter tag:1001 tag:1002# 删除元素srem tag:1001 go# 5. Sorted Set(有序集合):适用于排行榜、优先级队列等场景# 添加元素(score=分数,用于排序)zadd rank:100190zhangsan85lisi95wangwu# 获取有序集合(升序)zrange rank:10010-1withscores# 获取有序集合(降序)zrevrange rank:10010-1withscores# 获取元素分数zscore rank:1001 zhangsan# 删除元素zrem rank:1001 lisi三、实战模块:缓存设计与分布式锁
3.1 模块1:缓存设计模式(实战案例)
缓存设计的核心是“缓存数据库热点数据”,减少数据库访问压力,常用的缓存设计模式包括:Cache-Aside(旁路缓存)、Write-Through(写穿透)、Write-Back(写回),其中Cache-Aside模式最常用。
// 1. Cache-Aside模式(旁路缓存):最常用,适用于读多写少场景// 核心逻辑:查询时先查缓存,缓存没有则查数据库,再将数据存入缓存;更新时先更数据库,再删缓存importredis.clients.jedis.Jedis;importcom.alibaba.fastjson.JSON;publicclassCacheAsideDemo{privatefinalJedisjedis=newJedis("127.0.0.1",6379);privatefinalUserDaouserDao=newUserDao();// 数据库操作类// 1. 查询用户信息(先查缓存,再查数据库)publicUsergetUserById(LonguserId){Stringkey="user:"+userId;// 1. 先查缓存StringuserJson=jedis.get(key);if(userJson!=null){// 缓存命中,返回数据returnJSON.parseObject(userJson,User.class);}// 2. 缓存未命中,查数据库Useruser=userDao.selectById(userId);if(user!=null){// 3. 将数据库数据存入缓存(设置过期时间,避免缓存雪崩)jedis.setex(key,3600,JSON.toJSONString(user));}returnuser;}// 2. 更新用户信息(先更数据库,再删缓存)publicvoidupdateUser(Useruser){// 1. 更新数据库userDao.update(user);// 2. 删除缓存(避免缓存与数据库数据不一致)Stringkey="user:"+user.getId();jedis.del(key);}// 3. 删除用户信息(先删数据库,再删缓存)publicvoiddeleteUser(LonguserId){userDao.deleteById(userId);Stringkey="user:"+userId;jedis.del(key);}}// 2. 缓存key设计规范(避免key冲突、便于管理)// 规范:业务模块:对象:唯一标识[:属性]// 示例:// 用户模块:user:1001(用户信息)、user:1001:orders(用户订单)// 商品模块:product:2001(商品信息)、product:2001:stock(商品库存)// 缓存过期时间:根据业务场景设置,热点数据可设置1-2小时,非热点数据可设置更久3.2 模块2:缓存常见问题解决(穿透、击穿、雪崩)
缓存使用过程中,容易出现缓存穿透、缓存击穿、缓存雪崩三个问题,若不解决,会导致数据库压力剧增,甚至系统崩溃,本文提供针对性的解决方案。
// 1. 缓存穿透:查询不存在的数据,缓存和数据库都没有,导致每次都查数据库// 解决方案:缓存空值、布隆过滤器(推荐)// 方案1:缓存空值(简单易实现,适合数据量不大的场景)publicUsergetUserById(LonguserId){Stringkey="user:"+userId;StringuserJson=jedis.get(key);if(userJson!=null){// 缓存命中(包括空值)returnJSON.parseObject(userJson,User.class);}// 查数据库Useruser=userDao.selectById(userId);if(user!=null){jedis.setex(key,3600,JSON.toJSONString(user));}else{// 缓存空值,设置较短的过期时间(如60秒),避免缓存占用过多空间jedis.setex(key,60,JSON.toJSONString(null));}returnuser;}// 方案2:布隆过滤器(适合数据量大、查询频繁的场景,提前过滤不存在的key)importcom.google.common.hash.BloomFilter;importcom.google.common.hash.Funnels;publicclassBloomFilterDemo{// 初始化布隆过滤器(预计数据量100万,误判率0.01)privatefinalBloomFilter<Long>bloomFilter=BloomFilter.create(Funnels.longFunnel(),1000000,0.01);// 系统启动时,将所有用户ID存入布隆过滤器publicvoidinitBloomFilter(){List<Long>allUserId=userDao.selectAllUserId();for(LonguserId:allUserId){bloomFilter.put(userId);}}// 查询前先通过布隆过滤器判断key是否存在publicUsergetUserById(LonguserId){// 布隆过滤器判断不存在,直接返回null,不查缓存和数据库if(!bloomFilter.mightContain(userId)){returnnull;}// 后续逻辑和Cache-Aside模式一致Stringkey="user:"+userId;StringuserJson=jedis.get(key);if(userJson!=null){returnJSON.parseObject(userJson,User.class);}Useruser=userDao.selectById(userId);if(user!=null){jedis.setex(key,