后端兄弟们,别划走!
是不是每次项目上线,都提心吊胆?
测试环境一切正常,一到生产就崩;
流量稍微上来,接口就超时、页面打不开;

半夜被运维电话叫醒,排查半天,就因为一个缓存没配置好;
学了一堆分布式架构,落地时一团乱麻,堆砌中间件反而越改越糟!
做后端开发5年,我踩过的高可用坑,能写一本血泪史!
其实高可用真没那么复杂,不用搞花里胡哨的操作,核心就3件事:规避单点故障、扛住高并发、出问题能兜底。
今天这篇,全程无废话、纯实操,每一步都给可直接复制的代码,新手跟着做就能落地,老开发也能避开90%的线上坑!
建议收藏+转发,避免后面找不到!
一、先扎心:后端最头疼的3个高可用痛点,你中了几个?
做高可用之前,先搞懂我们要解决什么问题!
这3个痛点,几乎每个后端都踩过,看完直呼“太真实”!
痛点1:单点故障=线上崩服,一夜回到解放前
服务、数据库、缓存,只部署一台!
只要其中一个挂掉,整个系统直接瘫痪。
比如Redis崩了,所有请求直接冲数据库,分分钟压垮,用户投诉炸锅,你还得半夜爬起来救火!
痛点2:高并发扛不住,活动促销必掉链子
做活动、搞秒杀,流量突然暴涨;
接口响应变慢、报错,甚至直接拒绝服务;
明明代码没问题,就因为没做负载均衡,白白浪费服务器资源,还被老板骂!
痛点3:缓存用不好,反而拖垮整个系统
以为加了缓存就万事大吉?
缓存穿透、击穿、雪崩,只要踩中一个,数据库直接被压垮;
越优化越糟,最后怀疑人生:到底是缓存没用对,还是自己技术不行?
重点来了!这些问题,都有可落地的解决方案!
本文选型全程轻量、易维护,不用复杂微服务,中小项目、新手直接冲:
SpringBoot 2.7.x + Nginx 1.21.x + Redis Cluster 6.x + MySQL 8.0(主从) + Sentinel 1.8.x
二、实操第一步:搭建分布式架构,规避单点故障(必做!)
分布式架构的核心,说破了就一句话:多节点部署,不把鸡蛋放一个篮子里。
请求流向(极简版,一看就懂):
前端请求 → Nginx(负载均衡) → 多台SpringBoot服务 → Redis Cluster/MySQL主从
所有配置均可直接复制,重点看注释,不用自己瞎琢磨!
1. Nginx负载均衡配置(最基础,先搞定)
作用:把请求均匀分给多台SpringBoot服务,避免单台过载。
配置文件(nginx.conf),替换自己的服务IP即可:
# 负载均衡配置upstream springboot_server { server 192.168.1.101:8080 weight=1; # 服务节点1 server 192.168.1.102:8080 weight=1; # 服务节点2,均匀分发 ip_hash; # 可选,避免用户重复登录(用不到可删)}server { listen 80; # 默认监听80端口 server_name localhost; # 可替换成自己的域名/IP location / { proxy_pass http://springboot_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 传递真实IP }}实操说明:配置完执行 nginx -s reload 重启,访问接口,两台服务日志都有记录,就是成功了!
2. SpringBoot连接Redis Cluster(避免缓存单点)
单点Redis太容易崩,Redis Cluster自动分片、故障转移,配置超简单:
spring: redis: cluster: nodes: # 替换自己的Redis节点,至少3个主节点 - 192.168.1.103:6379 - 192.168.1.104:6379 - 192.168.1.105:6379 max-redirects: 3 # 默认即可,不用改 lettuce: pool: max-active: 100 # 连接池最大活跃数 max-wait: 3000 # 最大等待时间(毫秒) timeout: 5000 # 连接超时时间实操说明:建议搭3主3从,不用手动分片,文末源码附详细搭建步骤,直接跟着做!
3. MySQL主从配置+读写分离(数据库不崩,系统才稳)
数据库是系统命脉,单点数据库崩了,全完了!
主库写、从库读,既分担压力,又避免单点故障,步骤超详细!
① 主库+从库核心配置(直接复制)
主库配置(my.cnf / my.ini):
[mysqld]server-id=1 # 主库ID,唯一log-bin=mysql-bin # 开启二进制日志(用于同步)binlog-do-db=test # 需同步的数据库(替换自己的)binlog-ignore-db=mysql # 忽略系统库binlog_format=row # 同步格式,更安全从库配置(my.cnf / my.ini):
[mysqld]server-id=2 # 从库ID,唯一(别和主库重复)relay-log=mysql-relay-bin # 开启中继日志read-only=1 # 从库设为只读replicate-do-db=test # 同步的数据库,和主库一致② 主从同步启动步骤(关键,别错!)
- 主库执行:flush tables with read lock;(锁定表,避免同步出错)
- 主库导出数据:mysqldump -u root -p test > test.sql(test替换自己的数据库名)
- 把test.sql导入从库:mysql -u root -p test < test.sql
- 主库执行:show master status;(记好File和Position值,后面要用)
- 从库执行:change master to master_host='主库IP', master_user='root', master_password='主库密码', master_log_file='步骤4的File值', master_log_pos=步骤4的Position值;
- 从库执行:start slave;(启动同步)
- 从库执行:show slave status\G;(两个Running都是Yes,就是成功了!)
③ SpringBoot读写分离配置(直接复制代码)
application.yml配置:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource # 主库(写操作) master: url: jdbc:mysql://192.168.1.106:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: root password: 123456 # 从库(读操作) slave: url: jdbc:mysql://192.168.1.107:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: root password: 123456核心代码(不用改,仅调整mapper路径):
// 1. 动态数据源配置类@Configurationpublic class DynamicDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map三、实操第二步:缓存优化,解决3大痛点(重中之重!)
很多项目崩服,都是因为缓存没用好!
下面针对缓存穿透、击穿、雪崩,给可直接落地的解决方案,代码复制就能用!
1. 缓存穿透:无效请求打穿缓存?这样解
痛点:请求查不存在的数据(比如ID=-1),缓存和数据库都没有,所有请求直接冲数据库,长期必崩!
解决方案:缓存空值(新手优先,简单易落地)
@Servicepublic class ProductService { @Resource private RedisTemplate redisTemplate; @Resource private ProductMapper productMapper; private static final String CACHE_KEY = "product:info:"; public Product getProductById(Long id) { String key = CACHE_KEY + id; // 1. 先查缓存 Product product = (Product) redisTemplate.opsForValue().get(key); if (product != null) return product; // 2. 查数据库 product = productMapper.selectById(id); // 3. 数据库无数据,缓存空值(10分钟过期) if (product == null) { redisTemplate.opsForValue().set(key, null, 10, TimeUnit.MINUTES); return null; } // 4. 缓存有效数据(1小时过期) redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS); return product; }} 2. 缓存击穿:热点key过期?这样兜底
痛点:热门商品、高频接口的缓存key过期,大量请求同时查库,瞬间压垮数据库!
解决方案:热点key永不过期 + 分布式锁
@Servicepublic class ProductService { @Resource private RedisTemplate redisTemplate; @Resource private ProductMapper productMapper; private static final String CACHE_KEY = "product:info:"; private static final String LOCK_KEY = "product:lock:"; public Product getHotProductById(Long id) { String cacheKey = CACHE_KEY + id; String lockKey = LOCK_KEY + id; Product product = null; try { // 1. 查缓存(热点key永不过期) product = (Product) redisTemplate.opsForValue().get(cacheKey); if (product != null) return product; // 2. 获取分布式锁(避免并发查库) Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(lock)) { try { // 再次查缓存,防止并发重复查库 product = (Product) redisTemplate.opsForValue().get(cacheKey); if (product != null) return product; // 查数据库,存入缓存 product = productMapper.selectById(id); redisTemplate.opsForValue().set(cacheKey, product); } finally { // 释放锁(必须在finally中) redisTemplate.delete(lockKey); } } else { // 未获取锁,重试 Thread.sleep(50); return getHotProductById(id); } } catch (Exception e) { redisTemplate.delete(lockKey); // 异常时直接查库,保证接口可用 return productMapper.selectById(id); } return product; }} 3. 缓存雪崩:大量key同时过期?这样规避
痛点:大量缓存key同时过期,大量请求冲数据库,直接宕机!
解决方案:过期时间加随机值 + 缓存预热
@Servicepublic class CacheWarmupService implements CommandLineRunner { @Resource private RedisTemplate redisTemplate; @Resource private ProductMapper productMapper; private static final String CACHE_KEY = "product:info:"; // 系统启动后自动预热缓存 @Override public void run(String... args) throws Exception { // 查询热点数据(比如前100条热门商品) List hotProducts = productMapper.selectHotProducts(100); if (hotProducts.isEmpty()) return; // 存入缓存,过期时间加随机值(1小时±30分钟) for (Product product : hotProducts) { String key = CACHE_KEY + product.getId(); long expireTime = 3600 + new Random().nextInt(3600) - 1800; redisTemplate.opsForValue().set(key, product, expireTime, TimeUnit.SECONDS); } System.out.println("缓存预热完成,共加载" + hotProducts.size() + "条数据"); }} 四、实操第三步:故障兜底,高可用最后一道防线
就算架构搭得再完美,也难免出故障:Redis宕机、数据库异常、服务挂掉……
做好故障兜底,避免系统完全崩溃,给用户友好反馈,也能少挨骂!
核心方案:用Sentinel实现服务降级+熔断(比Hystrix更轻量、易配置)
1. 引入依赖(pom.xml)
<!-- Sentinel核心依赖 --> com.alibaba.cloud spring-cloud-starter-alibaba-sentinel 2.2.9.RELEASE <!-- Sentinel控制台依赖(可选,可视化配置) --> com.alibaba.csp sentinel-dashboard 1.8.6 2. 配置Sentinel(application.yml)
spring: cloud: sentinel: transport: dashboard: localhost:8080 # 控制台地址 port: 8719 # 客户端端口,默认即可 eager: true # 关闭懒加载,启动即注册3. 接口熔断降级代码(直接复制)
@RestController@RequestMapping("/product")public class ProductController { @Resource private ProductService productService; // 熔断降级配置,异常时调用兜底方法 @SentinelResource(value = "getProductById", fallback = "getProductFallback") @GetMapping("/{id}") public ResultVO getProductById(@PathVariable Long id) { Product product = productService.getProductById(id); return product == null ? ResultVO.fail("商品不存在") : ResultVO.success(product); } // 兜底方法(返回默认数据,保证接口不报错) public ResultVO getProductFallback(Long id) { Product defaultProduct = new Product(); defaultProduct.setId(id); defaultProduct.setName("系统繁忙,请稍后重试"); return ResultVO.success(defaultProduct); }} 五、博主避坑指南
这些坑,我在生产环境踩过不止一次,分享给大家,能避则避!
- 分布式架构别一开始就搞复杂,先落地“Nginx+Redis Cluster+MySQL主从”,再迭代优化;
- Redis缓存(除热点key)必须设过期时间,还要加随机值,避免缓存雪崩;
- MySQL主从同步后,一定要校验状态(两个Running都是Yes),否则数据不一致,业务必出问题;
- 故障兜底不能少,哪怕返回默认数据,也比接口报错、系统崩溃强;
- 一定要做系统监控(推荐Prometheus+Grafana),提前发现故障,不用半夜救火!
六、总结
其实SpringBoot高可用优化,真的没那么难!
核心就是3件事:规避单点故障、优化缓存、做好故障兜底。
后续会持续分享SpringBoot高可用进阶内容(微服务高可用、分布式事务),关注我,后端开发少踩坑、多成长!
❤️ 觉得有用,记得点赞+收藏+转发,让更多后端兄弟看到这份干货!
#SpringBoot #分布式架构 #高可用优化 #Java后端 #缓存优化 #后端开发 #程序员
💻 配套实训环境与动手练习
本文涉及的相关技术指令、开发环境与工具链已内置在边学边练在线实验室中,无需繁琐安装配置,随时在浏览器中实践体验: