Spring boot 集成redis、redislock

本文详细介绍了Springboot如何集成Redis,包括Redis的特点、数据类型、常用命令及应用场景。此外,还深入讨论了分布式锁的概念、实现原理,并提供了一种避免死锁的解决方案。最后,给出了Springboot中实现Redis分布式锁的示例代码。
摘要由CSDN通过智能技术生成

Spring boot 集成redis、redislock

一、redis

1. redis介绍

  • 定义:redis是用C语言开发的开源高性能基于内存运行的键值对NoSql数据库;
  • 特点:
    • 在6之前是单线程,之后便是多线程
    • 高效性:因为基于内存,读取速度是110000次/s,写的速度是81000次/s;
    • 原子性:redis所有操作都是原子性。支持对几个操作合并后的原子性操作
    • 数据类型丰富
    • 稳定性:持久化、主从复制(集群)
    • ttl(过期时间),事务,消息订阅;
  • 支持的数据类型:
    • string
      • redis最基本的类型,一个key对应一个value;
      • value可以是任何数据,如:图片、序列化对象
      • value最大512M
    • list
      • 一个key有多个value
      • 按照插入顺序排序,可以添加元素到列表的头部或尾部
      • 底层是双向链表,通过索引下标的操作中间的节点性能会较差
    • set
      • 相比list,set可以自动去重,set提供了判断某个成员是否在一个set集合内的重要接口
      • set是String的无序集合,底层其实是一个value为null的hash表。添加、删除、查找的复杂度都是O(1)
    • hash
      • 键值对集合
      • 是string类型的field和value的映射表,hash特别适合用于存储对象
    • zset(sorted set)
      • 不重复
      • 每个成员都关联了一个评分(score),这个评分被用来按照最低分到最高分的方式排序集合中的成员。集合成员唯一、评分不重复;
      • 可以根据评分或次序来获取一个范围的元素;
  • 应用场景:
    • 热点数据缓存,降低数据库io;
    • 分布式架构,session共享
    • 最新数据:通过list实现按自然时间排序的数据
    • 排行榜:利用zset(有序集合)
    • 时效性数据:如手机验证码,Expire过期
    • 计数器,秒杀:原子性、自增方法incr、decr
    • 去除大量数据中的重复数据:利用set集合
    • 构建队列:list集合
    • 发布订阅消息系统:pub/sub
  • 不适用:
    • 需要事务支持
    • 基于sql的结构化查询存储,处理复杂的关系,需要用户自定义条件的查询

2. redis常用命令

  • string
    • get key:获取key的值
    • set key v:设置key的值
    • del key:删除key(应用于所有类型)
    • incr key:将储存的值加上1
    • decr key:将储存的值减去1
    • incrby key amout:加上整数amount
    • decrby key amout:减去整数
    • amountincrbybyfloat key amout:加上浮点数amount字符串二进制
    • append key v:将值追加到key当前储存值的末尾
    • getrange key start end:获取下标start到end的字符串
    • setrange key offset v:将字符串看做二进制位串,并将位串中偏移量为offset的二进制位的值
    • getbit key offset:将字符串看做是二进制位串值为1的二进制位的数量,如果给定了可选的start偏移量和end偏移量,那么只对偏移量指定范围的二进制位进行统计;
    • bitop operation dest-key key-name [key-name …]:对一个或多个二进制位串进行 并and,或 or,异或XOR,非NOT 在内的任意一种安位运算符操作(bitwise operation),并将计算的结果放到dest -key里面
  • list
    • rpush key [v…]:将一个或多个加入列表右端
    • lpush key [v…]:将一个或多个加入列表左端
    • rpop key:移除并返回最右端的元素
    • lpop key:移除并返回列表最左端的元素
    • lindex key size:返回下标(偏移量)为size的元素
    • lrange key start end:返回从start 到end的元素 包含start和end
    • ltrim key start end:只保留从start 到end的元素 包含start和end
  • hash
    • hmget hkey key:获取多个值
    • hmset hkey key v:为多个key设置值
    • hdel hkey key:删除多个值并返回
    • hlen hkey 返回总数量
    • hexists hkey key:检查key是否存在在散列中
    • hkeys hkey:获取散列中所有key
    • hvals hkey:获取三列中所有值
    • hgetall hkey:获取散列
    • hincrby hkey key increment:为key的值上加上整数increment
    • hincrbyfloat hkey key increment:为key的值上加上浮点数increment
  • set
    • sadd key item:添加多个,返回新添加的个数(已存在的不算)
    • `srem key item:从集合移除多个元素 ,返回被移除元素的数量
    • sismember key item:检查元素item是否在集合中
    • scard key:返回集合总数
    • smembers key:返回所有元素
    • srandmember key cout:随机返回cout个元素 cout为正整数 随机元素不重复 相反可能会出现重复
    • spop key:随机的移除一个元素 并返回已删除的元素
    • smove key1 key2 item:如果key1中包含item 移除key1中的item 添加到key2中,成功返回1 失败返回`0
    • `差运算 sdiffstore newkey key key1:将存在于key集合但是不存在key1…集合的其他元素 放
    • `newkey里面(咬掉一口剩下的)
    • 交运算 sinter key:返回所有集合的交集(返回我们都有的的)
    • 交运算 sinterstore newkey key:返回多个集合的交集生成集合newkey
    • 并运算 sunion key:(返回我们不重复的所有元素 )
    • 并运算 sunion newkey key:结果放到new key中
  • zset
    • zadd key score member:添加多个
    • zerm key memer:移除多个
    • zcard key:返回所有成员
    • zincrby key incremnet member:将member成员的分值加上increment
    • zcount key min max:返回分值在 min和max中间的排名
    • zrank key member: 返回成员member在集合中的排名
    • zscore key member: 返回member的分值
    • zrange key start stop:返回 介于两者之间的成员

3. spring boot集成redis

  • pom
		<!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
  • 配置属性

    spring: 
    # redis配置
      redis:
        host: 192.168.5.128
        port: 6379
        password: 123456
        timeout: 1000
        jedis:
          pool:
            min-idle: 5 # 控制一个连接池里最小空闲jedis实例,默认值8
            max-active: 10 # 最大连接实例数,默认8
            max-idle: 10 # 控制一个连接池里最大空闲jedis实例,默认值8
            max-wait: 2000 # 等待可用连接时间单位为毫秒,默认为-1表示永不超时,一旦超过等待时间则直接抛出
    
  • 编码

    • 启动类:无需多余注解

    • 测试类:

      // 获取一个String类型的key
      System.out.println("获取一个String类型的key:" + stringRedisTemplate.opsForValue().get("name"));
      // 获取一个key值
      System.out.println("获取一个key值:" + redisTemplate.opsForValue().get("name"));
      // 判断某个key是否存在
      Boolean a = redisTemplate.hasKey("a");
      System.out.println("判断某个key是否存在:" + a);
      // 删除key
      if (a){
          redisTemplate.delete("a");
          System.out.println("删除了key为a");
      }
      // 指定key的失效时间:key,时间,单位
      redisTemplate.expire("name", 1000, TimeUnit.MILLISECONDS);
      // 获取某个key的过期时间
      System.out.println("获取某个key的过期时间:" + redisTemplate.getExpire("name"));
      

3. 异常

  • 出现问题的代码环境:
      //redis用的jdk默认的序列化,这样存进去会出现乱码
       redisTemplate.opsForValue().set("user","admin");
  • 进入容器查看key
127.0.0.1:6379> keys *
1) "name"
2) "a"
3) "\xac\xed\x00\x05t\x00\x04user"
4) "age"

  • 原因:

    • spring-data-redis的RedisTemplate<K, V>在操作redis时默认使用JdkSerializationRedisSerializer来进行序列化
  • 解决:

    • 方案一:更改序列化方式(不推荐)
    @Autowired(required = false)
        public void setRedisTemplate(RedisTemplate redisTemplate) {
            RedisSerializer stringSerializer = new StringRedisSerializer();
            redisTemplate.setKeySerializer(stringSerializer);
            redisTemplate.setValueSerializer(stringSerializer);
            redisTemplate.setHashKeySerializer(stringSerializer);
            redisTemplate.setHashValueSerializer(stringSerializer);
            this.redisTemplate = redisTemplate;
        }
    
    • 方案二:使用StringRedisTemplate
      @Autowired
       private StringRedisTemplate stringRedisTemplate;
    stringRedisTemplate.opsForValue().set("user","admin");
    System.out.println(stringRedisTemplate.opsForValue().get("name"));
    

4. StringRedisTemplate和RedisTemplate区别

  • RedisTemplate使用的是 JdkSerializationRedisSerializer
    • 可以用来存储对象,但是要实现Serializable接口
    • 以二进制方式存储,内容没有可读性
  • StringRedisTemplate使用的是 StringRedisSerializer序列化String
    • 主要用来存储字符串,StringRedisSerializer的泛型指定的是String,当存入对象的时候回报错:can not cast into String
    • 可见性强,更易维护

二、redis lock

1. 分布式锁

  • 定义:应用于分布式环境下多个节点之间进行同步或者协作的锁;
  • 特性:
    • 互斥:保证只有持有锁的实例中的线程才能操作
    • 可重入:同一个实例的同一个线程可以多次获取锁
    • 锁超时:支持超时自动释放锁,避免死锁;
    • 谁加的锁只能谁释放;

2. Redis lock实现原理

加锁时存入,释放锁则删除;

两个方法可以实现:

  • setnx(set if not exists):存在则不操作返回0,不存在则set返回1,这是redis的方法;
  • setIfAbsent:同setnx一样,set的key不存在则set并返回true,存在不操作,返回0;这是Java的方法;

3. 如何避免死锁

  • 在set的时候,再给key设置一个自动过期时间
    • 在2.6之前,语法是 setnx key val,不支持超时设置,要与expire配合
    • 在2.6之后,进行了增强,支持超时设置
  • 但setIfAbsent底层有一个设置过期时间的重载方法;

4. 在项目中配置redis lock

  • utils:自己封装一个redis lock utils,有加锁方法,释放锁方法,具体思路如下:
    • 加锁方法:
      • 使用setIfAbsent方法存入key,如果是第一次set则直接返回;操作成功
      • 如果之前已set,说明持有锁者还在使用资源,此时需要续费,自动延长过期时间
        • 使用getAndSet方法重新设置该key的值
    • 释放锁:
      • 删除该key:
        • 在删除之前,先判断该key的值是否为空且取出来的value要与传进来的value相等
@Slf4j
@Component
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean lockV3(String key, String value) {
        // 当加锁字段不存在时,返回true,
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        // 如果key存在,则取出来
        String oldValue = redisTemplate.opsForValue().get(key);
        // 判断key不为空,并且还没有过期
        if (Objects.nonNull(oldValue) && System.currentTimeMillis() > Long.parseLong(oldValue)) {
            // 获取原来的key并重新赋值
            String valueByGetAndSet = redisTemplate.opsForValue().getAndSet(key, value);
            // 复制成功或者旧值与现在的值相等
            if (Objects.isNull(valueByGetAndSet) || valueByGetAndSet.equals(oldValue)) {
                return true;
            }
        }

        return false;
    }


    public void unLockV2(String key, String value) {
        String oldValue = redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(oldValue) && oldValue.equals(value)) {
            try {
                redisTemplate.delete(key);
            } catch (Exception e) {
                log.error("解锁失败:{}", e);
            }
        }
    }
}

5. 分布式锁的实现方式

  • MySQL
    • 通过数据库事务+行锁实现
    • 缺点:
      • 需要手动提交事务,如果忘记提交,则会死锁,危险性高;可通过wait设置等待时间
      • 事务中如果有其他逻辑操作,会导致锁时间长,影响性能
    • 注意:查询要有明确主键,无主键则为表锁,涉及数据库操作统一使用for update加锁
  • redis:
    • setnx(set if not exists)
    • setIfAbsent
    • 缺点:
      • 如果set成功,还没来得及释放,服务挂了,这个key永远不会被获取到
      • set时,还没来得及expire,服务挂了,会造成锁不释放
  • zookeeper
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值