分步锁的测试开发
domain层开发及测试数据准备
- 修改pom.xml配置文件 ![屏幕快照 2019-04-11 01.25.41]()  
- 创建四层结构 ![屏幕快照 2019-04-11 01.14.53]()  
- 在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; }
- 进行测试. - 打开终端,连接mysql数据库, $ mysql -uroot -proot mysql> use interview; mysql> show tables; 启动PayApplication.class 在数据库中查看,表是否被创建 ![屏幕快照 2019-04-11 01.36.29]() 
插入测试数据 
插入测试数据![屏幕快照 2019-04-11 01.46.25]()  
- 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)
分布式锁的基本架构实现
- 在添加pom.xml中添加zookeeper依赖 ![屏幕快照 2019-04-11 03.59.42]()  
- 创建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;
    }
}
```
- 修改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;
    }
}
```
- 分布式锁实现 - 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; } }
- 分布式锁测试 运行程序时,需要事先打开 
 zkServer.sh start zkCli.sh- 在zk客户端中: ls / ls /zk-namespace get /zk-namespace - 启动程序,进行测试,去WEB UI 查看结果,在IDEA查看结果,在数据库查看结果 
 
                    
                     
                    
                 
                    
                
 
 
 
 
插入测试数据

插入测试数据
 
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号