分步锁的测试开发

domain层开发及测试数据准备

  1. 修改pom.xml配置文件 屏幕快照 2019-04-11 01.25.41

  2. 创建四层结构 屏幕快照 2019-04-11 01.14.53

  3. 在domain层,创建Orders.class和Iterms.class两个类

    Orders.class

    import lombok.Data;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table
    @Data
    public class Orders {
    
        @Id
        private String id;
    
        private String itemId;
    }
    

    Iterms.class

    import lombok.Data;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table
    @Data
    public class Items {
    
        @Id
        private String id;
    
        private String name;
    
        private Integer counts;
    
    }
    
  4. 进行测试.

    打开终端,连接mysql数据库, $ mysql -uroot -proot mysql> use interview; mysql> show tables; 启动PayApplication.class 在数据库中查看,表是否被创建 屏幕快照 2019-04-11 01.36.29 插入测试数据 屏幕快照 2019-04-11 01.46.25

  5. Domain层开发完毕



dao层开发

OrdersDAO.class

import com.next.interview.pay.domain.Items;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * 
 *Orders: <T> the domain type the repository manages
 *String: <ID> the type of the id of the entity the repository manages
 */
public interface ItemsDAO extends JpaRepository<Items, String> {
}

ItemsDAO.class

import com.next.interview.pay.domain.Items;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 *
 *Orders: <T> the domain type the repository manages
 *String: <ID> the type of the id of the entity the repository manages
 */
public interface ItemsDAO extends JpaRepository<Items, String> {
}

开发完毕~



service层开发

OrdersService.class

import com.next.interview.pay.dao.OrdersDAO;
import com.next.interview.pay.domain.Orders;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
@Slf4j    // 加注释
public class OrdersService {

    @Autowired
    private OrdersDAO ordersDAO;

    public boolean save(String itemId) {

        try {
            Orders orders = new Orders();
            orders.setId(UUID.randomUUID().toString());
            orders.setItemId(itemId);

            ordersDAO.save(orders);
            log.info("订单创建成功.....");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

ItemsService.class

package com.next.interview.pay.service;

import com.next.interview.pay.dao.ItemsDAO;

import com.next.interview.pay.domain.Items;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class ItemsService {

    @Autowired  //注进来  service层注入DAO层,通过 @Autowired 注进来
    private ItemsDAO itemsDAO;

    // 获取订单
    public Items getItem(String itemId) {
        return itemsDAO.findById(itemId).orElse(null);
    }

    // 存储订单
    public void save(Items items) {
        items.setId(UUID.randomUUID().toString());
        itemsDAO.save(items);
    }

    // 根据商品id获取它的库存数量
    public int getItemCounts(String itemId) {
        return itemsDAO.findById(itemId).orElse(null).getCounts();
    }

    // 订单创建后,需要减少库存数量
    public void reduceCount(String itemId, int count) {
        Items items = getItem(itemId);
        items.setCounts(items.getCounts() - count);
        itemsDAO.save(items);
    }
}

PayService.class

package com.next.interview.pay.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class PayService {

    @Autowired
    private OrdersService ordersService;

    @Autowired
    private ItemsService itemsService;


    public boolean buy(String itemId) {

        // 假设每次购买9个
        int buyCount = 9;

        // step1  判断库存
        int count = itemsService.getItemCounts(itemId);
        if (count < buyCount) {
            log.info("库存不足,下单失败。 购买数{}件,库存只有{}件", buyCount, count);
            return false;
        }

        // step2 创建订单
        boolean flag = ordersService.save(itemId);

        // TODO... 模拟高并发场景
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        // step3 扣库存
        if (flag) {
            itemsService.reduceCount(itemId,buyCount);
        } else {
            log.info("订单创建失败.....");
            return false;
        }
        return true;
    }
}

测试代码开发: (需要在测试目录中,创建新的测试类)

文件路径: pay-java/src/test/java/com/next/interview/pay/PayServiceTest.java PayServiceTest.class

package com.next.interview.pay;


import com.next.interview.pay.service.PayService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PayServiceTest {

    @Autowired
    private PayService payService;

    @Test
    public void testBuy() {
        payService.buy("1");
    }
}

Controller层开发(模拟高并发)

BuyController.class

import com.next.interview.pay.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BuyController {

    @Autowired
    private PayService payService;

    @GetMapping("/buy1")
    @ResponseBody
    public String buy1(String itemId) {

        if (!StringUtils.isEmpty(itemId)) {
            if (payService.buy(itemId)) {
                return "订单创建成功.... ";
            } else {
                return "订单创建失败.... ";
            }
        }else {
            return "条目ID不能为空";
        }
    }

    @GetMapping("/buy2")
    @ResponseBody
    public String buy2(String itemId) {

        if (!StringUtils.isEmpty(itemId)) {
            if (payService.buy(itemId)) {
                return "订单创建成功.... ";
            } else {
                return "订单创建失败.... ";
            }
        }else {
            return "条目ID不能为空";
        }
    }
}

重新启动 PayApplication.class程序,整个项目就跑起来了

分布式锁总体架构实现(Zookeeper)

分布式锁的基本架构实现

  1. 在添加pom.xml中添加zookeeper依赖 屏幕快照 2019-04-11 03.59.42

  2. 创建utils目录,创建DistributedLock.class文件

DistributedLock.class

```
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Configuration;

/**
 *
 * 分布式锁,基本架构
 */
@Configuration
@Slf4j
public class DistributedLock {

    private CuratorFramework client = null;

    private static final String ZK_LOCK = "pk-zk-locks";

    private static final String DISTRIBUTED_LOCK = "pk-distributed-lock";

    // 构造方法
    public DistributedLock() {

        client = CuratorFrameworkFactory.builder()
                .connectString("192.168.1.100:2181")
                .sessionTimeoutMs(10000)
                .retryPolicy(new ExponentialBackoffRetry(100, 5))
                .namespace("zk-namespace")
                .build();
        client.start();;
    }

    // 获得分布式锁
    public void getLock() {
        log.info("分布式锁创建成功.... ");

    }

    // 释放分布式锁: 订单创建成功或者异常的时候释放锁
    public boolean releaseLock() {

        log.info("分布式锁释放成功.... ");
        return true;
    }
}
```
  1. 修改PayService.class,在里面添加

    1 @Autowired private DistributedLock distributedLock; 2 distributedLock.getLock(); 3 distributedLock.releaseLock();

PayService.class

```
import com.next.interview.pay.utils.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class PayService {

    @Autowired
    private OrdersService ordersService;

    @Autowired
    private ItemsService itemsService;   

    @Autowired
    private DistributedLock distributedLock;    // 分布式锁的添加


    public boolean buy(String itemId) {

        distributedLock.getLock();    // 获取分布式锁

        // 假设每次购买9个
        int buyCount = 9;

        // step1  判断库存
        int count = itemsService.getItemCounts(itemId);
        if (count < buyCount) {
            log.info("库存不足,下单失败。 购买数{}件,库存只有{}件", buyCount, count);
            distributedLock.releaseLock();  // 释放分布式锁
            return false;
        }

        // step2 创建订单
        boolean flag = ordersService.save(itemId);

        // TODO... 模拟高并发场景
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            distributedLock.releaseLock();   // 释放分布式锁
            e.printStackTrace();
        }


        // step3 扣库存
        if (flag) {
            itemsService.reduceCount(itemId,buyCount);
            distributedLock.releaseLock();  // 释放分布式锁
        } else {
            log.info("订单创建失败.....");
            distributedLock.releaseLock();   // 释放分布式锁
            return false;
        }
        return true;
    }
}
```
  1. 分布式锁实现

    DistributedLock.class的完整版(基于Zookeeper)

    import lombok.extern.slf4j.Slf4j;
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.framework.recipes.cache.PathChildrenCache;
    import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
    import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
    import org.apache.curator.retry.ExponentialBackoffRetry;
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.ZooDefs;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     *
     * 分布式锁,基本架构
     */
    @Configuration
    @Slf4j
    public class DistributedLock {
    
        private CuratorFramework client = null;
    
        private static final String ZK_LOCK = "pk-zk-locks";
    
        private static final String DISTRIBUTED_LOCK = "pk-distributed-lock";
    
        private static CountDownLatch countDownLatch = new CountDownLatch(1);
    
        // 构造方法
        public DistributedLock() {
    
            client = CuratorFrameworkFactory.builder()
                    .connectString("192.168.1.100:2181")
                    .sessionTimeoutMs(10000)
                    .retryPolicy(new ExponentialBackoffRetry(100, 5))
                    .namespace("zk-namespace")
                    .build();
            client.start();;
        }
    
        @Bean
        public CuratorFramework getClient() {
    
            // ZK中最基本的东西
            client = client.usingNamespace("zk-namespace");
    
            try {
                if (client.checkExists().forPath("/" + ZK_LOCK) == null) {
                    client.create()
                            .creatingParentsIfNeeded()
                            .withMode(CreateMode.PERSISTENT)
                            .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE);
                }
    
                addWatch("/" + ZK_LOCK);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
        private void addWatch(String path) throws Exception {
    
            PathChildrenCache cache = new PathChildrenCache(client,path,true);
            cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
            cache.getListenable().addListener(new PathChildrenCacheListener() {
                @Override
                public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                    if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {
    
                        String path = event.getData().getPath();
    
                        if (path.contains(DISTRIBUTED_LOCK)) {
                            countDownLatch.countDown();
                        }
                    }
                }
            });
        }
    
        // 获得分布式锁
        public void getLock() {
            log.info("分布式锁创建成功.... ");
    
            while (true) {
                try {
                    client.create()
                            .creatingParentsIfNeeded()
                            .withMode(CreateMode.EPHEMERAL)
                            .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                            .forPath("/" + ZK_LOCK + "/" + DISTRIBUTED_LOCK);
    
                    log.info("分布式锁获得成功... ");
                } catch (Exception e) {
                    e.printStackTrace();
    
                    if (countDownLatch.getCount() <= 0) {
                        countDownLatch = new CountDownLatch(1);
                    }
    
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
    
                log.info("分布式锁获得成功... ");
            }
    
        }
    
        // 释放分布式锁: 订单创建成功或者异常的时候释放锁
        public boolean releaseLock() {
    
            try {
                if (client.checkExists().forPath("/" + ZK_LOCK + "/" + DISTRIBUTED_LOCK) != null) {
                    client.delete().forPath("/" + ZK_LOCK + "/" + DISTRIBUTED_LOCK);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
            log.info("分布式锁释放成功.... ");
            return true;
        }
    }
    
    
  2. 分布式锁测试 运行程序时,需要事先打开
    zkServer.sh start zkCli.sh

    在zk客户端中: ls / ls /zk-namespace get /zk-namespace

    启动程序,进行测试,去WEB UI 查看结果,在IDEA查看结果,在数据库查看结果

posted @ 2019-04-30 17:32  BBBone  阅读(337)  评论(0编辑  收藏  举报