当前位置: 首页 > article >正文

Redis从0到1详解(SpringBoot)

前言

在现代应用中,Redis 扮演着重要的角色,作为高性能的缓存和消息队列,它能够大大提高系统的响应速度和吞吐量。在 Spring Boot 项目中使用 Redis,不仅能通过简单的配置连接 Redis 服务,还能利用 Redis 提供的各种高效算法,如 LRU(最近最少使用)和 LFU(最不常用)来实现智能的数据管理。此外,分布式锁也可以通过 Redis 提供的功能来实现,保证多线程或多服务之间的数据一致性。本文将介绍如何在 Spring Boot 中配置 Redis,如何实现 Redis 分布式锁,如何利用 LRU 和 LFU 算法来进行数据淘汰,以及如何高效使用 RedisTemplate

Redis 6.0 多线程机制

Redis 在 6.0 版本之前是单线程的,主要通过单线程处理网络 I/O 和键值对的读写操作。6.0 引入了多线程后,通过多核 CPU 并行处理网络 I/O 提升性能,但核心命令依旧由单线程处理,确保了并发安全。
多线程 I/O 示例

+----------+       +----------+       +----------+
| Thread 1 |       | Thread 2 |       | Thread N |
+----------+       +----------+       +----------+
       |                   |                   |
       +---------+---------+---------+---------+
                 | Single-threaded execution |
                 +---------------------------+
Redis 特点
  1. 数据存储在内存中:读写速度极快。
  2. 单线程架构:通过多路 I/O 复用技术实现高效网络通信。
  3. 支持分布式锁:使用 Redisson 等工具。
  4. 多种数据结构支持ListSetHashzSetString
  5. 支持持久化AOFRDB 两种持久化方式。

Redis 应用场景与配置示例

1. 添加 Spring Boot Redis 依赖

确保 pom.xml 中包含以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置 Redis 连接

application.propertiesapplication.yml 中配置 Redis:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=2000

# 设置最大内存限制
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=5

# 设置 maxmemory 策略
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=5000

# 设置 Redis 淘汰策略
spring.redis.command-timeout=2000  # 设置命令超时
spring.redis.maxmemory=2gb  # 设置最大内存
spring.redis.maxmemory-policy=allkeys-lru  # 设置淘汰策略

分布式锁实现

相关依赖(使用 Redisson):

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.2</version>
</dependency>

示例代码(使用 Redisson):

// 初始化 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

// 获取锁对象
RLock lock = redisson.getLock("myLock");

// 加锁与解锁
try {
    lock.lock();
    // 业务逻辑
} finally {
    lock.unlock();
}

持久化配置

Redis 支持 RDB 和 AOF 两种方式,可以通过 redis.conf 文件配置:

# RDB 持久化
save 900 1   # 每 900 秒有 1 个 key 修改时触发 RDB 保存
save 300 10  # 每 300 秒有 10 个 key 修改时触发 RDB 保存

# AOF 持久化
appendonly yes               # 启用 AOF
appendfsync everysec         # 每秒同步 AOF 日志

跳表存储机制

在 Redis 的数据结构中,**zSet(有序集合)**的底层实现既可以通过 跳表(Skip List)也可以通过 压缩列表(Ziplist)来存储。最初的设计中,有序集合的数据是通过链表存储的,元素根据分值(score)从小到大有序排列。虽然链表的插入性能较快,但查询效率较低。

为了优化查询效率,Redis 在有序集合中实现了 跳表索引。跳表是一种通过多个层级的索引来加速查找过程的数据结构,其查询效率接近二分查找,但插入和删除的性能比哈希表更优秀。跳表的基本思想是通过多层索引缩短查询路径,从而提高增删改查的效率。

跳表结构示意图

跳表的层级结构通常如下所示:

Level 3:        o--------------->o
Level 2:        o------->o------->o
Level 1: o----->o----->o----->o----->o

其中,每一层都是一个有序链表,最底层(Level 1)包含所有的元素,而越高的层级则包含较少的元素。跳表的查询过程是从最高层开始,逐层向下进行,直到找到目标节点。

操作原理

跳表的查询过程类似于 二分查找,但是更加灵活。它从最高层的左侧开始,逐层向下查找。每一层的指针指向一部分元素,通过多层索引来快速定位目标节点。跳表的插入和删除操作也需要维护各层索引的更新,因此其时间复杂度为 O(log N)

查询过程

  1. 从跳表的最高层开始,沿着当前层的指针查找目标元素。
  2. 如果当前层的元素值大于目标值,就降到下一层。
  3. 重复此过程,直到找到目标元素或者到达跳表的最底层。
跳表在 Redis 中的应用

Redis 的有序集合(zSet)采用了跳表存储结构,利用跳表的索引层实现了高效的区间查询和排序。跳表能够快速支持范围查询、按分值排序以及增删操作。

Redis 中的 ZADDZRANGEZREM 等命令都基于跳表进行优化,确保了在大数据量情况下仍然能够保持较高的性能。

示例:在 Redis 中使用 zSet
@Service
public class RedisZSetService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // 添加元素到有序集合
    public void addToZSet(String key, String value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    // 获取有序集合中指定范围的元素
    public Set<String> getRangeByScore(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
    }

    // 删除有序集合中的元素
    public void removeFromZSet(String key, String value) {
        redisTemplate.opsForZSet().remove(key, value);
    }
}

在 Redis 中,zSet 的操作如 addrangeByScoreremove 都可以利用跳表提供的多层索引机制来高效执行,特别是在有大量数据时,跳表的查询性能显著优于链表。

总结

跳表(Skip List)作为 Redis zSet 底层的核心数据结构,通过多层索引提高了查询效率。其查询过程类似于二分查找,但更加灵活,通过层级跳跃来加速查找。跳表不仅在 Redis 中起到了提升查询性能的作用,还能够支持高效的插入和删除操作。这使得 Redis 在处理有序集合时,能够在大规模数据集下保持较高的性能。


Redis 淘汰策略

  • 1. 淘汰部分键的策略

    这类策略只针对设置了 TTL(过期时间)的键进行淘汰,避免影响永久存储的键。具体策略如下:

    • volatile-lru:从设置了过期时间的键中,优先移除最近最少使用的键。
    • volatile-lfu:从设置了过期时间的键中,优先移除使用频率最低的键。
    • volatile-ttl:从设置了过期时间的键中,移除即将过期的键(即剩余生存时间最短的键)。
    • volatile-random:从设置了过期时间的键中,随机移除一个键。

    2. 淘汰所有键的策略

    此类策略适用于内存不足时,需要处理所有键(无论是否设置了 TTL)的情况。具体策略如下:

    • allkeys-lru:从所有键中,优先移除最近最少使用的键。
    • allkeys-lfu:从所有键中,优先移除使用频率最低的键。
    • allkeys-random:从所有键中,随机移除一个键。
    • noeviction:不移除任何键,直接返回错误(通常是写入操作时返回 OOM 错误)。

    3. 总结

    • 按照范围分类:部分键(仅有过期时间的键) vs 所有键。
    • 按照算法分类:LRU(最近最少使用)、LFU(最少使用频率)、TTL(生存时间)、随机淘汰(random)、拒绝策略(noeviction)。

    4. 如何选择合适的淘汰策略

    不同的业务场景适合不同的淘汰策略。以下是常见场景及对应的淘汰策略建议:

    • noeviction:适用于数据写入非常重要且不能丢失的场景,例如队列或事务数据。
    • volatile-lru / allkeys-lru:适用于缓存热点数据,淘汰不常使用的数据。
    • volatile-lfu / allkeys-lfu:适用于访问频率差异较大的场景,淘汰低频访问数据。
    • volatile-ttl:适用于有明确时间限制的缓存数据。
    • random策略:适用于对淘汰数据要求不高的场景。

    5. 内存分配优化

    合理设置 Redis 内存使用策略,可以显著提升系统性能。以下是一些建议:

    • 合理设置 maxmemory:根据 Redis 实例的可用内存和业务需求,设置适当的内存限制。

      示例代码:

      maxmemory 2gb
      maxmemory-policy allkeys-lru
      
    • 使用合适的数据结构

      • 使用 hash 存储对象属性,减少键的占用。
      • 对于集合类数据,优先选择 setzset
      • 尽量避免过多的小键值对,合并数据以提升效率。

      示例代码(使用 hash 存储对象):

      HSET user:1000 name "Alice" age 30
      

    6. 过期时间管理

    合理管理过期时间,有助于减少无用缓存占用内存。以下是一些建议:

    • 设置合理的过期时间:对缓存数据,设置适当的 TTL 以避免长期占用内存。可以使用 EXPIREEXPIREAT 命令为键设置过期时间。

    • 批量删除长时间未访问的数据

      • 定期清理无用数据。
      • 使用 EXPIREEXPIREAT 指定过期时间。
    • 示例代码

      Spring Boot 中可以通过 RedisTemplate 来操作 Redis 数据,并设置过期时间等。

      示例代码:使用 RedisTemplate 设置过期时间
      @Service
      public class RedisService {
      
          @Autowired
          private RedisTemplate<String, Object> redisTemplate;
      
          // 设置一个带有过期时间的键
          public void setKeyWithExpiry(String key, Object value, long timeout) {
              redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
          }
      
          // 获取键值
          public Object getKey(String key) {
              return redisTemplate.opsForValue().get(key);
          }
      }
      

    7. 监控和调整

    • 在 Spring Boot 项目中,使用 RedisTemplate 可以方便地进行数据操作。你还可以定期监控 Redis 的内存使用情况,确保 Redis 不会因为内存不足而出现问题。可以使用 Redis 的 INFO memory 命令来查看内存使用情况。可以通过 @Scheduled 定时任务来定期检查 Redis 的状态。

      示例代码:定期监控 Redis 内存使用
      @Service
      public class RedisMonitorService {
      
          @Autowired
          private RedisTemplate<String, Object> redisTemplate;
      
          // 每隔一分钟检查 Redis 内存使用情况
          @Scheduled(fixedRate = 60000)
          public void monitorMemoryUsage() {
              String memoryStats = redisTemplate.getRequiredConnectionFactory().getConnection().info("memory");
              System.out.println("Redis Memory Stats: " + memoryStats);
          }
      }
      

性能优化

  • 开启压缩:
    • 使用 rdbcompression 配置来减少持久化文件大小。
  • 分片和集群:
    • 对于高并发和大数据量场景,使用 Redis Cluster 进行数据分布。
  • 减少阻塞操作:
    • 避免运行耗时的命令(如 keysflushall)。
    • 使用 SCAN 替代 KEYS

持久化与备份

  • 合理配置持久化:
    • 开启 RDB 快照保存关键数据,定期清理无用快照。
    • 使用 AOF 保证数据完整性,但注意文件大小和写性能。
  • 定期备份:防止数据丢失,结合云存储或异地存储。

主从复制风暴及解决

主从复制风暴是由于主节点向所有从节点分发 RDB 快照引起的,可以通过以下方法缓解:

  1. 配置 min-slaves-to-writemin-slaves-max-lag 参数。
  2. 从节点再挂载从节点,优化主从拓扑结构。

配置示例

replica-serve-stale-data yes
min-slaves-to-write 3
min-slaves-max-lag 10

实现 LRU 和 LFU 算法逻辑

LRU 逻辑实现
示例代码:
public class LRUCache<K, V> {
    private final int capacity;
    private final Map<K, V> cache;
    private final LinkedHashMap<K, Long> accessOrder;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.accessOrder = new LinkedHashMap<>(capacity, 0.75f, true);
    }

    public V get(K key) {
        if (!cache.containsKey(key)) return null;
        accessOrder.put(key, System.nanoTime());
        return cache.get(key);
    }

    public void put(K key, V value) {
        if (cache.size() >= capacity) {
            K leastUsedKey = accessOrder.keySet().iterator().next();
            cache.remove(leastUsedKey);
            accessOrder.remove(leastUsedKey);
        }
        cache.put(key, value);
        accessOrder.put(key, System.nanoTime());
    }
}
LFU 逻辑实现
LFU 的核心是记录访问频率:
public class LFUCache<K, V> {
    private final int capacity;
    private final Map<K, V> cache;
    private final Map<K, Integer> frequencies;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.frequencies = new HashMap<>();
    }

    public V get(K key) {
        if (!cache.containsKey(key)) return null;
        frequencies.put(key, frequencies.getOrDefault(key, 0) + 1);
        return cache.get(key);
    }

    public void put(K key, V value) {
        if (cache.size() >= capacity) {
            K leastFrequentKey = frequencies.entrySet()
                    .stream()
                    .min(Map.Entry.comparingByValue())
                    .get()
                    .getKey();
            cache.remove(leastFrequentKey);
            frequencies.remove(leastFrequentKey);
        }
        cache.put(key, value);
        frequencies.put(key, 1);
    }
}

使用 RedisTemplate 操作 Redis

在 Spring 项目中,通过 RedisTemplate 可以方便地操作 Redis 数据:

配置 RedisTemplate
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}
使用 RedisTemplate
@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 添加数据
    public void addData(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 获取数据
    public Object getData(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 删除数据
    public void deleteData(String key) {
        redisTemplate.delete(key);
    }
}
测试示例
@SpringBootTest
public class RedisServiceTest {

    @Autowired
    private RedisService redisService;

    @Test
    public void testRedisOperations() {
        redisService.addData("testKey", "testValue");
        String value = (String) redisService.getData("testKey");
        assertEquals("testValue", value);
        redisService.deleteData("testKey");
        assertNull(redisService.getData("testKey"));
    }
}

http://www.kler.cn/a/505966.html

相关文章:

  • C++算法第十六天
  • Elasticsearch:Jira 连接器教程第一部分
  • JavaScript-正则表达式方法(RegExp)
  • Mysql--实战篇--数据库设计(范式和反范式,数据表设计原则)
  • Onedrive精神分裂怎么办(有变更却不同步)
  • Qt/C++进程间通信:QSharedMemory 使用详解(附演示Demo)
  • 面试之《web安全问题》
  • http://noi.openjudge.cn/——4.7算法之搜索——13:Sticks
  • 计算机数据提取与固定
  • Java+Maven+GDAL
  • 图像识别opencv翻转
  • MacOS删除多余的Windows启动项
  • 性能测试工具Jmeter影响负载的X因素有哪些?
  • C#界面框架Avalonia中使用依赖注入
  • HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (三、影视搜索页功能实现)
  • 【AI】【RAG】使用WebUI部署RAG:数据优化与设置技巧详解
  • 《怪形重制版》V1.02官方学习版
  • matlab GUI 打包成exe可执行文件
  • Java设计模式——单例模式(特性、各种实现、懒汉式、饿汉式、内部类实现、枚举方式、双重校验+锁)
  • 活动预告 | CCF开源发展委员会开源供应链安全技术研讨会(2025第一期)——“大模型时代的开源供应链安全风控技术”...
  • http协议 tomact的基本使用
  • PHP政务招商系统
  • Electron 开发者的 Tauri 2.0 实战指南:窗口管理与系统集成
  • P3数据结构、数据类型、数字编码、字符编码:保姆级图文详解
  • 交流电压220V如何用单片机测量电压?
  • VM(虚拟机)和Linux的安装