curator(zookeeper)实现分布式锁简单代码

curator是对zookeeper进行了封装,可以实现分布式锁功能
1.基本原理,zookeeper是dataTree结构,每个节点是一个znode,znode有四种类型,
持久节点(客户端断开连接后znode不会删除),
临时节点(客户端断开连接后znode删除)
持久顺序节点(创建节点时,Zookeeper会根据创建的时间顺序给该节点名称进行编号,持久的)
临时顺序节点(创建节点时,Zookeeper会根据创建的时间顺序给该节点名称进行编号,临时的)

分布式锁就是利用了其中的临时顺序节点来实现的,原理如下:
先创建一个持久znode,连接进来的客户端如果想获得锁就要在这个持久znode下面创建子znode,创建之后客户端会把所有的znode都遍历一遍,看一下自己的znode编号是不是最小的,如果是最小的就获得锁,如果不是最小的,那么就watch他前面的那一个节点避免羊群效应(锁释放后,所有的client都去抢锁),当一个client执行完之后会把自己的znode删除,这时候他后面的znode会收到消息,遍历一遍看自己是不是最小的(既然收到消息,那此时当然是最小的),是最小的,然后就获得锁,以此类推

本节讲一下可重入锁

2.具体代码实现
创建一个springboot工程,导入依赖,和application.xml配置

<?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.levi</groupId>
    <artifactId>zk-code</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-client -->
<!--        <dependency>-->
<!--            <groupId>org.apache.curator</groupId>-->
<!--            <artifactId>curator-client</artifactId>-->
<!--            <version>4.2.0</version>-->
<!--        </dependency>-->

        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.2.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--        mysql连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
        <!--        自动生成代码-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--        velocity模板-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <!--        swagger-->
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger-bootstrap-ui-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>



</project>
spring:
  profiles:
    active: dev # 环境设置
  application:
    name: service-core # 服务名
  datasource: # mysql数据库连接
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://host:3306/seckill?serverTimezone=GMT%2B8&characterEncoding=utf-8
    username: root
    password: root

#mybatis xml文件地址
#mybatis-plus:
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#    mapper-locations: classpath:com/levi/mapper/xml/*.xml


zookeeper:
  lockpath: /parent-lock/lock
  address: host:2181

启动类

package com.levi;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = {"com.levi.mapper"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

数据库建表,简单测试
id,商品名称,库存在这里插入图片描述
mybstisplus逆向生成工具

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @ClassName CodeGenerator
 * @Description TODO
 * @Author Levi
 * @Date 2021/4/14 10:41
 **/
public class CodeGenerator {
    @Test
    public void genCode() {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("levi");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setIdType(IdType.AUTO); //主键策略
        gc.setSwagger2(true);//开启Swagger2模式
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://192.168.177.129:3306/seckill?serverTimezone=GMT%2B8&characterEncoding=utf-8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("St1282001316*");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.levi");
        pc.setEntity("pojo"); //此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok
        strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
        strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀(确保tinyint(1))
        strategy.setRestControllerStyle(true); //restful api风格控制器
        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();
    }
}

创建service接口,并且实现里面的方法

package com.levi.service;

public interface SecKillService {

    boolean buyProduct(Integer id, Integer quantity) throws Exception;
}

实现类

package com.levi.service.impl;

import com.levi.mapper.ProductMapper;
import com.levi.pojo.Product;
import com.levi.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀主要逻辑在这个里面
 */
@Service
@Slf4j
public class SecKillServiceImpl implements SecKillService {

    @Value("${zookeeper.lockpath}")
    private String lockpath;

    @Value("${zookeeper.address}")
    private String address;


    @Resource
    private ProductMapper productMapper;

    @Override
    public boolean buyProduct(Integer id, Integer quantity) throws Exception {
        RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework zookeeperClient = CuratorFrameworkFactory.newClient(address, 60000, 15000, retry);
        zookeeperClient.start();
        // create lock
        InterProcessMutex lock = new InterProcessMutex(zookeeperClient, lockpath);
        //等待2秒看有没有得到锁
        if ( lock.acquire(2,TimeUnit.SECONDS) ) {
            log.info("{}抢到了锁",Thread.currentThread().getId());
            try {
                // do some work inside of the critical section here
                Product product = productMapper.selectById(1);
                if (product.getStack() == 0 || product.getStack()<quantity) {
                    return false;
                }
                product.setStack(product.getStack() - 1);
                productMapper.updateById(product);
            }catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.release();
                log.info("{}释放了锁",Thread.currentThread().getId());
            }
        }else{
            //没有得到锁,直接返回false
            return false;
        }
        return true;
    }
}

创建controller测试,每个人买一个商品

package com.levi.controller;

import com.levi.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
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;

@RestController
@RequestMapping("zk")
@Slf4j
public class SecKillController {

    @Autowired
    private SecKillService secKillService;

    @GetMapping("secKill")
    public void secKill() throws Exception {
        if (!secKillService.buyProduct(1, 1)) {
            log.info("{}购买时候商品库存不足",Thread.currentThread().getId());
            System.out.println();
        }else {
            log.info("{}购买商品成功",Thread.currentThread().getId());
        }
    }

}

启动类,启动

package com.levi;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = {"com.levi.mapper"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

使用jmeter测试,创建10个线程,每个人买一个,但是数据库只有5个
在这里插入图片描述

在这里插入图片描述
最后只有5个购买成功
在这里插入图片描述
总结:zookeeper的锁力度大,redis的锁快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值