当一个系统越做越大时,各种细小的问题就会被放大。比如我们的一个单体项目(单体项目指的是各种模块都在同一个服务器上部署),它越做越大时,你会发现系统越来越复杂,新的开发者对这个系统的理解越来越困难,这时候就诞生出了 微服务架构。以后会讲微服务架构。
1. 高并发问题
1.1. 单节点问题
另外当我们的系统用户量越来越多时,并发量达到了好几百(几百的已经非常高了,并发量是指同一时间处理请求的数量,淘宝最高才几十万的并发量),这时如果我们的后端项目只部署在一台服务器上时,它有时会响应非常慢,因为它同时处理请求的数量有限,Tomcat 默认为 200 的最大连接数。所以我们会考虑使用更多的 Tomcat 服务器来处理用户请求。
然后我们使用多台服务器来处理用户请求,这时我们又遇到了新的问题,每台服务器的 IP 都是不一样的,我怎么指定用户的请求发送到哪个服务器呢?如果所有的用户都发送到同一台服务器那这台服务器不直接 GG?
这时候我们需要使用到 Nginx 的负载均衡,关于 Nginx 的负载均衡使用非常简单,大佬们都已经安排好了这些操作,我们只需要简单地配置就行了,我们只是站在了巨人的肩膀上,相关配置可以参考这篇文章:搭建 Tomcat 集群【Nginx 负载均衡】 - 掘金 (juejin.cn)
通过增加服务器的数量可以解决用户激增导致的服务器并发能力的缺陷,但是往往解决了一个问题又会产生新的问题,下面介绍多节点带来的问题。
1.2. 多节点问题
使用多节点可以提高我们项目的并发能力,但它也带来了很多的并发问题,常见的就是 商品超卖问题。
这是由于用户的请求达到了不同的服务器,由于用户的请求是在不同的服务器上进行处理的,所以即使在项目中使用了 synchronized 锁,也不能阻塞另一个服务器上的线程的执行,这就非常容易导致数据的不一致。
我们采用分布式锁来解决这个问题,通过使用 Redis 来实现分布式锁。下面我们介绍如何使用 Redis 来实现简单的分布式锁。
1.3. 分布式锁
Redis 提供了一个命令:setnx key value,在设置这个键时,如果对应的 key 存在,则不会进行任何操作,如果 key 不存在,则会设置键值对。
在不同的服务器上,我们在程序中对 Redis 进行操作,当程序使用 setnx 命令执行成功就执行后续代码,执行失败有不同的处理,后面再介绍。
当然简单的分布式锁原理就是这么个原理,就是通过 setnx 来实现的,不同服务器上的线程同时去设置 Redis 上的 key,设置成功就获取到了对应的锁。不过其中还要防范一些意外情况,下面来介绍一下这些可能的情况。
1.3.1. 锁释放问题
当一台服务器在 Redis 上设置了对应的 key 之后,也就是获取到了分布式锁之后,如果该线程死亡,或者服务器宕机导致分布式锁没有释放,该怎么办?
由于锁无法由服务器释放,所以我们考虑给这个 key 上一个过期时间,当超过这个时间之后,Redis 将自动删除这个 key,也就是移除锁。通过 expire key second 可以实现。
比如我添加了一个 china: strongest:
setnx china strongest
# 设置 10s 后过期
expire china 10
这样当服务器宕机时,也能保证服务的可用性。保证分布式锁能够释放。但是这又引出了新的问题,万一程序执行时间超过了 10 秒该怎么办?如果在程序还在执行的过程中释放掉了锁,这产生的后果将难以预料!
这里就引出 看门狗 的机制。实现的前提是 expire 还可以给 key 的过期时间延长,即修改它的过期时间,让它从新设置的时间重新开始计时,这就是看门狗的实现原理。
看门狗它实际上是服务器上的一个守护线程,它会在 Redis 上的 key 快过期时,重置 key 的过期时间。这样就能很好地解决当服务器宕机时,分布式锁不能及时释放的问题。
1.3.2. 锁设置失败的处理
前面说到当服务器线程尝试获取分布式锁可能会失败。这里我们根据自己的需要可以在获取锁失败之后重新尝试获取,直到获取成功,或者你可以在这个不断尝试获取锁的过程中设置超时时间,当超过这个时间就不再获取锁。也可以在第一次获取失败之后就直接通知用户相关的失败信息,让用户重新发起请求。
大概的获取分布式锁之后的处理基本是这个样子,重新获取锁的实现我们叫它 阻塞锁,只获取一次的实现,我们叫它 非阻塞锁。
1.4. Redisson
说了以上那么多,是想更好地理解分布式锁,出了问题时可以快速定位。当然平时使用分布式锁的话,已经有 Redisson 这个工具帮我们解决相关的问题,它的使用我就不介绍了,使用起来并不难,要深入学习的话还是得看官方文档。它的官网是这个:Redisson: Easy Redis Java client and Real-Time Data Platform