设为首页 收藏本站
查看: 2185|回复: 0

[经验分享] 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有测试代码(何志雄)

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-11-12 11:52:28 | 显示全部楼层 |阅读模式
转载请标明出处。



在分布式系统中,经常会出现需要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁。
redis集群的搭建,请见我的另外一篇文章:<>《redis3.0.1集群环境搭建》
可用于例如秒杀系统中的商品库存的管理。付完整代码及测试用例。



package com.gaojiasoft.gaojiaRedis;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.exceptions.JedisConnectionException;
/**
* 分布式锁管理器,支持对单个资源加锁解锁,或给一批资源的批量加锁及解锁
* @ClassName: DistributedLockManger
* @Description:  
* @author 何志雄 001
* @company 南京高嘉软件技术有限公司
* @date 2015年6月3日 下午5:44:06
*/
public class DistributedLockManger
{
private static final Logger LOGGER = LoggerFactory.getLogger(DistributedLockManger.class);
private static final int DEFAULT_SINGLE_EXPIRE_TIME = 3;
//    private static final int DEFAULT_BATCH_EXPIRE_TIME = 6;
//static的变量无法注解
@Autowired
JedisCluster jc;
private static DistributedLockManger lockManger;
public DistributedLockManger()
{
}
@PostConstruct
public void init()
{
lockManger = this;
lockManger.jc = this.jc;
}
/**
* 获取锁 如果锁可用   立即返回true,  否则立即返回false,作为非阻塞式锁使用
* @param key
* @param value
* @return
*/
public static boolean tryLock(String key , String value)
{
try
{
return tryLock(key, value, 0L, null);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return false;
}
/**
* 锁在给定的等待时间内空闲,则获取锁成功 返回true, 否则返回false,作为阻塞式锁使用
* @param key 锁键
* @param value 被谁锁定
* @param timeout 尝试获取锁时长,建议传递500,结合实践单位,则可表示500毫秒
* @param unit,建议传递TimeUnit.MILLISECONDS
* @return
* @throws InterruptedException
*/
public static boolean tryLock(String key , String value , long timeout , TimeUnit unit) throws InterruptedException
{
//纳秒
long begin = System.nanoTime();
do
{
//LOGGER.debug(&quot;{}尝试获得{}的锁.&quot;, value, key);
Long i = lockManger.jc.setnx(key, value);
if (i == 1)
{
lockManger.jc.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
LOGGER.debug(&quot;{}成功获取{}的锁,设置锁过期时间为{}秒 &quot;, value, key, DEFAULT_SINGLE_EXPIRE_TIME);
return true;
}
else
{
// 存在锁 ,但可能获取不到,原因是获取的一刹那间
//                String desc = lockManger.jc.get(key);
//                LOGGER.error(&quot;{}正被{}锁定.&quot;, key, desc);
}
if (timeout == 0)
{
break;
}
//在其睡眠的期间,锁可能被解,也可能又被他人占用,但会尝试继续获取锁直到指定的时间
Thread.sleep(100);
}
while ((System.nanoTime() - begin) < unit.toNanos(timeout));
//因超时没有获得锁
return false;
}
/**
* 释放单个锁
* @param key 锁键
* @param value 被谁释放
*/
public static void unLock(String key , String value)
{
try
{
lockManger.jc.del(key);
LOGGER.debug(&quot;{}锁被{}释放 .&quot;, key, value);
}
catch (JedisConnectionException je)
{
}
catch (Exception e)
{
}
}
public void test() throws InterruptedException
    {
        String productId = &quot;18061249844&quot;;
        String userId;
        for (int i = 1; i <= 500; i++)
        {
            //随机产生一个用户
            userId = UUID.randomUUID().toString();
            //该用户试图锁定(如果被锁,尝试等待300毫秒),在处理一些事情后,再释放锁
            testLock(productId, userId);
            Thread.sleep(20);
        }
    }
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(150, 150, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new BasicThreadFactory.Builder().daemon(true)
.namingPattern(&quot;mongo-oper-%d&quot;).build(), new ThreadPoolExecutor.CallerRunsPolicy());
private static void testLock(final String key , final String value)
{
executor.execute(new Runnable()
{
@Override
public void run()
{
try
{
//获取锁,如果不能立即获取,尝试等待1000毫秒
boolean isLock = DistributedLockManger.tryLock(key, value, 500, TimeUnit.MILLISECONDS);
if (isLock)
{
//doSomething(占用锁20毫秒到4秒,模拟处理事务)
long doSomeThingTime = RandomUtils.nextLong(20, 4000);
LOGGER.debug(&quot;{}将持有锁{}时长{}毫秒.&quot;, value, key, doSomeThingTime);
Thread.sleep(doSomeThingTime);
//然后释放锁
DistributedLockManger.unLock(key, value);
}
}
catch (Throwable th)
{
}
}
});
}
public JedisCluster getJc()
{
return jc;
}
public void setJc(JedisCluster jc)
{
this.jc = jc;
}
}


Spring配置:  
redis.properties


#最大闲置连接数
im.hs.server.redis.maxIdle=500
#最大连接数,超过此连接时操作redis会报错
im.hs.server.redis.maxTotal=5000
im.hs.server.redis.maxWaitTime=1000
im.hs.server.redis.testOnBorrow=true
#最小闲置连接数,spring启动的时候自动建立该数目的连接供应用程序使用,不够的时候会申请。
im.hs.server.redis.minIdle=300

spring-redis.xml
  


<?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>
<beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:p=&quot;http://www.springframework.org/schema/p&quot;
xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
xmlns:jee=&quot;http://www.springframework.org/schema/jee&quot; xmlns:tx=&quot;http://www.springframework.org/schema/tx&quot;
xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot;
xsi:schemaLocation=&quot;
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;>
<context:property-placeholder location=&quot;classpath:conf/redis/redis.properties&quot; />
<!-- 事实上,只需要连接1个节点就可以 -->
<bean id=&quot;culster7001&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.153&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7001&quot; />
</bean>
<bean id=&quot;culster7002&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.153&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7002&quot; />
</bean>
<bean id=&quot;culster7003&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.154&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7003&quot; />
</bean>
<bean id=&quot;culster7004&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.154&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7004&quot; />
</bean>
<bean id=&quot;culster7005&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.155&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7005&quot; />
</bean>
<bean id=&quot;culster7006&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.155&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7006&quot; />
</bean>
<bean id=&quot;culster7007&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.152&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7007&quot; />
</bean>
<bean id=&quot;culster7008&quot; class=&quot;redis.clients.jedis.HostAndPort&quot;>
<constructor-arg name=&quot;host&quot; value=&quot;192.168.62.152&quot; />
<constructor-arg name=&quot;port&quot; value=&quot;7008&quot; />
</bean>
<bean id=&quot;poolConfig&quot; class=&quot;redis.clients.jedis.JedisPoolConfig&quot;>
<property name=&quot;maxTotal&quot; value=&quot;${im.hs.server.redis.maxTotal}&quot; />
<property name=&quot;minIdle&quot; value=&quot;${im.hs.server.redis.minIdle}&quot; />
<property name=&quot;maxWaitMillis&quot; value=&quot;${im.hs.server.redis.maxWaitTime}&quot; />
<property name=&quot;maxIdle&quot; value=&quot;${im.hs.server.redis.maxIdle}&quot; />
<property name=&quot;testOnBorrow&quot; value=&quot;${im.hs.server.redis.testOnBorrow}&quot; />
<property name=&quot;testOnReturn&quot; value=&quot;true&quot; />
<property name=&quot;testWhileIdle&quot; value=&quot;true&quot; />
</bean>

<bean id=&quot;jc&quot; class=&quot;redis.clients.jedis.JedisCluster&quot;>
<constructor-arg name=&quot;nodes&quot;>
<set>
<ref bean=&quot;culster7001&quot; />
<ref bean=&quot;culster7002&quot; />
<ref bean=&quot;culster7003&quot; />
<ref bean=&quot;culster7004&quot; />
<ref bean=&quot;culster7005&quot; />
<ref bean=&quot;culster7006&quot; />
<ref bean=&quot;culster7007&quot; />
<ref bean=&quot;culster7008&quot; />
</set>
</constructor-arg>
<constructor-arg name=&quot;poolConfig&quot;> <ref bean=&quot;poolConfig&quot; /> </constructor-arg>
</bean>
<bean id=&quot;distributedLock&quot; class=&quot;com.gaojiasoft.gaojiaRedis.DistributedLockManger&quot; />
<context:annotation-config />
</beans>

  



测试代码:  


package com.gaojiasoft.gaojiaRedis;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ClusterJedisTester extends TestCase
{
private Logger logger = LoggerFactory.getLogger(ClusterJedisTester.class);
private static String[] list = new String[] { &quot;classpath:conf/redis/spring-redis.xml&quot; };
private static ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(list, true, null);
DistributedLockManger  distributedLock = (DistributedLockManger)context.getBean(&quot;distributedLock&quot;);
@Test
public void testLock() throws InterruptedException
{
distributedLock.test();
while(true)
{
Thread.sleep(1000);
}
}
}





  













版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-138295-1-1.html 上篇帖子: redis C接口hiredis初次使用 下篇帖子: Redis Java客户端Jredis
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表