SpringBoot 使用 Zookeeper 实现分布式锁

news/2024/10/6 22:31:41

之前的博客介绍过 zookeeper 的分布式锁,只不过是基于 Spring 的实现(技术太老了),现在肯定使用 SpringBoot 进行实现,因此有必要再写一篇博客。

有关 zookeeper 的部署,以及分布式锁细节,这里不再赘述,可以访问我之前编写的博客。

zookeeper 的单机和集群部署:https://www.cnblogs.com/studyjobs/p/18227639.html

使用 zookeeper 实现分布式锁:https://www.cnblogs.com/studyjobs/p/16488794.html


一、搭建工程

新建一个名称为 springboot_zk_lock 的 springboot 工程,结构如下图所示:

image

为了简单,本 demo 操作数据库就不写 service 了,直接使用 mapper 进行操作,首先看一下 pom 文件引用的依赖包:

<?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.jobs</groupId><artifactId>springboot_zk_lock</artifactId><version>1.0</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--导入 curator 的相关依赖包--><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.1.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.1.0</version></dependency><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.0</version></dependency><!--导入 druid 连接池依赖--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.26</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version></plugin></plugins></build>
</project>

本 demo 实现的简单案例就是:提供一个 http 接口,每次调用就相当于购买了 1 件商品,库存数量减一,直到数量为零为止。

这里简单的创建了一个 test 数据库,里面只有一个结构非常简单的表 stock,具体建表的 sql 语句如下:

DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock`  (`id` int(11) NOT NULL AUTO_INCREMENT,`num` int(11) NULL DEFAULT 0,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;INSERT INTO `stock` VALUES (1, 10);

将该工程部署多个,那么互相都是独立的,此时就无法使用 synchronized 来控制线程的并发访问了,只能使用分布式锁。

本 demo 实现使用 zookeeper 实现分布式锁,连接操作 zookeeper 使用的是第三方提供的 curator 相关的依赖包。

我们看一下 application.yml 配置文件,有关 zookeeper 的连接信息,需要我们自己定义配置内容:

server:port: 8080spring:datasource:# 使用 druid 连接池type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.136.128:3306/test?serverTimeZone=Asia/Shanghai&useSSL=falseusername: rootpassword: root# 自定义编写的连接 zookeeper 的配置信息
zk:# 如果是操作 zookeeper 集群,可以配置多个 zookeeper 地址# 多个地址之间用英文逗号分隔,如 ip1:port1,ip2:port2,ip3:port3connectString: 192.168.136.128:2181# zookeeper的会话超时时间(单位:毫秒,默认是 60 秒)sessionTimeoutMs: 60000# zookeeper的连接超时时间(单位:毫秒,默认是 15 秒)connectionTimeoutMs: 15000# zookeeper默认操作的根节点,所有的增删改查操作,默认在该节点下进行namespace: jobs

二、代码细节

数据库表 stock 只有 2 个字段,建立一个实体类:

package com.jobs.entity;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@TableName("stock")
@Data
public class Stock {@TableIdprivate Integer id;/*** 库存量*/private Integer num;
}

由于采用了 mybatis plus 技术,因此 mapper 的编写非常简单:

package com.jobs.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jobs.entity.Stock;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface StockMapper extends BaseMapper<Stock> {
}

接下来就是编写 zookeeper 的配置类,从 application.yml 中读取配置信息,创建 CuratorFramework 实例,加载都 Spring 容器中

package com.jobs.config;import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class zkconfig {@Value("${zk.connectString}")private String connectString;@Value("${zk.sessionTimeoutMs}")private Integer sessionTimeoutMs;@Value("${zk.connectionTimeoutMs}")private Integer connectionTimeoutMs;@Value("${zk.namespace}")private String namespace;//获取 Curator 的客户端连接@Beanpublic CuratorFramework getCuratorFramework(){//重试策略,如果连接失败,最多重试 3 次RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);CuratorFramework client =CuratorFrameworkFactory.builder().connectString(connectString).sessionTimeoutMs(sessionTimeoutMs).connectionTimeoutMs(connectionTimeoutMs).namespace(namespace).retryPolicy(retryPolicy).build();client.start();return client;}
}

最后就是提供一个 http 接口,可以通过浏览器进行访问

package com.jobs.controller;import com.jobs.entity.Stock;
import com.jobs.mapper.StockMapper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RequestMapping("/stock")
@RestController
public class StockController {@Autowiredprivate CuratorFramework curatorFramework;@Autowiredprivate StockMapper stockMapper;//为了简化逻辑,每次购买 1 件商品@GetMapping("/buy")public String stock() {String result;InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/mylock");try {//在 2 秒钟内,不断尝试获取锁,如果获得则继续执行,否则直接结束boolean locked = mutex.acquire(2000, TimeUnit.MILLISECONDS);if (locked) {Stock stock = stockMapper.selectById(1);if (stock.getNum() > 0) {stock.setNum(stock.getNum() - 1);stockMapper.updateById(stock);result = "商品库存扣减成功,剩余库存:" + stock.getNum();} else {result = "商品库存不足!";}//释放锁mutex.release();} else {result = "没有获取到锁,不能执行减库存操作!";}} catch (Exception ex) {result = "出现异常:" + ex.getMessage();}return result;}
}

有关 zookeeper 的分布式锁的测试效果,这里就介绍了。测试方案就是把本 demo 工程至少部署 2 个节点,然后使用浏览器频繁访问每个节点。

当然你也可以编写程序去频繁调用每个节点的接口,或者使用 nginx 对每个节点进行转发,然后使用 jemeter 压力测试工具去调用 nginx 接口。


本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_zk_lock.zip

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/42924.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

openwrt扩容相关

硬件:R2S 固件:openwrt(QiuSimons/YAOF) A:最近给R2S换了个带Docker的固件,目前大多数固件刷完之后都会多出一堆空闲空间。如何把这些空间给overlay和/opt/docker扩容? Q:系统->启动项->本地启动脚本 在 exit 0 前面加入脚本强行挂载mount /dev/mmcblk0p3 /opt/d…

LeetCode 974 Subarray Sums Divisible by K All In One

LeetCode 974 Subarray Sums Divisible by K All In One LeetCode 974 能被 K 整除的子数组之和LeetCode 974 Subarray Sums Divisible by K All In OneLeetCode 974 能被 K 整除的子数组之和errosfunction subarraysDivByK(nums: number[], k: number): number {// -5 / 0 / 5…

ansible高级操作 serial滚动更新

1.异步操作和轮询 默认情况下,剧本中的任务会一直处于打开状态,直到任务在每个节点上完成。这样可以会造成阻塞和超时,因此我们可以使用异步模式一次运行所有任务,然后轮询直到它们完成为止。Ansible本身就是采用的多线程来操作多个主机节点,可以使用-P来异步操作。现在所…

Scaling Memcache at Facebook

Memcached 是一种众所周知的、简单的内存缓存解决方案。本文描述了 Facebook 如何利用 memcached 作为构建块来构造和扩展一个分布式键值存储支持世界上最大的社交网络。 1.Introduction一个社交网络(FB)的基础架构通常需要以下允许实时通信(近似,允许一定的延迟), 动态地…

Body SweptSolid Composite Geometry

Body SweptSolid Composite Geometry 下图显示了应用此概念时使用的泛型类和关系。此外,概念可能对通用或标准化的行业实践和场景具有特别重要的意义。对于这些特定的使用场景,下表显示了用户可能采用的一般使用模式的推荐列表。 ##########################################…

Python 潮流周刊#55:分享 9 个高质量的技术类信息源!

大家好,我是猫哥,今天给大家分享几个高质量的技术类信息源。 本文分享的信息源都是周刊类型的,所谓周刊类,就是以固定每周的频率更新,每期分享很多精华内容的链接。它的特点是信息密度极高,可以节省你去查找信息的时间,高效的学习者都会喜欢这类内容。 如果不是看了这篇…

QT工具uic、moc、rcc

QT中的moc、uic、rcc 在学习QT的过程中接触到了moc、uic、rcc这几个名词moc(Meta-Object Compiler),元对象编译器,用于处理QT拓展的C++语法 uic(User Interface Compiler),用户界面编译器,将根据.ui文件生成相应的.h文件,例如根据mainwindow.ui生成ui_mainwindow.h rcc(Re…

TIA Portal软件学习(1)

1.TIA Portal中关于变量与数据类型:(常用地址包括I\Q\M\L\DB) 输入映像寄存器I0.0-10.7(一共可以到32767);输出映像寄存器Q0.0-Q0.7(同上)----由于输入输出是存在一定范围的所以引出了M\L\DB——中间寄存器M(全局):可以在变量表里进行选择格式; 临时变量寄存器L(局部)…