分布式锁实现思路及开源项目集成到springmvc并使用

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

在使用多线程时,为了让某一资源某一时刻只能有一个操作者,经常使用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包也要引入哦)

 1 <!--Redisson -->
 2         <dependency>
 3           <groupId>org.redisson</groupId>
 4           <artifactId>redisson</artifactId>
 5           <version>3.4.3</version>
 6             <exclusions>
 7                 <exclusion>
 8                     <groupId>org.slf4j</groupId>
 9                     <artifactId>slf4j-api</artifactId>
10                 </exclusion>
11                 <exclusion>
12                     <groupId>com.fasterxml.jackson.core</groupId>
13                     <artifactId>jackson-databind</artifactId>
14                 </exclusion>
15                 <exclusion>
16                     <groupId>com.fasterxml.jackson.core</groupId>
17                     <artifactId>jackson-core</artifactId>
18                 </exclusion>
19             </exclusions>
20         </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进行了测试,同时也可以部署两套,同时发起请求进行测试
 1     @Autowired
 2     private RedissonClient redissonClient;
 3
 4     private static AtomicInteger st = new AtomicInteger(0);
 5
 6     @RequestMapping("/test/redission")
 7     public void test() {
 8         st.getAndSet(0);
 9         for(int i=0;i<=9999;i++){
10             new Thread(new Runnable() {
11                 @Override
12                 public void run() {
13                     test(String.valueOf(st.getAndIncrement()));
14                 }
15             }).start();
16         }
17
18     }
19
20     public void test(String value) {
21         RLock rLock = redissonClient.getLock("anyLock");
22         boolean res = false;
23         try {
24             res = rLock.tryLock(200, 10, TimeUnit.SECONDS);
25             if (res) {
26                 System.out.println(String.format("%04d",Integer.valueOf(value)));
27                 //System.out.println("开始执行业务:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()) + ", 取得的值为:" + String.valueOf(value));
28                 if(Integer.valueOf(value) % 1000 == 0){
29                     Thread.sleep(1000);
30                 } else {
31                     //Thread.sleep(1000);
32                 }
33                 //System.out.println("业务执行结束:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()));
34             } else {
35                 System.out.println("not lock:"+String.format("%04d",Integer.valueOf(value)));
36             }
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         } finally {
40             if(rLock.isHeldByCurrentThread()){
41                 rLock.unlock();
42             }
43         }
44     }

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

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

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

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

时间: 2024-10-06 20:26:25

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

使用redis分布式锁来解决集群项目的定时任务冲突问题

import org.apache.commons.lang3.StringUtils; import org.springframework.scheduling.annotation.Scheduled; public class Test { // @Scheduled(cron="0 */1 * * * ?")//(每隔1分钟的整数倍) public void closeOrderTask(){ System.out.println("关闭订单定时任务启动"

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

一.简介 在日常开发过程中,大型的项目一般都会采用分布式架构,那么在分布式架构中若需要同时对一个变量进行操作时,可以采用分布式锁来解决变量访问冲突的问题,最典型的案例就是防止库存超卖,当然还有其他很多的控制方式,这篇文章我们讨论一下怎么使用ZooKeeper来实现分布式锁. 二.Curator 前面提到的分布式锁,在ZooKeeper中可以通过Curator来实现. 定义:Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开

基于Redis实现分布式锁-Redisson使用及源码分析

在分布式场景下,有很多种情况都需要实现最终一致性.在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和消息的持久化数据源),或者做全局XA事务(两阶段提交,数据源可分开),也可以借助消息中间件(消费者处理需要能幂等).通过Observer模式来发布领域事件可以提供很好的高并发性能,并且事件存储也能追溯更小粒度的事件数据,使各个应用系统拥有更好的自治性. 本文主要探讨另外一种实现分布式最终一致性的解决方案--采用分布式锁.基于分布式锁的解决

[转载] zookeeper 分布式锁服务

转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那一层来挡.当大量的行锁.表锁.事务充斥着数据库的时候.一般web应用很多的瓶颈都在数据库上,这里给大家介绍的是减轻数据库锁负担的一种方案,使用zookeeper分布式锁服务. zookeeper是hadoop下面的一个子项目, 用来协调跟hadoop相关的一些分布式的框架, 如hadoop, hiv

Java分布式锁实现详解

在进行大型网站技术架构设计以及业务实现的过程中,多少都会遇到需要使用分布式锁的情况.那么问题也就接踵而至,哪种分布式锁更适合我们的项目? 下面就这个问题,我做了一些分析: 分布式锁现状: 目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题. 分布式的CAP理论告诉我们"任何一个分布式系统都无法同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项.&qu

基于zookeeper实现分布式锁

一.分布式锁介绍 分布式锁主要用于在分布式环境中保护跨进程.跨主机.跨网络的共享资源实现互斥访问,以达到保证数据的一致性. 线程锁:大家都不陌生,主要用来给方法.代码块加锁.当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个线程在执行该段代码.当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段.但是,其余线程是可以访问该对象中的非加锁代码块的. 进程锁:也是为了控制同一操作系统中多个进程访问一个共享资源,只是因为

深入理解分布式锁

为什么需要分布式锁 如上图,在分布式系统中,订单模块为了迎战高并发,订单服务被横向拆分,拆分成了不同的进程,就像上图,两个人同时访问订单服务,然后订单系统1和订单系统2共用一个Mysql当成数据库,经过他们查询发现仅有一件商品,所以他们自个认为都可以下单 如果不加锁限制,可能会出现库存减为负数的情况 怎么办呢? 如上图 mysql自带行级锁,可以考虑使用它的行级锁,可以保证数据的安全,但是不足之处也跟着来了,使用MySql的行级锁,系统的中压力就全部集中在mysql,那mysql就是系统吞吐量的

DIOCP开源项目-高效稳定的服务端解决方案(DIOCP + 无锁队列 + ZeroMQ + QWorkers) 出炉了

[概述] 自从上次发布了[DIOCP开源项目-利用队列+0MQ+多进程逻辑处理,搭建稳定,高效,分布式的服务端]文章后,得到了很多朋友的支持和肯定.这加大了我的开发动力,经过几个晚上的熬夜,终于在昨天晚上,DEMO基本成型,今天再加入了QWorkers来做逻辑处理进程,进一步使得逻辑处理进程更加方便和高效.今天特意写篇blog来记录我的心得与大家分享. [功能实现说明] 沿用上次的草图 目前DEMO图上的功能都已经实现.下面谈谈各部分的实现. 通信服务, 由DIOCP实现,担当与客户端的通信工作

BIRT报表在开源项目jeesite项目中的集成

由于业务需求需要接触一些开源的报表,因为这方面需求量不是很大,而且小项目没办法直接购买润乾,帆软报表等,最近开始研究了一下开源的图表开发. 1.JFreeChart 优点网上例子代码多,上手方便,缺点生成的图表有些不够清晰,采用纯编码方式,不够直观. 2.BIRT报表,BIRT报表是IBM公司提供的开源项目 ,优点:大公司提供,品质值得信赖,而且还有集成在Eclipse中的开发工具,直观方便,而且和以前用过的其他报表的设计思路相差不大,可以生成复杂的图表.最大的好处是可以直接嵌入到已经开发好的J