拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Boot整合Redis实战:从配置到分布式锁的完整避坑指南

1. 为什么Spring Boot项目几乎绕不开Redis1.1 从一次缓存血案说起先讲个真实场景。去年我接手一个内部运营系统登录接口平均响应200ms高峰期数据库连接池被打满页面转圈转到用户直接关浏览器。翻代码一看用户session、权限字典、热点配置全查MySQL每次请求重复查三五次。当时我就知道——这项目缺Redis。Spring Boot整合Redis说白了两件事把经常查的数据放进内存以及让多个服务共享同一份状态。前者解决性能后者解决一致性。对于中小团队来说这是性价比最高的中间件选择不需要额外部署繁重的缓存集群一个单机Redis实例就能扛住绝大多数业务场景。这篇文章我会完整走一遍整合流程环境准备、依赖配置、序列化方案、RedisTemplate的使用姿势、注解缓存、分布式锁最后把平时最容易踩的坑集中捋一遍。后面每段都能直接抄作业也会解释为什么要这么写。1.2 整合前必须搞懂的三个核心概念很多新手一上来就搜Spring Boot Redis依赖加完依赖就跑结果连不上、乱码、缓存失效一脸懵。问题根子在于没搞懂三个概念我先用大白话讲清楚。第一个Redis的value不是直接存对象。Redis存的是字节序列你在Java里塞进去一个User对象Redis看到的就是一串二进制。这中间谁负责对象转字节、字节转对象序列化器。默认的JdkSerializationRedisSerializer能存但不友好肉眼看到的是\xAC\xED\x00\x05t...一团乱码别的语言也读不出来。这一步没配置好后面全是坑。第二个RedisTemplate和StringRedisTemplate是两兄弟。它们底层指向同一个Redis连接工厂但默认序列化器不同key和value的编码规则也不同。混着用就会出现StringRedisTemplate写入的keyRedisTemplate读不到这种诡异问题。第三个缓存注解并不神秘。Cacheable、CacheEvict这类注解本质上是Spring AOP在方法前后帮你调RedisTemplate。想让它好使必须配置CacheManager指定用什么序列化、过期多久。否则默认的ConcurrentMapCacheManager根本不用Redis只是个内存Map。这三个概念串起来你就知道整合Redis不是加依赖这么简单而是一条完整链路连接工厂 → 序列化器 → 操作模板 → 业务封装。2. 环境准备与依赖引入2.1 Redis服务端的安装与启动整合前得先有个能连的Redis服务。很多人第一步就卡在这不同系统安装方式差别不小。Windows环境官方Redis不支持Windows但微软维护过移植版GitHub上搜tporadowski/redis也能找到可用的Windows构建包。下载zip解压后目录下有redis-server.exe和redis-cli.exe。双击redis-server.exe或命令行执行redis-server.exe redis.windows.conf默认端口6379看到Ready to accept connections字样就说明起来了。这里注意Windows版Redis在后台驻留时控制台不能关否则服务就停了。想常驻可以用redis-server.exe --service-install redis.windows.conf --service-name Redis redis-server.exe --service-start --service-name RedisLinux环境用包管理器安装最省事。# Ubuntu / Debian apt install redis-server # CentOS / RHEL yum install redis然后启动并设置开机自启systemctl start redis systemctl enable redis改配置用/etc/redis/redis.conf重点是bind、protected-mode、requirepass这三项。默认bind 127.0.0.1只允许本机访问如果Spring Boot跑在另一台机器必须改成bind 0.0.0.0或指定IP同时设置requirepass密码不然6379端口暴露公网就是裸奔被挖矿脚本扫到几乎是秒级的事。Docker方式这个我实际工作中最常用因为起实例快、环境隔离、版本切换方便。docker run -d --name redis \ -p 6379:6379 \ -v /data/redis/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis/data:/data \ redis:7.2 \ redis-server /etc/redis/redis.conf装上后验证一把redis-cli -h 127.0.0.1 -p 6379 ping # 返回 PONG 说明服务正常提示公司服务器端口没开或防火墙拦着本地redis-cli连不上先用telnet ip 6379判断是网络问题还是服务问题。这个排查习惯能帮你省掉很多无谓的debug时间。2.2 Spring Boot依赖引入与版本选型Spring Boot 2.x时代引入依赖很简单dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependencySpring Boot 3.x需要配套引入commons-pool2连接池否则配置连接池参数不会生效如果用了Spring Boot 3.2及以上版本Jackson的版本兼容性需要注意建议直接跟随Boot的BOM管理。实际项目里我更推荐同时引入Apache Commons Pooldependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependencyRedisTemplate底层用Lettuce连接工厂Lettuce默认不带连接池但生产环境必须开池否则高并发下连接数不可控会出现Unable to connect to Redis的报错。所以这一步别省。版本选型的建议Spring Boot 2.7.x配Redis 5.0完全没问题Spring Boot 3.x建议Redis 6.2以上因为Lettuce对新版Redis的客户端缓存、ACL等特性支持更完整。JdkSerializationRedisSerializer在3.x里默认被移除了所以很多人升级后发现对象存不进去这里提前打个预防针。2.3 配置文件怎么写最稳application.yml里最基本的配置长这样spring: data: redis: host: 127.0.0.1 port: 6379 password: yourpassword database: 0 timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 3s几个关键参数我展开说下。database选择Redis默认有16个库0-15。不同业务模块隔离数据时可以用不同database比如session存0库、业务缓存存1库、分布式锁存2库。但注意这样做只是逻辑隔离不是物理隔离flushdb一条命令就能清空当前库。真要强隔离还是得拆实例。max-active连接池最大连接数。经验值是(线程池最大并发数) / 2左右我给压测过的项目配16基本够用。配太大反而浪费因为Lettuce本身是异步多路复用单连接可以并发发命令不像Jedis那样一连接一请求。timeout建议3秒超时太长会让接口响应变慢太短在高负载下容易误判连接失败。重点说下配置包名差异Spring Boot 2.x用spring.redis.*Spring Boot 3.x改用spring.data.redis.*。网上老帖子的配置直接复制到3.x项目里全是红叉。遇到配置不生效先检查这个前缀。3. 核心配置与序列化方案3.1 配置类连接工厂、序列化器、CacheManager依赖加好、配置写好接下来写核心配置类。直接给完整版本Configuration EnableCaching public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key使用String序列化可读性好 StringRedisSerializer stringSerializer new StringRedisSerializer(); // value使用GenericJackson2JsonRedisSerializer自动携带类型信息 GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())) .disableCachingNullValues(); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); } }这段代码几乎是整合Redis的标准答案但有几个细节值得讲透。为什么key用String序列化器因为可读性。用Jdk序列化keyredis-cli里看到的key是\xAC\xED\x00\x05t\x00\x08user:1排查问题根本没法看。String序列化的key就是普通的user:1直观可读还能用redis-cli --scan --pattern user:*做批量清理。为什么value用GenericJackson2JsonRedisSerializer它会在JSON里塞一个class字段反序列化时能还原成原来的对象类型。比如存个UserRedis里长这样{class: com.example.entity.User, id: 1, name: 张三}缺点是JSON体积比二进制大、占内存但可读性和跨语言能力远胜Jdk序列化团队排查问题、数据迁移都方便得多。3.2 序列化器选择的血泪教训序列化器这块坑最多我踩过的、帮别人排过的加起来能写一篇文章。第一个坑LocalDateTime反序列化报错。Jackson默认不支持Java 8时间类型。如果你没有引入jackson-datatype-jsr310或没注册模块存LocalDateTime字段时序列化能过反序列化直接抛异常。解决方式是在Jackson的ObjectMapper里注册JavaTimeModule并配置WRITE_DATES_AS_TIMESTAMPS为falseObjectMapper om new ObjectMapper(); om.registerModule(new JavaTimeModule()); om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(om);第二个坑无参构造函数缺失。JSON反序列化需要调用无参构造器你的实体类如果只写了带参构造器而没写无参运行时不报编译错但一存一取就裂开。所以用Redis存对象的实体务必保留无参构造。第三个坑RedisTemplateString, Object的泛型陷阱。很多人困惑为什么存进去的是User对象取出来变成LinkedHashMap。原因是你没配value序列化器默认用了Jdk序列化或ObjectMapper不带类型信息的Jackson。取出来时Jackson不知道目标类型只能转成LinkedHashMap。我在排查中遇到最多就是这个。解决方式就是上面配置类里的GenericJackson2JsonRedisSerializer它带上类型信息能正确还原对象。第四个坑跨服务反序列化。A服务用GenericJackson2JsonRedisSerializer存数据B服务换成了Fastjson2序列化器去读那必然报错。多服务共享Redis时序列化方案必须统一最好沉淀成一个公共依赖模块全团队强制用同一套。3.3 RedisTemplate 与 StringRedisTemplate 怎么选Spring Boot自动配置里默认注册了StringRedisTemplate它的key和value都是String序列化。业界习惯是纯字符串场景验证码、Token、分布式锁key用StringRedisTemplate足够代码简单明了。对象场景存用户信息、商品详情、配置对象用自定义的RedisTemplate更合适。但有一个原则必须记住同一个业务域内不要混用两个Template。因为key的序列化规则不同StringRedisTemplate写入的key是裸字符串RedisTemplate配合StringSerializer写入的key也是裸字符串两者其实能通用但如果你自定义Template时key用了JDK序列化那两边存的key编码完全不一样就出现查不到的灵异事件。我给团队定的规范是统一用自定义的RedisTemplate全部key显式加业务前缀比如auth:token:123、biz:user:456。查询和排查都方便。4. RedisTemplate实操与高频场景4.1 手动操作:String、Hash、List的使用姿势配置好RedisTemplate最常见的就是手动操作。先看操作字符串Autowired private RedisTemplateString, Object redisTemplate; // 写入30分钟过期 redisTemplate.opsForValue().set(biz:user: userId, user, 30, TimeUnit.MINUTES); // 读取 User user (User) redisTemplate.opsForValue().get(biz:user: userId); // 删除 redisTemplate.delete(biz:user: userId);Hash场景是最容易被低估的。比如记录商品的各维度库存HashOperationsString, String, Object hashOps redisTemplate.opsForHash(); hashOps.put(biz:sku:stock:1001, warehouseA, 10); hashOps.put(biz:sku:stock:1001, warehouseB, 20); Integer total hashOps.values(biz:sku:stock:1001) .stream().mapToInt(v - (Integer) v).sum();Hash的优点是字段级别操作不需要整体取出再写回修改一个字段不会产生并发覆盖问题。这在秒杀库存、购物车这类场景非常实用。List场景适合做简单的消息队列或操作日志。比如用leftPush入队、rightPop出队就是一个最简的生产消费模型redisTemplate.opsForList().leftPush(biz:log:queue, logMessage); Object msg redisTemplate.opsForList().rightPop(biz:log:queue, 3, TimeUnit.SECONDS);rightPop带阻塞参数队列为空时会等待指定时间这种轮询方式比while(true) sleep省资源得多。4.2 注解缓存集成Cacheable、CacheEvict、CachePut整合Redis的一大价值是可以直接使用Spring的缓存注解让缓存逻辑和业务代码解耦。配置好CacheManager后在Service方法上标注注解即可。Service public class UserService { Cacheable(cacheNames user, key #id) public User getUserById(Long id) { // 模拟DB查询 return userMapper.selectById(id); } CachePut(cacheNames user, key #user.id) public User updateUser(User user) { userMapper.updateById(user); return user; } CacheEvict(cacheNames user, key #id) public void deleteUser(Long id) { userMapper.deleteById(id); } }三个注解的分工Cacheable先查缓存命中直接返回不执行方法没命中才执行方法并把结果存入缓存。CachePut每次执行方法并把返回结果更新到缓存适合更新数据 刷新缓存。CacheEvict执行方法后删除缓存适合删除数据 清缓存。实际项目里最常用的组合是查用Cacheable改和删用CachePut或CacheEvict。这里有个重要的坑CacheEvict默认先执行方法再删缓存如果删除缓存失败会导致脏数据。升级方案是使用CacheEvict(beforeInvocation true)在方法执行前删缓存配合DB操作失败回滚能尽量避免脏读窗口。还有一个细节key用SpEL表达式。#id取方法参数#user.id取对象属性。不写key时Spring默认会生成一个带参数信息的key可读性差、容易冲突。我建议一律显式声明key。4.3 分布式锁实战从手写到Redisson分布式锁是Redis在微服务场景最经典的应用。不引入额外框架的话最简写法如下public boolean tryLock(String lockKey, String requestId, long expireSeconds) { // SETNX 过期时间原子操作 Boolean result stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS); return Boolean.TRUE.equals(result); } public void unlock(String lockKey, String requestId) { // 只有持有者才能释放防止误删别人的锁 String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), List.of(lockKey), requestId); }手写锁要注意几个细节value必须用requestId标识持有者否则锁过期后A线程还在执行B线程拿到锁又很快释放结果把A的锁删了释放锁用Lua脚本保证校验持有者删除两步的原子性。不过手写锁只是入门生产环境我强烈建议直接用Redissondependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.27.2/version /dependencyAutowired private RedissonClient redissonClient; public void doWithLock(String key, Runnable action) { RLock lock redissonClient.getLock(lock: key); // waitTime等待获取锁的时间leaseTime锁自动释放时间 if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { try { action.run(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }Redisson的看门狗机制会在锁快要过期时自动续期避免业务没执行完锁就没了的问题。这比自己手写优雅太多强烈推荐。5. 常见问题排查与实战避坑5.1 连接失败类问题速查我在实际项目里最常遇到连接问题整理成一张速查表现象可能原因排查手段Unable to connect to Redis网络不通、端口未开放先telnet测试网络通断再确认防火墙策略Connection refusedRedis没启动redis-cli ping或看进程是否存活DENIED Redis is running in protected mode开启了protected-mode且未设置密码修改配置文件bind requirepass高并发下偶发超时连接池太小或Lettuce默认超时设置过短调大max-activetimeout设为3s左右NOAUTH Authentication requiredRedis设置了密码但应用没配检查application.yml的password字段排查连接问题有个固定套路先网络、再服务、再配置、再代码。别一上来就翻代码先用redis-cli连一下能连上说明网络和服务没问题问题一定在应用配置。连不上就逐层排查。5.2 缓存穿透、击穿、雪崩与缓存治理这三兄弟是Redis面试必考题也是生产环境必遇问题。很多人会背定义但整合Redis时经常忘了防范。缓存穿透查询一个不存在的key缓存没有请求直接打到DB。恶意攻击可以用大量不存在的ID把DB打垮。解决办法缓存空值即使查询结果为空也缓存一个空对象设置较短的过期时间比如2分钟避免大量重复查询DB。布隆过滤器在缓存前面加一层布隆过滤器快速判断key是否存在不存在的直接拦截。这个方案适合数据量大、key比较固定的场景。缓存击穿一个热点key过期瞬间大量请求同时打到DB。解决办法互斥锁重建缓存的逻辑加锁只让一个线程查DB其他线程等锁后直接读缓存。实现上可以利用Redis的SETNX。逻辑过期不设置物理过期时间而是存一个逻辑过期字段后台异步更新缓存。适合读多写少的场景但实现复杂度高。缓存雪崩大量key同时过期导致瞬间所有请求打到DB。解决办法过期时间加随机值比如固定30分钟再随机加1~5分钟错开过期时间。多级缓存本地缓存Caffeine Redis本地缓存能扛住一部分流量。这个方案我对接高并发项目时用得最多。// 设置随机过期时间避免雪崩 long baseExpire 30 * 60; long randomExpire ThreadLocalRandom.current().nextLong(60, 300); redisTemplate.opsForValue().set(key, value, baseExpire randomExpire, TimeUnit.SECONDS);这里再提一句Spring Boot整合Caffeine做本地缓存很方便把热点数据和Redis做两级配合效果远胜单用Redis。5.3 可视化工具与日常运维日常开发中我离不开两个工具。第一个是Redis Desktop Manager或它的开源分支Another Redis Desktop Manager图形化查看key、查看value、分析内存占用。连不上的时候先检查SSH隧道或直连方式的配置很多RDM连不上不是RDM的问题是Redis的bind和protected-mode没配置好。第二个是redis-cli用于线上快速排查尤其是redis-cli --bigkeys可以扫描大keyredis-cli --scan --pattern可以按前缀批量找key。大key是个隐患一个Hash里有几万字段读写耗时指数上升。我用redis-cli --bigkeys扫一遍就能定位然后拆分key或改用其他数据结构。5.4 序列化与泛型相关的疑难杂症再补几个常见问题都是群里我最多的。场景一用redisTemplate.opsForValue().get(key)取List 结果转类型报错或得到List edhashmap 。原因泛型类型擦除Jackson不知道要转成List里的哪个类型。解决办法反序列化时传TypeReference或者干脆在Service层用ObjectMapper手动转Object raw redisTemplate.opsForValue().get(key); ListUser users mapper.readValue(mapper.writeValueAsString(raw), new TypeReferenceListUser() {});不过这只是应急方案治本还是统一用GenericJackson2JsonRedisSerializer并确保实体类型信息完整。场景二Redis里能看到key但TTL一直是-1永不过期。原因你写入时没用set(key, value, timeout, TimeUnit)或者用opsForValue().set(key, value)后单独设置过期时间时顺序不对或key拼错了。排查时可以ttl key看剩余时间如果业务要求缓存必须过期而它永不过期那基本是代码里漏了过期时间参数。场景三更新数据后缓存没同步。原因代码里更新了DB但没调CachePut或CacheEvict或者注解的key拼写和查询时不一致。比如查询用#id更新用#user.id两边ID相同但SpEL写法不同生成的缓存key不同删了等于没删。排查用redis-cli按前缀扫描看旧key是否还残留。场景四Redis服务正常但应用启动时疯狂报错。原因极端情况下是ApplicationRunner或PostConstruct里提前执行了Redis操作而连接工厂还没完全初始化。解决方式延迟初始化或把预热逻辑放到ApplicationReadyEvent事件里执行。5.5 生产环境配置清单最后给一份我个人整理的生产环境配置项清单照着抄即可spring: data: redis: host: ${REDIS_HOST:127.0.0.1} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} database: 0 timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 3s shutdown-timeout: 200ms生产建议把host、port、password全部通过环境变量注入不要硬编码在yml里。这样环境切换不用改代码Docker部署时传环境变量就行。如果用了Redisson它也需要一份配置spring: redis: redisson: config: | singleServerConfig: address: redis://${REDIS_HOST:127.0.0.1}:${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} connectionPoolSize: 16 connectionMinimumIdleSize: 4Redisson和Spring Data Redis可以共存但注意别同时管理同一批key的连接池导致资源翻倍。我自己通常只保留Spring Data Redis做缓存操作Redisson只在需要分布式锁的模块单独引入。6. 结合个人经验的一些补充建议6.1 Key命名规范与团队约定踩了这么多坑之后我深刻体会到一件事Redis的坑80%源于规范缺失不是技术不行。我们团队现在强制执行一套key命名规范极大减少了线上问题。格式统一为业务域:模块:业务ID[:子ID]例如biz:user:1001:profile。理由很简单Redis不像MySQL有表结构key就是你的索引。命名规整了redis-cli --scan --pattern biz:user:*就能一次扫出所有用户相关key分析、清理、迁移都方便。没有规范时A写user:1001B写userInfo_1001C写user-1001同一份数据存了三分占内存不说更新时漏一个就出脏数据。过期时间也必须有约定验证码类5分钟Session类30分钟基础数据类1小时配置类永久但要有主动刷新机制。每个key写入时都必须显式指定过期时间不允许用-1的永久key除非你能明确说出它在业务上为什么不失效。6.2 监控与预警Redis看起来轻量生产上内存被打满、连接数被打满的事故我见过不止一次。所以整合完成后建议立刻配上基本监控。至少要做到四点info memory里的used_memory持续跟踪超过maxmemory的70%就预警info clients里的connected_clients持续跟踪超过连接池上限就有问题INFO stats里的keyspace_hits和keyspace_misses两个指标要对比看命中率低于80%说明缓存设计有问题慢查询用SLOWLOG GET 10查看经常有大key或复杂命令出现就要优化。这些可以自己写脚本定时拉取发到钉钉或企业微信群也可以用现成的监控大盘。Redis这种基础设施出事往往是秒级的没有预警就只能事后救火。另外提一个很多团队忽视的容量问题Redis是内存数据库不是无限大的磁盘。很多缓存数据往里一塞就不管了结果内存飙到几个Gmaxmemory没设置操作系统直接OOM。务必在配置里设置maxmemory并选择合适的淘汰策略。业务缓存场景一般用allkeys-lru只希望淘汰过期数据的用volatile-lru。运维上大key要定期清理不建议把一个几MB的JSON塞进value读写都会把单线程的Redis卡住。6.3 一个完整的落地小案例把前面所有内容串起来我讲一个最近帮团队做的登录token缓存方案这个案例能让你看到整合Redis在真实项目里的样子。用户登录成功后生成一个UUID作为token以auth:token:用户ID为key、用户信息为value存进Redis过期时间30分钟。每次请求进来拦截器从Header取token解析出用户ID后查Redis拿用户信息。这样用户状态从查库变成查内存登录鉴权接口的耗时有明显下降而且多实例部署时Token天然共享——用户在一台机器登录另一台机器也能识别这在传统Session方案里很难做。核心代码大致这样Service public class AuthService { private static final String TOKEN_PREFIX auth:token:; public String login(String username, String password) { // 校验用户名密码... String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set( TOKEN_PREFIX token, userId, 30, TimeUnit.MINUTES); return token; } public Long getUserIdByToken(String token) { Object value redisTemplate.opsForValue().get(TOKEN_PREFIX token); // 这里把Integer转Long避免类型不一致 return value null ? null : ((Number) value).longValue(); } public void logout(String token) { redisTemplate.delete(TOKEN_PREFIX token); } }注意value存userId时直接传Long取出时用(Number)强转再longValue()就能避免Integer/Long类型歧义引发的ClassCastException。这个细节是我在真实上线后踩出来的加上后基本没再出现类型转换问题。说到这我最想强调的还是那句话整合Redis本身不难难的是把每一步都做得稳。依赖、配置、序列化、模板、缓存策略、锁、监控每一环都有正确姿势和能跑但坑很多两种写法。按照上面这套流程走下来你已经避开了我这些年踩过的大多数坑。我的个人习惯是每接一个新项目第一件事先确认Redis版本和Spring Boot版本的兼容性第二件事定好key命名规范和序列化方案第三件事配置连接池和监控。这三件事做完后面开发就是流水线作业。Redis会从一个偶尔出问题的组件变成默默扛住所有压力的基础设施这种感觉还是挺踏实的。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门