概念
CAP定理
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
图解
单机锁分布式架构下只能锁住当前机器,而不能实现个节点使用同一把锁
Redis:基于 redis 的 setnx()、expire() 方法
基于ZooKeeper
基于redisson
基于数据库排他锁
mysql:基于乐观锁或者mvcc机制
基于 Redlock 做分布式锁
docker run -di --name=redis -p 6379:6379 redis
*(若是没有镜像会先自动拉取)
<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.0modelVersion>
<groupId>com.example.demogroupId>
<artifactId>springboot-redisartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>springboot-redisname>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>com.examplegroupId>
<artifactId>springboot-masterartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.12.0version>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.5.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-module-junit4artifactId>
<version>2.0.0version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-api-mockito2artifactId>
<version>2.0.0version>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-extensionartifactId>
<version>3.3.1version>
<scope>compilescope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
plugins>
build>
project>
spring:
redis:
database: 0
host: 192.168.31.112
port: 6379
timeout: 5000
commandTimeout: 5000
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();
}
}
package com.example.demo.service;
/**
* @author Adam
* @version 1.0
* @description
* @date 2022/5/15
*/
public interface DistributedLockService{
/**
* Service
*/
void getScheduleResultRedisLock();
}
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 | 设计性开放 | 繁琐,删除锁设计时要考虑原子性 |