深入:分布式锁(底层原理+实现解析)

什么是分布式锁?

概念

CAP定理

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。

为什么要有分布式锁?单机锁不能完成么?

图解

image-20220516202003027

单机锁分布式架构下只能锁住当前机器,而不能实现个节点使用同一把锁

如何设计分布式锁

  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
  • 这把锁要是一把可重入锁(避免死锁)
  • 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
  • 这把锁最好是一把公平锁(根据业务需求考虑要不要这条)
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

底层实现原理

Redis:基于 redis 的 setnx()、expire() 方法

基于ZooKeeper

  • 原理:利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
  • 缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。

基于redisson

基于数据库排他锁

mysql:基于乐观锁或者mvcc机制

基于 Redlock 做分布式锁

  • Redlock 是 Redis 的作者 antirez 给出的集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)。

服务部署及代码实现

Linux容器化部署Redis

docker run -di --name=redis -p 6379:6379 redis 

*(若是没有镜像会先自动拉取)

依赖引入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example.demo</groupId>
	<artifactId>springboot-redis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot-redis</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>com.example</groupId>
		<artifactId>springboot-master</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!-- redisson 作为分布式锁等功能框架-->
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.12.0</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.5.0</version>
		</dependency>


		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			  <groupId>org.powermock</groupId>
			  <artifactId>powermock-module-junit4</artifactId>
			  <version>2.0.0</version>
			  <scope>test</scope>
		</dependency>
		<!-- PowerMock Mockito2 API -->
		<dependency>
			  <groupId>org.powermock</groupId>
			  <artifactId>powermock-api-mockito2</artifactId>
			  <version>2.0.0</version>
			  <scope>test</scope>
		</dependency>
		<dependency>
			  <groupId>junit</groupId>
			  <artifactId>junit</artifactId>
			  <version>4.12</version>
			  <scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-extension</artifactId>
			<version>3.3.1</version>
			<scope>compile</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
	</build>


</project>

配置文件编写

spring:
  redis:
    database: 0
    host: 192.168.31.112
    port: 6379
    timeout: 5000
    commandTimeout: 5000

controller编写

package com.example.demo.controller;

import com.example.demo.service.DistributedLockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;

/**
 * @author Adam
 * @version 1.0
 * @description
 * @date 2022/5/10
 */
@RestController
@RequestMapping("/string/redis")
public class TestStringRedisTemplateController {
    private static final Logger log = LoggerFactory.getLogger(TestController.class);
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DistributedLockService distributedLockService;

    @PostMapping("/setKey")
    public void setKey(@RequestParam String key,@RequestParam String value) {
        ValueOperations<String, String> s = stringRedisTemplate.opsForValue();
        s.set(key,value);
    }

    @GetMapping("/getKey/{key}")
    public Object getKey(@PathVariable("key") String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 使用Redis实现分布式锁
     */
    @GetMapping("/schedule")
    public void distributedLock (){
        distributedLockService.getScheduleResultRedisLock();
    }
}

Service编写

package com.example.demo.service;

/**
 * @author Adam
 * @version 1.0
 * @description
 * @date 2022/5/15
 */
public interface DistributedLockService{
    /**
     * Service
     */
    void getScheduleResultRedisLock();
}

ServiceImpl编写

package com.example.demo.service.impl;

import com.example.demo.service.DistributedLockService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Adam
 * @version 1.0
 * @description
 * @date 2022/5/15
 */
@Slf4j
@Service("DistributedLockService")
public class DistributedLockServiceImpl implements DistributedLockService {
    final static String lockName = "scheduleLock";
    final static String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
            "then\n" +
            "    return redis.call(\"del\",KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void getScheduleResultRedisLock() {
        //redis value 存储UUID防止删错锁
        String uuid = UUID.randomUUID().toString();
        int i = 0;
        //占锁(设置过期时间及单位)
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, uuid, 10, TimeUnit.SECONDS);
        if (isLock) {
            //true表示没有锁,此次创建锁并执行业务处理
            log.info("分布式锁创建成功,uuid:" + uuid);
            try {
                //业务逻辑处理
                System.out.println("业务处理");
            } finally {
                //直接删除锁
                /**
                 *  stringRedisTemplate.delete(lockName);
                 */

                //判断是否是自己加的锁(uuid)再删
                /**
                 *  if (uuid.equals(stringRedisTemplate.opsForValue().get(lockName))){
                 *      stringRedisTemplate.delete(lockName);
                 *  }
                 */

                //使用Redis支持的lua脚本方式删除锁,保证操作的原子性,预防死锁
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class),
                        Arrays.asList(lockName),
                        uuid);
                log.info("删除锁成功");
            }
        } else {
            //false 则加锁失败自旋重试
            i++;
            log.info("加锁失败,第{}次加锁", i);
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (Exception e) {
            }
            //重试
            if (i < 10) {
                getScheduleResultRedisLock();
            }
        }
    }

    /**
     * Redisson实现分布式锁
     */
    public void redissonLock(){
        //获取锁
        final RLock lock = redissonClient.getLock(lockName);
        //加锁
        lock.lock();
        log.info("加锁成功!");
        try {
            log.info("执行业务逻辑……");
        }finally {
            lock.unlock();
            log.info("解锁成功!");
        }
    }
}

各方法实现优缺点对比

实现方式优点缺点
数据库排他锁简单,易于理解操作数据库需要一定的开销,行级锁并不一定靠谱,性能不靠谱
RedLock性能高失效时间设置导致的并发问题
ZooKeeper解决了单点问题、不可重入问题、非阻塞问题以及锁无法释放的问题,实现简单。性能上可能并没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。ZK 中创建和删除节点只能通过 Leader 服务器来执行,然后将数据同步到所有的 Follower 机器上。还需要对 ZK的原理有所了解。
Redisson性能高,实现简单脏数据和数据一致性问题
Redis设计性开放繁琐,删除锁设计时要考虑原子性
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Adam`南帝·梁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值