常用的分布式锁和 Redis 和 zk 两种分布式锁的对比:https://www.cnblogs.com/codingmode/p/15331731.html
要点:
借助中间件Redis的setnx()命令实现分布式锁。setnx()命令只会对不存在的key设值,返回1代表获取锁成功。对存在的key设值,会返回0代表获取锁失败。
    public boolean lock1(KeyPrefix prefix, String key, String value, Long lockExpireTimeOut,
                         Long lockWaitTimeOut) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + key;
            Long deadTimeLine = System.currentTimeMillis() + lockWaitTimeOut;
 
            for (;;) {
                String result = jedis.set(realKey, value, "NX", "PX", lockExpireTimeOut);
 
                if ("OK".equals(result)) {
                    return true;
                }
 
                lockWaitTimeOut = deadTimeLine - System.currentTimeMillis();
 
                if (lockWaitTimeOut <= 0L) {
                    return false;
                }
            }
        } catch (Exception ex) {
            log.info("lock error");
        } finally {
            returnToPool(jedis);
        }
 
        return false;
    }

  

    public boolean unlock1(KeyPrefix prefix, String key, String value) {
 
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + key;
 
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 
            Object result = jedis.eval(luaScript, Collections.singletonList(realKey),
                    Collections.singletonList(value));
 
            if ("1".equals(result)) {
                return true;
            }
 
        } catch (Exception ex) {
            log.info("unlock error");
        } finally {
            returnToPool(jedis);
        }
        return false;
 
    }

一、 redis分布式锁原理
实现原理
获取Redis锁的命令:
SET resource_name my_random_value NX PX 30000
resource_name:资源名称,可根据不同的业务区分不同的锁
my_random_value:随机值,每个线程的随机值都不同,用于释放锁时的校验,最好使用当前线程的id
NX:key不存在的时候设置成功,key存在则设置不成功
PX:自动失效时间,出现异常情况,锁可以过期自动释放
利用NX的原子性,多个线程并发时,只有一个线程可以设置成功,设置成功即可获得锁,可以执行后续的业务处理。
如果出现异常,超过锁的有效期,锁自动释放,释放锁采用Redis的delete命令
释放锁时校验之前设置的随机数,相同才能释放。
二、基于Redis的Setnx实现分布式锁
POM
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.properties 配置redis:spring.redis.host=192.168.73.130

package com.example.distributelock.controller;

import com.example.distributelock.lock.RedisLock;
import com.example.distributelock.lock.ZkLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class RedisLockController {
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("redisLock")
    public String redisLock(){
        log.info("我进入了方法!");
        try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
            if (redisLock.getLock()) {
                log.info("我进入了锁!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }

    @RequestMapping("zkLock")
    public String zkLock(){
        log.info("我进入了方法!");
        try (ZkLock zkLock = new ZkLock("localhost:2181","order")){
            if (zkLock.getLock()) {
                log.info("我进入了锁!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}

Redis的Setnx实现分布式锁操作。
解锁:利用Redis的lua脚本来实现解锁操作的原子性。参考:https://blog.csdn.net/varyall/article/details/117913979

package com.example.distributelock.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@Slf4j
public class RedisLock implements AutoCloseable {

    private RedisTemplate redisTemplate;
    private String key;
    private String value;
    //单位:秒
    private int expireTime;

    public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime=expireTime;
        this.value = UUID.randomUUID().toString();
    }

    /**
     * 获取分布式锁
     * @return
     */
    public boolean getLock(){
        RedisCallback<Boolean> redisCallback = connection -> {
            //设置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            //设置过期时间
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            //执行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return result;
        };

        //获取分布式锁
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        return lock;
    }

    public boolean unLock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);

        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果:"+result);
        return result;
    }


    @Override
    public void close() throws Exception {
        unLock();
    }
}

redis缓存穿透,击穿和雪崩以及解决方案:https://blog.csdn.net/m0_37937394/article/details/122564362
一:redis雪崩
redis雪崩是指redis在某个时间大量失效,突然造成数据库访问压力急剧增大,像雪崩一样,redis雪崩危害巨大,甚至有可能服务器宕机,给公司造成巨大的经济损失。
解决方案:设置超时时间的时候要设置随机值,不要设置固定值
二: redis缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
1.设置并发锁,防止请求大量请求数据库,如果获取到锁了,去数据库查询,如果没有,说明有其他线程在查询数据库,那么只需要重试一下就好了。

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
	       if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
		       value = db.get(key);
		       redis.set(key, value, expire_secs);
		       redis.del(key_mutex);
	       } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
		      Thread.sleep(50);
		      get(key);  //重试
	       }
       } else {
              return value;      
       }
}

 

2.设置拦截器,对于不存在得key,进行拦截
三:缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
1.设置热点数据永不过期。
2.加互斥锁:其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

static Lock reenLock = new ReentrantLock();
public String findPubConfigByKey1(String key) throws InterruptedException {
        PubConfig result = new PubConfig();
        // 从缓存读取数据
        result = redisService.getObject(PubConfigKeyConstants.TABLE_NAME + "_"+key, PubConfig.class) ;
        if (result== null ) {
            if (reenLock.tryLock()) {
                try {
                    System.out.println("拿到锁了,从DB获取数据库后写入缓存");
                    // 从数据库查询数据
                    result = pubConfigRepository.queryPubConfigInfoByKey(key);
                    // 将查询到的数据写入缓存
                    Gson g = new Gson();
                    String value = g.toJson(result);
                    redisService.setNx(PubConfigKeyConstants.TABLE_NAME + "_"+key, value);
                } finally {
                    reenLock.unlock();// 释放锁
                }
 
            } else {
                // 先查一下缓存
                result = redisService.getObject(PubConfigKeyConstants.TABLE_NAME + "_"+key, PubConfig.class) ;
                if (result== null) {
                    System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
                    Thread.sleep(100);// 小憩一会儿
                    return findPubConfigByKey1(key);// 重试
                }
            }
        }
        return result.getValue();
}

https://baijiahao.baidu.com/s?id=1707897582913572517&wfr=spider&for=pc

posted on 2022-07-04 13:47  邢帅杰  阅读(150)  评论(0编辑  收藏  举报