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

书法网站建设建站案例

书法网站建设,建站案例,静态手机网站基础,wordpress host头攻击背景 Redis分布式锁很有用处#xff0c;在秒杀、抢购、订单、限流特别是一些用到异步分布式并行处理任务时频繁的用到#xff0c;可以说它是一个BS架构的应用中最高频使用的技术之一。 但是我们经常会碰到这样的一个问题#xff0c;那就是我们都按照标准做了但有时运行着、… 背景 Redis分布式锁很有用处在秒杀、抢购、订单、限流特别是一些用到异步分布式并行处理任务时频繁的用到可以说它是一个BS架构的应用中最高频使用的技术之一。 但是我们经常会碰到这样的一个问题那就是我们都按照标准做了但有时运行着、运行着就是没锁住的问题。 一旦出了这样的问题特别难调试以及排查因为在异步并行的环境下计算机代码的执行是乱序的而且有一个“概率”问题。往往测10次结果都是对的。此时测试团队以为这次交付没有问题了于是布署上线而上了线后会产生要么一次都不对或者前10次对的第11次就是不对的。 要知道锁的问题出了事不是小事。一旦出事对用户来说就和“死机”一样死活无法操作了亦或者时操作的结果乱通知、乱扣钱、随机不能下单此时后台唯有找到锁键值然后人为的把这个键值给“剁”掉才能解决。 因此今天就借着刚排查的2个生产问题我们把锁的机制彻底的了解一下。 Redis锁的正确使用方式 //使用RedissonClient锁Autowired private RedissonClient redissonSentinel;//申明锁 RLock lock null; lock redissonSentinel.getLock(lockKey);if (lock ! null lock.isLocked()) {//已经有一个任务在进行了因此不能执行此时系统需要根据业务逻辑或进入等待或者return } try{lock.tryLock(0, TimeUnit.SECONDS);// 用自续约锁上锁接着下面就是做某事了 }catch(Exception e){}finally { //直接按此解锁永不出错也不要在finally里去判断而是强制在finally里关闭-大厂佳实践try {lock.unlock();} catch (Exception e) {} } 以上是一个标准的Redis分布式锁的标准公式下面给出配置 redis:password: 111111nodes: 192.1.0.11:7001redisson:nodes: redis://192.1.0.11:27001,redis://192.1.0.12:27001,redis://192.1.0.13:7001:27003sentinel: nodes: 192.1.0.11:27001,192.1.0.12:27001,192.1.0.13:27001master: master1subscriptionsPerConnection: 50 #分布式锁必设此参数可以考虑放大它占用redis连接subscriptionConnectionPoolSize: 200 #分布式锁必设此参数可以考虑放大它占用redis连接 千万不要忘了这两关键字很多人不设的话那么会出现生产上订单、并发一多直接会抛出redis锁连接不够用的错 subscriptionsPerConnectionsubscriptionConnectionPoolSize 生产典型问题 下面我们就来看自以为锁住了但是在生产上随机的“飘”的问题要么锁死要么就没锁住的具体案例来讲解Redis分布式锁的一些坑吧。 每个用户只可以有一个文件导出没但没锁住 具体场景 每个用户在一个数据展式面板里查看数据看到了自己要的数据就可以选择1万条做导出导出时用户可以关闭当前页面甚至退出后台任务导完后会以消息形式通知到用户用户在自己的个人头像上可以看到一个小红点闪出。 需求 根据需求这是一个云上的SAAS应用我们对普通用户只提供同时只可以有一个导出任务在后台运行的机制。 当后台己有一条任务正在导出时用户此时在数据面板里就算点几十次“导出”都因该提醒用户“当前您有一个导出任务正在进行中”。 实际有问题代码 我看了一下代码还挺公整的它是这么判断的。 //使用RedissonClient锁 Service public class ExportService{Autowiredprivate RedissonClient redissonSentinel;Asyncpublic void exportTask(){String redisLockEXPORT_TASK_LOCK_KEY:companyId:loginId//申明锁RLock lock null; lock redissonSentinel.getLock(lockKey);if (lock ! null lock.isLocked()) {//已经有一个任务在进行了因此不能执行此时系统需要根据业务逻辑或进入等待或者return}try{lock.tryLock(0, TimeUnit.SECONDS);// 用自续约锁上锁接着下面就是做某事了}catch(Exception e){}finally { //直接按此解锁永不出错也不要在finally里去判断而是强制在finally里关闭-大厂佳实践try {lock.unlock();} catch (Exception e) {}}} } 对锁认识上的误区 我一眼就看出了问题但我没有声张我让开发和他的Leader以及我们的架构师一起来看。我这么提出问题让他们自己开动脑筋去想这个问题。 1. 首先我们看到这个锁用companyIdloginId的确是可以做到锁的这个key唯一 2. 但实际是没锁住因为前端用户在一个任务没有导完后再点按钮有时可以并发出两条导出任务有时只能并发出一条这是事实那么肯定不是这个key唯一的问题 3. 我们一起打开redis客户端用命令来查看服务器在导出任务时锁产生的情况的确是看到产生的这个锁的key对于不同的人是唯一的key 我的问题是锁的key是唯一的就一定会被锁住吗 三个人搔搔头回答我可能吧 哈哈问题就出在这。 都以为只要锁的key是唯一这个key被锁住了那另一个操作带着同样的lock key进来获取到的状态就一定是“已经上锁”。 这个认知上错误了 对于Redis分布式锁正确的认知 锁是存在于服务器上的它不存在于客户端同一个key来锁固然没错但是我们看到了这个方法是一个被标为Async的。 于是同一个客户点击一次就会生成一个Service类的exportTask进程。再点击一次又生成了一个Service类的exportTask进程。 当有10个exportTask进程时我们虽然用的都是同一个lock key String redisLockEXPORT_TASK_LOCK_KEY:companyId:loginId 但是别忘了这个Service方法的完整运行机制是怎么样的 Asyncpublic void exportTask(){String redisLockEXPORT_TASK_LOCK_KEY:companyId:loginId//申明锁RLock lock null; lock redissonSentinel.getLock(lockKey);if (lock ! null lock.isLocked()) {//已经有一个任务在进行了因此不能执行此时系统需要根据业务逻辑或进入等待或者return}try{lock.tryLock(0, TimeUnit.SECONDS);// 用自续约锁上锁 看到没 每次都要RLock locknull再lock redissonSentinel.getLock(lockKey)一下。 此时后台有10个exportTask这10个exportTask彼此都在实例化自己的锁、锁语句。这下好玩了因为是异步的是乱序的所以此时发生了这么一件肉眼不可见的事 exportTask1刚锁住正在操作导出还没有操作完时。exportTask2进程被创建时把这个锁的状态给置成“初始化状态了“。exportTask2于是在exportTask1还没有完成任务和释放锁时就又可以接着执行了。 这就是我们说的“没锁住”。 嘿嘿这一切是Async惹的祸。 如何改 但我们又必须让这个方法是一个Async的因此怎么办 把这个锁“上浮”到controller层。 在RestController层的public ResponseBean exportDataAPI方法里如下申明 RLock uploadLock null;uploadLock exportService.canLock(companyId);if (uploadLock ! null !uploadLock.isLocked()) {message 导出中;exportService.exportTask(ut, data, uploadLock);}else{logger.info(有一个任务已经在导出当前步骤不执行)} 在Service方法中放入一个canLock方法如下 public RLock canLock(int companyId) {StringBuilder lockKeySB new StringBuilder();lockKeySB.append(FoodTrainLLMConstants.LLM_UPLOAD_LOCK).append(companyId);RLock lock redissonSentinel.getLock(lockKeySB.toString());return lock;} 然后我们在Service中如此改写原有逻辑 //使用RedissonClient锁 Service public class ExportService{Autowiredprivate RedissonClient redissonSentinel;Asyncpublic void exportTask(Rlock lock){try{lock.tryLock(0, TimeUnit.SECONDS);// 用自续约锁上锁接着下面就是做某事了}catch(Exception e){}finally { //直接按此解锁永不出错也不要在finally里去判断而是强制在finally里关闭-大厂佳实践try {lock.unlock();} catch (Exception e) {}}} } 这就是把锁“上浮”让其真正把一整个“进程”给锁住于是这个问题就可以被解决了。 小结 这种错误一般在于非Async中就算一开始写成了错误的那种写法你也发现不了这是因为一切都是同步的。 而一旦但有了Async后上述生产问题就发生了。 多线程中希望每个用户只可以存在一个查询任务但实际没有锁住 这是另一个场景但是其也发生在一个Async方法中。 即在一个Asynce标注的方法中还有一个while而要锁是锁的while中的步骤。 于是我们看到了这样的代码 Async public void backendQueryImageTask(String ut, int companyId, String loginId, String userInputPrompt, String taskId) throws Exception { while (System.currentTimeMillis() - startTime timeoutMillis) {String lockKey Query_Image_Redis_Lock_PREFIX companyId : uid;RLock lock null; lock redissonSentinel.getLock(lockKey);if (lock ! null lock.isLocked()) {logger.info(当前是backendQueryImageTask图片还在生成中另一个任务在查询中当前任务不进行);continue;}try {lock.tryLock(0, TimeUnit.SECONDS);// 上锁 。。。。。。catch (Exception e) {logger.error(Error in backendQueryImageTask, e);return;} finally {try {lock.unlock();} catch (Exception e) {}} 实际代码问题 这个问题和第一个问题其实是一样因为锁是运行在服务器端的它的状态不是维持在客户端。因此当这个方法如果是Asynce时代表着后台会存在若干进程而我们这次的需求是在每一个进程里再有一个while而在while中运行时必须锁住。 但实际没有锁住也正是因为每一次循环时另一个进程把同一把本己锁住正在执行任务的锁的状态给连续的做了这样的操作 RLock locknull - redissonSentinel.getLock(lockKey); 这就破坏了还在上锁的服务器上的同一把锁的状态导致了这个锁失效。 如何改 String lockKey Query_Image_Redis_Lock_PREFIX companyId : uid;RLock lock null; lock redissonSentinel.getLock(lockKey);while (System.currentTimeMillis() - startTime timeoutMillis) {if (lock ! null lock.isLocked()) {logger.info(当前是backendQueryImageTask图片还在生成中另一个任务在查询中当前任务不进行);continue;}try {lock.tryLock(0, TimeUnit.SECONDS);// 上锁//do something} catch (Exception e) {logger.error(Error in backendQueryImageTask, e);return;} finally {try {lock.unlock();} catch (Exception e) {}}} 把锁的申明外置到while循环外即可成功达到我们的要求。 总结 锁一定是存在于服务器的锁要锁的这个范围本身是异步运行的因为如果是同步操作也没有这个锁的必要了。正因为是异步所以服务器上的锁的对象是同一个而当这个对象在异步并行时是乱序的因此就会存在一个子进程“污染”到了另一个子进程里的锁对象。 为了成功把一组进程、业务原子方法锁住这个锁的范围必须控制在其外层且这个锁的初始状态只可以被初始化一次。 此处我们考虑第一个例子中为什么把锁放在controller方法中这个锁就可以成功锁住Service里的方法呢 这是因为每次用户在Controller方法中就算RLock locknull时此时初始化的lock不是同一个对象这是Controller方法的特性。 而只有当redissonSentinel.getLock(lockKey);时才会去拿服务端的锁而此时这把锁的状态如果还没有被释放那么就一定是被锁住的。 附redisson自续约锁的概念 当我们这样操作时 lock.tryLock(0, TimeUnit.SECONDS);// 上锁 很多人会习惯性的在参数里把这个0改成30或者60。这样做反而是画蛇添足、错误的做法。 因为加上了一个确切的数字后就会有问题你怎么知道这个方法正好执行了30秒或者是60秒就一定完成了呢如果这个方法是需要62秒怎么办你不是把方法给打断了。 因此Redisson特有的自续约锁就是把这个值设成0。于是在后台Redisson锁会先给到锁30秒时间。 当第20多秒还没有碰到有用户调用finally里的unlock时它会再给这个key延续30秒。。。再没执行完再给它30秒。 直到碰到finally块里被显示的调用了unlock那么代表任务结束这把锁的状态才会变成“释放”。
http://www.tj-hxxt.cn/news/221831.html

相关文章:

  • 网站平台建设缴纳什么税普通电脑怎么做网站服务器
  • 网站有中文源码加英文怎么做全网平台整合营销推广
  • 网站推广需要域名迁移优化大师安卓版
  • 卫辉网站建设网站建设公司湖南
  • 建网站郑州网站建设 新手从
  • 机械行业网站建设北京望京企业网站建设
  • 工信部的网站备案信息查询医疗微网站建设计划书
  • 网站做蜘蛛池有用吗广州网站排名优化
  • 手机网站大全免费成都到西安
  • 烟台企业自助建站系统温州外贸网站推广
  • 网站建设费用大全app应用公司
  • 局域网网站怎样做数据库企业名称注册查询官网入口
  • 网站建设要学编程吗企业邮箱怎么获取
  • 长春地区网站建设专业建设验收网站
  • 168网站建设临沂专门做网站的
  • 网站logo做h1标签网站 页面风格 建设
  • 建设银行e房通网站建设部执业考试中心网站
  • 国家建设官方网站 163com箱登录
  • 广东网站建设联系电话青岛网站seo收费标准
  • wps的ppt做网站超链接漳州招商局规划建设局网站
  • 天津网站页面设计建网站的公司有哪些
  • 可信赖的郑州网站建设wordpress主题4mudi
  • 晋城市企业网站safari浏览器下载
  • 拼多多网站网站如何收录快
  • 网站开发 技术难点小型企业做网站的价格
  • 学校网站建设调查表做网站的客户需求报告答案
  • 织梦视频网站源码暴雪游戏官网
  • wordpress多站点批量添加关键字
  • 个人网站建设报价重庆装修公司哪家最好
  • 如何选择佛山网站建设网站建设ui设计