免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Redis从入门到实战:安装、数据类型与高并发场景全解析

Redis从入门到实战:安装、数据类型与高并发场景全解析 1. 先把Redis装好下载与安装全流程Redis的使用起点永远是先把环境跑起来。很多新手第一次接触redis时最容易卡壳的不是命令记不住而是“下载什么版本”“怎么安装”“装完怎么验证”。这一节我把常见的安装方式从头到尾捋一遍覆盖Windows、Linux、macOS三种环境照着手动操作就可以。1.1 选择版本与下载途径先说版本。Redis官方目前维护的稳定分支主要是7.2和7.46.2还在维护期但已经属于老版本了。个人项目和中小型业务我推荐直接上7.2以上版本7.0开始引入的ACL、Redis Function、多AOF重写等能力对后面做权限管理和复杂逻辑都更友好。如果你只是在本地学习命令那版本差异不大选个最新的稳定版就行。下载渠道方面官方站点redis.io提供了源码包Linux/macOS下用源码编译是标准做法。Windows环境稍微特殊一点Redis官方并不提供Windows原生安装包而是推荐使用WSL。如果不想开WSL可以找Microsoft维护的Redis on Windows镜像或者使用第三方编译好的zip包这类包在GitHub上搜redis-windows或memurai就能找到适合临时测试用但不建议直接拿来做生产环境。1.2 三种平台安装实操与验证LinuxCentOS/Ubuntu系源码编译安装是我最推荐的方式因为可以通过编译参数控制可执行文件、配置路径和内存分配器。以7.2.5版本为例wget https://download.redis.io/releases/redis-7.2.5.tar.gz tar xzf redis-7.2.5.tar.gz cd redis-7.2.5 make make install PREFIX/usr/local/redis这里有个细节make install PREFIX...如果不指定prefix默认装到/usr/local/bin下面相关文件会比较分散。指定了PREFIX所有二进制都会集中在指定目录的bin子目录里后续维护、卸载都方便。Ubuntu/Debian系则更简单可以直接用aptsudo apt update sudo apt install redis-server安装完成后Redis会自动注册成systemd服务用systemctl status redis-server检查状态即可。验证安装的核心命令只有两条redis-cli ping返回PONG说明服务已经起来了。再看一下版本信息redis-server --version redis-cli --statmacOSmacOS下有两条路用Homebrew最省事一条命令搞定brew install redis brew services start redis如果喜欢手动控制也可以走源码编译过程和Linux完全一样。实测下来brew安装的好处是自动处理好路径和服务管理但对Redis源码里的日志和配置位置没有太严格的管控需要自己熟悉目录结构。Windows生产环境我不建议在Windows上直接跑Redis但日常学命令和开发调试用WSL2是最稳的wsl --install -d Ubuntu # 进入WSL后 sudo apt update sudo apt install redis-server如果你不想装WSL退而求其次可以用GitHub上的redis-windows项目下载zip包解压后直接运行redis-server.exe。需要注意这类镜像通常停留在较旧的Redis版本某些新特性比如RESP3协议的部分功能用不了。装完之后别急着敲命令先做一次“体检”用redis-cli ping确认连通再用redis-cli info server看进程版本、运行模式、端口等关键信息确认配置加载正常。这步虽然简单但能省掉很多后面排查的冤枉路。2. 数据类型才是Redis的“内功”Redis入门者最容易犯的一个错误是把它当成“高级版的HashMap”所有东西都往String里塞。等数据量上来之后才发现查询效率、内存占用、并发控制全都不对劲。Redis真正值钱的是它的数据类型——每种类型都对应一套独有的操作命令和适用场景选对类型后面写业务逻辑会顺滑很多。2.1 String类型最基础也最容易用错String是Redis里最基础的类型一个key对应一个valuevalue最大能存512MB。常用命令包括SET、GET、MSET、MGET、INCR、DECR、SETEX、SETNX等。我开发时用String最多的是这两个场景一是缓存把数据库查询结果直接缓存进Rediskey用业务前缀加IDvalue用JSON二是计数器点赞数、PV统计、限流计数都可以用INCR轻松搞定原子性天然保证。# 缓存场景 SET user:1001 {name:zhangsan,age:20} EX 300 GET user:1001 # 计数场景 INCR page:view:20240101 INCRBY page:view:20240101 100这里需要特别提醒一个坑INCR只对整数类型的value有效。如果value本身是“12abc”这种非纯数字串执行INCR会报错。我见过不少新手在业务里把用户ID转成字符串后去做自增结果直接抛异常。关于String的使用还有两个容易忽略的操作第一SET命令的NX和XX参数。SET key value NX表示只有当key不存在时才设置常用于分布式锁。SET key value XX则相反只有当key存在时才更新。这两个参数加上EX过期时间配合起来用能覆盖很多原子操作的场景。第二SETEX和SETNX的区别。SETEX同时完成设值和过期时间设置比分开用SET和EXPIRE更安全减少了两个命令之间时间窗口里key意外释放的问题。这在分布式环境下尤为重要。2.2 Hash类型对象数据的最优解Hash类型底层是一个字符串字段和字符串值之间的映射表非常适合存储“对象”。一个用户有name、age、email等多个字段如果用String存需要拼一个序列化后的JSON改一个字段就得整存整取用Hash字段级增删改查都很方便。 HSET user:1001 name zhangsan age 20 email zhangsanexample.com HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age 1Hash的优点不需要我多啰嗦关键分享两个实战经验一是什么时候不该用Hash。如果一个对象的字段是动态增加的比如一个订单可能有1到100个商品条目用Hash会导致key数量失控而且HGETALL在字段超多时还可能阻塞Redis单线程这种场景更推荐用List存放子项ID再用String或Hash保存每个子项的详情。二是Hash的节省内存效果。当字段数小于某个阈值、字段值较短时Redis会使用listpack编码内存占用比dict编码小很多。实测一个包含5个字段的Hash对象在listpack编码下比String存储JSON串能省30%到40%的内存。如果一个业务里有百万级对象这个省下来的量就很可观了。2.3 List类型队列与时间线场景的默认选择List底层的结构比较有意思元素少的时候用quicklist元素多了之后自动转成linkedlist。它的特性是支持从两端推入LPUSH/RPUSH和弹出LPOP/RPOP所以两种最常见的用法是栈和队列。我做消息队列时通常用LPUSH BRPOP组合# 生产者 LPUSH task:queue job1 LPUSH task:queue job2 # 消费者阻塞弹出 BRPOP task:queue 0BRPOP的第二个参数是超时时间0表示永不超时这是List做MQ的核心优势——客户端不会空转轮询而是被Redis阻塞住数据到达时立即唤醒配合超时上限可以做到低延迟消费。List还有几个很容易被忽略但很实用的命令LRANGE key start stop按索引范围取元素做分页很好用但注意列表很长时LRANGE 0 -1会一次性把所有元素都取出来内存会爆要控制分页大小。LTRIM key start stop只保留start到stop区间的元素其余全部删除。这是做“最新N条记录”的最佳工具每次LPUSH后跟一条LTRIM key 0 N-1列表永远只保留最新的N条。LINSERT key BEFORE|AFTER pivot value在指定值的前或后插入元素双链表操作适用于需要动态调整顺序的场景。2.4 Set类型无序集合与点赞关注Set对应数学上的“集合”元素不重复且天然无序。常用命令有SADD、SREM、SMEMBERS、SISMEMBER、SINTER、SUNION、SCARD等。点赞、收藏、标签、好友关系只要有“去重”和“集合运算”需求的场景几乎都能用Set解决。举一个实际案例一个UGC平台做“我关注的人的动态流”动态ID都存到Set里每次取动态流时用SINTER求多个关注集合的交集就能快速过滤掉不关注的用户发布的动态。 SADD user:1001:follow 2001 2002 2003 SADD user:1001:follow 2004 SCARD user:1001:follow # 关注数 SISMEMBER user:1001:follow 2002 # 是否已关注 SINTER user:1001:follow user:2002:follow # 共同关注这里分享我踩过的一个坑SINTER如果集合很大计算量会很大Redis是单线程的长时间运算会阻塞其他命令。我遇到过线上一个集合有百万级别成员做交集计算时Redis的延迟从不到1毫秒涨到了秒级别。所以Set运算只适合中小规模数据集大集合场景要么裁剪数据要么借助外部计算引擎。2.5 ZSet类型有序集合与排行榜ZSet是在Set基础上加了score字段元素按score从小到大排序。这个特性让它在排行榜、延时队列、滑动窗口限流等场景里几乎碾压一切方案。 ZADD player:rank 100 player1 ZADD player:rank 88 player2 ZRANGE player:rank 0 -1 WITHSCORES ZREVRANGE player:rank 0 9 # 前10名从大到小 ZINCRBY player:rank 12 player1 # 加分ZSet用得妙的地方很多这几种是我实际开发中觉得性价比最高的排行榜写入时直接用ZADD设置分数查询时用ZREVRANGE拿名次单条命令完成比MySQL舒服太多。延时队列score存任务的执行时间戳消费者用ZRANGEBYSCORE key -inf now LIMIT 0 1拿到期任务配合哨兵循环就能做可靠延时队列。滑动窗口限流score存请求时间戳每次请求前用ZREMRANGEBYSCORE清掉窗口外的记录再用ZCARD统计窗口内请求数达到阈值就拒绝。还有一个细节Distributed锁相关场景也常借助ZSet多个节点同时抢锁时把节点信息写入同一个ZSetscore为获取时间通过ZRANK判断谁最先请求从而实现公平锁的排队效果。不过这个用法相对少见更多是作为理解ZSet有序性的练习题。3. 进阶数据结构与真实场景对应五种基础类型能覆盖大部分业务但Redis还有几个冷门却极其有用的高级类型Bitmaps、HyperLogLog、GEO和Stream。这几个类型各自的适用场景非常明确用对了能让系统性能提升一大截。3.1 Bitmaps、HyperLogLog、GEO、Stream各自能干的事BitmapsBitmaps本质上还是String类型但是按bit位操作。擅长做“布尔状态存储”比如用户是否登录过、是否签到过、某个商品的某些属性标记。一个bit只占1位内存一亿用户只需约12.5MB内存非常省。典型场景是连续签到统计# 假设用户ID在100001签到天数为第30天直接置位 SETBIT sign:202501 100001 1 BITCOUNT sign:202501 # 统计当月签到总人数 GETBIT sign:202501 100001 # 查用户是否签到过HyperLogLog基数统计类型用来做UV统计不重复用户数标准误差率约0.81%。它最大优势是内存极低无论统计多少数据采用固定12KB内存结构。 PFADD page:uv 1001 1002 1003 PFADD page:uv 1001 PFCOUNT page:uv # 返回3UV统计的精确度不是关键需求时HyperLogLog就是最好的选择。但它是概率估算误差范围在预算时要心里有数绝对不能拿来做计费、限流等对准确度苛刻的功能。GEO地理位置类型底层是ZSet但精度更高。可以存经纬度坐标支持计算两点间距离、查找某个半径内的元素。美团的“附近的人”、打车软件的“附近司机”底层都是GEO。 GEOADD driver:loc 116.397 39.908 driver1 GEOADD driver:loc 121.473 31.230 driver2 GEOSEARCH driver:loc FROMLONLAT 116.4 39.9 BYRADIUS 5 km ASCStreamRedis 5.0引入的消息队列类型比List更正式支持消费者组、消息确认、死信队列等。项目里如果不想引入Kafka这类重型MQ又需要可靠消息投递Stream是最合适的替代方案。 XADD event:stream * user_id 1001 action login XREAD COUNT 10 STREAMS event:stream 0-0 XGROUP CREATE event:stream group1 0Stream的姿态是“内存里的持久化消息队列”这是它区别于RabbitMQ等磁盘队列的关键特性。3.2 高并发场景下Redis的典型用法串讲把数据类型组合起来就能拼出很多高并发场景的完整方案。我以“秒杀系统”为例串一遍商品详情用String做读缓存可售卖件数用DECR做原子扣减用户购买记录用Set去重防超卖抢购时间窗口用ZSet做限流消息通知用Stream做异步推送。整个链路下来MySQL只用来做最终数据落盘大部分热路径都打在Redis里单机QPS能轻松破万。另一个常见组合是“缓存异步同步”。一个登录系统里用户token和用户信息分别用String和Hash存热点更新时更新Redis后异步同步到MySQL。组件间的数据流转用Stream做缓冲不会因为同步失败就直接丢数据可以配合ACK机制重试。这套组合让我在多个项目里避免了缓存穿透和同步链路崩溃的坑。再举一个“排行榜定时任务”的例子每日赛榜用ZSet写入当天数据凌晨定时任务把前一天榜单Score加一个偏移量后合并到总榜ZSet这样随时能查到“本周”“本月”的累计排名而且查询都是毫秒级。4. 代码实操从Jedis到Spring Data Redis工具选型上Java生态用得最多的是Jedis和Lettuce两个客户端。Jedis是BIO模型连接数多时资源占用大Lettuce底层基于Netty使用单连接多线程复用吞吐量更高也是Spring Boot 2.0之后的默认客户端。我自己的项目更倾向于Lettuce不光是Spring生态便利更因为它天然支持Redis Sentinel和Cluster主从切换、路由分片都自动处理省去很多代码。4.1 Jedis基础示例直接看代码一段最简单可靠的Jedis连接和读写JedisPoolConfig config new JedisPoolConfig(); config.setMaxTotal(20); config.setMaxIdle(10); config.setMinIdle(5); try (JedisPool pool new JedisPool(config, 127.0.0.1, 6379, 2000, null)) { String key user:1001; try (Jedis jedis pool.getResource()) { jedis.set(key, {\name\:\zhangsan\}); String value jedis.get(key); System.out.println(value); } }注意几个地方一定要用JedisPool不要每次new Jedis不然连接管理完全失控。try-with-resources或者手动finally close是执行规范避免连接泄漏。pool.getResource()如果连接不够会按setMaxTotal限制等待这个等待时间不能太长否则高并发下线程会积压造成业务超时。4.2 Spring Boot集成与配置Spring Boot项目里引入依赖后主要的配置都在application.yml里spring: data: redis: host: 127.0.0.1 port: 6379 password: yourpassword timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 2s这里多提一句Spring Boot 2.4之后的配置前缀是spring.data.redis老版本是spring.redis升级框架时不要照抄旧配置容易踩坑。随后注入RedisTemplate做读写Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 设置key和hash key的序列化器为String避免乱码 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // value使用JDK序列化注意后面单独讨论 template.setValueSerializer(new JdkSerializationRedisSerializer()); template.setHashValueSerializer(new JdkSerializationRedisSerializer()); template.afterPropertiesSet(); return template; } }这里涉及到一个经典问题默认的RedisTemplate使用JDK序列化会把value转成二进制对象往redis里看就是“乱码”。如果业务侧主要存JSON建议直接把ValueSerializer改成GenericJackson2JsonRedisSerializer。很多新人第一次调Redis接口时看到乱码其实不是编码坏了而是序列化策略不对。4.3 序列化与缓存注解实战Spring Cache的Cacheable、CacheEvict注解与Redis结合是项目中减少重复代码的强大工具Service public class ProductService { Cacheable(cacheNames product, key #productId) public Product findById(Long productId) { return productMapper.selectById(productId); } CacheEvict(cacheNames product, key #product.id) public void update(Product product) { productMapper.updateById(product); } }Cacheable的逻辑是先去Redis找key为product::bookId的数据命中直接返回不命中执行方法并把结果写入缓存下次就可以直接命中。实测中这个方案能让热点商品详情的查询QPS从几百涨到几千而且代码继续简化。但缓存注解有几个坑要提醒一下默认是基于SimpleCacheManager不配Redis时缓存只存在内存里服务重启即失效。一定要确保配置了RedisCacheManager。key的生成规则如果不指定会使用默认的SimpleKeyGenerator多个方法参数时容易生成不同key导致缓存不命中。建议始终用key属性显式指定。Cacheable内部用的是RedisTemplate的序列化器如果只改了全局的String序列化还需要单独配置RedisCacheConfiguration里的序列化器否则value仍是JDK二进制。再分享一个经验缓存更新模式建议采用“双删”或“懒加载”而不要简单使用CacheEvict删除后依赖数据库重建。因为在高并发下删除缓存后瞬间有大量请求涌到DB此时如果DB读延迟稍高前面的线程就会再次打穿缓存形成缓存击穿。我在一个“商品详情”改造项目中采用“先更新DB再删缓存”的顺序并配合短暂的空值缓存cache empty value整个击穿问题就被压下去了。5. 常见问题与排查实录Redis用久了总要面对各种疑难杂症。我把这些年遇到过的大部分高频问题梳理一遍每条都附上我的排查思路和最终解法希望能帮你少踩几个坑。5.1 连接失败、乱码、内存报警连接失败Could not connect to Redis先确认三件事服务是否启动redis-cli ping、端口是否通telnet 127.0.0.1 6379、密码是否对配置文件里的requirepass。Windows下最常见的问题是防火墙拦截了6379端口Linux下则可能是bind 127.0.0.1导致外部IP连不上。如果配置了Sentinel还需要检查Sentinel配置里的master-name是否和主从一致。我把“配置了密码但客户端忘了设”和“绑定了本机IP但客户端连的是公网IP”列为本地开发两大高频失误。缓存显示乱码这个现象我详细说下通过redis-cli看key和value如果是\xAC\xED\x00\x05t...之类的怪字符说明JDK序列化桶生效了。几个处理策略如果不需要在Redis端做命令行查看和处理乱码不影响业务功能只是影响运维观察。如果需要在Redis端做统计、TTL操作建议value用JSON格式字符存储也就是使用GenericJackson2JsonRedisSerializer或直接手动JSON.toJSONString。注意换序列化器前后的数据不兼容会出现“原来写入的数据读出来还是乱码”的情况迁移时最好先清空相关缓存或做版本隔离。内存报警OOM command not allowed when used memory maxmemory这是Redis内存超过maxmemory限制后的保护行为。常见解决办法是检查大key和大量过期key堆积用redis-cli --bigkeys扫描。检查持久化策略RDB或AOF占用的内存也被计入不是只有数据。合理配置淘汰策略maxmemory-policy allkeys-lru适用于缓存场景但注意它可能误杀近期刚写的数据。如果是有状态的关键数据不要用淘汰策略而是优先扩容或做内存分层。5.2 缓存穿透、击穿、雪崩与数据一致性缓存穿透访问一个缓存和数据库中都不存在的数据每次都打到DB导致DB压力爆炸。对策通常是两个一是布隆过滤器在缓存前加一层过滤数据库中不存在的数据直接被拦截二是空值缓存把查询不到的结果以空对象缓存几分钟短期内能挡掉大部分穿透量。空值缓存注意设置比较短的过期时间防止大量空key堆积。缓存击穿热点key过期的一瞬间大量请求同时打到DB。对热点key可以采取“互斥更新”或“逻辑过期”互斥更新是更新缓存时加锁避免并发重建逻辑过期是设置一个比真实TTL长的逻辑过期时间字段由后台任务负责在逻辑过期后重建真实缓存查询请求如果发现逻辑过期则返回旧值并触发后台刷新。推荐后者用户体验更好。缓存雪崩大量key同一时间过期Redis直接变成“空城”请求全量打到DB。把缓存过期时间加一个随机抖动比如5到10分钟的随机数这是最经典的解法。同时还可以部署Redis主从和分片从高可用层面避免Redis节点宕机导致雪崩。数据一致性写操作如果先更新了DB后续更新缓存失败就会导致脏数据。我目前比较稳的方案是“先更新DB再删除缓存”然后通过消息队列异步补偿删除失败的缓存。如果对一致性要求极高可以在删除失败时做一个短时间TTL兜底确保缓存最多只有几秒的脏数据窗口。注意任何缓存一致性方案都无法做到绝对强一致最后都要接受“缓存短暂脏数据”的现实。做架构决策时不要追求很弱的极端一致而是基于业务容忍度给出明确的兜底策略。5.3 性能排查方法论与工具性能问题不要拍脑袋猜用工具说话redis-cli --latency看本机到Redis的网络和命令处理延迟。redis-cli --stat实时看连接数、内存、命中率等指标。redis-cli --bigkeys找出大key大key是阻塞主线程的主要元凶之一。比如一个百万成员的ZSet一次ZRANGE WITHSCORES就能让Redis卡住几秒钟。SLOWLOG GET 10查看慢命令日志定位哪些命令执行时间过长。redis-cli -h host -p port --hotkeysRedis 6.x之后找出访问频率最高的key检查是否命中热点缓存策略。线上排查时有一次我觉得是内存不足导致的全链路卡顿检查半天才发现是某个服务每秒执行几千次SMEMBERS全量取一个大Set那条命令一执行就要花300多毫秒直接把Redis主线程拖死了。用SLOWLOG定位后把全量改为增量SSCAN扫描延迟就回到了个位数毫秒。这类问题没有工具辅助根本难以定位。最后说一个我自己的习惯每次在Redis上做一次上线变更都会顺手记录变更前后的key数量、内存占用和平均延迟。时间长了你会发现大部分线上故障都是“量变到质变”的过程有了基线数据下一次性能突然恶化时判断起来就快很多。
返回列表