50、zookeeper,redis 分布式锁 的实现

1、分布式锁的产生

在单个JVM 多个线程中获取共享资源:可以用 synchronized 或者用 JUC里面的Lock锁,可以保证线程安全数据一致

在多个JVM 多个线程中获取共享资源:用 synchronized 或者用 JUC里面的Lock锁 不能保证数据一致,因为在分布式系统中存在多个JVM 抢占共享资源,解决这个问题要保证同一时间内保证有且仅有一个JVM处理,这个时候要锁的是JVM,这个就是分布式锁

2、分布式锁的实现,常用的解决方案:redis 、zookeeper

使用zookeeper实现分布式锁

添加依赖

<dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

编写ZkLock接口

public interface ZkLock {
    public void zkLock();
    public void zkUnLock();
}

编写一个抽象类实现ZkLock接口

public abstract class ZkAbstractTemplateLock implements ZkLock {
    public final String zkServer = "192.168.28.144:2181";
    public final int timeOut = 45 * 1000;
    public ZkClient zkClient = new ZkClient(zkServer,timeOut);
    protected final String path = "/zkClient0401";
    protected CountDownLatch countDownLatch = null;

    @Override
    public void zkLock() {
        if(tryZkLock()) {
            System.out.println(Thread.currentThread().getName()+ "\t" + "占用锁成功!");
        }else {
            waitZkLock();

            zkLock();
        }

    }

    public abstract boolean tryZkLock();

    public abstract void waitZkLock();


    @Override
    public void zkUnLock() {
        if(zkClient != null) {
            zkClient.close();
        }
        System.out.println(Thread.currentThread().getName()+ "\t" + "锁释放成功!");
        System.out.println();
        System.out.println();
    }
}

编写一个类继承 ZkAbstractTemplateLock 并实现抽象类中没有实现的方法

public class ZkDistrbutedLock extends ZkAbstractTemplateLock {
    @Override
    public boolean tryZkLock() {
        try {
            zkClient.createEphemeral(path);
            return true;
        }catch (Exception e) {
            return false;
        }
    }

    @Override
    public void waitZkLock() {
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                if(countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };
        zkClient.subscribeDataChanges(path,iZkDataListener);
        if(zkClient.exists(path)) {
            //只能等待,不能往下走
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            zkClient.unsubscribeDataChanges(path,iZkDataListener);
        }
    }
}

编写一个生产订单的工具类

public class CreateNumberUtil {
    private static int  number = 0;
    public int createOrderNumber() {
        return ++number;
    }
}

编写service 模拟实现生成订单的类

public class OrderService {
    private CreateNumberUtil createNumberUtil = new CreateNumberUtil();
    private ZkLock zkLock = new ZkDistrbutedLock();

    public void getOrderNumber () {
        zkLock.zkLock();
        try{
            System.out.println("生成订单成功!------------>: "+ createNumberUtil.createOrderNumber());
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            zkLock.zkUnLock();
        }
    }
}

多线程测试类

public static void main(String[] args) {

        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                new OrderService().getOrderNumber();
            }, String.valueOf(i)).start();
        }
    }

 2、redis的五大数据类型的落地应用

这些数据结构式如何使用的,用在那些应用场景?

string类型: 一般用于喜欢的文章的点赞,商品点赞

常用的命令:set k1 v1 、get k1 、incr k1,incrby k1 2、decr k1

4、redis做分布式锁

安装版本式6.0.8单机版的redis

version: '3'

services:
  redis:
    image: redis:6.0.8
    container_name: redis
    restart: always
    ports:
      - 6379:6379
    networks:
      - mynetwork
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf:rw
      - ./data:/data:rw
    command:
      /bin/bash -c "redis-server /usr/local/etc/redis/redis.conf "
networks:
  mynetwork:
    external: true

 第二种

version: '3.1'
services:
  master:
    restart: always
    image: redis:6.0.8
    container_name: redis-master
    ports:
      - 6379:6379

安装nginx做负载均衡请求分发到到两个服务:boot-redis01、boot-redis02

version: '3.1'
services:
  nginx:
    restart: always
    image: nginx
    container_name: nginx
    ports:
      - 81:80
    volumes:
      - ./conf/nginx.conf:/etc/nginx/nginx.conf
      - ./wwwroot:/usr/share/nginx/wwwroot

 

创建两个一模一样的项目:boot-redis01端口号:1111 和 boot-redis02端口号:2222

添加pom.xml依赖

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

编写application.properties 文件

server.port=2222

spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=

spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

编写主启动类

@SpringBootApplication
public class BootRedis02Application {

    public static void main(String[] args) {
        SpringApplication.run(BootRedis02Application.class, args);
    }

}

编写redis的配置类RedisConfig类

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
    @Bean
    public Redisson redisson () {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}

编写controller业务类

@RestController
public class GoodsController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private Redisson redisson;

    @GetMapping(value = "/buyGoods")
    public String buyGoods() {
        RLock redissonLock = redisson.getLock("atguiguLock");
        redissonLock.lock();
        try {
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result==null? 0 : Integer.parseInt(result);
            if(goodsNumber > 0) {
                int redNumber = goodsNumber -1;
                stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(redNumber));
                System.out.println("成功购买到商品 "+ "库存还剩下:" + redNumber+ "服务端口号: " + serverPort);
                return "成功购买到商品 "+ "库存还剩下:" + redNumber+ "服务端口号: " + serverPort;
            }else {
                System.out.println("活动已经结束!");
                return "活动已经结束";
            }
        }finally {
            if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
                redissonLock.unlock();
            }
        }
    }
}

 

posted @ 2021-07-06 13:34  shunnWcs  阅读(83)  评论(0)    收藏  举报