zoukankan      html  css  js  c++  java
  • 使用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个定时任务,模拟多个节点的情况,注意,每次启动的时候,需要修改配置文件中的对应的

    server.port=7902 

    将端口号改成不同。
    测试结果如下:

    节点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值了,从而实现了类似加锁的功能。

  • 相关阅读:
    jquery实现选项卡(两句即可实现)
    常用特效积累
    jquery学习笔记
    idong常用js总结
    织梦添加幻灯片的方法
    LeetCode "Copy List with Random Pointer"
    LeetCode "Remove Nth Node From End of List"
    LeetCode "Sqrt(x)"
    LeetCode "Construct Binary Tree from Inorder and Postorder Traversal"
    LeetCode "Construct Binary Tree from Preorder and Inorder Traversal"
  • 原文地址:https://www.cnblogs.com/zhaoyan001/p/9006570.html
Copyright © 2011-2022 走看看