上述的代码是框架性的代码,现在来讲解如何使用上面的简单框架来写一个秒杀函数 。
先定义一个接口,接口里定义了一个秒杀方法:
public interface SeckillInterface {/***现在暂时只支持在接口方法上注解*/ //cacheLock注解可能产生并发的方法 @CacheLock(lockedPrefix="TEST_PREFIX") public void secKill(String userID,@LockedObject Long commidityID);//最简单的秒杀方法,参数是用户ID和商品ID 。可能有多个线程争抢一个商品,所以商品ID加上LockedObject注解}上述SeckillInterface接口的实现类,即秒杀的具体实现:
public class SecKillImpl implements SeckillInterface{ static Map<Long, Long> inventory ; static{ inventory = new HashMap<>(); inventory.put(10000001L, 10000l); inventory.put(10000002L, 10000l); } @Override public void secKill(String arg1, Long arg2) { //最简单的秒杀,这里仅作为demo示例 reduceInventory(arg2); } //模拟秒杀操作,姑且认为一个秒杀就是将库存减一,实际情景要复杂的多 public Long reduceInventory(Long commodityId){ inventory.put(commodityId,inventory.get(commodityId) - 1); return inventory.get(commodityId); }}模拟秒杀场景,1000个线程来争抢两个商品:
@Test public void testSecKill(){ int threadCount = 1000; int splitPoint = 500; CountDownLatch endCount = new CountDownLatch(threadCount); CountDownLatch beginCount = new CountDownLatch(1); SecKillImpl testClass = new SecKillImpl(); Thread[] threads = new Thread[threadCount]; //起500个线程,秒杀第一个商品 for(int i= 0;i < splitPoint;i++){ threads[i] = new Thread(new Runnable() { public void run() { try { //等待在一个信号量上,挂起 beginCount.await(); //用动态代理的方式调用secKill方法 SeckillInterface proxy = (SeckillInterface) Proxy.newProxyInstance(SeckillInterface.class.getClassLoader(),new Class[]{SeckillInterface.class}, new CacheLockInterceptor(testClass)); proxy.secKill("test", commidityId1); endCount.countDown(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); threads[i].start(); } //再起500个线程,秒杀第二件商品 for(int i= splitPoint;i < threadCount;i++){ threads[i] = new Thread(new Runnable() { public void run() { try { //等待在一个信号量上,挂起 beginCount.await(); //用动态代理的方式调用secKill方法 SeckillInterface proxy = (SeckillInterface) Proxy.newProxyInstance(SeckillInterface.class.getClassLoader(),new Class[]{SeckillInterface.class}, new CacheLockInterceptor(testClass)); proxy.secKill("test", commidityId2); //testClass.testFunc("test", 10000001L); endCount.countDown(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); threads[i].start(); } long startTime = System.currentTimeMillis(); //主线程释放开始信号量,并等待结束信号量,这样做保证1000个线程做到完全同时执行,保证测试的正确性 beginCount.countDown(); try { //主线程等待结束信号量 endCount.await(); //观察秒杀结果是否正确 System.out.println(SecKillImpl.inventory.get(commidityId1)); System.out.println(SecKillImpl.inventory.get(commidityId2)); System.out.println("error count" + CacheLockInterceptor.ERROR_COUNT); System.out.println("total cost " + (System.currentTimeMillis() - startTime)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }在正确的预想下,应该每个商品的库存都减少了500,在多次试验后,实际情况符合预想 。如果不采用锁机制,会出现库存减少499,498的情况 。
这里采用了动态代理的方法,利用注解和反射机制得到分布式锁ID,进行加锁和释放锁操作 。当然也可以直接在方法进行这些操作,采用动态代理也是为了能够将锁操作代码集中在代理中,便于维护 。
通常秒杀场景发生在web项目中,可以考虑利用spring的AOP特性将锁操作代码置于切面中,当然AOP本质上也是动态代理 。
小结
这篇文章从业务场景出发,从抽象到实现阐述了如何利用redis实现分布式锁,完成简单的秒杀功能,也记录了笔者思考的过程,希望能给阅读到本篇文章的人一些启发 。
原文:https://blog.csdn.net/u010359884/article/details/50310387
作者:Isfire
来源:CSDN
推荐阅读
- 分布式系统常见概念
- Redis深度历险,最全命令总结
- 如何基于 MySQL 主从模式搭建上万并发的系统架构?
- 分布式系统:Zookeeper一致性级别分析
- 推荐一款nginx+redis+ehcache高并发与高可用缓存架构
- Redis 核心原理和架构
- 关于redis学会这8点就够了
- Redis实现统计网站访问人数的功能
- 如何做可靠的分布式锁,Redlock真的可行么
- 了解分布式架构,让你的软件架构之路越走越顺