SpringBoot整合!关于分布式锁原理的一些学习与思考

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: SpringBoot整合!关于分布式锁原理的一些学习与思考

第二个请求到来后,被服务器2获取,继续去询问,管理红包的妹子,我这边要分10块,管理红包的妹子先查了下还有99999900,那就说:好,给你10块。那就剩下99999890块

等到第1000w个请求到来后,服务器100拿到请求,继续去询问,管理红包的妹子,你要100,妹子翻了翻白眼,对你说,就剩1块了,爱要不要,那这个时候就只能给你1块了(1块也是钱啊,买根辣条还是可以的)。

这些请求编号1,2不代表执行的先后顺序,正式的场景下,应该是 100台服务器每个服务器持有一个请求去访问负责管理红包的妹子(服务),那在管红包的妹子那里同时会接收到100个请求,这个时候就需要在负责红包的妹子那里加个锁就可以了(抛绣球),你们100个服务器谁拿到锁(抢到绣球),谁就进来和我谈,我给你分,其他人就等着去吧

经过上面的分布式锁的处理后,马云爸爸终于放心了,决定给红包团队每人加一个鸡腿。

简化的结构图如下:

3. 分布式锁的实现有哪些?

说到分布式锁的实现,还是有很多的,有数据库方式的,有redis分布式锁,有zookeeper分布式锁等等~

我们如果采用redis作为分布式锁,那么上图中负“责红包的妹子(服务)”,就可以替换成redis,请自行脑补。

3.1 为什么redis可以实现分布式锁?

首先redis是单线程的,这里的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。

在实际的操作中过程大致是这样子的:

服务器1要去访问发红包的妹子,也就是redis,那么他会在redis中通过"setnx key value" 操作设置一个key 进去,value是啥不重要,重要的是要有一个key,也就是一个标记,而且这个key你爱叫啥叫啥,只要所有的服务器设置的key相同就可以。

假设我们设置一个,如下图

那么我们可以看到会返回一个1,那就代表了成功。

如果再来一个请求去设置同样的key,如下图:

这个时候会返回0,那就代表失败了。

那么我们就可以通过这个操作去判断是不是当前可以拿到锁,或者说可以去访问“负责发红包的妹子”,如果返回1,那我就开始去执行后面的逻辑,如果返回0,那就说明已经被人占用了,我就要继续等待。

当服务器1拿到锁之后,进行了业务处理,完成后,还需要释放锁,如下图所示:

删除成功返回1,那么其他的服务器就可以继续重复上面的步骤去设置这个key,以达到获取锁的目的。

当然以上的操作是在redis客户端直接进行的,通过程序调用的话,肯定就不能这么写,比如java 就需要通过jedis 去调用,但是整个处理逻辑基本都是一样的

通过上面的方式,我们好像是解决了分布式锁的问题,但是想想还有没有什么问题呢??

对,问题还是有的,可能会有死锁的问题发生,比如服务器1设置完之后,获取了锁之后,忽然发生了宕机。

那后续的删除key操作就没法执行,这个key会一直在redis中存在,其他服务器每次去检查,都会返回0,他们都会认为有人在使用锁,我需要等。

为了解决这个死锁的问题,我们就就需要给key 设置有效期了。

设置的方式有2种:

1. 第一种就是在set完key之后,直接设置key的有效期 “expire key timeout” ,为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。

这种方式相当于,把锁持有的有效期,交给了redis去控制。如果时间到了,你还没有给我删除key,那redis就直接给你删了,其他服务器就可以继续去setnx获取锁。

2. 第二种方式,就是把删除key权利交给其他的服务器,那这个时候就需要用到value值了,

比如服务器1,设置了value 也就是 timeout 为 当前时间+1 秒 ,这个时候服务器2 通过get 发现时间已经超过系统当前时间了,那就说明服务器1没有释放锁,服务器1可能出问题了,

服务器2就开始执行删除key操作,并且继续执行setnx 操作。

但是这块有一个问题,也就是,不光你服务器2可能会发现服务器1超时了,服务器3也可能会发现,如果刚好,服务器2,setnx操作完成,服务器3就接着删除,是不是服务器3也可以setnx成功了?

那就等于是服务器2和服务器3都拿到锁了,那就问题大了。这个时候怎么办呢?

这个时候需要用到  “GETSET  key value” 命令了。这个命令的意思就是获取当前key的值,并且设置新的值。

假设服务器2发现key过期了,开始调用 getset 命令,然后用获取的时间判断是否过期,如果获取的时间仍然是过期的,那就说明拿到锁了。

如果没有,则说明在服务2执行getset之前,服务器3可能也发现锁过期了,并且在服务器2之前执行了getset操作,重新设置了过期时间。

那么服务器2就需要放弃后续的操作,继续等待服务器3释放锁或者去监测key的有效期是否过期。

这块其实有一个小问题是,服务器3已经修改了有效期,拿到锁之后,服务器2,也修改了有效期,但是没能拿到锁,但是这个有效期的时间已经被在服务器3的基础上有增加一些,但是这种影响其实还是很小的,几乎可以忽略不计。

3.2 为什么zookeeper可以实现分布式锁?

百度百科是这么介绍的:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。

那对于我们初次认识的人,可以理解成ZooKeeper就像是我们的电脑文件系统,我们可以在d盘中创建文件夹a,并且可以继续在文件夹a中创建 文件夹a1,a2。

那我们的文件系统有什么特点??那就是同一个目录下文件名称不能重复,同样ZooKeeper也是这样的。

在ZooKeeper所有的节点,也就是文件夹称作 Znode,而且这个Znode节点是可以存储数据的。

我们可以通过“ create /zkjjj nice” 来创建一个节点,这个命令就表示,在跟目录下创建一个zkjjj的节点,值是nice。同样这里的值,和我在前面说的redis中的一样,没什么意义,你随便给。

另外ZooKeeper可以创建4种类型的节点,分别是:

  1. 持久性节点
  2. 持久性顺序节点
  3. 临时性节点
  4. 临时性顺序节点

首先说下持久性节点和临时性节点的区别,持久性节点表示只要你创建了这个节点,那不管你ZooKeeper的客户端是否断开连接,ZooKeeper的服务端都会记录这个节点。

临时性节点刚好相反,一旦你ZooKeeper客户端断开了连接,那ZooKeeper服务端就不再保存这个节点。

再说下顺序性节点,顺序性节点是指,在创建节点的时候,ZooKeeper会自动给节点编号比如0000001 ,0000002 这种的。

最后说下,zookeeper有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)等,zookeeper会通知客户端。

下面我们继续结合我们上面的分红包场景,描述下在zookeeper中如何加锁。

假设服务器1,创建了一个节点 /zkjjj ,成功了,那服务器1就获取了锁,服务器2再去创建相同的锁,那么他就会失败,这个时候他就就只能监听这个节点的变化。

等到服务器1,处理完业务,删除了节点后,他就会得到通知,然后去创建同样的节点,获取锁处理业务,再删除节点,后续的100台服务器与之类似

注意这里的100台服务器并不是挨个去执行上面的创建节点的操作,而是并发的,当服务器1创建成功,那么剩下的99个就都会注册监听这个节点,等通知,以此类推。

但是大家有没有注意到,这里还是有问题的,还是会有死锁的情况存在,对不对?

当服务器1创建了节点后挂了,没能删除,那其他99台服务器就会一直等通知,那就完蛋了。。。

这个时候呢,就需要用到临时性节点了,我们前面说过了,临时性节点的特点是客户端一旦断开,就会丢失,也就是当服务器1创建了节点后,如果挂了。

那这个节点会自动被删除,这样后续的其他服务器,就可以继续去创建节点,获取锁了。

但是我们可能还需要注意到一点,就是惊群效应:举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到…

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
9 0
|
4天前
|
存储 Dubbo Java
分布式 RPC 底层原理详解,看这篇就够了!
本文详解分布式RPC的底层原理与系统设计,大厂面试高频,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 RPC 底层原理详解,看这篇就够了!
|
6天前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
42 17
|
8天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
34 9
|
28天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
34 9
|
27天前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
49 4
|
29天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
1月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
56 2
|
1月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
84 1
|
1月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
25 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现