前言

今日上班听到同事在准备面试题分布式锁(准备溜溜球),随即加入了群聊复习了一波,于是有了这篇小作文。

场景

本文中的演示 DEMO, 以下订单减库存为例。

无锁裸奔表现

示例代码:

先来模拟一个库存服务呗!

    /// <summary>
/// 模拟库存服务
/// </summary>
public class StockService
{
private static RedisClient cli = new RedisClient("127.0.0.1:6379"); /// <summary>
/// 减库存操作
/// </summary>
/// <param name="goodsCount">商品数</param>
/// <returns></returns>
public bool ReduceStock(int goodsCount)
{
var stockCount = cli.Get<int>("StockCount");
if (stockCount > 0 && stockCount >= goodsCount)
{
stockCount -= goodsCount;
cli.Set("StockCount", stockCount, 10);
Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购成功!库存数:{stockCount}");
return true;
} Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购失败!"); return false;
}
}

模拟500个并发请求,开始测试。

        static void Main(string[] args)
{
var stockService = new StockService(); // 初始化库存
var cli = new RedisClient("127.0.0.1:6379");
cli.Set("StockCount", 10, 10); // 模拟 500 个并发
Parallel.For(0, 500, (i) => { Task.Run(() => { stockService.ReduceStock(1); }); });
}

执行完成后,结果如下图所示:

我们的库存只有 10 个,截图可见,至少有 29 个请求抢购成功了,出现了超卖的现象。

上分布式锁表现

针对无锁情况下出现的并发问题,如果是单体应用,用 lock 可以解决,但不适用于分布式应用。FreeRedis 中已有现成实现的分布式锁,我们先来看看是如何使用的吧!

修改一下订单服务代码:

    /// <summary>
/// 模拟库存服务
/// </summary>
public class StockService
{
private static RedisClient cli = new RedisClient("127.0.0.1:6379");
private static readonly string _distributedLockKey = "DISTRIBUTEDLOCKKEY"; /// <summary>
/// 减库存操作
/// </summary>
/// <param name="goodsCount">商品数</param>
/// <returns></returns>
public bool ReduceStock(int goodsCount)
{
// 取锁
var lockObj = cli.Lock(_distributedLockKey, 1);
if (lockObj != null)
{
var stockCount = cli.Get<int>("StockCount");
if (stockCount > 0 && stockCount >= goodsCount)
{
stockCount -= goodsCount;
cli.Set("StockCount", stockCount, 10);
Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购成功!库存数:{stockCount}");
lockObj.Unlock(); // 解锁
return true;
} Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购失败!");
lockObj.Unlock(); // 解锁
} return false;
}
}

执行结果如下所示:

从输出结果中可以看出,库存有序的扣除中,确实只有 10 个请求是抢购成功。

看看 FreeRedis 实现的分布式锁

通过上面示例可以看见,分布式锁的使用无非就是 LockUnLock 的操作。我这里直接用编辑器调试进去看了,就不是上 GitHub 上下载代码看了。体验不好,还请担待。

上锁

  1. 循环检测获取锁操作是否过期,过期直接返回 Null, 否则继续步骤二
  2. SetNx 设置值,如果成功,创建分布式锁对象,否则线程等待一会,继续第一步,如此循环

为啥不可以设置唯一值呢?在没有启动自动续时(看门狗机制),业务执行时间超过了锁的过期时间时,会引发问题。

  • 比如说现在 请求1请求2请求3 同时过来,请求1 先抢到了锁,开始执行。
  • 但是 请求1 的业务执行时间比较长,锁已经过期失效了,业务还没有执行完成。这时 请求2 获取到锁,执行自己的业务。就出现了 请求1请求2 并发执行了
  • 请求1 执行完自己的业务的时候,执行解锁操作,因为键值都一样,会误把 请求2 的锁给释放掉,导致故障

通过设置值的唯一,当删除缓存的时候,还需要判断一下值是不是一致,来防止误释放其他锁。

看门狗机制



  1. 定时执行 Refresh 方法
  2. 通过 lua 脚本设置新的过期时间,不成功的话(已解锁),删除定时器

解锁

  1. 通过 lua 脚本匹配 都一样的key, 才能删除

分布式锁的坑参考连接

FreeRedis分布式锁实现以及使用的更多相关文章

  1. 使用redis构建可靠分布式锁

    关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了. 分布式锁的多种实现方式 分布式锁总结 对于分布式锁的几种实现方式的优劣,这里再列举下 1. 数据库实现方式 优点:易理解 缺 ...

  2. 分布式锁1 Java常用技术方案

    前言:       由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资 ...

  3. .net 分布式架构之分布式锁实现

    分布式锁 经常用于在解决分布式环境下的业务一致性和协调分布式环境. 实际业务场景中,比如说解决并发一瞬间的重复下单,重复确认收货,重复发现金券等. 使用分布式锁的场景一般不能太多. 开源地址:http ...

  4. 分布式任务&分布式锁(li)

    目前系统中存在批量审批.批量授权等各个操作,批量操作中可能因为处理机器.线程不同,造成刷新缓存丢失授权等信息,如批量审批同一用户权限多个权限申请后,流程平台并发的发送多个http请求到acl不同服务器 ...

  5. jedisLock—redis分布式锁实现

    一.使用分布式锁要满足的几个条件: 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现) 共享资源(各个系统访问同一个资源,资源的载体 ...

  6. ZooKeeper 笔记(6) 分布式锁

    目前分布式锁,比较成熟.主流的方案有基于redis及基于zookeeper的二种方案. 大体来讲,基于redis的分布式锁核心指令为SETNX,即如果目标key存在,写入缓存失败返回0,反之如果目标k ...

  7. 一次基于etcd的分布式锁自动延时失败问题的排查

    今天在测试基于etcd的分布式锁过程中,在测试获取锁后,释放之前超出TTL时长的情况下自动延长TTL这部分功能,在延长指定key的TTL时总是返回404错误信息,在对目标KEY更新TTL时目标KEY已 ...

  8. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  9. 用Redis实现分布式锁

    Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists.这系列的命令非常有用,这里讲使用SETNX来实现分布式锁 ...

随机推荐

  1. python博客大全

    python技术博客 egon博客 计算机基础系列一:计算机硬件 - linhaifeng - 博客园 https://www.zhihu.com/people/xiaoyuanqujing #ego ...

  2. MySQL 回表查询 & 索引覆盖优化

    回表查询 先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据 建表示例 mysql> create table user( -> id int(10) auto_incre ...

  3. favicon.ico All In One

    favicon.ico All In One link rel="icon" type="image/x-icon" href="http://exa ...

  4. js swap array

    js swap array ES6 swap array 就地交换 no need let , const [ b, a, ] = [ a, b, ]; // ES6 swap const arr = ...

  5. Flutter 在mixin中使用setState

    相关问题 创建mixin import 'package:flutter/material.dart'; mixin JobsMixin<T extends StatefulWidget> ...

  6. vue页面切换过渡

    <!--<transition name="slide-left">--> <router-view></router-view>& ...

  7. PAA房产智慧社区:解决社区管理服务的痛点难点

    社区,是社交与生活的舞台,更是家的延伸.社区之所有能够有所创新发展,得益于借助数字化和智能化.智能化给社区带来的便利体现在社区门禁可以人脸识别:AI的摄像头可以自动捕获异常的现象,便于社区管理员第一时 ...

  8. Captain Technology INC浅谈新能源汽车的未来

    近日全世界上最大的资管公司贝莱德向位于的英国电动汽车初创公司Arrival投资1.18亿美元,且该公司已有投资者亚马逊和美国第二大汽车制造商福特汽车参投.中国最知名的电动车公司蔚来股价单日大涨22%, ...

  9. 「NGK每日快讯」11.24日NGK公链第22期官方快讯!

  10. 彻底解决Asp.netCore WebApi 3.1 跨域时的预检查204 options重复请求的问题

    Asp.netCore WebApi 3.1 跨域的预检查options问题 1:我们直接使用core跨域的中间件 ,注入跨域服务, services.AddCors(options => { ...