使用redis来实现分布式锁
在实际的工作中,有部分的特定场景需要使用到分布式锁来进行跨服务器资源的统一调配。之前在一家医疗互联网公司,因为黄牛抢号等原因,造成同一个患者同一时段在同一个医生处,挂到了两个及以上的号,我对之前我司实现的代码进行了封装和改进,在github上提供了源码,有需要的朋友,可以下载代码,并用maven打包成jar包,使用起来比较方便。
源码地址:https://github.com/mantuliu/distributed
核心feature:
redis的setnx()方法,此方法提供了一个原子操作,可以保证有且只有一个分布式的调用返回值为1,在分布式锁的概念里,则代表此锁被此次调用的线程占用;
redis的expire()方法,通过此方法来设置此锁的过期时间;
对于死锁的情况,封装的分布式锁包可以自动解锁;
每次获取锁的线程,都会被标识,当此线程还没有释放锁时,此线程继续调用trylock方法,还可以获得该锁,只有获取该锁的线程才有unlock()释放锁的权利;
支持trylock()和trylock(long timeout, TimeUnit unit)两种方式;
使用应用服务器提供的jedis实例;
对于当前服务器试图获取该锁的线程数量进行监控,当数量大于阀值时,后续线程在trylock(long timeout, TimeUnit unit)及trylock()时,直接返回失败,阀值可以设置;
锁的过期时间默认值是5秒,可以根据实际情况进行设置;
锁的键值前缀默认值是mantu:dislock:,可以根据实际情况进行设置;
通过LockSupport类来获取锁,使得试图获取同一把锁的线程得到的对象是同一个。
源码解析:
CommonType类
public class CommonType {
public static int WAITLOCKERS = 2;//当前服务器等待锁的线程数量,如果超过或等于此值,当前线程直接返回,不再等待锁
public static String REDISKEY="mantu:dislock:";//redis下key前缀
public static int LOCKEXPIRETIME = 5;//锁的过期时间,单位秒,默认5秒过期
}
DisLock接口
public interface DisLock{
boolean tryLock(Jedis jedis);
boolean tryLock(long time, TimeUnit unit,Jedis jedis) throws InterruptedException;
void unlock(Jedis jedis);
}
RedisDisLock类,实际的管理锁的类
public class RedisDisLock implements DisLock{
private static final Logger LOG = LoggerFactory.getLogger(RedisDisLock.class);
private transient Thread exclusiveOwnerThread;
String lockKey="";
AtomicInteger waitToLock=new AtomicInteger(0);
public RedisDisLock(String lockKey){
this.lockKey=CommonType.REDISKEY+lockKey;
}
public boolean tryLock(Jedis jedis) {
Thread thread = Thread.currentThread();
if(thread==this.getExclusiveOwnerThread()){
return true;
}
Long i = jedis.setnx(lockKey, System.currentTimeMillis()+"");
if(i.intValue()==1){
jedis.expire(lockKey, CommonType.LOCKEXPIRETIME);
setExclusiveOwnerThread(thread);
return true;
}
else{//对于可能性非常低的死锁情况进行解锁
String initTime = jedis.get(lockKey);
if(initTime==null){
LOG.debug("initTime's value is null");
return false;
}
long iniTime=0L;
try{
iniTime = Long.parseLong(initTime);
}
catch(NumberFormatException nfex){
LOG.warn(nfex.getMessage());
jedis.expire(lockKey, 1);
return false;
}
if(((System.currentTimeMillis()-iniTime)/1000-CommonType.LOCKEXPIRETIME-1)>0){
String oldTime = jedis.getSet(lockKey, System.currentTimeMillis()+"");//对于及其极端的情况,lock被线程1处理掉了,但是又被线程2getset新的值了,通过下一次调用trylock()方法处理
if(oldTime==null){
LOG.info("oldTime is null");
return false;
}
if(initTime.equals(oldTime)){
release(jedis);
}
}
}
return false;
}
public boolean tryLock(long timeout, TimeUnit unit,Jedis jedis) throws InterruptedException {
long nanosTimeout = unit.toNanos(timeout);
long lastTime = System.nanoTime();
if(tryLock(jedis)){
return true;
}
try{
int waitLockers = waitToLock.getAndIncrement();
if(waitLockers>=CommonType.WAITLOCKERS){
LOG.debug("wait the lock' thread num is much,so return flase");
return false;
}
for(;;){
if(tryLock(jedis)){
return true;
}
if (nanosTimeout <= 0){
LOG.debug("getlock timeout");
return false;
}
if(nanosTimeout>100000){
LockSupport.parkNanos(100000);//中断100毫秒
}
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (nanosTimeout <= 0){
LOG.debug("getlock timeout");
return false;
}
if (Thread.interrupted()){
throw new InterruptedException();
}
}
}
finally{
waitToLock.decrementAndGet();
}
}
public void unlock(Jedis jedis) {
Thread thread = Thread.currentThread();
if(thread==this.getExclusiveOwnerThread()){
LOG.debug("unlock the thread {}",thread.getId());
release(jedis);
}
}
private void release(Jedis jedis){
setExclusiveOwnerThread(null);
jedis.del(lockKey);
}
/**
* Sets the thread that currently owns exclusive access. A
* <tt>null</tt> argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* <tt>volatile</tt> field accesses.
*/
protected final void setExclusiveOwnerThread(Thread t) {
exclusiveOwnerThread = t;
}
/**
* Returns the thread last set by
* <tt>setExclusiveOwnerThread</tt>, or <tt>null</tt> if never
* set. This method does not otherwise impose any synchronization
* or <tt>volatile</tt> field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
LockSupport类,实际的业务代码首先要通过LockSupport来获取redis锁的对象,再使用
public class LockSupport {
static ConcurrentHashMap <String,RedisDisLock>lockMap = new ConcurrentHashMap<String,RedisDisLock>();
public static DisLock getRedisLock(String lockKey){
RedisDisLock lock=null;
if(lockMap.contains(lockKey)){
lock = lockMap.get(lockKey);
}
else{
RedisDisLock lockN = new RedisDisLock(lockKey);
lock = lockMap.putIfAbsent(lockKey, lockN);
if(lock==null){
lock=lockN;
}
}
return lock;
}
}
RedisDisLockTest类是使用此jar的demo代码
public class RedisDisLockTest {
public static void main(String [] args){
RedisDisLockTest test = new RedisDisLockTest();
//test.testOrder();
//test.testOrder2();
//test.testNOUnlock();
test.testOtherUnlock();
}
public void testOrder(){
JedisPool jp = new JedisPool("127.0.0.1",6379);
for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
OrderThread th = new OrderThread("123456",jedis);
th.start();
}
}
public void testOrder2(){
JedisPool jp = new JedisPool("127.0.0.1",6379);
for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
OrderThread th = new OrderThread("1234567",jedis);
th.start();
}
}
public void testNOUnlock(){
JedisPool jp = new JedisPool("127.0.0.1",6379);
for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
TestNOUnlock th = new TestNOUnlock("12345678",jedis);
th.start();
}
}
public void testOtherUnlock(){
JedisPool jp = new JedisPool("127.0.0.1",6379);
for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
TestOtherUnlock th = new TestOtherUnlock("unlock",jedis);
th.start();
}
}
class OrderThread extends Thread{
String lockKey="";
Jedis jedis;
public OrderThread(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
try {
if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){
System.out.println("订单"+lockKey+"创建成功!");
lock.unlock(jedis);
}
else{
System.out.println("没有成功获取到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class TestNOUnlock extends Thread{
String lockKey="";
Jedis jedis;
public TestNOUnlock(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
try {
if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){
System.out.println("订单"+lockKey+"创建成功!");
//lock.unlock(jedis);//no unlock
}
else{
System.out.println("没有成功获取到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class TestOtherUnlock extends Thread{
String lockKey="";
Jedis jedis;
public TestOtherUnlock(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
if(lock.tryLock(jedis)){
System.out.println("订单"+lockKey+"创建成功!");
//lock.unlock(jedis);//no unlock
}
else{
lock.unlock(jedis);
System.out.println("TestOtherUnlock没有成功获取到锁");
}
}
}
}
使用redis来实现分布式锁的更多相关文章
- 基于redis实现的分布式锁
基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...
- 一个Redis实现的分布式锁
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...
- 基于Redis的简单分布式锁的原理
参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...
- redis客户端、分布式锁及数据一致性
Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...
- Redis系列(二)--分布式锁、分布式ID简单实现及思路
分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...
- 在redis上实现分布式锁
/** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...
- 如何用redis正确实现分布式锁?
先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...
- redis系列:分布式锁
redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
#(1)redis分布式锁 官方叫做RedLock算法,是redis官方支持的分布式锁算法. 这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点创建了 ...
- Redis如何实现分布式锁
今天我们来聊一聊分布式锁的那些事. 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作.在计算机科学中,锁(lock ...
随机推荐
- SGU 246. Black & White(数论)
题意: 有2*n-1个黑色和白色的珠子组成的环形项链,求至少需要多少颗黑色珠子才能使任意排列的项链中都存在两个黑珠间有n个珠子. (2*n-1<=2^31-1); Solution: 先分析n= ...
- linux vim 基本操作
(一定要在英文输入法的状态下才有效)vi:实际上linux 上的 vi 不是真正的 vi,而是 vim;纯的 vi只在某些 unix 系统上还存在纯 的vi里面不支持退格键盘了,当按退格键盘以后,不是 ...
- ecshop标签
页面标题 {$page_title}页面关键字 {$keywords} 产品分类 父分类列表 {foreach from=$cate ...
- php多线程即时通讯
即时通讯:推送消息http://www.workerman.net/
- Cohort Analysis Using Python
Cohort Analysis是将某一个时期内的用户划分为一个cohort,并将多个cohort进行时间上的某个属性的比较的一种分析方法.Cohort Analysis在有些场景下非常有用.比如一个网 ...
- WPF实现导航的几种方式
下面是展示的是几种导航方式: 我们来具体看下xaml文件 <Page x:Class="WPF实现Navigation.Page1" xmlns="http://s ...
- ibatis错误
java.lang.IllegalArgumentException: Mapped Statement collection already contains value for com.regin ...
- 最近Get到的一些HTML/CSS中的小点(一)
1.<em>和<strong>标签都是用来强调一段话中的某几个文字.<em>默认斜体,<strong>默认粗体.在强调语气上<strong> ...
- EasyPHP的Apache报错
今天安装了最新版本的软件:EasyPHP-DevServer-14.1VC11-install.exe 启动报错: 打开Cport软件: 可见80端口被系统占用,导致Apache不能启动. (1)手动 ...
- ajax post data 获取不到数据,注意 content-type的设置
ajax post data 获取不到数据,注意 content-type的设置 .post/get关于 jQuery data 传递数据.网上各种获取不到数据,乱码之类的.好吧今天我也遇到了,网 ...