当前位置: 首页 > news >正文

你手写过一把锁吗?你对轮询缓存怎么看?

当多个线程同时去操作一块内存的数据时如果不做一些限制,极其可能出现数据一致性问题。这时候,我们用一把锁锁住这块数据,持有钥匙者可以进入,不持有者等待钥匙用完再分配。所以在我看来啊,锁的本质就是一个标志位,代表当前线程是否有权限去操作目标内存,但是你的这把锁要穿透当前线程视野,穿透当前实例内存,穿透当前模块层级,到达整个系统可见共享的层次,且处理上要及时释放,再三过滤一切会出现死锁的情况。

所以常见的分布式锁,在可见性由redis缓存实现的解决方案里,通过大家都到redis这块实例上去拿钥匙,恰好进行同一代码块时 通常会将方法名以及时间戳带上某些id等按照一定规则作为key,value不要太大(大key可是会出现问题的,笔者生产环境就遇到过大key造成的数据流异常缓慢直接熔断请求)。为避免死锁也会保证在finally里强制释放锁。

实现lock接口的可重入锁与其使用demo

public class ReentrantTimeoutLock implements Lock {private static class Sync extends AbstractQueuedSynchronizer {private static final int FREE = 0;private static final int LOCKED = 1;private Thread owner = null;private int recursionCount = 0;@Overrideprotected boolean tryAcquire(int arg) {Thread currentThread = Thread.currentThread();int state = getState();if (state == FREE) {if (compareAndSetState(FREE, LOCKED)) {owner = currentThread;recursionCount = 1;return true;}} else if (currentThread == owner) {recursionCount++;return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {if (Thread.currentThread() != owner) {throw new IllegalMonitorStateException("Lock not owned by current thread");}recursionCount--;if (recursionCount == 0) {owner = null;setState(FREE);}return true;}@Overrideprotected boolean isHeldExclusively() {return owner == Thread.currentThread();}Condition newCondition() {return new ConditionObject();}}private final Sync sync = new Sync();private final long timeout;public ReentrantTimeoutLock(long timeout) {this.timeout = timeout;}@Overridepublic void lock() {sync.acquire(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long timeout,TimeUnit timeUnit) throws InterruptedException {return sync.tryAcquireNanos(1,timeUnit.toNanos(timeout));}@Overridepublic void unlock() {sync.release(1);}@Overridepublic Condition newCondition() {return sync.newCondition();}
}

使用这把锁我们可以简单做一个接口上的ip限流与请求限流

@Component
public class AccessLimit {private final Lock lock = new ReentrantTimeoutLock(500); // 创建可重入锁对象private final HashMap<String, Long> ipAccesses = new HashMap<>(); // 存储IP地址访问时间private final HashMap<String, Long> apiAccesses = new HashMap<>(); // 存储接口访问时间/*** Limit access.** @param ipAddress      the ip address* @param limitPerSecond the limit per second* @param apiName        the api name*/public void limitAccess(String ipAddress, int limitPerSecond, String apiName) {try {lock.lock(); // 获取锁long currentTime = System.currentTimeMillis();Long lastIPAccess = ipAccesses.get(ipAddress);if (lastIPAccess != null && currentTime - lastIPAccess < 1000 / limitPerSecond) {throw new RuntimeException("IP refuse");}Long lastApiAccess = apiAccesses.get(apiName);if (lastApiAccess != null && currentTime - lastApiAccess < 1000 / limitPerSecond) {throw new RuntimeException("API refuse");}ipAccesses.put(ipAddress, currentTime);apiAccesses.put(apiName, currentTime);} finally {lock.unlock(); // 释放锁}}/*** Release access.** @param ipAddress the ip address* @param apiName   the api name*/public void releaseAccess(String ipAddress, String apiName) {try {lock.lock(); // 获取锁ipAccesses.remove(ipAddress);apiAccesses.remove(apiName);} finally {lock.unlock(); // 释放锁}}
}

真实ip的获取在请求头的x-forwarded-for属性里

String ip = request.getHeader("x-forwarded-for");

对于如何获取真实ip的方法读者自行查找笔者以前的文章。

将限流组件使用在登录接口上如下形式

    @GetMapping("/login")@ResponseBody@ApiOperation(value = "登录", notes = "请求被限制3秒内单一ip无法连续访问,接口3秒内无法连续访问,无需携带token")@ApiImplicitParams({@ApiImplicitParam(name = "managerPhone", value = "管理员电话", required = true, dataTypeClass = String.class),@ApiImplicitParam(name = "password", value = "密码", required = true, dataTypeClass = String.class),@ApiImplicitParam(name = "request", value = "请求对象", required = true, dataTypeClass = HttpServletRequest.class)})public GeneralResponse<Manager> managerLogin(String managerPhone, String password, HttpServletRequest request) {accessLimit.limitAccess(HttpFactory.getIpAddress(request), 3, "managerLogin");ResponseTwoArgInterface<String, String, GeneralResponse<Manager>> responseThreeArgInterface = (value1, value2) -> {Manager manager = managerService.managerLogin(value1, value2);if (Objects.nonNull(manager)) {jedisService.createToken(value1 + "&" + value2, MagicNumber.DEFAULT_OPTION_COUNT, MagicNumber.DEFAULT_TIME_OUT);return GeneralResponse.ServerSuccess(manager, "管理员登录成功");} else {return GeneralResponse.ServerError("管理员登录失败:请检查数据库在线状态或查看日志排查");}};return responseThreeArgInterface.returnResponse(managerPhone, password);}

经过测试可行。

使用redis设计一把分布式锁并设计轮询任务

@Component
public class RedisLock {private static final JedisPool jedisPool = JedisFactory.getJedisPool();private static final Jedis jedis = jedisPool.getResource();public Boolean getLock(String key, int expireTime) {try {jedis.auth(RedisConfig.AuthPassword);/*获取锁,如果上锁成功返回1*/Long lockIsSuccess = jedis.setnx(key, System.currentTimeMillis() + "");if (lockIsSuccess != null && lockIsSuccess == 1L) {/*锁计时,每一把锁都应设置一个计时释放的时间*/jedis.expire(key, expireTime);return true;} else {String lockGoneTime = jedis.get(key);if (lockGoneTime == null) {lockIsSuccess = jedis.setnx(key, System.currentTimeMillis() + "");if (lockIsSuccess != null && lockIsSuccess == 1L) {jedis.expire(key, expireTime);}return lockIsSuccess != null && lockIsSuccess == 1L;} else {long currentTimeMillis = System.currentTimeMillis();if (currentTimeMillis - Long.parseLong(lockGoneTime) < expireTime * 1000L) {return false;} else {String lockNowTime = jedis.getSet(key, currentTimeMillis + "");if (lockNowTime == null || lockNowTime.equals(lockGoneTime)) {jedis.expire(key, expireTime);}return lockNowTime == null || lockNowTime.equals(lockGoneTime);}}}}finally {jedis.close();}}public void unLock(String key){try {jedis.auth(RedisConfig.AuthPassword);jedis.expire(key, 0);}finally {jedis.close();}}
}

这把锁也是可重入锁,这里的key读者可以自行设计,需要注意的是redis连接池设置,以及客户端连接后要及时释放资源;

将这把锁运用到轮询任务组件中的代码如下:

@Component
@DependsOn(value = {"ThreadPool"})
@Slf4j
public class RoundCheckTask {@Resourceprivate ThreadPoolTaskExecutor threadPool;@Resourceprivate RedisLock redisLock;private static final JedisPool jedisPool = JedisFactory.getJedisPool();private static final Jedis jedis = jedisPool.getResource();public void roundCheckCache(String key, String taskId, int roundTime) {jedis.auth(RedisConfig.AuthPassword);/*尝试获取一把轮询任务的锁,key由时间戳组成保证并发抢锁*/Boolean lock = redisLock.getLock(key, roundTime);if (lock) {try {long start = System.currentTimeMillis();threadPool.execute(() -> {while (true) {Long ttl = jedis.ttl(taskId);if (ttl == null || ttl == 0L) {runTaskThread(taskId, roundTime);break;}if (System.currentTimeMillis() - start > roundTime && ttl > roundTime) {/*循环进入的时间大于轮询时长直接退出轮询*/break;}try {Thread.sleep(500);} catch (Exception e) {throw new RuntimeException("thread sleep exception");}}}, MagicNumber.DEFAULT_TIME_OUT);} catch (Exception e) {throw new RuntimeException("start round check thread is fail");} finally {redisLock.unLock(key);jedis.close();}} else {jedis.close();}}public void runTaskThread(String taskId, int runTime) {jedis.auth(RedisConfig.AuthPassword);Boolean lock = redisLock.getLock(taskId, runTime);if (lock) {try {CountDownLatch countDownLatch = new CountDownLatch(1);threadPool.execute(() -> {/*执行业务逻辑*/System.out.println(taskId);countDownLatch.countDown();});countDownLatch.await();} catch (Exception e) {throw new RuntimeException("task service running error");} finally {redisLock.unLock(taskId);jedis.close();}} else {jedis.close();}}}

这里轮询的逻辑也就不仔细讲解了,核心的地方就在

                        Long ttl = jedis.ttl(taskId);if (ttl == null || ttl == 0L) {runTaskThread(taskId, roundTime);break;}if (System.currentTimeMillis() - start > roundTime && ttl > roundTime) {/*循环进入的时间大于轮询时长直接退出轮询*/break;}try {Thread.sleep(500);} catch (Exception e) {throw new RuntimeException("thread sleep exception");}

此处使用计数器来等待业务逻辑的执行。

对于redis锁的实现方案,redisson是不错的选型,有兴趣的读者可以了解了解redisson的redlock

最后笔者祝各位劳动节快乐~ 

相关文章:

你手写过一把锁吗?你对轮询缓存怎么看?

当多个线程同时去操作一块内存的数据时如果不做一些限制&#xff0c;极其可能出现数据一致性问题。这时候&#xff0c;我们用一把锁锁住这块数据&#xff0c;持有钥匙者可以进入&#xff0c;不持有者等待钥匙用完再分配。所以在我看来啊&#xff0c;锁的本质就是一个标志位&…...

深入理解 spring-boot-starter-parent

目录 一、前言二、Maven继承三、分析spring-boot-starter-parent四、Maven单继承问题五、不继承spring-boot-starter-parent需要注意的 一、前言 在idea当中创建springboot项目的时候都会继承一个spring-boot-starter-parent作为父类&#xff0c;假如不继承我们的项目就不能使…...

基于SpringBoot的线上日志阅读器

软件特点 部署后能通过浏览器查看线上日志。支持Linux、Windows服务器。采用随机读取的方式&#xff0c;支持大文件的读取。支持实时打印新增的日志&#xff08;类终端&#xff09;。支持日志搜索。 使用手册 基本页面 配置路径 配置日志所在的目录&#xff0c;配置后按回车…...

【Leetcode -405.数字转换为十六进制数 - 409.最长回文串】

Leetcode Leetcode -405.数字转换为十六进制数Leetcode - 409.最长回文串 Leetcode -405.数字转换为十六进制数 题目&#xff1a;给定一个整数&#xff0c;编写一个算法将这个数转换为十六进制数。对于负整数&#xff0c;我们通常使用 补码运算 方法。 注意 : 十六进制中所有…...

剑指 Offer:003 前 n 个数字二进制中 1 的个数

题目&#xff1a; 给定一个非负整数 n&#xff0c;请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数&#xff0c;并输出一个数组 示例&#xff1a; 1、 输入: n 2 输出: [0,1,1] 解释: 0 --> 0 1 --> 1 2 --> 10 2、 输入: n 5 输出: [0,1,1,2,1,2] 解释: 0 …...

DDD系列:二、应用架构设计演变

作用: ​ 通过规定一个固定的架构设计&#xff0c;可以让团队内有一个统一的开发规范&#xff0c;降低沟通成本&#xff0c;提升效率和代码质量。 目标&#xff1a; ​ 在做架构设计时&#xff0c;一个好的架构应该需要实现以下几个目标&#xff1a; 独立于UI&#xff1a;前…...

Spring-IOC

IOC概念和原理 什么是IOC 控制反转&#xff0c;为了将系统的耦合度降低&#xff0c;把对象的创建和对象直接的调用过程权限交给Spring进行管理。 IOC底层原理 XML解析 ​ 通过Java代码解析XML配置文件或者注解得到对应的类的全路径&#xff0c;获取对应的Class类 Class clazz …...

基于Java语言开发B/S架构实现的云HIS

一、云HIS系统框架简介 1、技术框架 &#xff08;1&#xff09;总体框架&#xff1a; SaaS应用&#xff0c;全浏览器访问 前后端分离&#xff0c;多服务协同 服务可拆分&#xff0c;功能易扩展 &#xff08;2&#xff09;技术细节&#xff1a; 前端&#xff1a;AngularNg…...

清洁赛道新势力,米博凭“减法”突围?

在五四青年节这个特殊的日子&#xff0c;方太旗下的高端智能清洁品牌“米博”发布了新一代无滚布洗地机7系列。 5月4日晚&#xff0c;米博以“减法生活&#xff0c;净请7代”为主题&#xff0c;举办了新品发布会。在发布会上&#xff0c;从小红书翻红的董洁作为方太集团米博产…...

代码随想录训练营Day6| 242、349、202、1

242. 有效的字母异位词 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 class Solution {public boolean isAnagram(String s, String t)…...

IP-GUARD如何通过网络控制策略禁止应用程序联网?

如何通过网络控制策略禁止应用程序联网? 可以在控制台-高级-网络控制中,添加以下策略: 动作:“禁止” 应用程序:填写要禁止的程序(以QQ示例) 如何禁止没有安装客户端的电脑访问客户端电脑? 可以给所有客户端设置只允许客户端电脑访问的网络控制策略; 在控制台左边的…...

Java RSA密钥转换,从RSAPrivateKey得到RSAPublicKey

概述&#xff1a; 在Java编程中&#xff0c;我们经常用到如下一段代码来生成RSA公私钥&#xff0c;分别拿到公私钥然后加解密计算&#xff1a; KeyPairGenerator keyPairGen; keyPairGen KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048, new S…...

Android 12.0 Launcher3仿ios长按app图标实现抖动动画开始拖拽停止动画

1.概述 在12.0的系统rom定制化开发中,在对系统原生Launcher3的定制需求中,也有好多功能定制的,在ios等电子产品中 的一些好用的功能,也是可以被拿来借用的,所以在最近的产品开发需求中,需求要求模仿ios的 功能实现长按app图标实现抖动动画,接下来看如何分析该功能的实现…...

【五一创作】50道Java面试题

Java中的四种访问权限控制符分别是什么&#xff1f; 答&#xff1a;Java中的四种访问权限控制符分别是public、protected、default和private。 Java中的反射是什么&#xff1f;有什么作用&#xff1f; 答&#xff1a;Java中的反射是指在程序运行时动态获取类的信息和调用对象…...

4。计算机组成原理(3)指令系统

嵌入式软件开发&#xff0c;非科班专业必须掌握的基本计算机知识 核心知识点&#xff1a;数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 指令系统&#xff08;Instruction Set&#xff09;是计算机体系结构的关键组成部分之一&#xff0c;它定义了处…...

【Elasticsearch】NLP简单应用

文章目录 NLP简介ES中的自然语言处理(NLP)NLP演示将opennlp插件放在ESplugins路径中下载NER模型配置opennlp重启ES、验证 NLP简介 NLP代表自然语言处理&#xff0c;是计算机科学和人工智能领域的一个分支。它涉及使用计算机来处理、分析和生成自然语言&#xff0c;例如英语、中…...

3. 云计算的落地实践(下)

本章讲解知识点 云计算如何落地实践ISO镜像文件创建虚拟机入门创建数据节点配置VMWare创建虚拟机三种网络模式1. 云计算的落地实践 上一章我们讲了云计算的业界实践,即:搭建IaaS后,用于创建虚拟机,在虚拟机上部署PaaS,用于管理同时部署在虚拟机上的容器,这就是业界普遍的…...

javaEE+mysql学生竞赛管理系统

本系统是基于JAVA平台开发的一套学生竞赛信息管理的系统。系统采用JSP为编程语言。数据库采用Mysql建立数据之间的转换。论文主要介绍了本课题的开发背景&#xff0c;所要完成的功能和开发的过程。重点的说明了系统设计的重点、设计思想、难点技术和解决方案。 本课题的目的是使…...

车辆出险记录查询API接口

车辆出险记录接口可以帮助车主、保险公司、交通管理部门等各方快速查询车辆的出险记录&#xff0c;了解车辆风险情况、核算保险费用等。这篇文章将探讨车辆出险记录接口的作用、应用场景、使用方式以及一些注意事项。 作用&#xff1a; 车辆出险记录接口主要解决了快速获取车…...

MySQL的概念,编译及安装

一.数据库的基本概念 1、数据&#xff08;Data&#xff09; • 描述事物的符号记录 • 包括数字&#xff0c;文字&#xff0c;图形&#xff0c;图像&#xff0c;声音&#xff0c;档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...