溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Redis實(shí)現(xiàn)高并發(fā)分布式鎖

發(fā)布時間:2020-07-23 12:23:14 來源:網(wǎng)絡(luò) 閱讀:423 作者:nineteens 欄目:編程語言

  分布式鎖場景

  在分布式環(huán)境下多個操作需要以原子的方式執(zhí)行

  首先啟一個springboot項(xiàng)目,再引入redis依賴包:

  org.springframework.boot

  spring-boot-starter-data-redis

  2.2.2.RELEASE

  以下是一個扣減庫存的接口作為例子:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  return "end";

  }

  }

  1.單實(shí)例應(yīng)用場景

  以上代碼使用JMeter壓測工具進(jìn)行調(diào)用,設(shè)置參數(shù)為:

  Number Of Threads[users]:100

  Ramp Up Period[in seconds]:0

  Loop Count:2

  用單個web調(diào)用,結(jié)果出現(xiàn)并發(fā)問題:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  解決方案:加入同步鎖(synchronized)

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  synchronized(this) {

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  return "end";

  }

  }

  }

  2.多實(shí)例分布式場景

  以上代碼,比如有多個應(yīng)用程序,用nginx做負(fù)載均衡,進(jìn)行同時調(diào)用壓測

  兩個程序存在同樣的扣減,出現(xiàn)并發(fā)現(xiàn)象。

  第一個應(yīng)用扣減結(jié)果顯示:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  第二個應(yīng)用扣減結(jié)果顯示:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  解決方案:redis的setnx方法(可參考SETNX的api)

  多個線程setnx調(diào)用時,有且僅有一個線程會拿到這把鎖,所以拿到鎖的執(zhí)行業(yè)務(wù)代碼,最后釋放掉鎖,代碼如下:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  String lockkey = "lockkey";

  Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx

  if(!result) {

  return "";

  }

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  springRedisTemplate.delete(lockkey);

  return "end";

  }

  }

  調(diào)用200次,壓測結(jié)果顯示還是有問題,只減掉了一部分:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  這時,加大壓測次數(shù),結(jié)果正常了:

  第一個應(yīng)用扣減結(jié)果顯示:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  第二個應(yīng)用扣減結(jié)果顯示:

  

Redis實(shí)現(xiàn)高并發(fā)分布式鎖


  這個只是因?yàn)榧哟罅苏{(diào)用次數(shù),執(zhí)行業(yè)務(wù)代碼需要一點(diǎn)時間,這段時間拒絕了很多等待獲取鎖的請求。但是,還是有問題,假如redis服務(wù)掛掉了,拋出異常了,這時鎖不會被釋放掉,出現(xiàn)死鎖問題,可以添加try catch處理,代碼如下:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  String lockkey = "lockkey";

  try{鄭州專業(yè)婦科醫(yī)院 http://fk.zyfuke.com/

  Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx

  if(!result) {

  return "";

  }

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  }finally{

  springRedisTemplate.delete(lockkey);

  }

  return "end";

  }

  }

  這時,Redis服務(wù)掛掉導(dǎo)致死鎖的問題解決了,但是,如果服務(wù)器果宕機(jī)了,又會導(dǎo)致鎖不能被釋放的現(xiàn)象,所以可以設(shè)置超時時間為10s,代碼如下:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  String lockkey = "lockkey";

  try{

  Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue",10,TimeUnit.SECONDS);//jedis.setnx

  //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx

  //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);

  if(!result) {

  return "";

  }

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  }finally{

  springRedisTemplate.delete(lockkey);

  }

  return "end";

  }

  }

  這時,如果有一個線程執(zhí)行需要15s,當(dāng)執(zhí)行到10s時第二個線程進(jìn)來拿到這把鎖,會出現(xiàn)多個線程拿到同一把鎖執(zhí)行,在第一個線程執(zhí)行完時會釋放掉第二個線程的鎖,以此類推…就會導(dǎo)致鎖的永久失效。所以,只能自己釋放自己的鎖,可以給當(dāng)前線程取一個名字,代碼如下:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  String lockkey = "lockkey";

  String clientId = UUID.randomUUID().toString();

  try{

  Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,clientId ,10,TimeUnit.SECONDS);//jedis.setnx

  //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx

  //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);

  if(!result) {

  return "";

  }

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  }finally{

  springRedisTemplate.delete(lockkey);

  }

  return "end";

  }

  }

  永久失效的問題解決了,但是,如果第一個線程執(zhí)行15s,還是會存在多個線程擁有同一把鎖的現(xiàn)象。所以,需要續(xù)期超時時間,當(dāng)一個線程執(zhí)行5s后對超時時間進(jìn)行續(xù)期都10s,就可以解決了,續(xù)期設(shè)置可以借助redission工具。

  Redission使用

  Redission分布式鎖實(shí)現(xiàn)原理

  pom.xml

  org.redisson

  redisson

  3.6.5

  Application.java啟動類

  @bean

  public Redission redission {

  //此為單機(jī)模式

  Config config = new Config();

  config.useSingleServer().setAddress("redis://120.0.0.1:6379").setDatabase(0);

  return (Redission)Redission.creat(config);

  }

  最終解決以上所有問題的代碼如下:

  @RestController

  public class IndexController {

  @Autowired

  private StringRedisTemplate stringRedisTemplate;

  @Autowired

  private Redissionredission;

  @RequestMapping("/deduct_stock")

  public Stirng deductStock() {

  String lockkey = "lockkey";

  //String clientId = UUID.randomUUID().toString();

  RLock lock = redission.getLock();

  try{

  //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,clientId ,10,TimeUnit.SECONDS);//jedis.setnx

  //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx

  //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);

  //加鎖:redission默認(rèn)超時時間為30s,每10s續(xù)期一次,也可以自己設(shè)置時間

  lock.lock(60,TimeUnit.SECONDS);

  int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)

  if (stock > 0) {

  int realStock = stock - 1;

  stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)

  System.out.println(扣減成功,剩余庫存:" + realStock + "");

  } else {

  System.out.println(扣減失敗,庫存不足!" );

  }

  }finally{

  lock.unlock();

  //springRedisTemplate.delete(lockkey);

  }

  return "end";

  }

  }

  高并發(fā)分布式鎖的問題得到解決。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI