第七部分:分布式锁 —— 共享资源的安全访问
分布式系统里,多个进程要想互斥地访问同一份共享资源——比如库存扣减、定时任务的执行——这事儿光靠单机锁是搞不定的。这时候,就得请出分布式锁了。

7.1 基于 Redis 的分布式锁
先说最经典的一种实现:Redis 的 SET NX EX 命令。逻辑上很直观——尝试设置一个键值对,只有当键不存在时才成功。一旦成功,就意味着拿到了锁。
基础版本大概长这样:
String lockKey = "lock:product:1001";
String requestId = UUID.randomUUID().toString();
Boolean success = redis.setnx(lockKey, requestId, 30, TimeUnit.SECONDS);
if (success) {
try {
// 执行业务逻辑
} finally {
// 释放锁:使用 Lua 脚本保证原子性(先判断再删除)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
}
}
这个版本直接就能用,但有一个老生常谈的问题:单点故障。如果 Redis 主从切换,锁有可能就丢了。为此,Redis 的作者提出过 Redlock——用多个独立 Redis 节点,多数派加锁。不过实话讲,这个方案在业界争议很大,核心问题在于时钟漂移和 GC 停顿都可能导致锁的意外失效。
更务实的做法,是直接用 Redisson 框架。它封装了看门狗(Watchdog)自动续期机制,可以避免锁过期但任务还没执行完的尴尬情况:
RLock lock = redisson.getLock("myLock");
lock.lock(10, TimeUnit.SECONDS);
try {
// ...
} finally {
lock.unlock();
}
7.2 基于 ZooKeeper 的分布式锁
另一条路径是利用 ZooKeeper 的临时顺序节点来实现锁。每个客户端在锁节点下创建一个临时顺序节点,序号最小的那个获得锁。后序的节点只需要监听前一个节点的删除事件就行,这在实现上天然就是公平锁。
它的优势在于强一致性——有 ZAB 协议做背书,没有单点问题,而且自带心跳检测机制,基本上不用担心死锁。代价也很清楚:性能比 Redis 低一些,适合并发量不那么夸张的场景。
7.3 基于数据库的唯一索引
如果不想引入额外组件,还可以直接用数据库的唯一键约束当锁。比如执行 INSERT INTO distributed_lock(lock_key, node_id) VALUES ('key', 'node1'),插入成功就拿到锁,删掉就释放。性能确实差,但胜在简单可靠,某些边缘场景下反而很管用。
第八部分:服务治理与微服务 —— 管理分布式系统的“千军万马”
一旦服务的数量膨胀到几十甚至上百个,就得有一整套治理体系出来撑场面:服务注册与发现、配置管理、路由、容错、监控……缺一不可。
8.1 服务注册与发现
说白了,服务启动时要把自己的地址注册到注册中心(像 Nacos、Eureka、Consul、ZooKeeper 这些),消费者再从注册中心去拉提供者的列表。这样一来,服务的上下线就能被动态感知到。
拿 Spring Cloud Alibaba 的 Nacos 来说:
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
消费者服务直接通过服务名调用,背后有 Ribbon 做负载均衡:
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/call")
public String call() {
return restTemplate.getForObject("https://service-provider/hello", String.class);
}
}
8.2 配置中心
集中管理各环境的配置,改完配置动态生效、不用重启,这个能力在微服务体系里几乎是标配。Nacos Config、Apollo、Consul Key/Value 都是主流选择。
以 Apollo 为例:
@Value("${timeout:100}")
private int timeout;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
// 刷新相关 Bean
}
8.3 服务调用与负载均衡
除了 RestTemplate + Ribbon 的传统方案,声明式 HTTP 客户端 Feign 也很常见:
@FeignClient(name = "user-service", fallback = UserFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}
8.4 熔断、降级、限流 —— Hystrix / Sentinel
这三种手段是保障分布式系统韧性的核心模式。
熔断(Circuit Breaker)
当某个服务调用的失败率达到阈值(比如 50%),断路器会打开,后续请求直接快速失败或走降级逻辑,阻止级联故障。经过一段时间,系统会放少量请求去测试服务是否恢复(半开状态)。
降级(Fallback)
服务不可用或系统负载过高时,提供一种“有损”的替代响应——返回缓存数据、友好提示等。降级逻辑可以在客户端或服务端实现。
限流(Rate Limiting)
限制单位时间内的请求数量,超过阈值的请求要么被直接拒绝(返回 429 Too Many Requests),要么排队等待。常见的算法有令牌桶、漏桶、计数器。
来看 Sentinel 的实战:
@SentinelResource(value = "getUser", blockHandler = "handleBlock", fallback = "getUserFallback")
public User getUser(Long id) {
// 业务逻辑
}
public User handleBlock(Long id, BlockException ex) {
// 限流或熔断时的处理
return new User(-1L, "系统繁忙");
}
public User getUserFallback(Long id, Throwable t) {
// 所有异常的处理(包括业务异常)
return new User(-1L, "服务降级");
}
配置限流规则:
List rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("getUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100); // 每秒 100 QPS
rules.add(rule);
FlowRuleManager.loadRules(rules); 