锁->分布式锁->准实时方案

2023-11-03

概述:并发量由低到高,单机到集群,java对锁、分布式锁、准实时方案的概要实现;全文以商品抢购为例。

目录

1. 锁

2.分布式锁

2.1高可用

2.2性能调优

3.准实时方案

3.1性能提升

3.2高可用

正文:

1.锁

lock和synchronized均可,单机;
实例:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 锁控制类
 * @author Administrator
 * @date 20230523
 */
@RestController
@RequestMapping("/locks")
public class LockController {

    /**
     * 商品库存
     */
    public static int productInventoryNums = 100;

    @PostMapping("/standalone")
    public synchronized String standAlone(){
        if(productInventoryNums > 0){
            productInventoryNums--;
        } else {
            return "fail";
        }
        return "success";
    }
}```
```

2.分布式锁

先结论再说原理(此处暂未涉及数据强一致性,放在原理中进行分析);

结论:

package com.xxxx.distributelocks.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 商品抢购
 * 并发
 * @author py
 * @date 20230526
 */
@RestController
@RequestMapping("/products")
public class ProductsController {

    @Autowired
    private Redisson redisson;
    /**
     * 商品剩余库存
     * 从数据库或缓存中读取
     */
    public static int productNums = 100;

    /**
     * 下单
     * @return
     */
    @PostMapping("/buyproduct")
    public String order(String productId){
        //获取锁
        RLock rLock = redisson.getLock(productId);
        try {
            //加锁
            rLock.lock();
            if(productNums > 0){
                productNums--;
            } else {
                //商品已售罄
                return "fail";
            }
            //更新库存
            //库存写入缓存或数据库
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            rLock.unlock();
        }
        return "success";
    }
}

此处可对部分场景进行优化,第一种是读多写少,或许这也是大多数互联网公司采用的方案;
1)读多写少

`

/**
     * 下单
     * @param productId
     * @return
     */
    @PostMapping("/buyproduct1")
    public String writeOrder(String productId){
        //获取锁
        RReadWriteLock rwLock = redisson.getReadWriteLock(productId);
        RLock writeLock = rwLock.writeLock();
        try {
            //此处为写场景,故加写锁
            writeLock.lock();
            if(productNums > 0){
                productNums--;
            } else {
                //商品已售罄
                return "fail";
            }
            //更新库存
            //库存写入缓存或数据库
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            writeLock.unlock();
        }
        return "success";
    }

    /**
     * 读商品
     * @param productId
     * @return
     */
    @PostMapping("/readproduct")
    public String readOrder(String productId){
        //获取锁
        RReadWriteLock rwLock = redisson.getReadWriteLock(productId);
        RLock readLock = rwLock.readLock();
        try {
            //此处为读场景,故加写锁
            readLock.lock();
            if(productNums > 0){
                return  productNums + "";
            } else {
                //商品已售罄
                return "已售罄";
            }
            //更新库存
            //库存写入缓存或数据库
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            readLock.unlock();
        }
        return "售罄";
    }

2)读多写多场景
既然读多写也多,那还读写缓存有什么意义,直接用binlog更新缓存不香吗?

原理:

原理概述:通过setnx命令更新redis后(因为redis命令执行的单线程与数据分布式存储的特性,是分布式锁可用的前提),更新之后定时更新加锁的key(也就是业内说的锁续命逻辑);

`

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return this.evalWriteAsync(this.getRawName(),
 LongCodec.INSTANCE, command, 
"if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);",
 Collections.singletonList(
this.getRawName()), 
new Object[]{unit.toMillis(leaseTime), 
this.getLockName(threadId)}
);
    }

其中lua脚本本身命令的原子性操作;

然后再看锁续命逻辑
`

public RFuture tryLockAsync() {
return this.tryLockAsync(Thread.currentThread().getId());
}

private  RFuture tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture ttlRemainingFuture;
        if (leaseTime != -1L) {
            ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }

        CompletionStage<Long> f = ttlRemainingFuture.thenApply((ttlRemaining) -> {
            if (ttlRemaining == null) {
                if (leaseTime != -1L) {
                    this.internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    this.scheduleExpirationRenewal(threadId);
                }
            }

            return ttlRemaining;
        });
        return new CompletableFutureWrapper(f);
    }
protected void scheduleExpirationRenewal(long threadId) {
        RedissonBaseLock.ExpirationEntry entry = new RedissonBaseLock.ExpirationEntry();
        RedissonBaseLock.ExpirationEntry oldEntry = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);

            try {
                this.renewExpiration();
            } finally {
                if (Thread.currentThread().isInterrupted()) {
                    this.cancelExpirationRenewal(threadId);
                }

            }
        }

    }

添加监听定时回调逻辑;

与netty中监听回调有异曲同工之妙,netty中是虽然并没有减少总处理时间,但却提高了吞吐量,将线程尽可能活跃起来;

3.准实时方案

此处暂时分析原理与优化方案,代码实例后续补充;
准实时方案,顾名思义,服务降级,在业务允许的延迟范围内进行计算,比如消息队列存储消息,storm、flink等下游消费,计算输出;

优化方案:

此处针对部分场景说明;
第一步:数据分流
数据分流,比如可根据用户id强制将每个用户的计算数据分到指定机器上;
第二步:降级、加锁
200K缓存或200条记录,200毫秒超时入库等读写降级;
单机可用sychronized或lock;
第三步:锁优化
客户端并发持续上升,单机扛不住,计算单机计算量,多台机器分布式锁计算,锁优化如上。

数据一致性

很多业务场景要求数据抗风险能力极强,比如商品交易金钱一致性等;
异常情况:如redis节点在写入数据后还未来得及同步到从节点时主节点突然宕机情况,此时从节点需晋升为主节点但却丢失锁数据;
至于解决方案,业内通常采用的有:
1)zookeeper
2)redlock
原理概要:
1)其中zookeeper集群相对redis集群不同之处在于zookeeper集群在主从同步时只有超过半数节点同步成功后才会将请求响应回客户端,并且从节点在晋升为主节点时一定是同步成功锁的节点晋升成功,而redis是主节点写入成功后则给客户端响应结果,并且从晋升主的过程中投票机制只能大概率保证数据偏移量最大节点晋升成功;
故如果对性能要求高建议redis,对数据一致性要求高建议zookeeper;
2)redlock在业内虽存在部分争议,但其实现仍可圈可点,其实现原理为采用多台完全独立的redis节点(通常为奇数),只有半数机器同步成功后才响应到客户端,此处与zookeeper原理大同小异;

因个人水平有限,时间精力有限,后续提升待完善,抛砖引玉,望大家多多提出改善之处;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

锁->分布式锁->准实时方案 的相关文章

  • 如何使用固定数量的工作线程实现简单线程

    我正在寻找最简单 最直接的方法来实现以下内容 主程序实例化worker 执行任务的线程 Only n任务可以同时运行 When n已达到 不再有工人 开始直到计数 正在运行的线程回落到下方n 我觉得Executors newFixedThr
  • 在Java中将*s打印为三角形?

    我在 Java 课程中的作业是制作 3 个三角形 一份左对齐 一份右对齐 一份居中 我必须为什么类型的三角形制作一个菜单 然后输入需要多少行 三角形必须看起来像这样 到目前为止 我能够完成左对齐的三角形 但我似乎无法获得其他两个 我尝试用谷
  • android.view.InflateException:二进制 XML 文件行 #11:膨胀类 ImageView 时出错

    我只是尝试制作一个小的 android java xml 应用程序来计算游戏的分数 它给了我这个错误 Error inflateing class ImageView 有人知道解决方案吗 我实际上搜索了 ppl 说添加这个 android
  • 如何将 JSpinner 的值设置为特定日期

    我有一个JSpinner我添加到JPanel我想将其时间设置为 GregorianCalendar calendar JSpinner spinner new JSpinner spinner setModel model pom add
  • 如何在log4j的配置文件中为文件附加器提供环境变量路径

    我有一个log4j xml配置文件 和一个RollingFileAppender我需要提供用于存储日志的文件路径 问题是我的代码将作为可运行的 jar 部署在 Unix 机器上 所以如果我传递这样的参数 value logs message
  • 在命令行java中突出显示文本[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一项任务是重新创建 unix cal 程序 除了一部分之外 相当简单 今天 它突出显示了该数字 我不知道该怎么做 关于如何在 Ja
  • 如何为小程序提供对文件系统写入的访问权限

    我在设置小程序的策略文件时遇到问题 我是第一次这样做 不知道如何在java中设置小程序的策略文件 实际上我想授予小程序在文件系统上写入的权限 为此我必须向小程序授予文件权限 所以我创建了一个名为 java policy 的文件 并将以下代码
  • java中的单链表和双向链表?

    在java中 哪个集合接口可以有效地实现单链表和双向链表 请问代码示例吗 毫不奇怪 实现双向链表的正确接口是 LinkedList 看Java文档 http docs oracle com javase 8 docs api java ut
  • maven 无法下载 jacoco 0.7.10-SNAPSHOT jar

    我对此感到困惑 我的 pom xml 中有这个
  • 正则表达式在 Velocity 模板中不起作用

    我在 Test java 中尝试过这个 String regex lt s br s s gt String test1 lt br gt System out println test replaceAll regex 但是当我在速度模板
  • Java 中的 ExecuteUpdate sql 语句不起作用

    我正在学习如何将 SQL 与 Java 结合使用 我已成功安装 JDBC 驱动程序 并且能够从数据库读取记录并将其打印在屏幕上 我的问题发生在尝试执行更新或插入语句时 没有任何反应 这是我的代码 问题所在的方法 public static
  • Android 解析 JSON 卡在 get 任务上

    我正在尝试解析一些 JSON 数据 我的代码工作了一段时间 我不确定我改变了什么突然破坏了代码 当我运行代码时 我没有收到任何运行时错误或警告 我创建一个新的 AsyncTask 并执行它 当我打电话时 get 在这个新任务中 调试器在此行
  • 改变 Java 中凯撒移位的方向

    用户可以通过选择 1 向左或 2 向右移动字母来选择向左或向右移动 左边工作正常 右边不行 现在它显示了完全相同的循环 但我已经改变了所有 and 以不同的方式进行标记 最终我总是得到奇怪的字符 如何让程序将字符向相反方向移动 如果用户输入
  • 使用 HTTPServletRequestWrapper 包装请求参数

    我有一个可以验证 授权 REST 调用的过滤器 该过滤器需要访问请求参数 因此我为此编写了一个自定义 HTTPServletRequestWrapper import java util Collections import java ut
  • java swing:向 JTree 项目添加自定义图形按钮

    我想在 JTree 中的项目右侧添加一个带有小图标的附加按钮 这可以做到吗 如果是这样 怎么办 thanks Clamp 你在这方面成功了吗 我想做同样的事情 但很难让 JButton 响应用户 设置渲染器以显示按钮的过程很顺利 但所有鼠标
  • Java8:流映射同一流中的两个属性

    我有课Model带有以下签名 class Model private String stringA private String stringB public Model String stringA String stringB this
  • Java 中处理异步响应的设计模式

    我读过类似问答的答案 如何在 JAVA 中创建异步 HTTP 请求 https stackoverflow com questions 3142915 how do you create an asynchronous http reque
  • java中的预增量/后增量

    有人可以帮助我理解为什么 int i 1 int j 1 int k 1 int l 1 System out println i i System out println j j System out println k k System
  • java.lang.ClassCastException:com.sun.proxy.$Proxy8 无法转换为 org.openqa.selenium.internal.WrapsDriver

    我有以下切入点和 AspectJ 中给出的建议 Pointcut call org openqa selenium WebElement sendKeys public void onWebElementAction After onWeb
  • @Embeddable 中的 @GenerateValue

    我已将实体的 id 分离到一个单独的 Embeddable 类中 该实体如下 Entity Table name users public class Users EmbeddedId private Users pk id private

随机推荐

  • 这是最简单的java输出表情

    public static void main String args TODO Auto generated method stub System out println o 执行结果 不要质疑欧 我们java就是这么简单 适合刚入jav
  • 【面试总结】AI音频降噪方向相关面试题总结

    前情提要 相同的内容我也发布在了知乎上 由于本人也参与过AI音频降噪的相关项目 所以在面试的过程中也有很多相关的问题 这里提前吐槽一下 虽然Rnnoise这个模型效果不怎么好 但是这个方案相当于是这个领域的开辟的工程方案 所以有相当多的人会
  • java三种方式实现文件的上传

    1 实现文件的上传可以有好多途径 最简单的就是用sun公司提供的File类 可以简单的实现文件的上传和显示 try InputStream stream file getInputStream 把文件读入 Savefilepath requ
  • 两年外包生涯做完,感觉自己废了一半....

    先说一下自己的情况 大专生 17年通过校招进入湖南某软件公司 干了接近2年的点点点 今年年上旬 感觉自己不能够在这样下去了 长时间呆在一个舒适的环境会让一个人堕落 而我已经在一个企业干了五年的功能测试 已经让我变得不思进取 谈了1年的女朋友
  • java boolean类型占多少字节

    今天面试问到了这个问题 java中boolean类型到底占多少字节呢 到网上搜了下 最后采用了这个答案 答 我的结论是 1 boolean a true 这个a在JVM中占4个字节即 32位 2 boolean b new boolean
  • 【js基础】怎样理解闭包

    在JavaScript中 在ES6出现之前 只有函数作用域和全局作用域 在正常情况下 外界是无法访问函数内部变量的 但是在函数中 如果我们返回了另一个函数 这个返回的函数使用了外层函数的变量 那么外界能够通过返回的函数 获取外界函数内部的变
  • STM32下载程序的三种方法(串口、ST-LINK、 ST-LINK Utility)

    ST LINK v2接线及下载程序 ST Link V2 ST Link v2是STM8 STM32系列单片机的在线仿真器和下载器 STM8采用SWIM接口模式 STM32采用的是SWD接口模式 因此ST Link出生就带有两种接口模式 S
  • Vue-i18n框架学习总结

    Vue框架 Vue i18n学习总结 1 概述 Vue I18n 是 Vue js 的国际化插件 格局比较大 具体怎么解释还是不太好说 直接看用法就能明白 简单说一下为什么叫这个名字 internationalization i 中间的18
  • Unity Toggle组件踩坑使用笔记

    项目中需要用到排序功能 两种排序 一个型号 一个是评分 当用户点击型号或者评分的时候 物品列表中的物品需要重新排序 有点类似游戏中的背包 希望武器按照品质或者强化等级排序 最简单的方法是制作两个Button 同属同一个View 通过中介者模
  • LiveCharts遇到的问题及解决

    LiveCharts遇到的问题及解决 LiveCharts遇到的问题及解决 1 如何设置横纵轴分隔符为虚线 2 如何添加横纵轴线 1 如何设置横纵轴分隔符为虚线
  • cadence 旋转快捷键_cadence原理图快捷键整理

    Allegro Design Entry CIS 原理图 1 shift 鼠标滚轮 左右移动 2 Ctrl 鼠标滚轮 放大缩小 3 Alt 鼠标滚轮 上下移动 4 按下鼠标滚轮可任意方向拖动图纸 可以一直保持按下状态或者按一下松开 5 CT
  • vscode 标签的使用

    使用标签就可以快速跳转到某一段代码 十分方便 安装 首先 我们需要安装 设置快捷键 shift command p 调出命令行 输入bookmark 即可看到标签的相关指令 生成一个标签 设置一个你喜欢的快捷键 这代表 在光标所在的行上添加
  • LeetCode 5926. 买票需要的时间

    有 n 个人前来排队买票 其中第 0 人站在队伍 最前方 第 n 1 人站在队伍 最后方 给你一个下标从 0 开始的整数数组 tickets 数组长度为 n 其中第 i 人想要购买的票数为 tickets i 每个人买票都需要用掉 恰好 1
  • 软件显示获取服务器更新失败,闪耀暖暖获取更新服务器失败的解决方法

    今天是闪耀暖暖国服正式上线的日子 很多玩家都想第一时间进入游戏试玩 但是频繁有玩家出现网络连接失败的提示 这可愁坏了很多玩家 那么出现这个问题我们要怎么解决呢 下面就跟我一起来看看闪耀暖暖获取更新服务器失败的解决方法吧 一 官方服务器超载
  • unzip 错误 checkdir error: cannot create ctchain

    在mac中用unzip命令解压时出现下面错误 may Desktop SO unzip chain zip Archive chain zip checkdir error cannot create ctchain Illegal byt
  • 函数(1)

    目录 一 函数是什么 二 函数的分类 库函数 自定义函数 三 函数的参数 实际参数 实参 形式参数 形参 四 函数的调用 传值调用 传址调用 五 结束语 本章需要了解的重点主要包括以下几点 1 函数是什么 2 库函数 3 自定义函数 4 函
  • Day123.ElasticSearch:CAP定理、集群搭建、架构原理及分片、倒排索引、面试题

    目录 一 CAP定理 二 ES集群 1 搭建集群 2 head 插件安装 3 集群测试 4 核心概念 二 架构原理及分片 一 ElasticSearch 分片 二 分片控制 三 分片原理 1 倒排索引 2 文档搜索 3 近实时搜索 缓存传递
  • Vue组件缓存之keep-alive正确使用姿势

    先来看一个项目中的需求 作为苦逼的前端开发者 我们无时无刻都要面对产品经理提的各种需求 比如下图这个场景 场景 从首页的点击导航进入列表页 列表页点击列表进入 该 数据详情页 从详情页返回 希望列表页缓存 不重新渲染数据 这样会提高用户体验
  • ROCKCHIP-Rv1126安装ARM64-ARCH-ARM-DEBIAN系统

    1 deboot qemu arm debian 64位 内核文件系统 安装依赖软件 sudo apt get install debian archive keyring gcc aarch64 linux gnu bison flex
  • 锁->分布式锁->准实时方案

    概述 并发量由低到高 单机到集群 java对锁 分布式锁 准实时方案的概要实现 全文以商品抢购为例 目录 1 锁 2 分布式锁 2 1高可用 2 2性能调优 3 准实时方案 3 1性能提升 3 2高可用 正文 1 锁 lock和synchr