范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

大厂面试题详解如何用Redis实现分布式锁?

  说一道常见面试题:
  使用Redis分布式锁的详细方案是什么?
  一个很简单的答案就是去使用 Redission 客户端。Redission 中的锁方案就是 Redis 分布式锁得比较完美的详细方案。
  那么,Redission 中的锁方案为什么会比较完美呢?
  正好,我用 Redis 做分布式锁经验十分丰富,在实际工作中,也探索过许多种使用 Redis 做分布式锁的方案,经过了无数血泪教训。
  所以,在谈及 Redission 锁为什么比较完美之前,先给大家看看我曾经使用 Redis 做分布式锁是遇到过的问题。
  我曾经用 Redis 做分布式锁是想去解决一个用户抢优惠券的问题。这个业务需求是这样的:当用户领完一张优惠券后,优惠券的数量必须相应减一,如果优惠券抢光了,就不允许用户再抢了。
  在实现时,先从数据库中先读出优惠券的数量进行判断,当优惠券大于 0,就进行允许领取优惠券,然后,再将优惠券数量减一后,写回数据库。
  当时由于请求数量比较多,所以,我们使用了三台服务器去做分流。
  这个时候会出现一个问题:
  如果其中一台服务器上的 A 应用获取到了优惠券的数量之后,由于处理相关业务逻辑,未及时更新数据库的优惠券数量;在 A 应用处理业务逻辑的时候,另一台服务器上的 B 应用更新了优惠券数量。那么,等 A 应用去更新数据库中优惠券数量时,就会把 B 应用更新的优惠券数量覆盖掉。
  看到这里,可能有人比较奇怪,为什么这里不直接使用 SQL: update 优惠券表 set 优惠券数量 = 优惠券数量 - 1 where 优惠券id = xxx
  原因是这样做,在没有分布式锁的协调下,优惠券数量可能直接会出现负数。因为当前优惠券数量为 1 的时候,如果两个用户通过两台服务器同时发起抢优惠券的请求,都满足优惠券大于 0 每个条件,然后都执行这条 SQL 说了句,结果优惠券数量直接变成 -1 了。
  还有人说可以用乐观锁,比如使用如下 SQL: update 优惠券表 set 优惠券数量 = 优惠券数量 - 1 where 优惠券id = xxx and version = xx
  这种方式就在一定几率下,很可能出现数据一直更新不上,导致长时间重试的情况。
  所以,经过综合考虑,我们就采用了 Redis 分布式锁,通过互斥的方式,以防止多个客户端同时更新优惠券数量的方案。
  当时,我们首先想到的就是使用 Redis 的 setnx 命令,setnx 命令其实就是 set if not exists 的简写。
  当 key 设置值成功后,则返回 1,否则就返回 0。所以,这里 setnx 设置成功可以表示成获取到锁,如果失败,则说明已经有锁,可以被视作获取锁失败。 setnx lock true
  如果想要释放锁,执行任务 del 指令,把 key 删除即可。 del lock
  利用这个特性,我们就可以让系统在执行优惠券逻辑之前,先去 Redis 中执行 setnx 指令。再根据指令执行结果,去判断是否获取到锁。如果获取到了,就继续执行业务,执行完再使用 del 指令去释放锁。如果没有获取到,就等待一定时间,重新再去获取锁。
  乍一看,这一切没什么问题,使用 setnx 指令确实起到了想要的互斥效果。
  但是,这是建立在所有运行环境都是正常的情况下的。一旦运行环境出现了异常,问题就出现了。
  想一下,持有锁的应用突然崩溃了,或者所在的服务器宕机了,会出现什么情况?
  这会造成死锁——持有锁的应用无法释放锁,其他应用根本也没有机会再去获取锁了。这会造成巨大的线上事故,我们要改进方案,解决这个问题。
  怎么解决呢?咱们可以看到,造成死锁的根源是,一旦持有锁的应用出现问题,就不会去释放锁。从这个方向思考,可以在 Redis 上给 key 一个过期时间。
  这样的话,即使出现问题,key 也会在一段时间后释放,是不是就解决了这个问题呢?实际上,大家也确实是这么做的。
  不过,由于 setnx 这个指令本身无法设置超时时间,所以一般会采用两种办法来做这件事:
  1、采用 lua 脚本,在使用 setnx 指令之后,再使用 expire 命令去给 key 设置过期时间。 if redis.call("SETNX", "lock", "true") == 1 then   local expireResult = redis.call("expire", "lock", "10")   if expireResult == 1 then       return "success"   else       return "expire failed"   end else   return "setnx not null" end
  2、直接使用 set(key,value,NX,EX,timeout) 指令,同时设置锁和超时时间。 redis.call("SET", "lock", "true", "NX", "PX", "10000")
  以上两种方法,使用哪种方式都可以。
  释放锁的脚本两种方式都一样,直接调用 Redis 的 del 指令即可。
  到目前为止,我们的锁既起到了互斥效果,又不会因为某些持有锁的系统出现问题,导致死锁了。这样就完美了吗?
  假设有这样一种情况,如果一个持有锁的应用,其持有的时间超过了我们设定的超时时间会怎样呢?会出现两种情况: 发现系统在 Redis 中设置的 key 还存在 发现系统在 Redis 中设置的 key 不存在
  出现第一种情况比较正常。因为你毕竟执行任务超时了,key 被正常清除也是符合逻辑的。
  但是最可怕的是第二种情况,发现设置的 key 还存在。这说明什么?说明当前存在的 key,是另外的应用设置的。
  这时候如果持有锁超时的应用调用 del 指令去删除锁时,就会把别人设置的锁误删除,这会直接导致系统业务出现问题。
  所以,为了解决这个问题,我们需要继续对 Redis 脚本进行改动……毁灭吧,累了……
  首先,我们要让应用在获取锁的时候,去设置一个只有应用自己知道的独一无二的值。
  通过这个唯一值,系统在释放锁的时候,就能识别出这锁是不是自己设置的。如果是自己设置的,就释放锁,也就是删除 key;如果不是,则什么都不做。
  脚本如下: if redis.call("SETNX", "lock", ARGV[1]) == 1 then 	local expireResult = redis.call("expire", "lock", "10") 	if expireResult == 1 then     	return "success" 	else     	return "expire failed" 	end else   return "setnx not null" end
  或者 redis.call("SET", "lock", ARGV[1], "NX", "PX", "10000")
  这里,ARGV[1] 是一个可传入的参数变量,可以传入唯一值。比如一个只有自己知道的 UUID 的值,或者通过雪球算法,生成只有自己持有的唯一 ID。
  释放锁的脚本改成这样: if redis.call("get", "lock") == ARGV[1]    then  	    return redis.call("del", "lock")    else  	    return 0  end
  可以看到,从业务角度,无论如何,我们的分布式锁已经可以满足真正的业务需求了。能互斥,不死锁,不会误删除别人的锁,只有自己上的锁,自己可以释放。
  一切都是那么美好!!!
  可惜,还有个隐患,我们并未排除。这个隐患就是 Redis 自身。
  要知道,lua 脚本都是用在 Redis 的单例上的。一旦 Redis 本身出现了问题,我们的分布式锁就没法用了,分布式锁没法用,对业务的正常运行会造成重大影响,这是我们无法接受的。
  所以,我们需要把 Redis 搞成高可用的。一般来讲,解决 Redis 高可用的问题,都是使用主从集群。
  但是搞主从集群,又会引入新的问题。主要问题在于,Redis 的主从数据同步有延迟。这种延迟会产生一个边界条件:当主机上的 Redis 已经被人建好了锁,但是锁数据还未同步到从机时,主机宕了。随后,从机提升为主机,此时从机上是没有以前主机设置好的锁数据的——锁丢了……丢了……了……
  到这里,终于可以介绍 Redission(开源 Redis 客户端)了,我们来看看它怎么是实现 Redis 分布式锁的。
  Redission 实现分布式锁的思想很简单,无论是主从集群还是 Redis Cluster 集群,它会对集群中的每个 Redis,挨个去执行设置 Redis 锁的脚本,也就是集群中的每个 Redis 都会包含设置好的锁数据。
  我们通过一个例子来介绍一下。
  假设 Redis 集群有 5 台机器,同时根据评估,锁的超时时间设置成 10 秒比较合适。
  第 1 步,咱们先算出集群总的等待时间,集群总的等待时间是 5 秒(锁的超时时间 10 秒 / 2)。
  第 2 步,用 5 秒除以 5 台机器数量,结果是 1 秒。这个 1 秒是连接每台 Redis 可接受的等待时间。
  第 3 步,依次连接 5 台 Redis,并执行 lua 脚本设置锁,然后再做判断: 如果在 5 秒之内,5 台机器都有执行结果,并且半数以上(也就是 3 台)机器设置锁成功,则认为设置锁成功;少于半数机器设置锁成功,则认为失败。 如果超过 5 秒,不管几台机器设置锁成功,都认为设置锁失败。比如,前 4 台设置成功一共花了 3 秒,但是最后 1 台机器用了 2 秒也没结果,总的等待时间已经超过了 5 秒,即使半数以上成功,这也算作失败。
  再额外多说一句,在很多业务逻辑里,其实对锁的超时时间是没有需求的。
  比如,凌晨批量执行处理的任务,可能需要分布式锁保证任务不会被重复执行。此时,任务要执行多长时间是不明确的。如果设置分布式锁的超时时间在这里,并没有太大意义。但是,不设置超时时间,又会引发死锁问题。
  所以,解决这种问题的通用办法是,每个持有锁的客户端都启动一个后台线程,通过执行特定的 lua 脚本,去不断地刷新 Redis 中的 key 超时时间,使得在任务执行完成前,key 不会被清除掉。
  脚本如下: if redis.call("get", "lock") == ARGV[1]    then      return redis.call("expire", "lock", "10") 	else      return 0  end
  其中,ARGV[1] 是可传入的参数变量,表示持有锁的系统的唯一值,也就是只有持有锁的客户端才能刷新 key 的超时时间。
  到此为止,一个完整的分布式锁才算实现完毕。总结实现方案如下: 使用 set 命令设置锁标记,必须有超时时间,以便客户端崩溃,也可以释放锁; 对于不需要超时时间的,需要自己实现一个能不断刷新锁超时时间的线程; 每个获取锁的客户端,在 Redis 中设置的 value 必须是独一无二的,以便识别出是由哪个客户端设置的锁; 分布式集群中,直接每台机器设置一样的超时时间和锁标记; 为了保证集群设置的锁不会因为网络问题导致某些已经设置的锁出现超时的情况,必须合理设置网络等待时间和锁超时时间。
  这个分布式锁满足如下四个条件: 任意时刻只能有一个客户端持有锁; 不能发生死锁,有一个客户端持有锁期间出现了问题没有解锁,也能保证后面别的客户端继续去持有锁; 加锁和解锁必须是同一个客户端,客户端自己加的锁只能自己去解; 只要大多数 Redis 节点正常,客户端就能正常使用锁。
  当然,在 Redission 中的脚本,为了保证锁的可重入,又对 lua 脚本做了一定的修改,现在把完整的 lua 脚本贴在下面。
  获取锁的 lua 脚本: 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]);
  对应的刷新锁超时时间的脚本: if (redis.call("hexists", KEYS[1], ARGV[2]) == 1) then  	redis.call("pexpire", KEYS[1], ARGV[1]);    return 1;  end;   return 0;
  对应的释放锁的脚本: if (redis.call("hexists", KEYS[1], ARGV[3]) == 0) then 	return nil; end;  local counter = redis.call("hincrby", KEYS[1], ARGV[3], -1);   if (counter > 0) then 	redis.call("pexpire", KEYS[1], ARGV[2]); return 0; else 	redis.call("del", KEYS[1]); 	redis.call("publish", KEYS[2], ARGV[1]); return 1; end; return nil;
  到现在为止,使用 Redis 作为分布式锁的详细方案就写完了。
  我既写了一步一坑的坎坷经历,也写明了各个问题和解决问题的细节,希望大家看完能有所收获。
  最后再给大家提个醒,使用 Redis 集群做分布式锁,有一定的争议性,还需要大家在实际用的时候,根据现实情况,做出更好的选择和取舍。
  如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,,咱们下期见。
  收藏 等于白嫖,点赞才是真情。
  亲爱的小伙伴们,有需要JAVA面试文档资料请点赞 转发,关注我后,私信我领取
  原文 https://www.cnblogs.com/siyuanwai/p/16011836.html

理性看待创业式亏损,小康股份(601127。SH)业绩拐点临近4月29日,小康股份公布2021年度及2022Q1业绩报告,作为去年中国汽车市场重要的价值体现者,报告一经发出就引起了市场广泛关注。对于这只新能源汽车华为汽车等众多热门概念交汇的标荣耀加速清仓,超窄边框6纳米芯片66W快充,从2199跌至1289元荣耀手机创立于2013年,当时是华为推出为了对抗小米的子品牌,在大哥华为的支持下,加上自身不错的性价比,荣耀也一度成为国内第一大互联网手机品牌,很多喜欢高性价比手机的花粉,都会选择快递业复苏步伐加快网购商品迟迟不发货已经发货的快件被退回4月份以来,受吉林山西上海等多地疫情影响,快递业发展有所放缓,部分分拨中心和营业网点暂时停运,多地出现不同程度网络中断。从数据上看,影响更直观85后大数据专家扎根湖北让智医助理助力基层看好病看准病科技工作需要日积月累的学习和可持续性的创新。5月3日,科大讯飞集团大数据专家讯飞华中(武汉)有限公司医疗大数据技术总工肖桥仍在研发一线忙碌着,手握7项专利,这名34岁的青年科技工作续航超1000公里值得买的5款新能源汽车近二年在汽车市场,新能源电动车可以说是异军突起成为了众多车友追逐的目标,很多人都梦想买一辆新能源车回去尝尝鲜,可是又担心它的续航问题,买回去只能当一辆买菜的代步车,今天我就是为了这小康股份4月新能源汽车销售8552辆,同比增长187。565月5日,资本邦了解到,A股公司小康股份(601127。SH)发布4月份产销快报。报告期内,公司新能源汽车产量为7738辆,同比增长182。2销售量为8552辆,同比增长187。5汽车免费WiFi的原理是什么?汽车免费WIFI,我不知道您强调的是免费还是强调wifi这个功能。现在很多公交车上都开通了免费wifi功能,和私家车一样,主要是车上配备了具有上网功能的无线上网设备,然后装上上网卡功能机首选降至849元!8000毫安大电池充电1次能用3天的神机来了功能机首选,降至849元!8000毫安大电池,充电1次能用3天的神机来了本文原创,禁止搬运和抄袭,违者必究!导语对于购买手机的时候,除了考虑手上的预算之外,大多数的消费者会下意识考鸿海或将组装苹果汽车,瞄准高端市场,实现全自动驾驶近日,我们从相关渠道获知,苹果汽车开发案正如火如荼进行中,苹果很可能委托组装iPhone的长期合作伙伴鸿海负责汽车生产,而苹果车的发布仍将在2025年或甚至是2024年底,虽然实际宁德时代们的新生意汽车动力电池回收,为环保还是想赚钱?从2013年政策补贴,新能源汽车的生产进入快车道以来,到2020年为止,国内已经累积了20万吨废弃的动力电池。而到2030年的时候,全球动力电池的数量将累计达到1200万吨。这么多新能源汽车我想买哪吒V哪吒V典雅白续航300km400km8W起带回家每公里电费约0。06元N95级空气滤芯宽敞5人座前排双安全气囊倒车雷达倒车影像(动态辅助线)金融礼至高可享5000元金融贴息置换礼享
比尔盖茨把敌人变成朋友美国的RealNetworKs公司曾经向美国联邦法院提起公诉,指控比尔。盖茨的微软公司违反反垄断法,并要求其赔偿十亿美元。但在官司还没有结束的情况下,RealNetworKs公司的OPPO试产5500mAh新机,强续航低价格,红米守门员位置受挑战有一个很奇怪的现象,在OPPO这个手机品牌中,其实充斥着不少科研技术,除了华为之外,OPPO的科研专利是国产中最多的。但令人不解的是,直到现在OPPO依旧拿不出像华为一样的自研处理请问一直使用苹果的用户,是什么理由让你们坚持不换手机?现在是全家都用苹果手机,之前全家都用华为用到怨得摔机,原因不用多说了,不是我不爱国。你好,很高兴回答你为什么一直使用苹果手机的这个问题,我以前一直用诺基亚的手机,后来苹果手机走红后12256GB的iQOONeo5,如今依然值得入手,降至2289元体验如何?iQOONeo5搭载的骁龙870,次旗舰芯片,日用和骁龙888无异,甚至机身温度还更低点,此外还有一颗独立芯片,既能提高游戏的帧率还能降低游戏时的功耗,在游戏原神上的帧率测试也直追iPhone14外观被曝光,异形屏幕太惹眼,价格成最大悬念现在的苹果已经是中国市场第一大智能手机品牌,他的综合实力当然是毋庸置疑的,有能力为消费者提供极致体验的产品,也是众多友商应该学习的榜样。确实,凭借着iPhone13系列的出色发挥,日本倾国之力,研发超8倍容量电池,我国究竟该如何应对呢?提到电池,想必对于大部分人来说并不陌生,也可能目前正在打交道,因为各种各样的电池充斥在生活的方方面面,小到家里的遥控器,大到家里的新能源汽车。尤其是近年来运用在新能源汽车上的锂电池网络机顶盒什么牌子好?数码胡哥总结2022年网络机顶盒排行榜随着网速不断的提速,现在不少小伙伴都会购买一款网络机顶盒在家里看定制化的节目,毕竟网络机顶盒广告少,还可以看电视剧一次性看个够,实在是居家必备小家电。最近也在后台收到不少小伙伴的私建议将小区物业不安装充电桩,按照消防安全隐患严厉处罚随着新能源产业的蓬勃发展,电动自行车和电动汽车市场占有量将快速增长。各类电动车将快速走进千家万户。今后,电动车将成为居民出行的重要交通工具。因此,在居民小区安装电动车充电桩已经成为焦点分析蔚小理联手增资欣旺达,车企的电池焦虑比想象中严重文李安琪编辑苏建勋动力电池的供应已成为车企的头等大事,蔚小理正在出手加码电池企业。2月24日晚,欣旺达发布公告,公司董事会审议通过了相关增资议案,同意19家企业以24。3亿元人民币明天起,这些新规将影响你我生活明天三月将正式开启啦不仅学生们迎来了开学季一些新规也将从明天开始正式走进我们的生活来看看吧2021年度个人所得税综合所得汇算清缴启动,办理时间为2022年3月1日至6月30日。互联美国对俄罗斯芯片供应下手了芯东西(公众号aichip001)编译高歌编辑Panken芯东西2月28日报道,近日,随着俄乌冲突愈演愈烈,美国欧盟日本等多个国家或地区已宣布对俄罗斯进行制裁。据外媒报道,由于美国