🔥 高可用架构实战指南:告别半夜被叫醒的噩梦!
从"宕机恐惧症"到"睡得安稳" 💪
🎯 开篇:你是否也有这样的经历?
想象一下这个场景:
🌙 深夜2点,你正在温暖的被窝里做着美梦...
📱 突然! 手机疯狂震动,运维小哥哭着打电话:
"老王!!!服务器又挂了!用户群里都炸锅了!!!"
😱 这种感觉,就像你正在享受火锅时突然被告知厨房着火了一样"酸爽"...
但是! 如果你的系统有了高可用架构,就像给服务器买了"全险",即使某台机器罢工,整个系统依然稳如老狗!
今天,老王就来分享如何打造一个让你安心睡觉的高可用架构!
💡 一、什么是高可用架构?
🎯 核心定义
高可用架构(High Availability) = 让系统在最大限度内保持持续可用的架构设计
用大白话讲:让你的系统像"打不死的小强"一样顽强! 🪳
🚗 生活化理解
高可用架构就像城市的交通系统:
-
❌ 单点故障:只有一条路,堵车了全城瘫痪
-
✅ 高可用设计:多条道路、立交桥、地铁、公交,一条路堵了还有其他选择
📊 可用性指标:几个9的奥秘
| 🎯 可用性 | ⏰ 年停机时间 | 📅 月停机时间 | 📆 周停机时间 | 🕐 日停机时间 |
|-----------|---------------|---------------|---------------|---------------|
| 90% | 36.5天 | 72小时 | 16.8小时 | 2.4小时 |
| 99% | 3.65天 | 7.2小时 | 1.68小时 | 14.4分钟 |
| 99.9% | 8.76小时 | 43.2分钟 | 10.1分钟 | 1.44分钟 |
| 99.99% | 52.56分钟 | 4.32分钟 | 1.01分钟 | 8.64秒 |
| 99.999% | 5.26分钟 | 25.9秒 | 6.05秒 | 0.864秒 |
💰 记住:从99%到99.9%容易,但从99.9%到99.99%,难度和成本会指数级增长!
🛠️ 二、FEMA评估法:高可用设计的四步走
🔍 什么是FEMA?
FEMA不是美国联邦应急管理局,在我们这里是高可用设计的四个关键步骤:
🚨 F - Failure(故障识别)
问自己:什么地方可能出问题?
常见故障类型:
-
💻 硬件故障:服务器宕机、磁盘损坏、网络中断
-
🐛 软件故障:程序Bug、内存泄漏、死锁
-
👨💻 人为故障:误操作、配置错误、发布事故
-
🌐 外部故障:机房断电、网络攻击、第三方服务异常
java
// 💡 故障场景示例:数据库连接池耗尽
@Service
publicclassDatabaseService {
@Autowired
privateDataSourcedataSource;
// ❌ 危险:没有超时和重试机制
publicUserfindUser(Longid) {
try (Connectionconn=dataSource.getConnection()) {
// 如果数据库响应慢,连接池很快就会耗尽
PreparedStatementstmt=conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setLong(1, id);
ResultSetrs=stmt.executeQuery();
if (rs.next()) {
returnmapToUser(rs);
}
} catch (SQLExceptione) {
// 💥 直接抛异常,没有降级处理
thrownewRuntimeException("数据库查询失败", e);
}
returnnull;
}
}
📈 E - Effect(影响评估)
问自己:故障会造成什么影响?
影响评估维度:
-
🎯 业务影响:核心功能 vs 边缘功能
-
👥 用户影响:影响用户数量和类型
-
💰 经济损失:每分钟损失多少钱
-
🏢 品牌影响:对公司声誉的损害
java
// 💡 影响评估工具类
@Component
publicclassFailureImpactCalculator {
publicenumBusinessCriticality {
CRITICAL("核心业务", 1.0), // 登录、支付、下单
IMPORTANT("重要业务", 0.7), // 搜索、推荐
NORMAL("一般业务", 0.3), // 评论、分享
LOW("边缘业务", 0.1); // 统计、日志
privatefinalStringdescription;
privatefinaldoubleweight;
BusinessCriticality(Stringdescription, doubleweight) {
this.description= description;
this.weight= weight;
}
}
// 计算故障影响分数
publicdoublecalculateImpactScore(
BusinessCriticalitycriticality,
intaffectedUsers,
doublerevenuePerMinute) {
returncriticality.weight* affectedUsers * revenuePerMinute;
}
}
🛡️ M - Mitigation(缓解措施)
问自己:如何减少故障影响?
常用缓解策略:
-
🔄 冗余设计:多实例、多机房
-
⚡ 快速恢复:自动重启、故障转移
-
📉 降级处理:关闭非核心功能
-
🚦 限流保护:防止雪崩效应
java
// 💡 自动降级示例
@Service
publicclassRecommendationService {
@Autowired
privateRedisTemplate<String, Object> redisTemplate;
@Autowired
privateRecommendationAlgorithmalgorithm;
// ✅ 带降级的推荐服务
@CircuitBreaker(name ="recommendation", fallbackMethod ="getFallbackRecommendations")
@TimeLimiter(name ="recommendation")
publicCompletableFuture<List<Product>> getRecommendations(LonguserId) {
returnCompletableFuture.supplyAsync(() -> {
try {
// 先尝试从缓存获取
List<Product> cached=getCachedRecommendations(userId);
if (cached !=null) {
return cached;
}
// 调用推荐算法
List<Product> recommendations=algorithm.recommend(userId);
// 缓存结果
cacheRecommendations(userId, recommendations);
return recommendations;
} catch (Exceptione) {
log.warn("推荐服务异常,使用降级方案", e);
throw e; // 触发熔断器
}
});
}
// 🔄 降级方案:返回热门商品
publicCompletableFuture<List<Product>> getFallbackRecommendations(LonguserId, Exceptionex) {
log.info("推荐服务降级,返回热门商品,用户ID: {}", userId);
returnCompletableFuture.supplyAsync(() -> {
// 返回预设的热门商品列表
returngetHotProducts();
});
}
}
🎯 A - Availability(可用性目标)
问自己:需要达到什么可用性水平?
设定合理的可用性目标:
-
💳 支付系统:99.99%(年停机52分钟)
-
🛒 电商核心:99.9%(年停机8.76小时)
-
📱 社交功能:99%(年停机3.65天)
java
// 💡 可用性监控服务
@Service
publicclassAvailabilityMonitor {
privatefinalMeterRegistrymeterRegistry;
publicAvailabilityMonitor(MeterRegistrymeterRegistry) {
this.meterRegistry= meterRegistry;
}
// 记录服务可用性
publicvoidrecordServiceAvailability(StringserviceName, booleanisAvailable) {
meterRegistry.gauge("service.availability",
Tags.of("service", serviceName),
isAvailable ?1.0:0.0);
}
// 计算可用性百分比
@Scheduled(fixedRate =60000) // 每分钟计算一次
publicvoidcalculateAvailability() {
// 获取过去24小时的数据
doubleavailability=calculateLast24HoursAvailability();
meterRegistry.gauge("system.availability.24h", availability);
// 如果可用性低于目标,发送告警
if (availability <0.999) { // 低于99.9%
sendAvailabilityAlert(availability);
}
}
}
🗄️ 三、存储高可用:数据的"保险箱"
🔄 3.1 主备模式(Master-Slave)
原理:一主一备,主库挂了备库顶上
就像公司的正副总经理,正总经理出差时副总经理代理工作!
java
// 💡 主备切换配置
@Configuration
publicclassMasterSlaveConfig {
@Bean
@Primary
publicDataSourcemasterDataSource() {
HikariConfigconfig=newHikariConfig();
config.setJdbcUrl("jdbc:mysql://master-db:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(20);
// 🔧 连接测试配置
config.setConnectionTestQuery("SELECT 1");
config.setTestWhileIdle(true);
config.setValidationTimeout(3000);
returnnewHikariDataSource(config);
}
@Bean
publicDataSourceslaveDataSource() {
HikariConfigconfig=newHikariConfig();
config.setJdbcUrl("jdbc:mysql://slave-db:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(30); // 读库可以配置更多连接
config.setReadOnly(true);
returnnewHikariDataSource(config);
}
}
🔗 3.2 主从复制(Master-Slave Replication)
原理:主库负责写,从库负责读,数据实时同步
就像老师讲课(主库写),学生做笔记(从库读)!
java
// 💡 读写分离实现
@Repository
publicclassUserRepository {
@Autowired
@Qualifier("masterDataSource")
privateDataSourcemasterDataSource;
@Autowired
@Qualifier("slaveDataSource")
privateDataSourceslaveDataSource;
// ✍️ 写操作:走主库
@Transactional
publicvoidsaveUser(Useruser) {
JdbcTemplatemasterTemplate=newJdbcTemplate(masterDataSource);
Stringsql="INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)";
masterTemplate.update(sql, user.getName(), user.getEmail(), newDate());
log.info("用户保存成功,ID: {}", user.getId());
}
// 📖 读操作:走从库
@Transactional(readOnly =true)
publicList<User> findActiveUsers() {
JdbcTemplateslaveTemplate=newJdbcTemplate(slaveDataSource);
Stringsql="SELECT * FROM users WHERE status = 'ACTIVE' ORDER BY created_at DESC";
returnslaveTemplate.query(sql, newBeanPropertyRowMapper<>(User.class));
}
// 🔍 强一致性读取:必须走主库
@Transactional(readOnly =true)
publicUserfindUserById(Longid) {
JdbcTemplatemasterTemplate=newJdbcTemplate(masterDataSource);
Stringsql="SELECT * FROM users WHERE id = ?";
returnmasterTemplate.queryForObject(sql, newBeanPropertyRowMapper<>(User.class), id);
}
}
🏘️ 3.3 集群模式(Cluster)
原理:多个节点组成集群,共同提供服务
就像一个小区有多个单元楼,坏了一栋还有其他的!
🎯 集中式 vs 分散式数据集群
🏢 集中式集群:像大型购物中心
-
✅ 管理简单,数据一致性强
-
❌ 成本高,扩展性有限
-
🎯 适合:金融、支付等强一致性场景
🏪 分散式集群:像便利店连锁
-
✅ 扩展性强,成本相对较低
-
❌ 管理复杂,最终一致性
-
🎯 适合:电商、社交等大规模场景
java
// 💡 MongoDB分片集群配置
@Configuration
publicclassMongoShardingConfig {
@Bean
publicMongoClientmongoClient() {
// 分片集群连接
StringconnectionString="mongodb://mongos1:27017,mongos2:27017/mydb";
MongoClientSettingssettings=MongoClientSettings.builder()
.applyConnectionString(newConnectionString(connectionString))
.readPreference(ReadPreference.secondaryPreferred()) // 优先读从库
.writeConcern(WriteConcern.MAJORITY) // 大多数节点确认写入
.readConcern(ReadConcern.MAJORITY) // 大多数节点确认读取
.build();
returnMongoClients.create(settings);
}
}
// 💡 分片数据访问
@Service
publicclassOrderService {
@Autowired
privateMongoTemplatemongoTemplate;
// 🔍 基于分片键查询(高效)
publicList<Order> findOrdersByUserId(StringuserId) {
Queryquery=newQuery(Criteria.where("userId").is(userId));
returnmongoTemplate.find(query, Order.class);
}
// 📊 跨分片查询(相对较慢)
publicList<Order> findOrdersByStatus(Stringstatus) {
Queryquery=newQuery(Criteria.where("status").is(status));
returnmongoTemplate.find(query, Order.class);
}
}
⚡ 四、计算高可用:让服务"永不宕机"
🔄 4.1 主备模式(Active-Passive)
原理:主服务器处理请求,备服务器待命
就像值班医生,主治医生忙不过来时,备班医生立即顶上!
java
// 💡 健康检查服务
@RestController
publicclassHealthCheckController {
@Autowired
privateDatabaseHealthIndicatordatabaseHealth;
@Autowired
privateRedisHealthIndicatorredisHealth;
// 🏥 健康检查接口
@GetMapping("/health")
publicResponseEntity<Map<String, Object>> healthCheck() {
Map<String, Object> health=newHashMap<>();
booleanisHealthy=true;
// 检查数据库
booleandbHealthy=databaseHealth.isHealthy();
health.put("database", dbHealthy ?"UP":"DOWN");
isHealthy &= dbHealthy;
// 检查Redis
booleanredisHealthy=redisHealth.isHealthy();
health.put("redis", redisHealthy ?"UP":"DOWN");
isHealthy &= redisHealthy;
// 检查磁盘空间
booleandiskHealthy=checkDiskSpace();
health.put("disk", diskHealthy ?"UP":"DOWN");
isHealthy &= diskHealthy;
health.put("status", isHealthy ?"UP":"DOWN");
health.put("timestamp", System.currentTimeMillis());
return isHealthy ?
ResponseEntity.ok(health) :
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(health);
}
privatebooleancheckDiskSpace() {
Fileroot=newFile("/");
longfreeSpace=root.getFreeSpace();
longtotalSpace=root.getTotalSpace();
doublefreePercentage= (double) freeSpace / totalSpace *100;
return freePercentage >10.0; // 剩余空间大于10%
}
}
⚖️ 4.2 负载均衡(Load Balance)
原理:多个服务器分担请求压力
就像银行的多个窗口,客户可以选择人少的窗口办业务!
java
// 💡 自定义负载均衡器
@Component
publicclassCustomLoadBalancer {
privatefinalList<String> serverList=Arrays.asList(
"http://server1:8080",
"http://server2:8080",
"http://server3:8080"
);
privatefinalAtomicIntegercounter=newAtomicInteger(0);
privatefinalMap<String, Integer> serverWeights=newHashMap<>();
publicCustomLoadBalancer() {
// 设置服务器权重
serverWeights.put("http://server1:8080", 3); // 高配置服务器
serverWeights.put("http://server2:8080", 2); // 中配置服务器
serverWeights.put("http://server3:8080", 1); // 低配置服务器
}
// 🔄 轮询算法
publicStringroundRobin() {
intindex=counter.getAndIncrement() %serverList.size();
returnserverList.get(index);
}
// ⚖️ 加权轮询算法
publicStringweightedRoundRobin() {
inttotalWeight=serverWeights.values().stream().mapToInt(Integer::intValue).sum();
intrandomWeight=newRandom().nextInt(totalWeight);
intcurrentWeight=0;
for (Map.Entry<String, Integer> entry:serverWeights.entrySet()) {
currentWeight +=entry.getValue();
if (randomWeight < currentWeight) {
returnentry.getKey();
}
}
returnserverList.get(0); // 默认返回第一个
}
// 🎯 最少连接算法
publicStringleastConnections() {
// 实际实现中需要维护每个服务器的连接数
returnserverList.stream()
.min(Comparator.comparing(this::getConnectionCount))
.orElse(serverList.get(0));
}
privateintgetConnectionCount(Stringserver) {
// 这里应该返回实际的连接数
returnnewRandom().nextInt(100);
}
}
🔗 4.3 集群模式(Cluster)
原理:多个节点协同工作,提供统一服务
就像足球队,每个位置都有人,配合默契!
java
// 💡 集群节点管理
@Service
publicclassClusterManager {
privatefinalMap<String, ClusterNode> nodes=newConcurrentHashMap<>();
privatefinalScheduledExecutorServicescheduler=Executors.newScheduledThreadPool(2);
@PostConstruct
publicvoidinit() {
// 启动节点健康检查
scheduler.scheduleAtFixedRate(this::checkNodesHealth, 0, 30, TimeUnit.SECONDS);
// 启动负载均衡
scheduler.scheduleAtFixedRate(this::balanceLoad, 0, 60, TimeUnit.SECONDS);
}
// 🏥 节点健康检查
privatevoidcheckNodesHealth() {
nodes.values().parallelStream().forEach(node -> {
try {
booleanisHealthy=pingNode(node);
node.setHealthy(isHealthy);
node.setLastCheckTime(System.currentTimeMillis());
if (!isHealthy) {
log.warn("节点 {} 健康检查失败", node.getAddress());
handleUnhealthyNode(node);
}
} catch (Exceptione) {
log.error("检查节点 {} 时发生异常", node.getAddress(), e);
node.setHealthy(false);
}
});
}
// 📡 ping节点
privatebooleanpingNode(ClusterNodenode) {
try {
RestTemplaterestTemplate=newRestTemplate();
restTemplate.getForObject(node.getAddress() +"/health", String.class);
returntrue;
} catch (Exceptione) {
returnfalse;
}
}
// 🚨 处理不健康节点
privatevoidhandleUnhealthyNode(ClusterNodenode) {
// 从负载均衡中移除
removeFromLoadBalancer(node);
// 尝试重启节点
if (node.getFailureCount() <3) {
restartNode(node);
} else {
// 标记为永久下线,需要人工介入
node.setStatus(NodeStatus.OFFLINE);
sendAlert("节点 "+node.getAddress() +" 多次重启失败,需要人工处理");
}
}
}
🌐 五、业务高可用:异地多活的艺术
🏢 5.1 同城双活
原理:在同一城市的不同机房部署相同服务
就像在同一个城市开两家分店,一家出问题另一家继续营业!
java
// 💡 同城双活配置
@Configuration
publicclassDualActiveConfig {
@Value("${datacenter.current}")
privateStringcurrentDataCenter;
@Bean
publicDataCenterRouterdataCenterRouter() {
returnnewDataCenterRouter(currentDataCenter);
}
}
@Service
publicclassDataCenterRouter {
privatefinalStringcurrentDC;
privatefinalMap<String, String> dcMapping;
publicDataCenterRouter(StringcurrentDC) {
this.currentDC= currentDC;
this.dcMapping=Map.of(
"DC1", "http://dc1.example.com",
"DC2", "http://dc2.example.com"
);
}
// 🔀 智能路由
publicStringrouteRequest(StringuserId) {
// 根据用户ID哈希决定路由到哪个数据中心
inthash=userId.hashCode();
StringtargetDC= (hash %2==0) ?"DC1":"DC2";
// 如果目标数据中心不可用,路由到当前数据中心
if (!isDataCenterHealthy(targetDC)) {
log.warn("数据中心 {} 不可用,路由到当前数据中心 {}", targetDC, currentDC);
returndcMapping.get(currentDC);
}
returndcMapping.get(targetDC);
}
privatebooleanisDataCenterHealthy(Stringdc) {
// 实际实现中会检查数据中心的健康状态
returntrue;
}
}
🌍 5.2 异地多活
原理:在不同城市部署服务,实现地理级别的容灾
就像全国连锁店,北京的店关了,上海的店照常营业!
java
// 💡 异地多活架构
@Service
publicclassMultiRegionService {
privatefinalMap<String, RegionConfig> regions;
privatefinalConsistentHash<String> hashRing;
publicMultiRegionService() {
this.regions=Map.of(
"beijing", newRegionConfig("北京", "http://bj.api.com", 1),
"shanghai", newRegionConfig("上海", "http://sh.api.com", 1),
"guangzhou", newRegionConfig("广州", "http://gz.api.com", 1)
);
// 构建一致性哈希环
this.hashRing=newConsistentHash<>(regions.keySet());
}
// 🎯 根据用户位置路由
publicStringrouteByLocation(StringuserId, StringuserLocation) {
// 优先路由到用户所在地区
StringpreferredRegion=getPreferredRegion(userLocation);
if (regions.containsKey(preferredRegion) &&
isRegionHealthy(preferredRegion)) {
returnregions.get(preferredRegion).getEndpoint();
}
// 如果首选地区不可用,使用一致性哈希选择备用地区
StringbackupRegion=hashRing.get(userId);
returnregions.get(backupRegion).getEndpoint();
}
// 📊 数据同步
@Async
publicvoidsyncDataAcrossRegions(StringdataType, Objectdata) {
regions.values().parallelStream()
.filter(region ->isRegionHealthy(region.getName()))
.forEach(region -> {
try {
syncToRegion(region, dataType, data);
} catch (Exceptione) {
log.error("同步数据到地区 {} 失败", region.getName(), e);
// 加入重试队列
addToRetryQueue(region.getName(), dataType, data);
}
});
}
}
🛡️ 六、接口级故障方案:最后一道防线
📉 6.1 服务降级(Degradation)
原理:关闭非核心功能,保证核心功能正常
就像手机没电时关闭蓝牙、WiFi,保证通话功能!
java
// 💡 智能降级服务
@Service
publicclassDegradationService {
privatefinalMap<String, Boolean> featureFlags=newConcurrentHashMap<>();
privatefinalRedisTemplate<String, Object> redisTemplate;
// 🎛️ 功能开关
publicbooleanisFeatureEnabled(StringfeatureName) {
// 先检查本地缓存
BooleanlocalFlag=featureFlags.get(featureName);
if (localFlag !=null) {
return localFlag;
}
// 再检查Redis
try {
BooleanredisFlag= (Boolean) redisTemplate.opsForValue()
.get("feature:"+ featureName);
if (redisFlag !=null) {
featureFlags.put(featureName, redisFlag); // 更新本地缓存
return redisFlag;
}
} catch (Exceptione) {
log.warn("获取功能开关失败,使用默认值", e);
}
// 默认开启
returntrue;
}
// 🔧 动态调整功能开关
publicvoidsetFeatureFlag(StringfeatureName, booleanenabled) {
try {
// 更新Redis
redisTemplate.opsForValue().set("feature:"+ featureName, enabled);
// 更新本地缓存
featureFlags.put(featureName, enabled);
log.info("功能开关已更新: {} = {}", featureName, enabled);
} catch (Exceptione) {
log.error("更新功能开关失败", e);
}
}
}
// 💡 使用降级的推荐服务
@Service
publicclassRecommendationService {
@Autowired
privateDegradationServicedegradationService;
publicList<Product> getRecommendations(LonguserId) {
// 🎯 检查推荐功能是否开启
if (!degradationService.isFeatureEnabled("recommendation")) {
log.info("推荐功能已降级,返回热门商品");
returngetHotProducts(); // 降级方案
}
try {
// 正常推荐逻辑
returncalculateRecommendations(userId);
} catch (Exceptione) {
log.error("推荐计算失败,自动降级", e);
// 自动降级
degradationService.setFeatureFlag("recommendation", false);
returngetHotProducts();
}
}
}
⚡ 6.2 熔断器(Circuit Breaker)
原理:当服务异常率过高时,暂时停止调用
就像家里的保险丝,电流过大时自动断开保护电器!
java
// 💡 自定义熔断器
@Component
publicclassCustomCircuitBreaker {
privateenumState {
CLOSED, // 关闭状态:正常调用
OPEN, // 打开状态:拒绝调用
HALF_OPEN // 半开状态:尝试恢复
}
privateStatestate=State.CLOSED;
privateintfailureCount=0;
privatelonglastFailureTime=0;
privatefinalintfailureThreshold=5; // 失败阈值
privatefinallongtimeout=60000; // 超时时间(毫秒)
// 🔄 执行带熔断保护的调用
public <T> Texecute(Supplier<T> operation, Supplier<T> fallback) {
if (state ==State.OPEN) {
// 检查是否可以尝试恢复
if (System.currentTimeMillis() - lastFailureTime > timeout) {
state =State.HALF_OPEN;
log.info("熔断器进入半开状态,尝试恢复");
} else {
log.warn("熔断器开启中,执行降级逻辑");
returnfallback.get();
}
}
try {
Tresult=operation.get();
// 调用成功,重置计数器
if (state ==State.HALF_OPEN) {
state =State.CLOSED;
log.info("熔断器恢复正常");
}
failureCount =0;
return result;
} catch (Exceptione) {
// 调用失败,增加失败计数
failureCount++;
lastFailureTime =System.currentTimeMillis();
if (failureCount >= failureThreshold) {
state =State.OPEN;
log.error("熔断器开启,失败次数: {}", failureCount);
}
log.warn("服务调用失败,执行降级逻辑", e);
returnfallback.get();
}
}
}
// 💡 使用熔断器的服务
@Service
publicclassPaymentService {
@Autowired
privateCustomCircuitBreakercircuitBreaker;
@Autowired
privateThirdPartyPaymentAPIpaymentAPI;
publicPaymentResultprocessPayment(PaymentRequestrequest) {
returncircuitBreaker.execute(
// 🎯 正常逻辑
() -> {
returnpaymentAPI.pay(request);
},
// 🔄 降级逻辑
() -> {
log.warn("支付服务熔断,订单进入待处理队列");
// 将订单放入队列,稍后重试
addToRetryQueue(request);
returnPaymentResult.builder()
.status("PENDING")
.message("支付请求已提交,请稍后查看结果")
.build();
}
);
}
}
🚦 6.3 限流(Rate Limiting)
原理:控制请求速率,防止系统过载
就像高速公路收费站,控制车流量防止拥堵!
java
// 💡 令牌桶限流器
@Component
publicclassTokenBucketRateLimiter {
privatefinalMap<String, TokenBucket> buckets=newConcurrentHashMap<>();
// 🪣 令牌桶
privatestaticclassTokenBucket {
privatefinalintcapacity; // 桶容量
privatefinaldoublerefillRate; // 令牌生成速率(每秒)
privatedoubletokens; // 当前令牌数
privatelonglastRefillTime; // 上次补充时间
publicTokenBucket(intcapacity, doublerefillRate) {
this.capacity= capacity;
this.refillRate= refillRate;
this.tokens= capacity;
this.lastRefillTime=System.currentTimeMillis();
}
// 🎫 尝试获取令牌
publicsynchronizedbooleantryAcquire(intpermits) {
refill(); // 先补充令牌
if (tokens >= permits) {
tokens -= permits;
returntrue;
}
returnfalse;
}
// 🔄 补充令牌
privatevoidrefill() {
longnow=System.currentTimeMillis();
doubletokensToAdd= (now - lastRefillTime) /1000.0* refillRate;
tokens =Math.min(capacity, tokens + tokensToAdd);
lastRefillTime = now;
}
}
// 🎯 获取或创建令牌桶
publicTokenBucketgetBucket(Stringkey, intcapacity, doublerefillRate) {
returnbuckets.computeIfAbsent(key,
k ->newTokenBucket(capacity, refillRate));
}
// ✅ 检查是否允许请求
publicbooleanisAllowed(Stringkey, intpermits) {
TokenBucketbucket=getBucket(key, 100, 10.0); // 默认配置
returnbucket.tryAcquire(permits);
}
}
// 💡 限流拦截器
@Component
publicclassRateLimitInterceptorimplementsHandlerInterceptor {
@Autowired
privateTokenBucketRateLimiterrateLimiter;
@Override
publicbooleanpreHandle(HttpServletRequestrequest,
HttpServletResponseresponse,
Objecthandler) throwsException {
// 🔍 获取限流key(可以是IP、用户ID等)
StringclientIP=getClientIP(request);
StringrateLimitKey="ip:"+ clientIP;
// 🚦 检查限流
if (!rateLimiter.isAllowed(rateLimitKey, 1)) {
log.warn("请求被限流,IP: {}", clientIP);
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType("application/json;charset=UTF-8");
StringerrorResponse="""
{
"error": "请求过于频繁",
"message": "请稍后再试",
"code": 429
}
""";
response.getWriter().write(errorResponse);
returnfalse;
}
returntrue;
}
privateStringgetClientIP(HttpServletRequestrequest) {
StringxForwardedFor=request.getHeader("X-Forwarded-For");
if (xForwardedFor !=null&&!xForwardedFor.isEmpty()) {
returnxForwardedFor.split(",")[0].trim();
}
StringxRealIP=request.getHeader("X-Real-IP");
if (xRealIP !=null&&!xRealIP.isEmpty()) {
return xRealIP;
}
returnrequest.getRemoteAddr();
}
}
🎯 七、总结与实践建议
📝 核心要点回顾
1.🎯 FEMA评估法:系统化的高可用设计方法
-Failure:识别可能的故障点
-Effect:评估故障影响范围
-Mitigation:制定缓解措施
-Availability:设定可用性目标
2.🗄️ 存储高可用:数据的安全保障
-
主备模式:简单可靠
-
主从复制:读写分离
-
集群模式:水平扩展
3.⚡ 计算高可用:服务的稳定运行
-
负载均衡:分散压力
-
健康检查:及时发现问题
-
自动故障转移:快速恢复
4.🌐 业务高可用:地理级别容灾
-
同城双活:本地容灾
-
异地多活:地理容灾
-
数据同步:保证一致性
5.🛡️ 接口级保护:最后一道防线
-
服务降级:保证核心功能
-
熔断器:防止雪崩
-
限流:控制访问速率
💡 实践建议
🚀 新手入门
1.从监控开始:先能看到问题,再解决问题
2.单点改造:识别系统中的单点故障,逐一改造
3.渐进式改进:不要一次性大改,小步快跑
🎯 进阶优化
1.自动化运维:减少人为操作,提高响应速度
2.容量规划:提前预估系统容量,避免突发流量
3.演练验证:定期进行故障演练,验证高可用方案
🏆 高级实践
1.混沌工程:主动注入故障,测试系统韧性
2.全链路监控:端到端的性能和可用性监控
3.智能运维:基于AI的故障预测和自动修复
🤔 思考题
1.💭 场景题:如果你的电商系统在双11期间遇到数据库主库宕机,你会如何应对?
2.🔧 设计题:设计一个支持100万QPS的秒杀系统,需要考虑哪些高可用措施?
3.📊 分析题:比较集中式和分散式数据集群在不同业务场景下的优劣势?
🎉 结语
高可用架构不是一蹴而就的,它需要我们:
-
🎯 系统性思考:用FEMA方法全面分析
-
🔧 渐进式改进:从最关键的地方开始
-
📊 持续监控:数据驱动的优化决策
-
🚀 不断演练:实战中验证和完善
记住:高可用架构的目标不是追求100%的完美,而是在成本和收益之间找到最佳平衡点。
从今天开始,给你的系统加上这些"保险"吧!让我们一起告别半夜被叫醒的噩梦,享受安稳的睡眠! 😴
📢 互动时间
👍 觉得有用请点赞,让更多同学看到这篇干货!
💬 留言区聊聊:你在工作中遇到过哪些高可用的坑?是怎么解决的?
🔄 转发分享:帮助更多技术同学告别"宕机恐惧症"!
📱 关注我:更多架构干货持续更新中...
作者简介:张小黑,互联网大厂高级开发,专注分享实用的技术干货。让复杂的技术变得简单易懂!