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

怎么做不占CPU的网站泰安人才网广纳英才招聘信息

怎么做不占CPU的网站,泰安人才网广纳英才招聘信息,seo的优化方案,网站发布小说封面怎么做前言 平时的工作中#xff0c;由于生产环境中的项目是需要部署在多台服务器中的#xff0c;所以经常会面临解决分布式场景下数据一致性的问题#xff0c;那么就需要引入分布式锁来解决这一问题。 针对分布式锁的实现#xff0c;目前比较常用的就如下几种方案#xff1a;…前言 平时的工作中由于生产环境中的项目是需要部署在多台服务器中的所以经常会面临解决分布式场景下数据一致性的问题那么就需要引入分布式锁来解决这一问题。 针对分布式锁的实现目前比较常用的就如下几种方案 基于数据库实现分布式锁基于Redis实现分布式锁 【本文】基于Zookeeper实现分布式锁 接下来这个系列文章会跟大家一块探讨这三种方案本篇为Redis实现分布式锁篇。 Redis分布式环境搭建推荐基于Docker的Redis集群搭建 Redis分布式锁一览 说到 redis 锁能搜到的或者说常用的无非就下面这两个 setNX Lua脚本 【本文】redisson RLock可重入锁 接下来我们一一探索这两个的实现本文为 setNX Lua脚本 实现篇。 1、setNX 完整语法SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL] 必选参数说明 SET命令key待设置的keyvalue设置的key的value最好为随机字符串 可选参数说明 NX表示key不存在时才设置如果存在则返回 null XX表示key存在时才设置如果不存在则返回NULL PX millseconds设置过期时间过期时间精确为毫秒 EX seconds设置过期时间过期时间精确为秒 注意其实我们常说的通过 Redis 的 setnx 命令来实现分布式锁并不是直接使用 Redis 的 setnx 命令因为在老版本之前 setnx 命令语法为「setnx key value」并不支持同时设置过期时间的操作那么就需要再执行 expire 过期时间的命令这样的话加锁就成了两个命令原子性就得不到保障所以通常需要配合 Lua 脚本使用而从 Redis 2.6.12 版本后set 命令开始整合了 setex 的功能并且 set 本身就已经包含了设置过期时间因此常说的 setnx 命令实则只用 set 命令就可以实现了只是参数上加上了 NX 等参数。 大致说一下用 setnx 命令实现分布式锁的流程 在 Redis 2.6.12 版本之后Redis 支持原子命令加锁我们可以通过向 Redis 发送 「set key value NX 过期时间」 命令实现原子的加锁操作。比如某个客户端想要获取一个 key 为 niceyoo 的锁此时需要执行 「set niceyoo random_value NX PX 30000」 在这我们设置了 30 秒的锁自动过期时间超过 30 秒自动释放。 如果 setnx 命令返回 ok说明拿到了锁此时我们就可以做一些业务逻辑处理业务处理完之后需要释放锁释放锁一般就是执行 Redis 的 del 删除指令「del niceyoo」 如果 setnx 命令返回 nil说明拿锁失败被其他线程占用如下是模拟截图 注意这里在设置值的时候value 应该是随机字符串比如 UUID而不是随便用一个固定的字符串进去为什么这样做呢 value 的值设置为随机数主要是为了更安全的释放锁释放锁的时候需要检查 key 是否存在且 key 对应的 value 值是否和指定的值一样是一样的才能释放锁。 感觉这样说还是不清晰举个例子例如进程 A通过 setnx 指令获取锁成功命令中设置了加锁自动过期时间30 秒既然拿到锁了就开始执行业务吧但是进程 A 在接下来的执行业务逻辑期间程序响应时间竟然超过30秒了锁自动释放了而此时进程 B 进来了由于进程 A 设置的过期时间一到让进程 B 拿到锁了然后进程 B 又开始执行业务逻辑但是呢这时候进程 A 突然又回来了然后把进程 B 的锁得释放了然后进程 C 又拿到锁然后开始执行业务逻辑此时进程 B 又回来了释放了进程 C 的锁套娃开始了… 总之有了随机数的 value 后可以通过判断 key 对应的 value 值是否和指定的值一样是一样的才能释放锁。 接下来我们把 setnx 命令落地到项目实例中 代码环境SpringBoot2.2.2.RELEASE spring-boot-starter-data-redis StringRedisTemplate StringRedisTemplate 或者 RedisTemplate 下对应的 setnx 指令的 API 方法如下 /*** Set {code key} to hold the string {code value} if {code key} is absent.** param key must not be {literal null}.* param value* see a hrefhttp://redis.io/commands/setnxRedis Documentation: SETNX/a*/ Boolean setIfAbsent(K key, V value);这个地方再补充一下使用 jedis 跟使用 StringRedisTemplate 对应的 senx 命令的写法是有区别的jedis 下就是 set 方法而 StringRedisTemplate 下使用的是 setIfAbsent 方法 。 1Maven 依赖pom.xml ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.2.2.RELEASE/versionrelativePath/ /parentgroupIdcom.example/groupIdartifactIddemo-redis/artifactIdversion0.0.1-SNAPSHOT/versionnamedemo-redis/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Redis--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Lombok --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.10/version/dependency!-- Gson --dependencygroupIdcom.google.code.gson/groupIdartifactIdgson/artifactIdversion2.8.6/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project这里引入了 gson、redis 依赖。 2application.yml 配置文件 server:port: 6666servlet:context-path: /spring:redis:host: 127.0.0.1password:# 数据库索引 默认0database: 0port: 6379# 超时时间 Duration类型 3秒timeout: 3S# 日志 logging:# 输出级别level:root: infofile:# 指定路径path: redis-logs# 最大保存天数max-history: 7# 每个文件最大大小max-size: 5MB这里设置的服务端口为 6666大家可以根据自己环境修改。 3测试的 Controller Slf4j RestController RequestMapping(/test) public class TestController {Resourceprivate RedisTemplateString,Object redisTemplate;PostMapping(value /addUser)public String createOrder(User user) {String key user.getUsername();// 如下为使用UUID、固定字符串固定字符串容易出现线程不安全String value UUID.randomUUID().toString().replace(-,);// String value 123;/** setIfAbsent SET key value [NX] [XX] [EX seconds] [PX [millseconds]]* set expire time 5 mins*/Boolean flag redisTemplate.opsForValue().setIfAbsent(key, value, 20000, TimeUnit.MILLISECONDS);if (flag) {log.info({} 锁定成功开始处理业务, key);try {// 模拟处理业务逻辑Thread.sleep(1000 * 30);} catch (InterruptedException e) {e.printStackTrace();}// 判断是否是key对应的valueString lockValue redisTemplate.opsForValue().get(key);if (lockValue ! null lockValue.equals(value)) {redisTemplate.delete(key);log.info({} 解锁成功结束处理业务, key);}return SUCCESS;} else {log.info({} 获取锁失败, key);return 请稍后再试...;}}}大致流程就是通过 RedisTemplate 的 setIfAbsent() 方法获取原子锁并设置了锁自动过期时间为 20秒setIfAbsent() 方法返回 true表示加锁成功加锁成功后模拟了一段业务逻辑处理耗时30秒执行完逻辑之后调用 delete() 方法释放锁。 问题来了由于锁自动过期时间为 20秒而业务逻辑耗时为 30秒在不使用 random_value随机字符串下如果有多进程操作的话就会出现前面提到的套娃骚操作… 所以在删除锁之前我们先再次通过 get 命令获取加锁 key 的 value 值然后判断 value 跟加锁时设置的 value 是否一致这就看出 UUID 的重要性了如果一致就执行 delete() 方法释放锁否则不执行。 如下是使用「固定字符串」模拟的问题截图 两次加锁成功的时间间隔为11秒不足20秒显然不是一个进程的用户。 而在 value 使用 UUID 随机字符串时没有出现上述问题。 但随机字符串就真的安全了吗 不安全… 因为还是无法保证 redisTemplate.delete(key); 的原子操作在多进程下还是会有进程安全问题。 就有小伙伴可能钻牛角尖怎么就不能原子性操作了你在删除之前不都已经判断了吗 再举个例子比如进程 A 执行完业务逻辑在 redisTemplate.opsForValue().get(key); 获得 key 这一步执行没问题同时也进入了 if 判断中但是恰好这时候进程 A 的锁自动过期时间到了别问为啥就是这么巧而另一个进程 B 获得锁成功然后还没来得及执行进程 A 就执行了 delete(key) 释放了进程 B 的锁… 我操那你上边巴拉巴拉那么多说啥呢 咳咳解锁正确删除锁的方式之一为了保障原子性我们需要用 Lua 脚本进行完美解锁。 Lua脚本 可能有小伙伴不熟悉 Lua先简单介绍一下 Lua 脚本 Lua 是一种轻量小巧的脚本语言用标准 C 语言编写并以源代码形式开放 其设计目的是为了嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。 Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。 lua脚本优点 减少网络开销原先多次请求的逻辑放在 redis 服务器上完成。使用脚本减少了网络往返时延原子操作Redis会将整个脚本作为一个整体执行中间不会被其他命令插入想象为事务复用客户端发送的脚本会永久存储在Redis中意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑 先大致了解一下后面我会单独写一篇 Lua 从入门到放弃的文章。。 如下是Lua脚本通过 Redis 的 eval/evalsha 命令来运行 -- lua删除锁 -- KEYS和ARGV分别是以集合方式传入的参数对应上文的Test和uuid。 -- 如果对应的value等于传入的uuid。 if redis.call(get, KEYS[1]) ARGV[1] then -- 执行删除操作return redis.call(del, KEYS[1]) else -- 不成功返回0return 0 end好了看到 Lua 脚本了然后代码中如何使用 为了让大家更清楚我们在 SpringBoot 中使用这个 Lua 脚本 1在 resources 文件下创建 niceyoo.lua 文件 文件内容如下 if redis.call(get, KEYS[1]) ARGV[1]thenreturn redis.call(del, KEYS[1])elsereturn 0 end2修改 TestController 在 SpringBoot中是使用 DefaultRedisScript 类来加载脚本的并设置相应的数据类型来接收 Lua 脚本返回的数据这个泛型类在使用时设置泛型是什么类型脚本返回的结果就是用什么类型接收。 Slf4j RestController RequestMapping(/test) public class TestController {Resourceprivate RedisTemplateString,Object redisTemplate;private DefaultRedisScriptLong script;PostConstructpublic void init(){script new DefaultRedisScriptLong();script.setResultType(Long.class);script.setScriptSource(new ResourceScriptSource(new ClassPathResource(niceyoo.lua)));}PostMapping(value /addUser)public String createOrder(User user) {String key user.getUsername();String value UUID.randomUUID().toString().replace(-,);/** setIfAbsent SET key value [NX] [XX] [EX seconds] [PX [millseconds]]* set expire time 5 mins*/Boolean flag redisTemplate.opsForValue().setIfAbsent(key, value, 20000, TimeUnit.MILLISECONDS);if (flag) {log.info({} 锁定成功开始处理业务, key);try {// 模拟处理业务逻辑Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}// 业务逻辑处理完毕释放锁String lockValue redisTemplate.opsForValue().get(key).toString();if (lockValue ! null lockValue.equals(value)) {System.out.println(lockValue lockValue);ListString keys new ArrayList();keys.add(key);Long execute redisTemplate.execute(script, keys, lockValue);System.out.println(execute执行结果1表示执行del0表示未执行 execute);log.info({} 解锁成功结束处理业务, key);}return SUCCESS;} else {log.info({} 获取锁失败, key);return 请稍后再试...;}}}3测试结果 Lua 脚本替换 RedisTemplate 执行 delete() 方法测试结果如下 最后总结 1、所谓的 setnx 命令来实现分布式锁其实不是直接使用 Redis 的 setnx 命令因为 setnx 不支持设置自动释放锁的时间至于为什么要设置自动释放锁是因为防止被某个进程不释放锁而造成死锁的情况不支持设置过期时间就得分两步命令进行操作一步是 setnx key value一步是设置过期时间这种情况的弊端很显然无原子性操作。 2、 Redis 2.6.12 版本后set 命令开始整合了 setex 的功能并且 set 本身就已经包含了设置过期时间因此常说的 setnx 命令实则只用 set 命令就可以实现了只是参数上加上了 NX 等参数。 3、经过分析在使用 set key value nx px xxx 命令时value 最好是随机字符串这样可以防止业务代码执行时间超过设置的锁自动过期时间而导致再次释放锁时出现释放其他进程锁的情况套娃 4、尽管使用随机字符串的 value但是在释放锁时delete方法还是无法做到原子操作比如进程 A 执行完业务逻辑在准备释放锁时恰好这时候进程 A 的锁自动过期时间到了而另一个进程 B 获得锁成功然后 B 还没来得及执行进程 A 就执行了 delete(key) 释放了进程 B 的锁… 因此需要配合 Lua 脚本释放锁文章也给出了 SpringBoot 的使用示例。 至此带大家一块查看了 setnx 命令如何实现分布式锁但是下面还是要泼一下冷水… 经过测试在单机 Redis 模式下这种分布式锁简直是无敌求生欲纯个人看法咳咳没错你没看错单机下的 Redis 无敌… 所以在那些主从模式、哨兵模式、或者是 cluster 模式下可能会出现问题出现什么问题呢 setNX 的缺陷 setnx 琐最大的缺点就是它加锁时只作用在一个 Redis 节点上即使 Redis 通过 Sentinel(哨岗、哨兵) 保证高可用如果这个 master 节点由于某些原因发生了主从切换那么就会出现锁丢失的情况下面是个例子 在 Redis 的 master 节点上拿到了锁但是这个加锁的 key 还没有同步到 slave 节点master 故障发生故障转移slave 节点升级为 master节点上边 master 节点上的锁丢失。 有的时候甚至不单单是锁丢失这么简单新选出来的 master 节点可以重新获取同样的锁出现一把锁被拿两次的场景。 锁被拿两次也就不能满足安全性了… 尽管单机 Redis 下并不会出现如上问题但毕竟我们在生产环境中一般都是采用的集群模式所以这本身也是 Redis 分布式锁的诟病。 缺陷看完了怎么解决嘛~ 然后 Redis 的作者就提出了著名远洋的 RedLock 算法… 下节讲。 在写这篇文章过程中本来计划将 Redis 里的 setnx、redisson、redLock 一块写出来发一篇文章 但由于文章中贴了一些代码片段会让文章整体的节奏偏长不适用于后面自己的复习所以拆分成两篇文章 下一篇我们一块探索 Redisson RedLock 的分布式锁的实现。 2、Redisson RedLock 跳转链接https://www.cnblogs.com/niceyoo/p/13736140.html 博客园持续更新订阅关注未来我们一起成长。
http://www.zqtcl.cn/news/377747/

相关文章:

  • 石家庄网站制作公司排名前十可视化网站开发工具有哪些
  • 网站个人博客怎么做杭州网站改版公司电话
  • 烟台北京网站建设公司中国建筑信息资讯网
  • 硬盘做网站空间高端网站设计杭州
  • 南昌网站建设方案网站建设需求分析班级
  • 汉阳做网站关键词站长工具
  • 做海报图片的网站营销软件
  • 能先做网站再绑定域名吗石家庄公司建设网站
  • 设计网站的收费图是怎么做的公司网站简介怎么做
  • 医院网站案例结合七牛云做视频网站
  • wordpress数据库缓存插件aso优化吧
  • 网站二维码代码国贸汽车网站建设
  • 医疗网站建设多少钱信息查询类网站是怎么做的
  • 网站开发辅助工具搜索引擎推广实训
  • 如何用手机制作网站比价网站
  • 商城类网站备案四川全网推网络推广
  • 好设计购物网站wordpress 公网访问不了
  • 局域网网站建设需要什么条件wordpress文章列表高度
  • 长春怎样建网站?学服装设计培训机构
  • 怎么用织梦制作响应式布局网站阳江网红
  • 洛阳网站建站72建站网
  • 网站版权信息修改app开发公司资质
  • 用vs2015做网站教程天津红桥网站建设
  • 触屏网站开发四川住房建设厅网站
  • 百度商桥怎么接网站wordpress电影自动采集主题
  • 丽水做网站公司用vps建网站备案
  • 西安网站制作机构视频网站 备案
  • 北京城乡建设学校网站国内外贸网站建设公司
  • 万峰科技著.asp.net网站开发四酷全书电子工业出版社专业网站制作定制
  • 如何做好一个网站运营建公司网站的详细步骤