Redis技术解析

引言

在Java高级开发的道路上,对Redis的掌握是必不可少的一环。Redis,作为一款开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。本文将深入探讨Redis的核心技术,并结合Java开发环境,分享一些实用的应用技巧和最佳实践。

一、Redis基础介绍

Redis支持多种数据类型,常见的5种包括String(字符串)、Hash(哈希)、List(列表)、Set(集合)和 Zset(Sorted Set,有序集合),这些数据类型为开发者提供了丰富的操作接口。同时,Redis提供了丰富的命令,支持数据的增删改查、过期设置、持久化等功能。
以下是 Redis 的一些数据类型,以及其及其常用使用场景:

  1. String(字符串)

    • 数据类型描述:Redis 的 String 类型是一种最基本的数据类型,它是一个键值对的存储结构,其中键和值都是字符串类型。String 类型的特点是快速存储和读取,适用于存储一些简单的数据,如字符串、整数或浮点数等。
    • 使用场景
      • 缓存:由于 String 类型具有快速读写特性,因此常用于缓存用户会话信息、页面缓存等。
      • 计数器:由于 String 类型的 value 可以是数字,所以可以通过 INCR、DECR 等命令实现计数器功能,如分布式环境中统计系统的在线人数、秒杀等。
      • 存储图片、视频等二进制数据:由于 String 类型支持二进制安全,可以用来存放图片、视频等内容。
  2. Hash(哈希)

    • 数据类型描述:Hash 类型是一种键值对的存储结构,其中键是字符串类型,而值是一个哈希表,可以包含多个键值对。
    • 使用场景
      • 对象缓存:Hash 类型可以方便地存储对象信息,如用户信息、商品信息等。通过 hmget、hset 等命令可以实现对象的读取和修改。
      • 购物车实现:Hash 类型可以方便地实现购物车功能,如添加商品、修改商品数量、删除商品等。
  3. List(列表)

    • 数据类型描述:List 类型是一个双向链表,支持在链表的两端插入和删除元素。
    • 使用场景
      • 消息队列:List 类型可以实现简单的消息队列功能,如使用 lpush 在队列左侧插入元素,使用 rpop 从队列右侧取出元素。
      • 朋友圈时间线:可以将用户发布的朋友圈内容按时间顺序存储在 List 中,实现时间线功能。
  4. Set(集合)

    • 数据类型描述:Set 类型是一个无序的字符串集合,不允许有重复元素。
    • 使用场景
      • 用户标签:可以将用户的标签存储在一个 Set 中,方便查询用户的所有标签。
      • 交集、并集、差集运算:Set 类型支持交集、并集、差集等集合运算,可以用于实现共同好友、推荐好友等功能。
  5. Zset(Sorted Set,有序集合)

    • 数据类型描述:Zset 类型是一个有序的字符串集合,每个元素都会关联一个分数(double 类型),通过分数来为集合中的元素进行从小到大的排序。
    • 使用场景
      • 排行榜:Zset 类型可以很方便地实现排行榜功能,如按照用户积分进行排序的排行榜。
      • 延迟队列:可以将需要延迟处理的任务按照处理时间戳作为分数存储在 Zset 中,然后定时从 Zset 中取出最早需要处理的任务进行处理。
      • 范围查询:Zset 可以按照分数范围进行查询,这在一些需要范围查询的场景中非常有用。
  6. Bitmap(位图)

    • 并不是 Redis 的一个独立数据类型,但可以使用 String 类型来模拟位图操作。
    • 用于存储大量的开关状态信息,如用户是否访问过某个页面。
  7. HyperLogLog

    • 用于基数统计的算法。
    • 在不存储具体元素的情况下,估计一个集合中不重复元素的数量。
  8. Geo(地理位置)

    • 用于存储地理位置信息。
    • 支持基于位置的查询,如获取附近的地点。
  9. Stream(流)

    • Redis 5.0 版本新增加的数据结构。
    • 用于实现消息队列或日志系统,支持消费者组(consumer group)模式。

以上就是 Redis 的各种数据类型及其常用使用场景。需要注意的是,这些使用场景并不是绝对的,具体使用哪种数据类型还需要根据具体的业务需求和场景来选择。

二、Redis在Java中的应用

1. 作为缓存层

Redis的高性能和低延迟特性使其成为缓存层的理想选择。在Java应用中,我们可以使用Jedis或Lettuce等Redis客户端库来连接和操作Redis。通过将热点数据存储在Redis中,可以大大减少对数据库的访问,提高系统的整体性能。

实现细节:
  • 使用Jedis或Lettuce:Jedis和Lettuce是Java中常用的Redis客户端库。它们提供了丰富的API来连接和操作Redis。
// 使用Jedis
Jedis jedis = new Jedis("localhost");
System.out.println("Connection to server successfully");
// set key-value
jedis.set("runoobkey", "www.runoob.com");
// get the value
String strValue = jedis.get("runoobkey");
System.out.println("Stored string in redis:: " + strValue);
  • 缓存策略:设计合适的缓存策略,如LRU(最近最少使用)或LFU(最不经常使用)策略,来管理缓存的容量和淘汰机制。
  • 缓存失效:设置键的过期时间,以确保缓存数据的有效性。
jedis.expire("runoobkey", 60); // 设置key的过期时间为60秒

2. 分布式锁

Redis的setnx命令(在Redis 2.6.12及以后版本中推荐使用set命令的nx和px选项)可以实现分布式锁的功能。通过Redis的分布式锁,我们可以保证在分布式环境下对共享资源的互斥访问,避免数据的不一致。

实现细节:
  • 使用SETNX或SET命令的NX和PX选项:确保在分布式环境中对共享资源的互斥访问。
String lockKey = "mylock";
String requestId = UUID.randomUUID().toString();
String result = jedis.set(lockKey, requestId, "NX", "PX", 10000); // 锁10秒
if ("OK".equals(result)) {
    try {
        // 业务逻辑处理
    } finally {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    }
}

3. 消息队列

Redis的List数据类型可以实现简单的消息队列功能。生产者可以使用lpush命令将消息推送到队列中,消费者使用rpop或brpop命令从队列中取出消息进行处理。这种基于Redis的消息队列实现方式轻量级、易部署,适用于一些对性能要求不高的场景。

实现细节:
  • 使用List作为队列:LPUSH命令将消息推送到队列,RPOP或BRPOP命令从队列中取出消息。
// 生产者
jedis.lpush("mylist", "message1");
jedis.lpush("mylist", "message2");

// 消费者
List<String> list = jedis.brpop(0, "mylist");
System.out.println(list.get(1)); // 输出 message1 或 message2

4. 计数器

Redis的String数据类型支持incr和decr命令,可以实现计数器功能。这种计数器可以在分布式环境下使用,具有高性能和可扩展性。在Java应用中,我们可以使用Redis的计数器来实现如用户访问量统计、限流等功能。

实现细节:
  • 使用INCR或DECR命令:实现高性能的计数器功能。
jedis.incr("mycounter"); // 计数器加1
long count = jedis.get("mycounter").toLong(); // 获取计数器的值

5. 发布/订阅模式

Redis的发布/订阅模式是一种消息通信模式,发送者(发布者)发送消息到频道(channel),订阅了该频道的接收者(订阅者)都可以收到消息。

实现细节:
  • 发布消息:使用PUBLISH命令将消息发布到指定的频道。
  • 订阅频道:使用SUBSCRIBE命令订阅一个或多个频道。

在Java中,可以使用Jedis的PubSub接口来实现发布/订阅模式。

Jedis jedis = new Jedis("localhost");

// 订阅者
jedis.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
        System.out.println("Received <" + message + "> on " + channel + "!");
    }
    // ...其他回调方法...
}, "mychannel");

// 在另一个线程或进程中
// 发布者
jedis.publish("mychannel", "Hello, Redis!");

三、Redis高级特性

1. 持久化

Redis支持RDB和AOF两种持久化方式。RDB通过定期将内存中的数据以快照的方式写入磁盘,实现数据的持久化。AOF则将写命令追加到文件中,每次修改数据都会记录。开发者可以根据实际需求选择合适的持久化方式。
redis.conf配置文件中,可以配置RDB和AOF的相关参数。

2. 复制与集群

Redis支持主从复制和集群模式。通过主从复制,我们可以将数据从主服务器同步到从服务器,实现数据的备份和读取扩展。
在构建 Redis 的高可用方案时,通常采用以下几种常见的方法:

1. Redis Sentinel

Redis Sentinel 是 Redis 官方提供的用于监控和管理 Redis 主从复制的工具。它可以监控 Redis 实例的健康状况,并在主节点失效时自动进行主从切换,确保系统的高可用性。Redis Sentinel 通常采用三个或更多个 Sentinel 节点组成一个监控集群,通过投票来决定故障转移的结果。

2. Redis Cluster

Redis Cluster 是 Redis 官方提供的分布式 Redis 解决方案,它支持数据自动分片和数据复制,可以在多个节点之间分配数据,并提供故障转移和数据重新分片的功能。Redis Cluster 适用于大规模的 Redis 集群部署,可以提供更高的可用性和扩展性。

3. Redis Sentinel + Redis Cluster

将 Redis Sentinel 和 Redis Cluster 结合起来使用,可以提供更高级的高可用解决方案。在这种方案中,Redis Sentinel 负责监控和管理 Redis 实例的健康状态,并在必要时进行故障转移,而 Redis Cluster 则负责分布式数据存储和故障恢复。

4. Redisson

Redisson 是一个基于 Redis 的 Java 客户端库,提供了丰富的功能和高级特性,包括分布式锁、分布式对象、分布式集合等。通过 Redisson,可以实现更加灵活和可靠的高可用方案,例如基于 Redlock 算法的分布式锁,以及基于 Redis 主从复制的读写分离等。

3. 事务

Redis支持MULTI/EXEC命令来实现事务功能。在事务中,一系列命令会被打包成一个单独的单元进行执行,要么全部成功,要么全部失败。这种事务机制可以确保数据的一致性。

  • 使用MULTI、EXEC、DISCARD等命令来实现Redis的事务功能。在MULTI命令之后执行的命令会被放入一个队列中,直到EXEC命令被执行时,队列中的所有命令才会被原子性地执行。如果在执行过程中发生错误,可以使用DISCARD命令来取消事务。

四、最佳实践

1. 合理设置键的过期时间

通过合理设置键的过期时间,我们可以有效地控制Redis的内存使用,避免无效数据的堆积。同时,过期时间也可以作为缓存失效的一种机制,确保数据的实时性。

2. 监控与调优

对Redis进行监控和调优是确保系统稳定运行的关键。我们可以使用Redis自带的INFO命令或第三方监控工具来获取Redis的运行状态信息,如内存使用情况、连接数、命令执行时间等。根据这些信息,我们可以对Redis进行调优,提高系统的性能和稳定性。

3. 安全配置

Redis默认监听在6379端口上,并且没有设置密码认证。因此,在部署Redis时,我们需要配置合理的安全策略,如设置密码认证、绑定IP地址等,确保Redis的安全性。

4. 缓存穿透

定义:缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决方案

  1. 缓存空对象:当一个key不存在或者查询结果为空时,仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
  2. 布隆过滤器:布隆过滤器是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

5. 缓存击穿

定义:缓存击穿是指某个热点key非常热门,访问非常频繁,但这个key在缓存中不存在,导致每次请求都要去数据库查询,从而给数据库带来巨大的压力。

解决方案

  1. 使用互斥锁:当缓存失效时,不是立即去加载数据,而是先使用缓存工具的某些带成功操作返回值的操作(如Redis的setnx)去设置一个互斥锁,当操作返回成功时,再进行加载数据的操作并回设缓存;否则,就重试获取缓存。
  2. 热点数据预加载:在启动服务的时候,预先加载热点数据到缓存中,这样即使热点数据过期,也只是第一次请求会查询数据库,后续请求依然可以从缓存中获取。

6. 缓存雪崩

定义:缓存雪崩是指当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统(比如数据库)带来很大压力,甚至造成数据库宕机。

解决方案

  1. 缓存数据的过期时间设置随机:给缓存设置过期时间时,加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在某一时刻同时失效。
  2. 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  3. 二级缓存:A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2缓存失效时间设置为长期。
  4. 预先设置热门数据:在Redis启动的时候,预先将一些热点数据加载到缓存中,这样即使这些数据的缓存失效,也能从Redis中获取到数据,而不是直接查询数据库。

五、缓存淘汰策略

Redis 提供了多种缓存淘汰策略,这些策略决定了当 Redis 内存使用达到其 maxmemory 设置的上限时,如何选择和删除数据以释放内存。以下是 Redis 中常见的缓存淘汰算法:

  1. volatile-lru

    • 尝试淘汰设置了过期时间的 key。
    • 当 Redis 内存使用到达阈值的时候,会选择最近最少使用(Least Recently Used, LRU)的且设置了过期时间的 key 进行淘汰。
    • 没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不丢失。
  2. allkeys-lru

    • 淘汰整个数据集(包括设置了过期时间和未设置过期时间的 key)中最近最少使用的 key。
    • 不考虑 key 是否设置了过期时间,只根据访问频率来决定哪些 key 需要被淘汰。
  3. volatile-lfu

    • 尝试淘汰设置了过期时间的 key,但淘汰的依据是最少使用频率(Least Frequently Used, LFU)。
    • 类似于 volatile-lru,但使用了 LFU 算法而非 LRU 算法。
  4. allkeys-lfu

    • 淘汰整个数据集中最少使用频率的 key,无论它们是否设置了过期时间。
    • LFU 算法考虑了 key 的访问频率,而不仅仅是它们最后一次被访问的时间。
  5. volatile-random

    • 在设置了过期时间的 key 中随机选择 key 进行淘汰。
    • 这种策略不基于访问频率或最近访问时间,而是随机选择 key 进行淘汰。
  6. allkeys-random

    • 在整个数据集中随机选择 key 进行淘汰,无论它们是否设置了过期时间。
    • 同样,这种策略也不基于访问频率或最近访问时间。
  7. volatile-ttl

    • 淘汰设置了过期时间且剩余时间(TTL)最短的 key。
    • 这种策略基于 key 的剩余过期时间来决定哪些 key 需要被淘汰。
  8. noeviction

    • 不淘汰任何 key,而是返回一个错误。
    • 当内存使用达到 maxmemory 设置的上限时,如果尝试添加新的数据,Redis 将返回错误而不是淘汰任何现有的 key。这可能导致新写入的命令失败,但不会丢失现有数据。

在选择合适的淘汰策略时,需要考虑你的应用的需求和特性。例如,如果你的应用中的 key 都有明确的过期时间,并且你希望优先淘汰那些最久未使用的 key,那么 volatile-lru 可能是一个好的选择。如果你的应用需要快速响应并且不关心数据的持久性,那么 noeviction 策略可能不适合你,因为它会导致新写入的命令失败。

六、总结

Redis作为一款高性能的内存数据结构存储系统,在Java高级开发中发挥着重要的作用。通过掌握Redis的核心技术和应用技巧,我们可以更好地利用Redis来优化系统的性能、提高数据的实时性和一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/594801.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里实习生:面试阿里其实并没有那么难。

愉快的五一假期已经结束了, 又要投入到学习和工作当中了。 今天分享一位同学在阿里的Go后端实习面经详解, 希望对你有帮助。 Go里有哪些数据结构是并发安全的&#xff1f; 并发安全就是程序在并发的情况下执行的结果都是正确的&#xff1b; Go中数据类型分为两大类&#xff…

探秘Tailwind CSS:前端开发的加速器(Tailwind CSS让CSS编写更简洁)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 Tailwind CSS 📒📝 快速体验📝 深入学习⚓️ 相关链接 ⚓️📖 介绍 📖 在这个快速迭代的互联网时代,前端开发效率和设计质量的双重要求,使得开发者们不断寻求更高效的工具和方法。今天,我们要介绍的是一个能够极大…

【数据库原理及应用】期末复习汇总高校期末真题试卷03

试卷 一、选择题 1 数据库中存储的基本对象是_____。 A 数字 B 记录 C 元组 D 数据 2 下列不属于数据库管理系统主要功能的是_____。 A 数据定义 B 数据组织、存储和管理 C 数据模型转化 D 数据操纵 3 下列不属于数据模型要素的是______。 A 数据结构 B 数据字典 C 数据操作 D…

Python基础学习之装饰器

大家好&#xff0c;今天我想和大家分享一下Python中一个非常强大且优雅的特性——装饰器&#xff08;Decorators&#xff09;。装饰器在Python中是一种高级语法&#xff0c;它允许你在不修改函数或类的情况下&#xff0c;为其添加额外的功能。这不仅让代码更加整洁&#xff0c;…

Coze扣子开发指南:怎么使用功能强大的插件?

●插件是什么&#xff1f; 想象一下&#xff0c;你的机器人是一个玩具车&#xff0c;它本来只能跑直线。但是&#xff0c;如果你给它装上一些额外的小配件&#xff0c;比如翅膀&#xff0c;它就能飞&#xff1b;装上轮子&#xff0c;它就能在各种地形上跑。这些小配件&#xf…

关于IDEA中项目中各个方法、引用、注解等全部报错的情况

今天打开项目弹出很多提示框&#xff0c;也没注意&#xff0c;然后突然发现项目所有都在报错&#xff0c;不管是启动类还是方法类&#xff0c;各种注解、方法、引用等全红了&#xff0c;随便打开一个都是密密麻麻全红。 首先排查依赖和JDK等引用问题&#xff0c;包括我们的mave…

多线程使用说明

一、如何创建多线程 1、继承Thread类 如果调用run方法&#xff0c;相当于还是只有一条main线程&#xff0c;会把run的线程当成一条普通对象&#xff0c;如下&#xff0c;t会执行完再往下执行&#xff0c;这样t就不是一个线程类&#xff0c;而是一个普通的对象&#xff0c;所以必…

(四)机器学习在银行中的典型应用场景(模型) #CDA学习打卡

本文总结了机器学习在银行中的典型业务应用场景&#xff0c;包括客户管理、零售智能营销、公司智能营销、自然语言处理、运营管理以及图像识别。

通过自适应提示提升大语言模型的零样本推理能力

随着大模型&#xff08;LLMs&#xff09;的快速发展&#xff0c;它们在自然语言处理&#xff08;NLP&#xff09;任务上取得了前所未有的成就。特别是&#xff0c;LLMs展现出了强大的推理和规划能力&#xff0c;这得益于它们的少样本和零样本学习能力。然而&#xff0c;现有的方…

三分钟一条抖音爆款短视频,轻松日引500+创业粉,复制粘贴即可,简单好…

详情介绍 团队历经三个月终于给兄弟把这个抖音测试出来了过程就不说了全是泪 最近抖音拆解项目是比较火的&#xff0c;前段时间不行拉现在又是可以继续拆解拉我这边自己也实操的一个引流渠道 咱们为什么要通过抖音来引流创业粉啊 因为抖音和知乎的创业粉的质量还是比较高的 本次…

【SQL每日一练】统计复旦用户8月练题情况

文章目录 题目一、分析二、题解1.使用case...when..then2.使用if 题目 现在运营想要了解复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况&#xff0c;请取出相应明细数据&#xff0c;对于在8月份没有练习过的用户&#xff0c;答题数结果返回0. 示例代码&am…

线程安全的概念及原因

1.观察线程不安全 public class ThreadDemo {static class Counter {public int count 0;void increase() {count;}}public static void main(String[] args) throws InterruptedException {final Counter counter new Counter();Thread t1 new Thread(() -> {for (int …

腾讯云服务器之ssh远程连接登录

一、创建密钥绑定实例 创建密钥会自动下载一个私钥&#xff0c;把这个私钥复制到c盘 二、设置私钥权限 1、删除所有用户权限 2、添加当前用户权限 查看当前用户名 echo %USERNAME%三、ssh远程连接到服务器 ssh ubuntu175.xxx.xxx.112 -i C:\Crack\cs2.pem四、修改root密码 s…

构建第一个ArkTS应用之@LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储&#xff0c;通过Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility实例内&#xff0c;在页面间共享状态。 本文仅介绍LocalStorage使用场景和相关的装饰器&#xff1a;LocalStorageProp和LocalS…

修改JupyterNotebook文件存储位置

Jupyter Notebook 1、通过AnaConda安装Jupyter Notebok 2、在开始菜单里找到并打开Anaconda Prompt&#xff0c;输入如下命令&#xff0c;然后执行。 jupyter notebook --generate-config4、打开以下文件 找到 C:/Userzh/.../.jupyter 打开 jupyter_notebook_config.py 取消…

信息系统项目管理师——第20章高级项目管理

本章是将第三版的第20章、第21章、第18章、第25章、第2章的PRINCE2进行了合并&#xff0c;精简和新增了部分知识。选择、案例都会考。从2023年上半年考情来看 选择题&#xff0c;考3-4分&#xff0c;基本是课本原话&#xff0c;但是知识点比较分散&#xff0c;需要多刷题&#…

HTML5实现酷炫个人产品推广、工具推广、信息推广、个人主页、个人介绍、酷炫官网、门户网站模板源码

文章目录 1.设计来源1.1 主界面1.2 我的产品界面1.3 关于我们界面1.4 照片墙界面1.5 发展历程界面1.6 优秀人才界面1.7 热门产品界面1.8 联系我们界面 2.灵活调整模块3.效果和源码3.1 动态效果3.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.c…

python中怎么清屏

一、“Windows命令行窗口”下清屏&#xff0c;可用下面两种方法&#xff1a; 第一种方法&#xff0c;在命令行窗口输入&#xff1a; import os ios.system("cls") 第二种方法&#xff0c;在命令行窗口输入&#xff1a; import subprocess isubprocess.call("cl…

Rust语言系统编程实战(小北学习笔记)

前言 进入大学以来&#xff08;计算机应用技术——大数据方向&#xff09;&#xff0c;就像很多程序猿&#x1f412;一样&#xff0c;小北开始每学期学习一种新的编程语言。通过学习另一个编程语言&#xff0c;可以了解很多规范和规则&#xff0c;并得到了一些想法&#xff0c;…

Wireshark CLI | 过滤包含特定字符串的流

问题背景 源自于和朋友的一次技术讨论&#xff0c;关于 Wireshark 如何查找特定字符串所在的 TCP 流&#xff0c;原始问题如下&#xff1a; 仔细琢磨了下&#xff0c;基于我对 Wireshark 的使用经验&#xff0c;感觉一步到位实现比较困难&#xff0c;所以想着说用 Wireshark C…