咱们聊分布式系统和并发控制的时候,得先搞清楚 Spring 应用是怎么处理多线程请求的。不管你的应用是跑在一台机器上,还是跑在 Kubernetes 的 Pod 里面,Spring 都离不开多线程,才能高效地处理一大波并发请求。
说白了,Spring 应用服务器(比如 Tomcat、Jetty)都是靠线程池来处理并发请求的。所以,不管你是单机部署还是 Pod 部署,都会有一堆线程在跑。
单机部署:
- 应用就跑在一个 JVM 里面。
- 应用服务器会弄个线程池,里面一堆线程等着处理 HTTP 请求。
- 有请求来了,服务器就从池子里挑个闲着的线程去处理。
- 每个线程都会执行 Spring 的 DispatcherServlet,这家伙负责处理请求、调用控制器的方法,最后生成响应。
举个例子: 你把 Spring 应用部署到 Tomcat 服务器上,Tomcat 配置了 200 个线程的线程池。这时候,如果一大波用户同时访问你的应用,Tomcat 就会从线程池里挑 200 个线程出来干活。每个线程都独立执行 Spring 的请求处理逻辑,所以就等于 200 个线程在同时跑。
Pod 部署:
- 应用跑在 Kubernetes 集群里,可能是一个 Pod,也可能是好几个 Pod。
- 每个 Pod 都有自己的 JVM 和应用服务器。
- 每个应用服务器也都会弄个线程池来处理请求,跟单机部署一样。
- 所以,即使是 Pod 部署,每个 Pod 里面也会有一堆线程在同时跑。
再举个例子: 你把 Spring 应用部署到 3 个 Pod 里,每个 Pod 运行一个 Tomcat 服务器,每个 Tomcat 服务器配置了 100 个线程的线程池。当一大波用户同时访问你的应用时,Kubernetes 会把请求分发到不同的 Pod 上。每个 Pod 里的 Tomcat 服务器就会用 100 个线程来处理这些请求,那么加起来就有 300 个线程在同时跑。
多线程带来的并发问题:
这么多线程同时跑,很容易出现并发问题,比如多个线程同时修改同一个数据,结果就乱套了。这就需要用锁来控制并发。
Redis 锁来帮忙:
Redis 锁是一种分布式锁,可以用来控制多个线程对共享资源的访问。咱们可以用 Redis 的 SETNX
命令来实现分布式锁。
Redis 锁的唯一性:
不管是用单机 Redis 还是分布式 Redis (比如 Redis Cluster),都能保证锁的唯一性。这是因为 SETNX
命令是原子操作,在任何时刻,只有一个客户端能成功执行 SETNX
并拿到锁。
“客户端” 在这里指的是什么呢?
"客户端" 指的是任何连接到 Redis 服务器并发送命令的程序,比如你的 Spring 应用。
即使 Redis Cluster 由多个节点组成,SETNX
命令的原子性也依然有保证。
怎么用 Redis 锁:
- 当一个线程要修改共享资源的时候,先尝试用
SETNX
命令获取锁。 - 如果获取成功,就说明拿到了锁,可以放心地修改共享资源。
- 修改完之后,再用
DEL
命令释放锁。 - 如果获取锁失败,就说明有其他线程已经拿到了锁,那就等等,或者直接返回错误。
总结一下:
不管是单机部署还是 Pod 部署,Spring 应用都离不开多线程来处理并发请求。但是多线程容易引发并发问题,这时候就需要用 Redis 锁来控制并发,保证数据的一致性。 Redis 锁的唯一性可以由单机 Redis 和分布式 Redis 来保证,不用担心锁会重复。