JAVA RedisTemplate实现(加锁/解锁) 解决高并发问题

基于传统的单机模式下的并发锁,已远远不能满足当下高并发大负载的情况,当下常用的并发处理如下

       1、使用synchronized关键字

        2、select    for update   乐观锁

        3、使用redis实现同步锁

方案一 适合单机模式,

方案二 虽然满足多节点服务实例但 对变更操作的吞吐量有影响

方案三 基于redis nosql数据库  在效率与横向扩展方面都大大优于前两种方案

redis  单线程   在自身设计上一定程度可避免想成不安全  再者其效率高于关系型数据库

本次实现锁机制  基于redis 的两个指令 查询 redis.cn  网站  

指令一:SETNX key value

key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。

返回值

Integer reply, 特定值:

  • 1 如果key被设置了
  • 0 如果key没有被设置

 指令二:GETSET key value

自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。

返回值

bulk-string-reply: 返回之前的旧值,如果之前Key不存在将返回nil

步骤一, pom文件

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>

步骤二,RedisCacheAutoConfiguration

package com.wh.whcloud.df.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wh.whcloud.core.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis缓存配置
 *
 * @author 
 */
@Slf4j
@Configuration
@AutoConfigureAfter({RedisAutoConfiguration.class})
public class RedisCacheAutoConfiguration {

    /**
     * 重新配置一个RedisTemplate
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setDefaultSerializer(jackson2JsonRedisSerializer);
        log.info("RedisTemplate配置 [{}]", template);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(RedisUtil.class)
    @ConditionalOnBean(RedisTemplate.class)
    public RedisUtil redisUtils(RedisTemplate redisTemplate) {
        RedisUtil redisUtil =   new RedisUtil(redisTemplate);
        log.info("RedisUtil [{}]", redisUtil);
        return redisUtil;
    }
}

步骤三, RedisLock 加解锁实现

package com.wh.whcloud.util;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

public class RedisLock {

    private static final Long SUCCESS = 1L;
    private long timeout = 9999; //获取锁的超时时间

    /**
     * 加锁,无阻塞
     *
     * @param
     * @param
     * @return
     */
    public static Boolean tryLock(RedisTemplate redisTemplate, String key, String value, long expireTime) {
        try {
            //SET命令返回OK ,则证明获取锁成功
            Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
                return ret;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

/*
        Long start = System.currentTimeMillis();
        for(;;){
        //SET命令返回OK ,则证明获取锁成功
        Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
            return ret;
        //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
        long end = System.currentTimeMillis() - start;
        if (end>=timeout) {
            return false;
        }
    }*/


    /**
     * 解锁
     *
     * @param
     * @param
     * @return
     */
    public static Boolean unlock(RedisTemplate redisTemplate, String key, String value) {
        try {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
            //redis脚本执行
            //Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value))
            Object result = redisTemplate.delete(Collections.singletonList(key));
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

}

注意:   

redisTemplate.execute 是redis脚本执行,可能报错, 具体参考: https://blog.csdn.net/cevery/article/details/108303919

测试:我这里自己创建两个 线程测试

@Slf4j
@Component
public class Schedule {

  @Resource
  private RedisTemplate redisTemplate;

  @Scheduled(cron = "0 0/1 * * * ? ")
  public void getDeviceStatus() throws InterruptedException {

    //启动两个线程,测试哪一个能够悠闲抢到Redis锁
    Test1Runnable test1 = new Test1Runnable();
    new Thread(test1).start();
    Test2Runnable test2 = new Test2Runnable();
    new Thread(test2).start();
  }

  //自定义个唯一的Key值
  public String key = "ZX123456789";
  //保存数据方法
  public boolean testSave(){
    //获取锁
    boolean isOK = RedisLock.tryLock(redisTemplate, key, "key+1", 2000);
    //处理业务 ,然后释放锁
    if(isOK){
      System.out.println("处理完业务,释放锁==="+RedisLock.unlock(redisTemplate, key, "key+1"));
    }
    return isOK;
  }

  class Test1Runnable implements Runnable {
    @Override
    public void run() {
      boolean result = testSave();
      log.info(Thread.currentThread().getName()+"Test1获取锁:"+result);
    }
  }

  class Test2Runnable implements Runnable {
    @Override
    public void run() {
      boolean result = testSave();
      log.info(Thread.currentThread().getName()+"Test2获取锁:"+result);
    }
  }
}

效果:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值