前言
其实针对分布式锁这个问题,我们并不陌生,这里简单说明一下什么是分布式锁。
如果问一个问题:多线程环境下如何保证线程同步,其实我们可以有很多可说的——synchronized,cas锁,可重入锁等。但是这个只是针对单点系统下能正常保证数据和操作同步,但是现在更多的应用都是微服务的架构方式,单点应用在业务较为复杂的场景下其实有着很高的成本,为了降低成本,许多公司开始采用分布式应用,这就带来许多问题,分布式环境下,不同JVM进程中如何保证数据和代码的同步?这个问题也有很多人进行了解答,而且都解答的非常形象——什么是分布式锁——漫画。这里就不再介绍。只是分布式锁的实现有三种常见的方式:1、数据库分布式锁,2、zookeeper实现分布式锁,3、Redis实现分布式锁。
这一系列博客主要通过实例实现这三种方式,这一篇博客主要介绍基础环境的搭建,后续依次介绍三种实现方式。
构建多模块应用
1、创建一个Maven的普通工程
这里只是创建一个Maven的普通工程,往往有时候,我们构建springboot应用的时候,我们会通过spring.io这个组件去构建,但是会在pom文件中出现parent这个元素,如果通过spring.io这个组件构建的springboot应用,需要删除pom文件中parent这个元素。构建完成之后删除src文件。
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learn</groupId>
<artifactId>springboot_distributelock</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>distribute_lock</description>
<modules>
<module>lock-model</module>
<module>lock-servce</module>
</modules>
<properties>
<java.version>1.8</java.version>
</properties>
<!-- 阿里云maven仓库 -->
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
主要是元素,告知该父应用的几个模块
2、建立两个子模块
lock-model
所有数据库访问的内容都在这个模块中,所有对数据库的操作均放在这个模块。
pom文件中指定parent为第一步中建立的应用,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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>springboot_distributelock</artifactId>
<groupId>com.learn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lock-model</artifactId>
<properties>
<java.version>1.8</java.version>
<mybatis-spring-boot.version>1.1.1</mybatis-spring-boot.version>
<mybatis-pagehelper.version>4.1.2</mybatis-pagehelper.version>
<lombok.version>1.16.10</lombok.version>
</properties>
<dependencies>
<!--spring-mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<!--for page-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${mybatis-pagehelper.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>
整体结构如下:
主要是一些实体类和数据访问的Mapper。
lock-service
主要的核心模块,之后我们所有分布式锁的实例都会在这个模块中完成。pom文件中的parent属性需要指定为第一步中建立的应用,整个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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>springboot_distributelock</artifactId>
<groupId>com.learn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lock-service</artifactId>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>1.3.3.RELEASE</spring-boot.version>
<slf4j.version>1.7.13</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<mysql.version>5.1.37</mysql.version>
<druid.version>1.0.16</druid.version>
<guava.version>19.0</guava.version>
<joda-time.version>2.9.2</joda-time.version>
<poi.version>3.15</poi.version>
<weixin-java-cp.version>2.5.1</weixin-java-cp.version>
<elastic-job.version>2.1.4</elastic-job.version>
<retrofit.version>2.3.0</retrofit.version>
<cglib.version>3.1</cglib.version>
<dubbo.version>2.8.4</dubbo.version>
<resteasy.version>3.0.14.Final</resteasy.version>
<disconf.version>2.6.36</disconf.version>
<commons-fileupload.version>1.3.1</commons-fileupload.version>
<curator.version>2.10.0</curator.version>
<zookeeper.version>3.4.6</zookeeper.version>
<redisson.version>3.8.2</redisson.version>
</properties>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<!--依赖平级模块-->
<dependency>
<groupId>com.learn</groupId>
<artifactId>lock-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--log-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- zookeeper start -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<!-- zookeeper end -->
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
基本上常用的组件都在pom文件中有所体现。
配置文件中配置相关数据库访问,日志路径等基本信息,暂时没有用上redis和zookeeper,后续配置加上的时候会进一步补充。
server.port=8999
logging.level.com.learn.lockservce=debug
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/distribute_lock?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.initial-size=5
spring.datasource.min-idle=5
spring.datasource.max-active=20
spring.datasource.max-wait=6000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
spring.datasource.logSlowSql=true
#Mapper.xml文件包
mybatis.mapper-locations=classpath:mappers/*.xml
#实体文件包
mybatis.type-aliases-package=com.learn.lockmodel.entity
之后在启动类中完成MyBatis,Mapper接口配置的指定
package com.learn.lockservce;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@MapperScan(basePackages = "com.learn.lockmodel.mapper")
public class LockServceApplication {
public static void main(String[] args) {
SpringApplication.run(LockServceApplication.class, args);
}
}
之后在MySQL客户端中执行以下SQL
USE distribute_lock;
DROP TABLE IF EXISTS crm_order;
CREATE TABLE crm_order (
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
user_id INT(11) DEFAULT NULL COMMENT '用户id',
mobile VARCHAR(255) DEFAULT NULL COMMENT '手机号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_active INT(255) DEFAULT '1' COMMENT '是否有效(1=有效;0=无效)',
PRIMARY KEY (id),
UNIQUE KEY idx_mobile (mobile,is_active) USING BTREE
) ENGINE=INNODB AUTO_INCREMENT=139 DEFAULT CHARSET=utf8 COMMENT='crm抢单记录表';
INSERT INTO crm_order(id,user_id,mobile,create_time,update_time,is_active) VALUES (60,233,'15627284603','2018-10-31 22:05:17',NULL,1),(61,233,'15627284604','2018-10-31 22:11:23',NULL,1),(72,234,'15627284605','2018-10-31 22:15:07',NULL,1),(73,236,'15627284606','2018-10-31 22:22:28',NULL,1),(138,235,'15627284607','2018-10-31 22:25:30',NULL,1);
DROP TABLE IF EXISTS product_lock;
CREATE TABLE product_lock (
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
product_no VARCHAR(255) DEFAULT NULL COMMENT '产品编号',
stock INT(11) DEFAULT NULL COMMENT '库存量',
version INT(11) DEFAULT NULL COMMENT '版本号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY idx_unique (id) USING BTREE
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='产品信息-数据库级别lock测试';
INSERT INTO product_lock(id,product_no,stock,version,create_time,update_time) VALUES (1,'10010',265,735,'2018-10-17 21:16:33','2020-01-19 21:24:37');
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
user_name VARCHAR(255) DEFAULT NULL COMMENT '用户名',
email VARCHAR(255) DEFAULT NULL COMMENT '邮箱',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_time TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id)
) ENGINE=INNODB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
INSERT INTO user(id,user_name,email,create_time,update_time) VALUES (233,'learn','[email protected]','2018-10-27 11:23:24',NULL),(234,'SteadyJack','[email protected]','2018-10-27 11:23:24',NULL),(235,'linsen','[email protected]','2018-10-27 11:23:24',NULL),(236,'jack','[email protected]','2018-10-27 11:23:24',NULL);
所有工作完成之后,启动程序,如果正常启动说明基本环境搭建完成,后续我们会在这个基础上完成分布式锁的三种具体实现。这里也写个简单的controller自己测试一下。
总结
搭建基本环境,没啥可总结的。