分布式锁的应用场景与分布式锁实现(四):基于MySQL实现分布式锁与分布式锁总结

news/2024/10/28 18:23:35/

分布式锁的应用场景与分布式锁实现(三):基于Zookeeper实现分布式锁

基于MySQL实现分布式锁

​ 不管是JVM锁还是MySQL锁,为了保证线程的并发安全,都提供了悲观独占排他锁。所以独占排他也是分布式锁的基本要求。

​ 可以利用唯一键索引不能重复插入的特点表现,设计表如下:

CREATE TABLE `db_lock` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`lock_name` varchar(50) NOT NULL COMMENT '锁名',`class_name` varchar(100) DEFAULT NULL COMMENT '类名',`method_name` varchar(50) DEFAULT NULL COMMENT '方法名',`server_name` varchar(50) DEFAULT NULL COMMENT '服务器ip',`thread_name` varchar(50) DEFAULT NULL COMMENT '线程名',`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '获取锁时间',`desc` varchar(100) DEFAULT NULL COMMENT '描述',PRIMARY KEY (`id`),UNIQUE KEY `idx_unique` (`lock_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1332899824461455363 DEFAULT CHARSET=utf8;

基本思路

​ synchronized关键字和ReentrantLock锁都是独占排他锁,即多个线程争抢一个资源时,同一时刻只有一个线程可以抢占该资源,其他线程只能阻塞等待,知道占有资源的线程释放该资源。

1606620944823

  • 1、线程同时获取锁(insert)
  • 2、获取成功,执行业务逻辑,执行完成释放锁(delete)
  • 3、其他线程等待重试

代码实现

​ 新建数据库实体类:

package tech.msop.distributed.lock.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("db_lock")
public class LockEntity {@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 锁名*/private String lockName;/*** 类名*/private String className;/*** 方法名*/private String methodName;/*** 服务器IP*/private String serverName;/*** 线程名*/private String threadName;/*** 获得锁时间*/private Date createTime;/*** 描述*/private String desc;
}

​ 新增Mapper文件

package tech.msop.distributed.lock.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import tech.msop.distributed.lock.entity.LockEntity;public interface LockMapper extends BaseMapper<LockEntity> {
}

​ 改造服务方法,支持MySQL分布式锁

package tech.msop.distributed.lock.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.*;
import org.apache.curator.framework.recipes.shared.SharedCount;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import tech.msop.distributed.lock.constants.StockConstant;
import tech.msop.distributed.lock.entity.LockEntity;
import tech.msop.distributed.lock.entity.StockEntity;
import tech.msop.distributed.lock.lock.DistributedLockClient;
import tech.msop.distributed.lock.lock.DistributedRedisLock;
import tech.msop.distributed.lock.mapper.LockMapper;
import tech.msop.distributed.lock.mapper.StockMapper;
import tech.msop.distributed.lock.service.IStockService;
import tech.msop.distributed.lock.zk.ZkClient;import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** 库存服务实现类 <br/>*/
@Service
@Slf4j
public class StockServiceImpl extends ServiceImpl<StockMapper, StockEntity>implements IStockService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate DistributedLockClient distributedLockClient;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate ZkClient zkClient;@Autowiredprivate CuratorFramework curatorFramework;@Autowiredprivate LockMapper lockMapper;/*** 减库存*/@Overridepublic void checkAndLock() {// 加锁LockEntity lock = new LockEntity();lock.setLockName("lock");lock.setClassName(this.getClass().getName());lock.setCreateTime(new Date());try {this.lockMapper.insert(lock);// 1. 查询库存信息String stock = redisTemplate.opsForValue().get("stock").toString();// 2. 判断库存是否充足if (stock != null && stock.length() != 0) {Integer st = Integer.valueOf(stock);if (st > 0) {// 3.扣减库存redisTemplate.opsForValue().set("stock", String.valueOf(--st));}}// 释放锁lockMapper.deleteById(lock.getId());} catch (Exception ex) {// 获取锁失败,重试try {Thread.sleep(50);this.checkAndLock();} catch (InterruptedException e) {e.printStackTrace();}}}
}

缺陷及解决方案

缺点:

  • 1、这把锁依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
    • 解决方案:给 锁 数据库搭建主备
  • 2、这把锁没有失效世家你,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再或得到锁
    • 解决方案:只要做一个定时任务,每隔一定时间就把数据库中的超时数据清理一遍
  • 3、这把锁是非重入的,同一个线程在释放锁之前无法再次获取该锁。因为数据库中数据已经存在了
    • 解决方案:记录获取锁的主机信息和线程信息,如果相同线程要获取锁,直接重入。
  • 4、受限于数据库性能,并发能力有限
    • 解决方案:无法解决

MySQL分布式锁总结

  • 独占排他互斥使用:借助唯一键索引
  • 防死锁:
    • 客户端程序获取到锁之后,客户端程序的服务器宕机。给锁记录添加一个获取锁时间列,额外的定时器检查获取锁的系统时间和当前时间的差值是否超过了阈值
    • 不可重入:通过记录服务器信息、线程信息与重入次数实现可重入性
  • 防误删:借助于ID的唯一性防止误删除
  • 原子性:一个写操作,还可以借助于MySQL的悲观锁实现
  • 可重入:通过记录服务器信息、线程信息与重入次数实现可重入性
  • 自动续期:服务器内的定时器重置获取锁的系统时间
  • 单机故障:搭建MySQL主备
  • 集群情况下锁机制失效问题。

分布式锁总结

三种方式实现分布式锁的依据:

  • Redis:基于Key的唯一性
  • Zookeeper:基于znode节点的唯一性
  • MySQL:基于唯一键索引

​ 实现复杂性或者难度角度:Zookeeper > Redis > 数据库

​ 实际性能角度:Redis > Zookeeper > 数据库

​ 可靠性角度:Zookeeper > Redis = 数据库

​ 这三种方式都不是尽善尽美,我们可以根据实际业务情况选择最适合的方案:

​ 如果追求极致性能可以选择:Redis

​ 如果追求可靠性可以选择:Zookeeper

​ 实现独占排他,对性能 对可靠性要求都不高的情况下,只是简单了解,可以选择:MySQL

常见锁分类:

  • 悲观锁: 具有强烈的独占和排他特性,在整个数据处理过程中,将数据处于锁定状态。适合于写比较多,会阻塞读操作。
  • 乐观锁: 采取了更加宽松的加锁机制,大多是基于数据版本(Version)及时间戳来实现。适合于读比较多,不会阻塞读。
  • 独占锁、互斥锁、排他锁: 保证在任一时刻,只能被一个线程独占排他持有。如Java中的synchronized、ReentrantLock
  • 共享锁: 可同时被多个线程共享持有。如CountDownLatch倒计数器、Semaphore信号量。
  • 可重入锁: 又名递归锁。同一个线程在外层方法获取锁的时候,在进入内层方法时会自动获取锁。
  • 不可重入锁: 例如早期的synchronized
  • 公平锁: 有优先级的锁,先来先得,谁先申请锁就先获取到锁。
  • 非公平锁: 无优先级的锁,后来者也机会先获取到锁。
  • 自旋锁: 当线程尝试获取锁失败时(锁已经被其他线程占用了),无限循环重试获取到锁。
  • 阻塞锁: 当线程尝试获取锁失败时,线程进入阻塞状态,直到接受信号被唤醒。在竞争激烈情况下,性能较高。
  • 读锁: 共享锁。
  • 写锁: 独占排他锁。
  • 偏向锁: 一直被一个线程所访问,那么该线程会自动获取锁。
  • 轻量级锁(CAS): 当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
  • 重量级锁: 当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋到一定次数的时候(10次),还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。

以上其实就是synchronized的锁升级过程

  • 表级锁: 对整张表加锁,加锁快开销小,不会出现死锁,但并发度低,会增加锁冲突的概率。
  • 行级锁: 是MySQL粒度最小的锁,只针对操作行,可大大减少锁冲突概率,并发度高,但加锁慢,开销大,会出现死锁。

http://www.ppmy.cn/news/206824.html

相关文章

内网穿透常见方式推荐

文章目录 1.可以使用nginx1.1首先用ssh打开公网服务器和内网服务器的连接通道1.2 nginx转发服务 2.中微子内网穿透2.1 服务端docker部署指定自己的mysql数据库 2.2服务端使用jar包自行部署2.3 客户端jar部署2.4 管理后台配置 1.可以使用nginx 1.1首先用ssh打开公网服务器和内网…

【全面突击数据结构与算法001】绪论篇,数据结构的基本概念

&#x1f341;前言 &#x1f451;作者主页&#xff1a;&#x1f449;CSDN丨博客园 &#x1f3c6;学习交流&#xff1a;&#x1f449;在下周周ovoの社区 &#x1f48e;全面突击数据结构与算法系列专栏&#xff1a;&#x1f449;数据结构与算法专栏 PS&#xff1a;本篇文章主要综…

MySQL学习-数据库创建-数据库增删改查语句-事务-索引

MySQL学习 前言 SQL是结构化查询语言的缩写&#xff0c;用于管理关系数据库(RDBMS)中的数据。SQL语言由IBM公司的Donald Chamberlin和Raymond Boyce于20世纪70年代开发而来&#xff0c;是关系型数据库最常用的管理语言。 使用SQL语言可以实现关系型数据库中的数据处理、数据…

投影仪显示无法连接服务器失败怎么办,电脑和投影仪连不上怎么办

电脑和投影仪连不上怎么办 如今使用投影仪的朋友越来越多了,可是有时候看着别人能连上投影仪可是自己的电脑拿去插上后却连不上,这是怎么回事呢?这里简单分析一下吧! 首先我们在插上相关的数据线之后可以按快捷键FnF4进行连接,一般电脑的F4键是两个屏幕的形式,当然有的则是F3或…

买投影仪选当贝还是极米,哪个投影仪最好用

投影仪相比电视可以表现出来更大的画面&#xff0c;所以现在很多人都会考虑给家里买一个投影仪&#xff0c;日常看电视电影会有更好的体验。目前选择投影仪有很多品牌&#xff0c;实力强劲的有两个品牌&#xff0c;一个是当贝&#xff0c;另外一个是极米&#xff0c;很多人也都…

高流明投影仪品牌,这份投影仪行业数据告诉你答案

近期&#xff0c;据专业数据分析洛图科技6月《中国智能投影仪零售市场月度追踪》报告显示&#xff0c; 在618整体大促情况下&#xff0c;6月智能投影仪市场销量为38.4万台&#xff0c;同比增长36.4%&#xff0c;环比增长68.4%&#xff1b;上半年 智能投影仪累计销量达到172.3万…

使用record_msg保存binary点云出错解决办法

1. 问题现象 使用python的record_msg包解析record包时&#xff0c;会出现无法保存的报错。 Traceback (most recent call last): File “scripts/parse_record.py”, line 35, in main(sys.argv) File “scripts/parse_record.py”, line 32, in main cloud_parser.parse(mes…

家用投影仪品牌推荐,如何选择家用投影仪?

挑选高性价比投影仪&#xff0c;需要对比投影仪的硬件参数&#xff0c;再从硬件参数、价格这两个指标中挑出其中最高性价比的产品。目前市场上投影仪的需求很高&#xff0c;导致现在的投影仪商家品牌众多&#xff0c;各品牌又有各种机型的投影仪。 在这里我就先挑出三款&#x…