你手写过一把锁吗?你对轮询缓存怎么看?
当多个线程同时去操作一块内存的数据时如果不做一些限制,极其可能出现数据一致性问题。这时候,我们用一把锁锁住这块数据,持有钥匙者可以进入,不持有者等待钥匙用完再分配。所以在我看来啊,锁的本质就是一个标志位,代表当前线程是否有权限去操作目标内存,但是你的这把锁要穿透当前线程视野,穿透当前实例内存,穿透当前模块层级,到达整个系统可见共享的层次,且处理上要及时释放,再三过滤一切会出现死锁的情况。
所以常见的分布式锁,在可见性由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
最后笔者祝各位劳动节快乐~
相关文章:
你手写过一把锁吗?你对轮询缓存怎么看?
当多个线程同时去操作一块内存的数据时如果不做一些限制,极其可能出现数据一致性问题。这时候,我们用一把锁锁住这块数据,持有钥匙者可以进入,不持有者等待钥匙用完再分配。所以在我看来啊,锁的本质就是一个标志位&…...

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

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

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

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

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

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

基于Java语言开发B/S架构实现的云HIS
一、云HIS系统框架简介 1、技术框架 (1)总体框架: SaaS应用,全浏览器访问 前后端分离,多服务协同 服务可拆分,功能易扩展 (2)技术细节: 前端:AngularNg…...

清洁赛道新势力,米博凭“减法”突围?
在五四青年节这个特殊的日子,方太旗下的高端智能清洁品牌“米博”发布了新一代无滚布洗地机7系列。 5月4日晚,米博以“减法生活,净请7代”为主题,举办了新品发布会。在发布会上,从小红书翻红的董洁作为方太集团米博产…...
代码随想录训练营Day6| 242、349、202、1
242. 有效的字母异位词 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 class Solution {public boolean isAnagram(String s, String t)…...

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

Java RSA密钥转换,从RSAPrivateKey得到RSAPublicKey
概述: 在Java编程中,我们经常用到如下一段代码来生成RSA公私钥,分别拿到公私钥然后加解密计算: 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中的四种访问权限控制符分别是什么? 答:Java中的四种访问权限控制符分别是public、protected、default和private。 Java中的反射是什么?有什么作用? 答:Java中的反射是指在程序运行时动态获取类的信息和调用对象…...

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

【Elasticsearch】NLP简单应用
文章目录 NLP简介ES中的自然语言处理(NLP)NLP演示将opennlp插件放在ESplugins路径中下载NER模型配置opennlp重启ES、验证 NLP简介 NLP代表自然语言处理,是计算机科学和人工智能领域的一个分支。它涉及使用计算机来处理、分析和生成自然语言,例如英语、中…...
3. 云计算的落地实践(下)
本章讲解知识点 云计算如何落地实践ISO镜像文件创建虚拟机入门创建数据节点配置VMWare创建虚拟机三种网络模式1. 云计算的落地实践 上一章我们讲了云计算的业界实践,即:搭建IaaS后,用于创建虚拟机,在虚拟机上部署PaaS,用于管理同时部署在虚拟机上的容器,这就是业界普遍的…...

javaEE+mysql学生竞赛管理系统
本系统是基于JAVA平台开发的一套学生竞赛信息管理的系统。系统采用JSP为编程语言。数据库采用Mysql建立数据之间的转换。论文主要介绍了本课题的开发背景,所要完成的功能和开发的过程。重点的说明了系统设计的重点、设计思想、难点技术和解决方案。 本课题的目的是使…...
车辆出险记录查询API接口
车辆出险记录接口可以帮助车主、保险公司、交通管理部门等各方快速查询车辆的出险记录,了解车辆风险情况、核算保险费用等。这篇文章将探讨车辆出险记录接口的作用、应用场景、使用方式以及一些注意事项。 作用: 车辆出险记录接口主要解决了快速获取车…...

MySQL的概念,编译及安装
一.数据库的基本概念 1、数据(Data) • 描述事物的符号记录 • 包括数字,文字,图形,图像,声音,档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...