分布式锁顾名思义就是在分布式系统下的锁,而使用锁的唯一目的就是为了防止多个请求同时对某一个资源进行竞争性读写

在使用多线程时,为了让某一资源某一时刻只能有一个操作者,经常使用synchronized,这点大家都很熟悉

那什么时候使用分布式锁?

当一套项目只部署一套的时候,使用synchronized就可以了,但是当同一套项目部署了多套,即进行分布式部署时,

假设部署了同样的A,B,C三套系统,系统里面有一个操作同一时刻只允许一个用户进行操作,如上所说,只部署一套时,用synchronized限定可以达到要求

现在部署三套之后,如果 a1,b1,c1三个甚至更多用户来同时访问ABC三套系统中只能有一个人操作的方法时,则都可以进行操作。synchronized是不是没达到设计效果

所以:

只有当项目进行分布式部署且有限定不能同时操作的资源时,才会使用分布式锁。

明确了啥时候用,那么该如何用,怎么设计?

按照在学java之初的思路,应该设置一个全局的标识,假设为 flag = true

如果某一时刻某个线程来获取的时候,发现是true,就表示该线程获取到锁了,并改为false,其它线程来发现是false就等一段时间再试,获得锁的线程执行完了,修改为false以便其它线程使用

那么问题来了:

1.怎么存储这个全局的flag,因为要频繁的读取修改

2.怎么保证同一时刻只有一个线程取得锁, 如果两个线程同时来判断flag,都发现是true,那么两个线程都获取到锁了,达不到目的

带着这两个问题,正式步入正题:

由于需要频繁的读取,而存储的值很简单,则考虑使用缓存,而redis就相当符合需要,redis可以达到每秒100,000次的读写,而且可以供多个项目同时操作

存储问题解决了,那么怎么保证同一时刻只有一个线程获取到锁,这就需要用到redis相关的命令了

redis中有一个setnx(set if not exist)命令,表示如果没有这个key就设值并返回1,如果key已经存在则返回0

由于redis是单线程单进程的基于内存操作的工具,所以同一时刻只会有一个命令执行成功。

所以,可以简单的使用setnx命令来进行锁的获取,如果返回的是1,表示获取到了锁,就开始执行业务逻辑,完成之后删除key,其它线程才可以获取到锁

但是问题又来了,如果已经获取到锁的线程由于执行出错等原因,一直不释放锁(delete key),那么其它线程则永远也无法获取到锁,这就和死锁一样吧

所以,释放锁的策略很重要

redis 有一个expire命令,可以让key在一定时间后失效(自动删除),但如果成功设置了key但expire来没设置成功时服务就挂了,并且程序又执行出错死锁了一直不释放锁怎么办?

这时就需要其它线程来进行解锁,其它线程解锁的判断条件就至关重要,必须明确啥时候可以解锁

参考了很多相关文章发现其中一种比较好的策略:

redis中的value设置为 当前当前时间+失效时间,使用setnx命令成功获取锁后,执行任务,如果执行成功,则删除key,如果执行失败,导致锁不释放,则由其它线程来释放锁

当其它线程通过get key 获取到时间发现已经超时了,则可以进行锁的获取,

其它线程通过使用getset命令来对key进行设置,如果返回的值(旧值) 等于 自己发送过去设置的值(新值),则表示当前线程获取到了锁,如果不一致,则表示其它线程获取到了锁,

疑问来了,如果getset执行成功了,但是返回的值和该线程设置的值不一致,会不会影响其它线程?  不会哈,因为这个时间改动范围是很小很小的,可以忽略了

各个服务器时间一定要同步哦

以上就是基本的实现思路了

在自己实现过程中,发现了一个较好的开源项目,也是基于redis, 地址:https://github.com/redisson/redisson/wiki

并且可以快速的和springmvc等框架集成

下面红色标注的就是我在集成过程中遇见的问题,一定要小心

1.pom中对jar包进行引入(jedis相关jar包也要引入哦)

 <!--Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.4.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>

2.增加spring集成配置文件 applicationContext-redission.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:redisson="http://redisson.org/schema/redisson"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://redisson.org/schema/redisson classpath:org/redisson/spring/support/redisson-1.1.xsd"> <redisson:client id="redissonClient">
<redisson:single-server address="${redis.address}"/>
</redisson:client> <!-- <redisson:client> -->
<!-- <redisson:cluster-servers> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7000" /> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7001" /> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7002" /> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7003" /> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7004" /> -->
<!-- <redisson:node-address value="redis://192.168.0.32:7005" /> -->
<!-- </redisson:cluster-servers> -->
<!-- </redisson:client> --> </beans>

注意address值格式为:  redis://ip:port  如: redis://192.168.0.32:6379

classpath:org/redisson/spring/support/redisson-1.1.xsd 注意定义文件是这么引入的,开源wiki里面可不是这么写的,小坑了我一下

3.使用就简单了,我简单写了一个demo进行了测试,同时也可以部署两套,同时发起请求进行测试
     @Autowired
private RedissonClient redissonClient; private static AtomicInteger st = new AtomicInteger(0); @RequestMapping("/test/redission")
public void test() {
st.getAndSet(0);
for(int i=0;i<=9999;i++){
new Thread(new Runnable() {
@Override
public void run() {
test(String.valueOf(st.getAndIncrement()));
}
}).start();
} } public void test(String value) {
RLock rLock = redissonClient.getLock("anyLock");
boolean res = false;
try {
res = rLock.tryLock(200, 10, TimeUnit.SECONDS);
if (res) {
System.out.println(String.format("%04d",Integer.valueOf(value)));
//System.out.println("开始执行业务:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()) + ", 取得的值为:" + String.valueOf(value));
if(Integer.valueOf(value) % 1000 == 0){
Thread.sleep(1000);
} else {
//Thread.sleep(1000);
}
//System.out.println("业务执行结束:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()));
} else {
System.out.println("not lock:"+String.format("%04d",Integer.valueOf(value)));
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(rLock.isHeldByCurrentThread()){
rLock.unlock();
}
}
}

启动多个线程模拟并发访问,根据值来进行区分,同时可以调整超时时间来进行测试锁超时时的情况,具体使用参照:https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers

在我进行压力测试过程中发现,使用公平锁效率要低很多,其它的锁暂时还没进行过压力测试,不知道具体情况、

但具体业务中,肯定是不同业务使用不同的锁,千万不要整个系统中不同的业务都使用一个锁哈,只要不相互关联就要完全分开

在具体的项目代码中可以采用一个标准的模板模式来进行统一管理,调用方集成后实现业务逻辑段代码就好了

package personal.changw.xiao.web.service.impl;

import java.util.concurrent.TimeUnit;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired; import personal.changw.xiao.web.exception.SystemError;
import personal.changw.xiao.web.vo.common.Result; /**
* @since 2017年07月31日 上午11:02:29
* @author 肖昌伟 changw.xiao@qq.com
* @description 分布式锁抽象实现方法
*/
public abstract class DistributedLockService { @Autowired
private RedissonClient redisson; @SuppressWarnings("finally")
public Result doDistributedLockBusiness(String key, Object param) { RLock lock = redisson.getLock(key); Result result = new Result(); boolean res = false;
try {
res = lock.tryLock(50, 10, TimeUnit.SECONDS);
if (res) {
result = distributedLockBusinessLogic(param);
} else {
result = new Result(SystemError.DISTRIBUTED_BUSINESS_FAILD.getCode(), SystemError.DISTRIBUTED_BUSINESS_FAILD.getMessage());
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
return result;
}
} /**
* 分布式任务具体业务逻辑,需在具体的service方法中实现
* @param pram
* @return
*/
abstract Result distributedLockBusinessLogic(Object pram);
}

然后在具体的业务处理方法中对其进行实现就好了,以获取唯一sessionId为例

package personal.changw.xiao.web.service.impl;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import personal.changw.xiao.web.constant.Constants;
import personal.changw.xiao.web.dao.UserDao;
import personal.changw.xiao.web.service.UserService;
import personal.changw.xiao.web.utils.Page;
import personal.changw.xiao.web.vo.UserInfo;
import personal.changw.xiao.web.vo.common.Result;
import personal.changw.xiao.web.vo.param.UserQueryParam; @Service
public class UserServiceImpl extends DistributedLockService implements UserService { public static AtomicLong SESSION_ID = new AtomicLong(4567812L); @Autowired
UserDao userDao; @Autowired
RedisTemplate<String, String> redis; @Override
public UserInfo getByUserName(String userName) {
return userDao.getByUserName(userName);
} /**
* 获取唯一的sessionId分布式业务逻辑
   * 实现具体的分布式锁中的业务逻辑
*/
@Override
Result distributedLockBusinessLogic(Object obj) {
redis.opsForValue().increment(String.valueOf(obj), 1L);
return new Result(redis.opsForValue().get(String.valueOf(obj)));
} /**
* 调用分布式锁任务获取唯一的sessionId
   * 调用抽象类中的方法
*/
@Override
public String getSessionId() {
Result result = this.doDistributedLockBusiness(Constants.SESSEION_ID, Constants.SESSEION_ID);
return String.valueOf(result.getContent());
} @Override
public List<UserInfo> listUserByPgae(Page<UserInfo> page, UserQueryParam param) {
page.setConditions(param);
return userDao.listUserByPgae(page);
} }

红色部分就是需要实现的地方,集成后自己完成相关业务逻辑就好了,这样便于统一调整分布式锁相关配置

以上纯属个人思路,有错误的地方敬请指正

分布式锁实现思路及开源项目集成到springmvc并使用的更多相关文章

  1. redis整理:常用命令,雪崩击穿穿透原因及方案,分布式锁实现思路,分布式锁redission(更新中)

    redis个人整理笔记 reids常见数据结构 基本类型 String: 普通key-value Hash: 类似hashMap List: 双向链表 Set: 不可重复 SortedSet: 不可重 ...

  2. Redisson实现分布式锁(3)—项目落地实现

    Redisson实现分布式锁(3)-项目落地实现 有关Redisson实现分布式锁前面写了两篇博客作为该项目落地的铺垫. 1.Redisson实现分布式锁(1)---原理 2.Redisson实现分布 ...

  3. Etcd 使用场景:通过分布式锁思路实现自动选主

    分布式锁?选主? 分布式锁可以保证当有多台实例同时竞争一把锁时,只有一个人会成功,其他的都是失败.诸如共享资源修改.幂等.频控等场景都可以通过分布式锁来实现. 还有一种场景,也可以通过分布式锁来实现, ...

  4. ZooKeeper学习(二)ZooKeeper实现分布式锁

    一.简介 在日常开发过程中,大型的项目一般都会采用分布式架构,那么在分布式架构中若需要同时对一个变量进行操作时,可以采用分布式锁来解决变量访问冲突的问题,最典型的案例就是防止库存超卖,当然还有其他很多 ...

  5. 【Redis】利用 Redis 实现分布式锁

    技术背景 首先我们需要先来了解下什么是分布式锁,以及为什么需要分布式锁. 对于这个问题,我们可以简单将锁分为两种--内存级锁以及分布式锁,内存级锁即我们在 Java 中的 synchronized 关 ...

  6. Redis分布式锁 (图解-秒懂-史上最全)

    文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...

  7. SpringCloud(5)之分布式锁实现

    01为什么用分布式锁 在讨论这个问题之前,我们先来看一个业务场景:系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用 ...

  8. MVC使用Redis实现分布式锁

    使用场景 在做Web项目的时候,有很多特殊的场景要使用到锁 比如说抢红包,资源分配,订单支付等场景 就拿抢红包来说,如果一个红包有5份,同时100个人抢如果没有用到锁的话 100个人同时并发都抢成功, ...

  9. 基于Redis实现简单的分布式锁

      在分布式场景下,有很多种情况都需要实现最终一致性.在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和消息的持久化数据源),或者做全局XA ...

随机推荐

  1. CSS-背景-渐变-文本格式化

    1.背景 1.背景色 属性:background-color 取值:合法的颜色值 注意:背景颜色和背景图片默认都从边框位置处开始填充 2.背景图片 属性:background-image 取值:url ...

  2. Django的学习(二)————Templates

    一.django的模板: 在settings.py的文件中可以看到并设置这个模板. 1.直接映射: 通过建立的文件夹(templates)和文件(html)来映射. <!DOCTYPE html ...

  3. 核心一:DI

    1.DI:中文名称:依赖注入 2.英文名称:(Dependency Injection) 3.DI是什么?? 3.1 DI和IoC是一样的 3.2 当一个类(A)中需要依赖另一类(B)对象时,把B赋值 ...

  4. java保存繁体字到数据库时就报错Incorrect string value: '\xF0\xA6\x8D\x8B\xE5\xA4...' for column 'name' at row 1

    问题分析 普通的字符串或者表情都是占位3个字节,所以utf8足够用了,但是移动端的表情符号占位是4个字节,普通的utf8就不够用了,为了应对无线互联网的机遇和挑战.避免 emoji 表情符号带来的问题 ...

  5. 6. Uniforms in American's Eyes 美国人眼里的制服

    6. Uniforms in American's Eyes 美国人眼里的制服 (1) Americans are proud of their variety and individuality,y ...

  6. UVa 10828 Back to Kernighan-Ritchie (数学期望 + 高斯消元)

    题意:给定一个 n 个结点的有向图,然后从 1 结点出发,从每个结点向每个后继结点的概率是相同的,当走到一个没有后继结点后,那么程序终止,然后问你经过每个结点的期望是次数是多少. 析:假设 i 结点的 ...

  7. 查阅JDK,collection与collections区别大

    看起来collection,和collections相像,但其中的差别之大你造吗? Collection是Collection层次结构中的根接口.Collection表示一组对象,这也对象也称为col ...

  8. speex编解码在android上实现

    以前在应用中使用到了Speex编解码,近来总结了一下Speex在android上的实现.Speex是一套主要针对语音的开源免费,无专利保护的音频压缩格式.Speex工程着力于通过提供一个可以替代高性能 ...

  9. centos rpm包下载地址

    这个是6.5的下载地址,其他版本可以退回目录找相应的版本 http://vault.centos.org/6.5/updates/x86_64/Packages/

  10. iOS 使用代码创建约束,实现自动布局

    ///与下面约束对象属性截图相对应//使用Auto Layout约束,禁止将Autoresizing Mask转换为约束 [self.funcView setTranslatesAutoresizin ...