使用Redis模拟简单分布式锁,解决单点故障的问题
需求描述:
最近做一个项目,项目中有一个功能,每天定时(凌晨1点)从数据库中获取需要爬虫的URL,并发送到对应的队列中,然后客户端监听对应的队列,然后执行任务。如果同时部署多个定时任务节点的话,每个节点都会去查数据库,然后将查到的url发送到队列中,这样的话,客户端就会执行很多重复的任务,如果不同时部署多个节点的话,又存在单点故障的风险。要解决这种类似的问题,可以使用分布式锁来实现,当节点获取到锁的时候就执行任务,没有获取到锁的时候,就不执行任务,这样就解决了多节点同时执行任务的问题,实现分布式锁有多种方法,例如zookeeper,数据库等,今天就用Redis来模拟实现一个简单的分布式锁,来解决这个问题。
一、新建工程
本例是基于前面的
springboot整合H2内存数据库,实现单元测试与数据库无关性
二、工程结构
三、添加配置文件
########################################################
###REDIS (RedisProperties) redis基本配置;
########################################################
# database name
spring.redis.database=0
# server host1 单机使用,对应服务器ip
#spring.redis.host=127.0.0.1
# server password 密码,如果没有设置可不配
#spring.redis.password=
#connection port 单机使用,对应端口号
#spring.redis.port=6379
# pool settings ...池配置
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
# name of Redis server 哨兵监听的Redis server的名称
spring.redis.sentinel.master=mymaster
# comma-separated list of host:port pairs 哨兵的配置列表
spring.redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26479,127.0.0.1:26579 ##########################################################
############jpa配置########################################
#########################################################
# 服务器端口号
server.port=7902
# 是否生成ddl语句
spring.jpa.generate-ddl=false
# 是否打印sql语句
spring.jpa.show-sql=true
# 自动生成ddl,由于指定了具体的ddl,此处设置为none
spring.jpa.hibernate.ddl-auto=none
# 使用H2数据库
spring.datasource.platform=h2
# 指定生成数据库的schema文件位置
spring.datasource.schema=classpath:schema.sql
# 指定插入数据库语句的脚本位置
spring.datasource.data=classpath:data.sql
# 配置日志打印信息
logging.level.root=INFO
logging.level.org.hibernate=INFO
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.hibernate.type.descriptor.sql.BasicExtractor=TRACE
logging.level.com.itmuch=DEBUG
四、定时任务实现
package com.chhliu.springboot.singlenode.solve.task; import java.util.List;
import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import com.chhliu.springboot.singlenode.solve.entity.User;
import com.chhliu.springboot.singlenode.solve.repository.UserRepository; @Service
public class ScheduledTasks { @Autowired
private UserRepository repository; @Autowired
private StringRedisTemplate stringRedisTemplate; private static final String LOCK = "task-job-lock"; private static final String KEY = "tasklock"; //每1分钟执行一次
@Scheduled(cron = "0 0/1 * * * ?")
public void reportCurrentByCron() throws InterruptedException{
boolean lock = false;
try{
// 获取锁
lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK);
System.out.println("是否获取到锁:"+lock);
if(lock){
// 如果在执行任务的过程中,程序突然挂了,为了避免程序因为中断而造成一直加锁的情况产生,20分钟后,key值失效,自动释放锁,
stringRedisTemplate.expire(KEY, 20, TimeUnit.MINUTES);
List<User> users = repository.findAll();
if(null != users && !users.isEmpty()){
for(User u:users){
System.out.println("name:"+u.getName());
}
}
// 模拟长时间任务
TimeUnit.MINUTES.sleep(3);
}else{
System.out.println("没有获取到锁,不执行任务!");
return;
}
}finally{// 无论如何,最终都要释放锁
if(lock){// 如果获取了锁,则释放锁
stringRedisTemplate.delete(KEY);
System.out.println("任务结束,释放锁!");
}else{
System.out.println("没有获取到锁,无需释放锁!");
}
}
}
}
五、测试
package com.chhliu.springboot.singlenode.solve; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication
@EnableScheduling
public class SinglenodeSolveApplication { public static void main(String[] args) {
SpringApplication.run(SinglenodeSolveApplication.class, args);
}
}
同时启动3个定时任务,模拟多个节点的情况,注意,每次启动的时候,需要修改配置文件中的对应的
将端口号改成不同。
测试结果如下:
节点1:
是否获取到锁:true
2017-01-22 14:37:00.099 INFO 1932 --- [pool-2-thread-1] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.balance as balance3_0_, user0_.name as name4_0_, user0_.username as username5_0_ from user user0_
name:张三
name:李四
name:王五
name:马六
任务结束,释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
上面的结果是节点1的执行结果。
节点2:
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
节点3:
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:false
没有获取到锁,不执行任务!
没有获取到锁,无需释放锁!
是否获取到锁:true // 此时节点1已经释放了锁,节点3获取到了锁
2017-01-22 14:41:00.072 INFO 5332 --- [pool-2-thread-1] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.balance as balance3_0_, user0_.name as name4_0_, user0_.username as username5_0_ from user user0_
name:张三
name:李四
name:王五
name:马六
以上测试结果是5分钟内的情况。
通过上面的测试,就基本上实现了利用模拟Redis的分布式锁来实现多节点中,同时只有一个节点在运行的目的。
六、原理分析
为什么,我们可以用Redis来实现简单的分布式锁的模拟了,这和Redis的一个命令相关,该命令是setnx key value
该命令的作用是,当往Redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入Redis并返回1,根据这个特性,我们在程序中,每次都调用setIfAbsent(该方法是setnx命令的实现)方法,来模拟是否获取到锁,如果返回true,则说明该key值不存在,表示获取到锁,如果返回false,则说明该key值存在,已经有程序在使用这个key值了,从而实现了类似加锁的功能。
使用Redis模拟简单分布式锁,解决单点故障的问题的更多相关文章
- 基于Redis的简单分布式锁的原理
参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...
- springmvc单Redis实例实现分布式锁(解决锁超时问题)
一.前言 关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完的问题, 这样会导致: A线程超时时间设为10s(为了解决死锁问题 ...
- Redis系列(二)--分布式锁、分布式ID简单实现及思路
分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...
- redis客户端、分布式锁及数据一致性
Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...
- C# 用Redis实现的分布式锁
Redis实现分布式锁(悲观锁/乐观锁) 对锁的概念和应用场景在此就不阐述了,网上搜索有很多解释,只是我搜索到的使用C#利用Redis的SetNX命令实现的锁虽然能用,但是都不太适合我需要的场景. 基 ...
- redis系列:分布式锁
redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- Redis如何实现分布式锁
今天我们来聊一聊分布式锁的那些事. 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作.在计算机科学中,锁(lock ...
- Redis高并发分布式锁详解
为什么需要分布式锁 1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock. 2.但是随 ...
- 一个Redis实现的分布式锁
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...
随机推荐
- 百年老图难倒谷歌AI,兔还是鸭?这是个问题
上面这张图,画的是鸭子还是兔子? 自从1892年首次出现在一本德国杂志上之后,这张图就一直持续引发争议.有些人只能看到一只兔子,有些人只能看到一只鸭子,有些人两个都能看出来. 心理学家用这张图证明了一 ...
- UOJ274 [清华集训2016] 温暖会指引我们前行 【LCT】【最大生成树】
题目分析: 差评,最大生成树裸题.hack数据还卡常. 代码: #include<bits/stdc++.h> using namespace std; ; struct LCT{ ],d ...
- Mysql 函数大全- 5.6 中文解释函数参考
mysql 函数大全 5.6 函数参考 5.6函数参考 (只翻译部分,细节查看相关英文版) 12.1功能和操作员参考 12.2表达式评估中的类型转换 12.3运营商 12.4控制流功能 12.5 ...
- PHP linux ZendGuardLoader.so: undefined symbol: executor_globals
/usr/xxx/php xxx/xxx.php 报了这个错. 本人出现此问题的原因: php执行程序路径错了. 解决: linux下执行 which php 命令 查看php真实路 ...
- 洛谷P4581 [BJOI2014]想法(玄学算法,拓扑排序)
洛谷题目传送门 萝卜大毒瘤 题意可以简化成这样:给一个DAG,求每个点能够从多少个入度为\(0\)的点到达(记为\(k\)). 一个随机做法:给每个入度为\(0\)的点随机一个权值,在DAG上求出每个 ...
- new 经典基础模板总结
NOIP-NOI-ZJOI基础模板总结 目录 C++语言和STL库操作 重载运算符操作 /* 重载运算符 格式 如重载小于号 这里是以x递减为第一关键字比较,y递减为第二关键字比较 */ bool o ...
- 洛谷P3740 【[HAOI2014]贴海报】
(呃...本蒟蒻的第一篇题解qwq)..不废话了讲正题..思路来源于铺地毯(-->传送门)..先算出每一个格子上覆盖的海报并把可见的海报做标记然后算出有多少海报是可见的..但是作为省选题怎么可能 ...
- php 限制类的对象类型
事实上,采用哪种处理参数类型的策略,取决于任何潜在bug的严重程度.通常PHP会根据语境自动转换大多数基本数据类型. 因此,你需要在检测类型.转换类型和依赖良好清晰的文档(无论决定用哪一种,都应该提供 ...
- This license xxx has been cancelled 解决
上节回顾:JetBrains全家桶破解思路 hosts屏蔽一下即可,Linux是:/etc/hosts 0.0.0.0 account.jetbrains.com 重新输入Code即可,最后补一个地址 ...
- 万物互联之~RPC专栏
3.RPC引入 上篇回顾:万物互联之~深入篇 Code:https://github.com/lotapp/BaseCode/tree/master/python/6.net/6.rpc/ 其他专栏最 ...