• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
自由的代价是孤独丶
博客园    首页    新随笔    联系   管理    订阅  订阅

redis专题总结

1. Redis 支持哪些数据类型

  1. String
存储结构:key-value形式

使用场景:
  * 缓冲-缓存数据,提高查询效率
  * 计数器-incr、decr命令实现访问计数
  * 分布式锁-setnx命令实现分布式锁
  * Session共享-存储用户会话信息

  1. Hash
存储结构:field-value组成的map,适合存储对象

使用场景:
 * 存储对象:用户信息、商品信息等
 * 购物车:用户id作为key、field作为商品id、value作为数量

  1. List
存储结构:双向链表、有序、可重复集合

使用场景:
 * 消息队列
 * 最新列表:朋友圈动态、最新文章等
 * 历史记录:用户搜索历史、浏览记录等

  1. Set
存储结构:哈希表、无序、不可重复集合

使用场景:
 * 标签系统:用户标签、文章标签
 * 共同好友:sinter求2个set集合的交集

  1. Sorted Set
存储结构:Set集合基础上为每个元素关联一个分数score、用于排序

使用场景:
 * 排行榜:积分排行、热搜排行






2. Redis 的特性

  1. 基于内存存储,速度极快
    数据主要在内存中。读写性能极高。

  1. 支持丰富的数据结构
    支持String、Hash、List、Set、Sorted Set等数据类型。

  1. 单线程与原子性
    命令操作是原子的,无需担心并发竞争。

  1. 持久化可选
    支持RDB与AOF持久化。

  1. 发布订阅与Lua脚本
    支持消息通信与复杂原子逻辑






3. Redis 为什么单线程操作的

前言:这里的单线程操作是指在redis中,同一个时间点只用一个CPU线程来处理客户端请求(即:同一个时刻只有一个命令在执行)


这样设计的原因:

 1. 单线程避免了多线程的锁竞争。因为Redis处理速度非常快,不用太考虑锁等待的时间。

 2. 单线程保证了每个redis命令的原子性,要么成功要么失败。






4. Redis 的持久化

持久化:因为redis是一个内存数据库,一旦redis重启后数据就会丢失。持久化就是将redis的数据持久化保存到硬盘上。


Redis 的持久化方案:

  1. RDB
  2. AOF

4.1 RDB持久化(默认开启)

会在一定时间内检测key的变化,达到要求就会将内存种的数据集快照写入磁盘,它恢复时是将快照文件直接读取到内存里。

RDB保存的文件是dump.rdb。


触发方式:

  1. 手动执行SAVE或者BGSAVE命令
  2. 配置自动触发(redis配置文件中配置)
    save 900 1  # 表示的就是如果900秒内,至少有一个key进行了修改,就持久化数据。
  1. 执行SHUTDOWN、FLUSHALL命令时也会触发



4.2 AOF持久化

记录所有写入操作(如SET、INCR、LPUSH 等),逐条追加写入一个日志文件(默认文件名为 appendonly.aof,可在配置文件自己重新指定文件名)。

需要我们手动开启:

# 将配置文件中 appendonly 设置为yes
appendonly yes

aof的写入策略:

# 默认是每秒同步一次写入数据。    
# 支持3种写入策略,可自行配置。 eversec(每秒同步)  always(每个写入操作都同步)  no(由操作系统决定何时同步,一般不会用这种)
appendfsync eversec



4.3 RDB与AOF生产环境下的抉择

建议:同时启用RDB与AOF。RBD用于定期备份和快速恢复数据,AOF保证数据安全性。


配置示例:

save 900 1 # 指定RBD自动触发条件
appendonly yes # 开启AOF
appendsync eversec # 指定AOF的写入策略
aof-use-rdb-preamble #开启混合持久化






5. Redis 过期删除策略

问题:redis种若有一个key只能存活1小时,那么redis是如何对这个key删除的?


redis有3种删除策略:

  • 定期删除 每隔100ms检查16个数据库(reids默认自带有16个数据)随机抽取每个库中20个设置过期时间的key,判断其是否过期,是就删除
  • 惰性删除 若定期删除没有随机抽取到那个过期的key,也不怕,惰性删除会在你查询那个key时,先去判断其是否过期,是就删除
  • 定时删除 在设置键过期的同时,创建一个定时器,让定时器在键过期时间来临时,执行对键的删除操作。

Redis采用 定期+惰性 的删除策略







6. Redis 常用命令

# 设置key和value值,若当前key不存在时,若存在则不执行该操作
setnx key value

# 设置key对应的value,并设置过期时间
setex key seconds value

# 为指定key设置过期时间
expire key seconds

# 查询指定key剩余过期时间
ttl key

# 返回指定key的value类型
type key

# 为指定key修改key名称
rename oldkey newkey

# 手动触发快照持久优化
SAVE   # 同步执行快照持久化,期间会阻塞其他命令的执行,直到完成。
BGSAVE   # 异步执行快照持久,不会阻塞其他命令的执行。(生产环境推荐使用)






7. SpringBoot整合Redis

  1. 添加依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 如果需要连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
  1. 配置文件
spring:
  redis:
    host: localhost
    port: 6379
    password: 123456  # 如果没有密码可不配置
    database: 0      # 默认使用0号数据库
    timeout: 3000ms  # 连接超时时间
    lettuce: # Lettuce 是一个高性能的 Redis Java 客户端,Spring Boot 2.x 默认使用 Lettuce 替代 Jedis。
      pool:
        max-active: 8      # 连接池最大连接数
        max-idle: 8        # 连接池最大空闲连接数
        min-idle: 0        # 连接池最小空闲连接数
        max-wait: -1ms     # 连接池最大阻塞等待时间
  1. 配置类(默认的RedisTemplate存储的是二进制数据,不可读,存储占用空间大。一般我们需要自定义RedisTemplate)
@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        
        // 启用默认类型信息,解决反序列化类型问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);

        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        
        return template;
    }
}






8. 分布式锁

锁的意义:防止多线程中共享资源的数据正确性。


单体项目中(只部署一台服务器时,我们完全可以使用同步代码块、同步方法、Lock锁这种方法去实现),但是一旦部署集群就不行了,例如:部署2台服务器,同一时刻2台服务器都收到请求去扣减库存,就有可能出现脏数据,因为前面说的那3种方式只保证了自己那台服务器的线程安全,它的锁只针对那台服务器。


实现分布式的方案:

  1. redis(最主流)
  2. mysql
  3. zookeeper

redis实现分布式锁的大概思路:

  • 核心思想:利用redis是单线程的特点(哪怕同一时刻来了10000个redis请求命令,也只能一个个执行),redis有个命令setnx key value(设置key和value值,若当前key不存在时,若存在则不执行该操作)

核心实现步骤:

  1. 将扣减库存的代码,放在redis(先利用setnx key value设值,看设的进去不,设的进去,就表示拿到了锁)后。
  2. 成功扣减库存后,在执行删除redis(把setnx key value设置的值删掉)。
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private Integer num =100;
    
    @RequestMapping("/deductStock")
    public String deductStock(){
        Boolean bool=stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "msj");
        if(!bool){
            System.out.println("扣减库存失败");
            return "end";
        }
        if (num > 0) {
            num -= 1;
        } else {
            return "库存不足";
        }
        stringRedisTemplate.delete("1ockKey");
        return "end";
    }



核心实现步骤优化:

  • 这套代码,还有很多问题需要处理(比较繁琐,有多种异常情况需要考虑)。

情况一:如果设置redis后,扣减库存代码出现异常,redis一直没有删除,导致死锁。可以用try catch finally实现,在finally中删除redis
情况二:如果解决了情况一,此时也设置了redis,服务器宕机了,finally代码块都还没来得及执行。可对redis设置过期时间,哪怕出现问题,到时间后,redis会删除,解决死锁

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private Integer num =100;
    
    @RequestMapping("/deductStock")
    public String deductStock(){
        try {
            Boolean bool=stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "msj", 10, TimeUnit.SECONDS);
            if(!bool){
                System.out.println("扣减库存失败");
                return "end";
            }
            if (num > 0) {
                num -= 1;
            } else {
                return "库存不足";
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            stringRedisTemplate.delete("1ockKey");
        }
        return "end";
    }

情况三:如果解决了情况一二,我现在扣减库存的代码耗时长,我需要15秒,当我在执行到第10秒的时候(reids过期自动删除了),恰在此时又来了个请求,它成功用redis的setnx key value设置了值,他就开始执行它的扣减库存,它才执行了5秒,我第一个扣减库存这时才执行完了。它就去执行删除redis,导致把人家第二个请求设置的reids给删除了。设置redis时,将线程id作为值进行存储,删除redis时,需要判断是否为当前线程,是的话才删除,从而避免删除人家的reids

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private Integer num =100;
    
    @RequestMapping("/deductStock")
    public String deductStock(){
        String threadId = String.valueOf(Thread.currentThread().getId());
        try {
            Boolean bool=stringRedisTemplate.opsForValue().setIfAbsent("lockKey", threadId, 10, TimeUnit.SECONDS);
            if(!bool){
                System.out.println("扣减库存失败");
                return "end";
            }
            if (num > 0) {
                num -= 1;
            } else {
                return "库存不足";
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (threadId.equals(stringRedisTemplate.opsForValue().get("lockKey"))) {
                stringRedisTemplate.delete("1ockKey");
            }
        }
        return "end";
    }



情况四:虽然你解决了情况一二三,但是现在需要执行15秒的那个请求还在执行,而10秒时,redis已经过期了,就导致另一个请求能执行删除库存,这样导致有可能还是出现超卖现象。应该要能实现锁的续期,如果这个请求耗时15秒,10秒应该过期时,应该对其续期,从而避免其他请求进行扣减库存。解决思路就是搞一个定时任务,每隔一段时间判断当前线程是否执行完毕,没有则增加redis的过期时间,太繁琐了。有专门的分布式锁框架Redisson







9. Redisson分布式锁

  1. 添加依赖
<dependencies>
    <!-- Spring Boot Redis Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Redisson -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.16.6</version>
    </dependency>
</dependencies>



  1. 配置文件
# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword  # 如果你的Redis设置了密码
  # Redisson配置
redisson:
  singleServerConfig:
    address: "redis://${spring.redis.host}:${spring.redis.port}"
    password: ${spring.redis.password}  # 如果你的Redis设置了密码
    # 其他Redisson配置...



  1. 使用
@Service
public class LockService {
    @Autowired
    private RedissonClient redissonClient;
    
    public void lockExample() {
        RLock lock = redissonClient.getLock("myLock");
        lock.lock(); // 获取锁,阻塞等待直到获取锁为止。如果锁已经被占用,则等待。
        try {
            // 执行你的代码块...
        } finally {
            lock.unlock(); // 释放锁。即使在代码块执行过程中发生异常,也要确保释放锁。
        }
    }
}
posted @ 2026-01-27 06:52  &emsp;不将就鸭  阅读(4)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3