tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 各种MQ

  • Nginx

  • IdentityServer

  • Redis

    • 初始Redis与安装
    • Redis常用的一些场景
    • Redis中的缓存失效
    • 关于Redis的一些问题
    • Redis的持久化策略
    • Redis从单机到集群等模式
    • Redis和MySql实现数据一致性
    • 通过Redis实现分布式锁
      • 前言
      • 分布式锁跟普通锁的区别
      • 通过Redis实现分布式锁
        • 解决超卖问题
        • 大致流程
        • 存在的问题
    • Redis的客户端介绍
  • Linux

  • 中间件
  • Redis
EffectTang
2023-11-27
目录

通过Redis实现分布式锁

# 通过Redis实现分布式锁

# 前言

在编程世界的初期,程序部署在一台服务器上,但是随着发展,我们的程序变得越发庞大且复杂,一台服务器的性能已经渐渐的无法承担我们的程序,于是我们开始将程序部署在不同的服务器上,也就是所谓的分布式,部署在多台服务器比起单独的一台有诸多优点:

  • 提高可用性:当一台服务器出现故障时,其他服务器仍可以继续提供服务。
  • 提高可扩展性:可以通过增加服务器数量来提高系统的处理能力和吞吐量。
  • 提高系统的稳定性:当某台服务器出现问题时,其他服务器仍然可以提供服务,从而减少整体的影响
  • 增强安全性:如果服务器受到攻击,不会影响其他服务器

俗话说得好,发展固然能带来便利,但也会带来新的问题。而分布式并发问题,就是其中的重中之重。

# 分布式锁跟普通锁的区别

传统的“锁”无法解决分布式中的并发问题,因为普通的锁是在同一个计算机内部使用的,它主要用于在同一进程中保护共享资源免受多个线程的竞争,它可以感知到当前计算机内部程序的运行情况,但是却无法感知其他计算机的运行情况。因此当多台计算机同时对同一个资源进行操作时,就无法保证程序的正确执行跟数据的一致性。

我们在处理分布式并发的问题时,不仅要处理并发的问题,还需要解决多台计算机之间的通信、协调和同步问题。

因此我们使用的分布式锁也必须具有解决上面2个问题的能力才行。其实当我们能够协调多个节点之间对共享资源的访问时(即:只有一台计算机可以访问特定资源,并且在该计算机访问完资源后,其他计算机才能访问)。分布式并发也就变成了普通的并发问题。

因此解决分布式并发的关键是,如何解决多台计算机之间的通信、协调和同步。

# 通过Redis实现分布式锁

# 解决超卖问题

每次在进行购买操作之前添加一个操作:将商品的唯一id(或者其他属性)作为key存储到redis中,其值为该商品的数量。

执行完对应商品库存数量的删减操作后,再去redis中删除对应商品的key。

其中的关键点在于对redis的存值操作,这里使用的是setnx,而不是普通的set命令。

setnx的特点是:当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写。通过该命令我们实现了只允许一个服务器对共享资源的操作,只有当redis返回1时,购买操作才能继续,否则只能等待。

总结:通过redis我们建立了多个服务器之间的联系,而setnx命令则实现了对同一资源的独立处理。

# 大致流程

# 购买操作前,将订单中的每个商品的唯一id作为key 进行redis存储
  # 每个key 对应的value为该商品的库存数量
  
#根据 redis返回进行判断 当前购买是否能进行
  	# 因为订单中可能存在已经卖光的订单 
boolean isLock = true;
for (int i = 0; i < 商品种类.size; i++) {
		Boolean ifExist = redis进行setnx操作
   if(isLock){
   			break;
   	}
  // 只要有一个商品不满足 则该订单则无法进行
 		isLock = isLock && ifAbsent;
}
if(isLock){
  # 如果被锁 则等待 或者其他操作——将部分被锁的商品解锁
}else{
# 如果没有被锁 则再查询一次商品的库存,看是否能进行操作
  # 库存满足 则继续进行订单的后续操作 库存减少等操作
}finally{
  # 完成操作 释放对应商品的锁
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在加锁成功后,还对库存进行一次查询,是因为在加锁的过程中,商品的数量还可能发生变化。

# 存在的问题

  • 在成功加锁后,进行订单的后续操作时,当前线程出现异常,导致无法释放对应商品的锁,怎么办呢?

在通过setnx的方式对商品进行加锁时,设置过期时间。这样即使因为异常,无法主动释放锁,一到时间后也会自行释放锁。

  • 在加上时间后,如果因为网络波动,可能导致某个订单执行时发生非主动释放锁的情况——订单还未执行完,还未主动释放锁,便因为时间到期,释放了锁。这样可能导致其他订单或者该订单出现超卖的情况

当前订单出现超卖的情况,因为当前订单锁被释放了,因此其他订单可以进行商品库存的扣除,从而导致到当前订单进行扣除时,出现超卖的情况

其他订单出现超卖的情况,因为当前订单A锁被释放了,其他订单B可以加锁了,在B订单进行时,A进行了锁释放,因为A-B商品相同,所以B的锁被释放了,因此B订单可能出现超卖的情况。

解决误删锁的情况:

在加锁的时候(往redis中设置key-value时),为每个商品设置唯⼀的value,用于在后续主动释放锁时,检测释放的锁是否是当初加的锁。即:在删除redis中的key时,先获取当前商品在redis中对应的value,如果获取的值与当前value相同,则释放锁,否则不删除redis中的key。这样就能实现误删锁。

但这并没有解决其他线程重新给某个商品加锁的情况,依然有出现超卖的可能。

俗话说得好,发展的过程中会解决一些问题,但也会带来新的问题。

上次更新: 2025/04/23, 16:23:16
Redis和MySql实现数据一致性
Redis的客户端介绍

← Redis和MySql实现数据一致性 Redis的客户端介绍→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式