• redisTemplate (实现nx分布式锁)


     方式1: 

    获取一个redis分布锁
    /**
         * 获取一个redis分布锁
         *
         * @param lockKey        锁住的key
         * @param lockExpireMils 锁住的时长。如果超时未解锁,视为加锁线程死亡,其他线程可夺取锁
         * @return
         */
        public boolean lock(String lockKey, long lockExpireMils) {
            return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
                long nowTime = System.currentTimeMillis();
                Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes());
                if (acquire) {
                    return Boolean.TRUE;
                } else {
                    byte[] value = connection.get(lockKey.getBytes());
                    if (Objects.nonNull(value) && value.length > 0) {
                        long oldTime = Long.parseLong(new String(value));
                        if (oldTime < nowTime) {
                            //connection.getSet:返回这个key的旧值并设置新值。
                            byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes());
                            //当key不存时会返回空,表示key不存在或者已在管道中使用
                            return oldValue == null ? false : Long.parseLong(new String(oldValue)) < nowTime;
                        }
                    }
                }
                return Boolean.FALSE;
            });
        }

    删除锁:(不建议,建议使用方式2的删除) // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除

    /**
         * redis 解锁:eval函数在redis集群环境中不支持, 具体查看spring源码
         * @See JedisClusterScriptingCommands
         *
         * Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
         * @param lockName
         * @param uniqueId
         */
        public void unlock(String lockName, String uniqueId) {
            String lockKey = LOCK_PREFIX+lockName;
            String successMsg = StrUtil.format("{}-{}解锁成功[Redis]!",lockName,uniqueId);
            String failMsg = StrUtil.format("{}-{}解锁失败[Redis]!",lockName,uniqueId);
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Boolean> redisScript = new DefaultRedisScript(script,Boolean.class);
            Boolean result = redisTemplate.execute(redisScript, new StringRedisSerializer(), new FastJsonRedisSerializer(Boolean.class), Collections.singletonList(lockKey),uniqueId);
            printResult(result, successMsg, failMsg);
        }

     

    -----------方式2---------------------------------------------

    原文:https://blog.csdn.net/qq_28397259/article/details/80839072

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisCluster;
    import redis.clients.jedis.JedisCommands;
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    /**
     * *************************************************************************
     * <PRE>
     *  @ClassName:    : RedisDistributedLock 
     *
     *  @Description:    : 
     *
     *  @Creation Date   : Jan 28, 2021 6:47:03 PM
     *
     *  @Author          :  Sea
     *  
     *
     * </PRE>
     **************************************************************************
     */
    @Component
    public class RedisDistributedLock{
     
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
     
        public static final String UNLOCK_LUA;
     
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("if redis.call("get",KEYS[1]) == ARGV[1] ");
            sb.append("then ");
            sb.append("    return redis.call("del",KEYS[1]) ");
            sb.append("else ");
            sb.append("    return 0 ");
            sb.append("end ");
            UNLOCK_LUA = sb.toString();
        }
     
        private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
     
        public boolean setLock(String key, long expire) {
            try {
                RedisCallback<String> callback = (connection) -> {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    return commands.set(key, uuid, "NX", "PX", expire);
                };
                String result = redisTemplate.execute(callback);
     
                return !StringUtils.isEmpty(result);
            } catch (Exception e) {
                logger.error("set redis occured an exception", e);
            }
            return false;
        }
     
        public String get(String key) {
            try {
                RedisCallback<String> callback = (connection) -> {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    return commands.get(key);
                };
                String result = redisTemplate.execute(callback);
                return result;
            } catch (Exception e) {
                logger.error("get redis occured an exception", e);
            }
            return "";
        }
     
        public boolean releaseLock(String key,String requestId) {
            // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
            try {
                List<String> keys = new ArrayList<>();
                keys.add(key);
                List<String> args = new ArrayList<>();
                args.add(requestId);
     
                // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
                // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
                RedisCallback<Long> callback = (connection) -> {
                    Object nativeConnection = connection.getNativeConnection();
                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                    // 集群模式
                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
     
                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    return 0L;
                };
                Long result = redisTemplate.execute(callback);
     
                return result != null && result > 0;
            } catch (Exception e) {
                logger.error("release lock occured an exception", e);
            } finally {
                // 清除掉ThreadLocal中的数据,避免内存溢出
                //lockFlag.remove();
            }
            return false;
        }
     
    }

    不管是加锁还是解锁,都要保持操作的原子性,否则就有可能产生死锁。

    springboot 项目 redisTempalte 配置

    pring.redis.database=0
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    #spring.redis.password=root
    #springboot2.x  need unit   ,db connect time out 
    spring.redis.timeout=60s
    spring.redis.jedis.pool.max-active=-1
    spring.redis.jedis.pool.max-wait=-1s
    spring.redis.jedis.pool.max-idle=300
    spring.redis.jedis.pool.min-idle=5
           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

    配置类:

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    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 java.lang.reflect.Method;
     
     
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport {
     
        @Bean
        public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
     
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(target.getClass().getName());
                    sb.append(method.getName());
                    for (Object obj : params) {
                        sb.append(obj.toString());
                    }
                    return sb.toString();
                }
            };
     
        }
     
        /**
         * 管理缓存
         *
         * @param redisTemplate
         * @return
         */
     
        @SuppressWarnings("rawtypes")
        @Bean
        public CacheManager CacheManager(RedisTemplate redisTemplate) {
            RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
            return rcm;
        }
     
     
        /**
         * redisTemplate配置
         *
         * @param factory
         * @return
         */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Bean
        public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
            StringRedisTemplate template = new StringRedisTemplate(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);
            template.setValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    }
  • 相关阅读:
    MongoDB知识
    SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
    SQL Server 触发器
    TSQL游标使用
    python和C#的区别
    bcp和SqlDataAdapter进行批量跟新插入方法
    SQL Server中索引视图用法详解
    hdu 1950 Bridging signals
    UVA 116 Unidirectional TSP
    poj 3230 Travel
  • 原文地址:https://www.cnblogs.com/lshan/p/14341434.html
Copyright © 2020-2023  润新知