1,课程回顾
2,本章重点
3,具体内容
3.1 前言

微服务的流行,使得现在基本都是分布式开发,也就是同一份代码会在多台机器上部署运行,此时若多台机器需要同步访问同一个资源(同一时间只能有一个节点机器在运行同一段代码),就需要使用到分布式锁。

分布式事务的锁_jvm

3.2 什么是分布式锁
在非分布式系统中(单机应用)一个共享的变量或者一个方法进行多线程同步访问,可以使用简单加锁(synchronized)方式实现,让同一时刻,只有一个线程执行。随着互联网发展,单机应用已经满足不了需求,分布式系统就出现了,假如不同系统或者是同一系统的不同节点(满足高并发实现集群),需要有共享资源(共享变量或者业务方法),控制他们同步访问的方式就叫分布式锁。通俗的说,就是在同一时刻,大量先线程访问分布式中的共享资源时,为了防止相互干扰,排斥其他线程,只让一个线程对共享变量进行更改访问,或者进行业务方法操作。
3.3 为什么要使用分布式锁
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
3.4 分布式锁应具备哪些条件
高可用 ,高性能获取和释放锁
具备锁的失效机制,防止死锁
具备非阻塞锁的特性,就是没有获取到锁的时候直接返回获取锁失败。
3.5 分布式锁常见实现方式
借助于数据库实现
要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。 还可以通过数据库的排他锁来实现分布式锁。 基于MySql的InnoDB引擎,可以使用查询语句加上for update实现加锁操作。
这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。
基于数据库的锁设计存在一下问题:
l 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
l 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
l 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
l 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
使用redis实现
相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。而且很多缓存是可以集群部署的,可以解决单点问题。
本章下面内容就是基于redis实现,在此不再做单独讲解。
使用zookeeper实现
基于zookeeper临时有序节点可以实现的分布式锁。
实现思路:
首先zookeeper中我们可以创建一个/distributed_lock持久化节点
然后再在/distributed_lock节点下创建自己的临时顺序节点,比如:/distributed_lock/task_00000000000, task_00000000001…
获取所有的/distributed_lock下的所有子节点,并排序
判读自己创建的节点是否最小值(第一位)
如果是,则获取得到锁,执行自己的业务逻辑,最后删除这个临时节点。(如果出错可以让会话断开,创建临时节点消失,有效防止锁定或者死锁)
如果不是最小值,则需要监听自己创建节点前一位节点的数据变化,并阻塞。
当前一位节点被删除时,我们需要通过递归来判断自己创建的节点是否在是最小的,如果是则执行5);如果不是则执行6)(就是递归循环的判断)
3.6 Redission简介
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
3.7 Springcloud+Redisson+redis集群实现分布式锁
1.1.1 引入JAR包
父项目中:

<!--分布式锁需要jar-->
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>

common项目引入:

    <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>

micro_servieces中引入jar:

   <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>

在common项目中编写相关工具类

package com.aaa.common.util;

import java.util.concurrent.TimeUnit;

/**
* fileName:DistributedLocker
* description:
* author:zz
* createTime:2020/1/29 10:48
* version:1.0.0
*/
public interface DistributedLocker {

/**
* 不带时间的锁定方法
* @param lockKey
*/
void lock(String lockKey);

/**
* 带时间的锁定方法,默认为秒(实现类中自己定义)
* @param lockKey
* @param timeout
*/
void lock(String lockKey, int timeout);

/**
* 带时间和单位的锁定方法
* @param lockKey
* @param unit
* @param timeout
*/
void lock(String lockKey, TimeUnit unit , int timeout);
/**
* 解锁方法
* @param lockKey
*/
void unlock(String lockKey);
}

接口实现类:RedissonDistributedLocker

package com.aaa.common.util;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

/**
* fileName:RedissonDistributedLocker
* description:
* author:zz
* createTime:2020/1/29 10:49
* version:1.0.0
*/
public class RedissonDistributedLocker implements DistributedLocker {

private RedissonClient redissonClient;

@Override
public void lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
}

@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}

@Override
public void lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
}

@Override
public void lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
}

/**
* 手动注入redissonClient方法
* @param redissonClient
*/
public void setRedissonClient(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
}

工具类调用上面接口的进行锁定或者解锁操作RedissLockUtil:
package com.aaa.common.util;

import java.util.concurrent.TimeUnit;

/**
* fileName:RedissLockUtil
* description:
* author:zz
* createTime:2020/1/29 10:56
* version:1.0.0
*/
public class RedissLockUtil {

private static DistributedLocker redissLock;

public static void setLocker(DistributedLocker locker) {
redissLock = locker;
}

public static void lock(String lockKey) {
redissLock.lock(lockKey);
}

public static void unlock(String lockKey) {
redissLock.unlock(lockKey);
}

/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public static void lock(String lockKey, int timeout) {
redissLock.lock(lockKey, timeout);
}

/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public static void lock(String lockKey, TimeUnit unit , int timeout) {
redissLock.lock(lockKey, unit, timeout);
}
}

加载配置文件的配置类 RedissonProperties:

package com.aaa.common.util;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
* fileName:RedissonProperties
* description:
* author:zz
* createTime:2020/1/29 10:54
* version:1.0.0
*/
@Configuration
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {

private int timeout;

private String address;

private String password;

private int connectionPoolSize;

private int connectionMinimumIdleSize;

private int slaveConnectionPoolSize;

private int masterConnectionPoolSize;

private String[] sentinelAddresses;

private String masterName;

public int getTimeout() {
return timeout;
}

public void setTimeout(int timeout) {
this.timeout = timeout;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getConnectionPoolSize() {
return connectionPoolSize;
}

public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}

public int getConnectionMinimumIdleSize() {
return connectionMinimumIdleSize;
}

public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
this.connectionMinimumIdleSize = connectionMinimumIdleSize;
}

public int getSlaveConnectionPoolSize() {
return slaveConnectionPoolSize;
}

public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) {
this.slaveConnectionPoolSize = slaveConnectionPoolSize;
}

public int getMasterConnectionPoolSize() {
return masterConnectionPoolSize;
}

public void setMasterConnectionPoolSize(int masterConnectionPoolSize) {
this.masterConnectionPoolSize = masterConnectionPoolSize;
}

public String[] getSentinelAddresses() {
return sentinelAddresses;
}

public void setSentinelAddresses(String[] sentinelAddresses) {
this.sentinelAddresses = sentinelAddresses;
}

public String getMasterName() {
return masterName;
}

public void setMasterName(String masterName) {
this.masterName = masterName;
}
}

在微服务项目中真正使用redisson做分布式锁
application.properties中添加下面配置:

#redisson配置   官网地址:https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95
#配置redis链接地址
redisson.address=redis://127.0.0.1:6379
#密码
redisson.password=
#命令等待超时
redisson.timeout = 3000
#连接池大小
redisson.connectionPoolSize=64
#最小空闲连接数
redisson.connectionMinimumIdleSize=10
#从节点连接池大小
redisson.slaveConnectionPoolSize = 250
#主节点连接池大小
redisson.masterConnectionPoolSize = 250

在微服务中编写RedissonAutoConfiguration:

package com.aaa.pay.config;

import com.aaa.common.util.DistributedLocker;
import com.aaa.common.util.RedissLockUtil;
import com.aaa.common.util.RedissonDistributedLocker;
import com.aaa.common.util.RedissonProperties;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* fileName:RedissonAutoConfiguration
* description:
* author:zz
* createTime:2020/1/29 11:03
* version:1.0.0
*/
@Configuration
@ConditionalOnClass(Config.class)
/**
* @ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
*/
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redssionProperties;

/**
* 哨兵模式自动装配
* @return
*/
// @Bean
// @ConditionalOnProperty(name="redisson.master-name")
RedissonClient redissonSentinel() {
Config config = new Config();
SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
.setMasterName(redssionProperties.getMasterName())
.setTimeout(redssionProperties.getTimeout())
.setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());

if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) {
serverConfig.setPassword(redssionProperties.getPassword());
}
return Redisson.create(config);
}

/**
* 单机模式自动装配
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "redisson",name="address") //ConditionalOnProperty 可以控制类或者方法是否生效
RedissonClient redissonSingle() {
Config config = new Config();
// System.out.println(redssionProperties.getAddress()+","+redssionProperties.getTimeout()+"...");
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redssionProperties.getAddress())
.setTimeout(redssionProperties.getTimeout())
.setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());

if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) {
serverConfig.setPassword(redssionProperties.getPassword());
}

return Redisson.create(config);

}

/**
* 装配locker类,并将实例注入到RedissLockUtil中
* @return
*/
@Bean
DistributedLocker distributedLocker(RedissonClient redissonClient) {
RedissonDistributedLocker locker = new RedissonDistributedLocker();
locker.setRedissonClient(redissonClient);
RedissLockUtil.setLocker(locker);
return locker;
}
}

4,知识点总结
5,本章面试题