SpringBoot基于Redis的分布式锁实现过程记录

网友投稿 299 2022-09-04


SpringBoot基于Redis的分布式锁实现过程记录

目录一、概述二、环境搭建三、模拟一个库存扣减的场景四、总结

一、概述

什么是分布式锁

在单机环境中,一般在多并发多线程场景下,出现多个线程去抢占一个资源,这个时候会出现线程同步问题,造成执行的结果没有达到预期。我们会用线程间加锁的方式,比如synchronized,lock,volatile,以及JVM并发包中提供的其他工具类去处理此问题。

但是随着技术的发展,分布式系统的出现,各个应用服务都部署在不同节点,由各自的JVM去操控,资源已经不是在 线程 之间的共享,而是变成了 进程 之间的共享,以上解决线程同步问题的办法已经无法满足。

因此,引入了分布式锁的概念。

分布式锁,既在分布式部署的环境下,通过在外部设置锁,让客户端之间互斥,当多应用发生对共享资源的抢占时,该资源同一时刻只能一个应用访问,从而保证数据一致性

分布式锁满足条件

互斥性:即同一时刻只能一个客户端获得锁,其他客户端必须等待获取锁的客户端主动释放锁或锁超时后再次对资源进行抢占。避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放),否则当一个客户端线程获得锁后没有主动释放,也没有设置超时时间,中途宕机等原因,其他线程就会一直获取不了锁具备可重入特性:同一个线程可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,如果没有可重入锁的支持,在第二次尝试获得锁时将会进入死锁状态。高可用:获取或释放锁的机制必须高可用且性能佳

分布式锁的重要性不言而喻,原因不在赘述,每一位菜鸟都有理由掌握它。提到分布式锁,解决方案更是乌泱乌泱的,如:

直接通过关系型数据库实现基于Redission实现基于Apache Curator实现…

本文暂时先介绍一种,基于Redission实现的方式

二、环境搭建

有一个简单的SpringBoot环境即可,便于测试:

依赖

org.springframework.boot

spring-boot-starter-data-redis

org.redisson

redisson

3.6.5

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

配置

server:

port: 7077

spring:

redis:

host: 192.144.228.170

database: 0

启动及配置类

package com.ideax.distributed;

import org.redisson.Redisson;

import org.redisson.api.RedissonClient;

import org.redisson.config.Config;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Bean;

@SpringBootApplication

public class DistributedApplication {

public static void main(String[] args) {

SpringApplication.run(DistributedApplication.class,args);

}

/**

* 配置redisson客户端

* @return org.redisson.Redisson

* @author zhangxs

* @date 2022-01-06 10:01

*/

@Bean

public Redisson redisson(){

// 单机模式

Config config = new Config();

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

return (Redisson) Redisson.create(config);

}

}

三、模拟一个库存扣减的场景

package com.ideax.distributed.controller;

import org.redisson.Redisson;

import org.redisson.api.RLock;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

import java.util.UUID;

import java.util.concurrent.TimeUnit;

/**

* 库存 前端控制器

* @author zhangxs

* @date 2022-01-06 09:46

*/

@RequestMapping("/inventory")

@RestController

public class InventoryController {

@Autowired

private StringRedisTemplate redisTemplate;

@Autowired

private Redisson redisson;

@GetMapping("/minus")

public ResponseEntity minusInventory(){

// 分布式高并发场景下,这样肯定不行

synchronized (this) {

int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));

if (stock > 0) {

int currentStock = stock - 1;

redisTemplate.opsForValue().set("stock", currentStock + "");

System.out.println("扣减成功,当前库存为" + currentStock);

} else {

System.out.println("库存不足,扣减失败!");

}

}

return ResponseEntity.ok("success");

}

@GetMapping("/minus1")

public ResponseEntity minusInventory1(){

// 相当于setnx命令

String lockKey = "lockKey";

// 务必加try-finally,因为如果服务挂了,锁还得释放

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

try {

// 相当于加锁

// Boolean absent = redisTemplate.ZNaHvrquumopsForValue().setIfAbsent(lockKey, "zxs");

// 上下两行不能分开写,如果这中间报异常了,依然出现死锁

// redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId,30,TimeUnit.SECONDS);

if (!absent) {

return ResponseEntity.notFound().build();

}

int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));

if (stock > 0) {

int currentStock = stock - 1;

redisTemplate.opsForValue().set("stock", currentStock + "");

System.out.println("扣减成功,当前库存为" + currentStock);

} else {

System.out.println("库存不足,扣减失败!");

}

} finally {

// 如果系统挂了呢,finally也不起作用了,因此还需要设置超时时间

// 释放锁之前,判断一下,务必释放的锁是自己持有的锁

if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {

redisTemplate.delete(lockKey);

}

}

return ResponseEntity.ok("success");

}

/**

* 终极方案

*/

@GetMapping("/minus2")

public ResponseEntity minusInventory2(){

// redisson解决方案

String lockKey = "lockKey";

RLock lock = redisson.getLock(lockKey);

try {

// 加锁

lock.lock();

int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));

if (stock > 0) {

int currentStock = stock - 1;

redisTemplate.opsForValue().set("stock", currentStock + "");

System.out.println("扣减成功,当前库存为" + currentStock);

} else {

System.out.println("库存不足,扣减失败!");

}

} finally {

// 释放锁

lock.unlock();

}

return ResponseEntiZNaHvrquumty.ok("success");

}

}

四、总结

@GetMapping("/minus") public ResponseEntity minusInventory():初步实现方式,在单线程环境下可以使用,但是在分布式高并发场景下,毫无疑问是肯定不行的@GetMapping("/minus1") public ResponseEntity minusInventory1():进阶实现方式,在一般的不拘小节的公司,勉强够用,但是你需要考虑一下,锁过期时间,到底设置多少才能完美呢?@GetMapping("/minus2") public ResponseEntity minusInventory2():终极实现方式,redisson帮我们解决了上面的实现方式出现的尴尬情况


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python:Resquest/Response
下一篇:Python:urllib2模块的基本使用(python urllib模块)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~