
单机时代,我们担心代码效率;互联网时代,我们担心的是"当一百台机器同时崩溃时,代码该如何自救"。
我刚入行时,以为架构就是选型:Spring Cloud还是Dubbo?MySQL还是PostgreSQL?Redis还是Memcached?后来在一次"双11"压测中,我负责的服务在峰值流量下集体"躺平",监控大盘一片飘红,我才顿悟:架构的本质,不是选什么技术,而是当故障发生时,系统能以多优雅的姿态"牺牲"。
互联网架构开发,从来不是堆砌中间件。它是在不确定的流量、不确定的网络、不确定的硬件故障中,用确定性的工程手段,守住系统"最后一公里"的可用性。这篇文章,我不讲微服务理论,也不画架构图,而是从五个"要命时刻"出发,分享那些用血泪换来的架构实践。代码极少,但每行都指向"系统能活下来"这个终极目标。
任何一个互联网系统,最先感受到流量压力的,一定是网关层。如果没有保护,上游一个突发流量就能把整个后端打挂。架构开发的第一课:在网关层实现"限流 + 熔断 + 降级"三位一体。
下面是用Spring Cloud Gateway + Resilience4j实现的一个精简组合:
# application.yml —— 网关的"保命配置"
spring:
cloud:
gateway:
routes:
- id: order_service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- name: CircuitBreaker
args:
name: orderBreaker
fallbackUri: forward:/fallback/order
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒允许100个请求
redis-rate-limiter.burstCapacity: 200 # 突发容量200配合一个极简的降级处理器:
@RestController
public class FallbackController {
@GetMapping("/fallback/order")
public Result<String> orderFallback() {
// 返回"有损"但可接受的响应
return Result.error("当前订单服务繁忙,请稍后重试或联系客服");
}
@GetMapping("/fallback/user")
public Result<String> userFallback() {
// 降级到本地缓存,而非直接报错
String cachedUser = localCache.get("default_user_info");
return Result.success(cachedUser != null ? cachedUser : "系统维护中");
}
}这两段配置+代码定义了系统的"生存边界":流量超过阈值就限流,下游故障就熔断,熔断后走降级逻辑。网关层的核心哲学:宁可给用户一个有礼貌的"拒绝",也不让整个系统在流量洪峰下"脑死亡"。
互联网架构最经典的"三兄弟"——缓存穿透、缓存击穿、缓存雪崩——每一个都能让DBA从梦中惊醒。其中"缓存击穿"(热点Key失效瞬间,海量请求直接打穿缓存打到DB)是最常见的线上事故元凶。
解决方案叫"互斥锁重建":当缓存失效时,只允许一个线程去DB查询重建,其他线程等待。
@Component
public class CacheGuard {
@Autowired
private StringRedisTemplate redisTemplate;
public String getWithMutex(String key, int expireSeconds, Supplier<String> dbLoader) {
// 1. 先查缓存
String cached = redisTemplate.opsForValue().get(key);
if (cached != null) return cached;
// 2. 缓存失效,尝试获取"重建锁"
String lockKey = "LOCK:" + key;
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(3));
if (Boolean.TRUE.equals(locked)) {
try {
// 3. 只有拿到锁的线程去DB查询
String freshData = dbLoader.get();
redisTemplate.opsForValue().set(key, freshData, expireSeconds, TimeUnit.SECONDS);
return freshData;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 4. 没拿到锁,等待100ms后重试(递归,需设重试上限)
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
return getWithMutex(key, expireSeconds, dbLoader);
}
}
}这20行代码是数据库的"护身符"。没有它,一个热门商品详情页在缓存失效的瞬间,就可能把数据库打挂。架构开发的核心能力,就是预判"多个线程同一瞬间抢夺同一个资源"的场景,并设计出"只让一个人干活,其他人排队"的机制。
很多系统在注册接口里同步发短信,发短信平台一卡顿,整个注册流程超时,用户以为系统挂了。架构的铁律是:主流程只做"必须做完才能响应的"事情,其他统统异步化。
Spring Boot中开启异步只需两个注解,但背后的架构思维却价值千金:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Service
public class UserService {
@Async // 此方法将异步执行,不阻塞主线程
public void sendWelcomeSms(String phone, String username) {
try {
smsClient.send(phone, "欢迎注册," + username + "!");
} catch (Exception e) {
log.error("短信发送失败,手机号:{}", phone, e);
// 失败后写入重试队列,不吞掉错误
retryQueue.push(phone, username);
}
}
public Result register(UserRegisterDTO dto) {
// 主流程只做:校验 + 入库
userDao.save(dto);
// 异步发短信,不等待结果
sendWelcomeSms(dto.getPhone(), dto.getUsername());
return Result.success("注册成功");
}
}核心只有两个注解 @EnableAsync 和 @Async,但它们背后的架构原则极其重要:识别出哪些操作是"非核心路径",把它们从主线程中剥离出去。短信发送失败、日志写入慢、统计数据更新延迟——这些都不应该影响用户的核心体验。
很多架构师死磕分布式事务的强一致性,用XA、TCC、Seata的AT模式,最终发现性能下降一个数量级。互联网架构开发的共识是:99%的场景不需要强一致性,最终一致性 + 补偿机制足够。
下面是用消息队列实现最终一致性的核心模式(示例用RocketMQ):
@Service
public class OrderService {
@Autowired
private RocketMQTemplate mqTemplate;
@Transactional
public void createOrder(OrderDTO order) {
// 1. 本地事务:生成订单(状态为"待确认")
order.setStatus(OrderStatus.PENDING);
orderDao.save(order);
// 2. 发送半事务消息(RocketMQ的事务消息)
TransactionSendResult sendResult = mqTemplate.sendMessageInTransaction(
"order-topic",
new Message("扣减库存", order.getProductId(), order.getQuantity()),
order.getOrderId()
);
if (sendResult.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
throw new BizException("库存扣减失败,订单回滚");
}
// 3. 本地事务提交后,将订单状态改为"已确认"
order.setStatus(OrderStatus.CONFIRMED);
orderDao.update(order);
}
}这个模式的精妙在于:订单的"创建"和"扣库存"不是原子操作,而是通过消息实现了"先记账,后执行"的最终一致性。如果库存扣减失败,会触发本地事务回滚和补偿消息。代码本身不长,但它背后的"放弃强一致,拥抱最终一致"的架构权衡,才是真正的价值所在。
讲完了所有代码,我要说说互联网架构开发中最"反直觉"的一件事:代码是最后一次被检查的东西。在此之前,架构要过三道关:
1. 单点故障排查 画出所有服务的依赖图,圈出任何"单点"(比如唯一的Redis节点、唯一的数据库主库),然后问:这个挂了怎么办?如果回答不出来,就需要引入主从切换、多活、或降级方案。
2. 全链路压测 这不是运维的工作,是架构师必须亲自盯的。压测要模拟"最坏情况":比如促销开始前5分钟,缓存全部失效(模拟雪崩);比如某个下游服务突然变慢3倍(模拟网络抖动)。我们的代码必须在这种"恶意环境"下依然体面地降级。
3. 混沌工程演练 每周一次随机杀死生产环境的容器(在可控范围内),观察系统是否能自动恢复。这不是"找罪受",而是用"已知的混乱"来免疫"未知的灾难"。
这三关不需要写一行业务代码,但它们决定了系统的"硬生存能力"。
在经历了几百次线上故障、数十次大促压测、以及无数次深夜救火后,我把互联网架构开发的精髓浓缩为三句话:
下一次当你准备设计一个新系统时,请先不要急着选型Spring Cloud还是Dubbo。拿起笔,画一张"故障树":如果Redis挂了怎么办?如果消息队列积压了怎么办?如果数据库主从延迟了1分钟怎么办?
这些问题的答案,才是互联网架构开发真正的"核心代码"。
全文代码总计不到80行。但在互联网架构的语境中,这80行代码支撑的,是每秒数万次请求的稳定流转、是核心业务在故障下的"体面落幕"、是用户感知不到的"无声守护"。架构师的功力,不在代码里,在代码之外的"假设检验"中。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。