Redis基础解析 | 从应用到实践,一篇文章通晓全局(修正版)

3,720 阅读29分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!


背景介绍

Redis,作为一款开源的、内存中的数据结构存储系统,以其卓越的性能和丰富的功能,在开发者社区中赢得了广泛的赞誉和应用。它支持多种数据类型,如字符串、列表、集合、哈希表和有序集合等,并且提供了多种功能,如发布/订阅、事务、Lua脚本等。Redis不仅可以用作数据库、缓存和消息中间件,还可以作为数据结构服务器,在多种场景下发挥着巨大的作用。

无论您是Redis的新手还是资深用户,本文都将为您提供有价值的信息和实用的技巧。让我们一起探索Redis的奥秘,为构建高效、稳定的数据存储系统助力!

文章大纲

在本文中,我们将为您呈现一篇全面而深入的Redis技术指南。从Redis的基础知识到高级集群管理,再到数据持久化与恢复策略,本文旨在帮助读者全面了解并掌握Redis的核心技术和应用实践。

下图便是总体文章的大纲:

在这里插入图片描述

本文将带您深入了解Redis的各项功能和技术细节。我们将从Redis的入门知识开始,逐步探讨其安装、连接、集群管理等方面的内容。

同时,我们还将重点关注Redis的持久化机制,包括RDBAOF两种持久化方式,以及如何在Redis重启后恢复之前的内存数据。此外,我们还将介绍Redis的数据导入导出方式,帮助您在实际应用中更加灵活地迁移和备份数据。

Redis入门概览

在高速的数据处理领域,Redis以其卓越的读写性能和多样化的数据结构,赢得了开发者的广泛赞誉。其读取速度高达110,000次/秒,写入速度也达到了81,000次/秒,这样的性能表现让Redis在众多数据存储系统中脱颖而出。

Redis核心特点

  • 存储模型:在众多NOSQL系统中,Redis以其独特的魅力和实用性脱颖而出。与传统的MySQL二维表格存储方式不同,Redis是一个开源的、基于ANSI C语言编写的key-value存储系统。

  • 持久化:Redis的数据也是主要存储在计算机内存中,这使得它拥有极高的读写性能。但Redis并不满足于此,它进一步引入了数据持久化的机制,Redis会周期性地更新数据到磁盘或将修改操作记录到追加文件中,从而确保了数据的持久化。

  • 原子性:Redis的所有操作都是原子性的,这意味着在执行过程中不会被其他操作打断,从而保证了数据的一致性和准确性。此外,Redis还支持多个操作的合并执行,进一步提高了原子性操作的效率和灵活性。

  • 数据结构:高性能和原子性操作外,Redis还支持多种数据结构,包括字符串、列表、哈希、集合和有序集合等。这些丰富的数据结构使得Redis能够轻松应对各种复杂的数据处理需求,为开发者提供了极大的便利。

  • 主从复制:Redis的主从复制(集群)是一种数据冗余和故障恢复机制,它通过将一台Redis服务器(主节点)的数据复制到其他Redis服务器(从节点)来实现。这种复制是单向的,只能从主节点复制到从节点。

  • 其他功能:它还支持数据的过期时间设置、事务处理、消息订阅等实用功能,使得Redis在多个场景中都能发挥出巨大的作用。

Redis常用场景

在这里插入图片描述

数据缓存(提高访问性能)

优化这一流程的关键在于减少不必要的数据库访问次数,并提升每次访问的效率

在应用程序与数据库交互的过程中,若直接执行频繁的读取操作而不加优化,往往会遭遇性能瓶颈的挑战,这主要是由于数据库系统对读写操作的次数及效率存在固有的限制。主要的流程如下图所示:

1723284995938.png

Redis将数据存储在内存中,使得数据读取操作可以直接从内存中获取,大大提高了数据访问速度。同时,由于内存中的数据读写速度远超数据库,因此Redis的读写效率非常高,下面是一个业务服务体系得到层次架构,从上到下面分别为缓存层+数据访问层。

Spring Cloud 微服务总体架构图.png

数据缓存机制并非普适的灵丹妙药,而是针对特定情境展现出其无与伦比的优势,尤其是当面对两种核心特性时,其效用尤为显著。这两种特性分别是:以读取操作为主导、写入操作相对较少的场景,以及数据内容不常发生变动的静态数据集,总体对的服务架构如下图所示:

  • 读多写少的应用场景中,数据缓存成为了提升系统性能的关键手段。由于此类场景中的数据访问模式主要集中于读取操作,而数据更新的频率较低,因此,通过缓存机制将高频访问的数据预先加载至快速访问的存储介质(如内存)中,可以极大地减少数据访问的延迟,提高系统响应速度。

  • 数据不经常变化的静态数据集而言,数据缓存同样具有举足轻重的意义。由于这类数据更新频率极低,甚至可能在很长一段时间内保持不变,因此,将此类数据缓存起来可以长期享受快速访问的便利,而无需担心缓存失效或数据不一致的问题。

读多写少的场景 + 数据不经常变化

当应用程序需要频繁地从数据库中检索数据时,直接无差别的查询操作可能会迅速耗尽数据库的处理能力,导致响应速度下降,甚至影响到其他并发操作的执行效率。

Spring Cloud 微服务总体架构图.png

从上述分析中可以清晰洞察到,Redis作为数据缓存层,其高效运作显著地拦截并处理了原本大量直接指向MySQL数据库的查询请求。这一策略不仅极大地减轻了MySQL数据库的负担,还显著提升了系统的响应速度和执行效率,有效降低了数据库的运营负荷。

内存的局限性和成本

由于内存速度快,因此Redis会将缓存存储到内存中,但是由于内存的成本还是很高的,所以不可能像IO磁盘那样,有一个巨大的空间可以让数据随意存储,因此需要一些当内存到达阈值的时候,需要进行淘汰以及覆盖内存数据的方式进行复用空间。

Redis内存数据的淘汰方案

Redis的内存淘汰方案是Redis在内存使用达到上限时,为了保持系统的稳定运行和性能,所采取的一系列策略来回收内存空间。这些策略允许Redis根据一定的规则淘汰(删除)部分数据,以便为新数据腾出空间。

Redis内存淘汰策略.png

  1. noeviction(默认策略)

    • 当内存不足以容纳新写入数据时,新写入操作会报错。这是Redis的默认淘汰策略,适用于对内存使用有严格限制,且不希望因内存不足而导致写操作失败的场景。
  2. 基于LRU算法的淘汰策略

    • allkeys-lru:从所有key中使用LRU算法进行淘汰。这种策略适用于读操作频繁,且希望保留最近被频繁访问的数据的场景。
    • volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰。这种策略适用于读操作频繁,且希望保留最近被频繁访问的、同时也有过期时间的数据的场景。
  3. 基于LFU算法的淘汰策略(Redis 4.0及以后版本)

    • allkeys-lfu:从所有key中基于LFU算法进行淘汰。LFU算法会统计每个key的访问频率,值越小淘汰优先级越高。这种策略同样适用于读操作频繁,但更侧重于保留那些被频繁访问的数据。
    • volatile-lfu:从设置了过期时间的key中基于LFU算法进行淘汰。与volatile-lru类似,但基于LFU算法进行筛选。
  4. 随机淘汰策略

    • allkeys-random:从所有key中随机淘汰数据。这种策略适用于对淘汰策略无特殊要求的场景,提供了一种简单而公平的淘汰方式。
    • volatile-random:从设置了过期时间的key中随机淘汰数据。与allkeys-random类似,但仅限于设置了过期时间的key。
  5. 基于过期时间的淘汰策略

    • volatile-ttl:在设置了过期时间的key中,淘汰过期时间剩余最短的。这种策略适用于希望优先淘汰即将过期的数据的场景。
缓存的两种形式

页面缓存经常用在CMS(content manage system)内存管理系统里面。数据缓存经常会用在页面的具体数据里面。

页面缓存

页面缓存机制显著优化了数据访问流程。首次请求时,系统直接从数据库中检索所需内容,并随后动态生成一个静态页面副本。

Spring Cloud 微服务总体架构图.png

此后,每当遇到对该页面的读取请求时,系统便直接加载并呈现这一预先生成的静态页面,从而避免了重复的数据库查询操作。

Spring Cloud 微服务总体架构图.png

数据缓存

数据缓存相比较页面缓存,更为复杂,因为多个模块的数据独立性及其对缓存位置的特定需求,简单地将整个页面缓存化会限制系统的灵活性和效率。

鉴于单个页面往往集成了多个模块,这些模块各自依赖于不同的缓存源来检索数据,因此直接应用传统意义上的页面缓存策略变得不切实际,如下图所示:

Spring Cloud 微服务总体架构图.png

更适合采用细粒度的缓存策略,即针对每个模块或数据片段单独管理其缓存逻辑,确保数据能够按需、高效且准确地从相应的缓存中读取,从而既满足了页面的多样性需求,又优化了资源利用与访问性能。

Spring Cloud 微服务总体架构图.png

会话缓存(临时数据存储)

Redis作为会话缓存(Session Cache),会话缓存主要用于保存Web会话信息,以便在用户浏览网站的不同页面之间保持数据的一致性和状态,下面便是不同的用户对应的一个映射关系在Redis中。

Spring Cloud 微服务总体架构图.png

在Web应用程序中,当用户首次访问网站时,服务器会为用户创建一个新的会话,并将该会话的ID发送给用户的浏览器。用户的浏览器会在后续的请求中携带这个会话ID,以便服务器能够识别并恢复用户的会话。

Spring Cloud 微服务总体架构图.png

服务器会将用户的会话数据存储在Redis中,并使用会话ID作为键。当用户发送请求时,服务器会从Redis中根据会话ID获取用户的会话数据,从而恢复用户的状态。

会话缓存的主要目的是在用户访问网站期间,保存用户的状态信息。这些信息可能包括用户的身份验证信息、购物车内容、个性化设置等。这些信息通常以键值对的形式存储在Redis中,每个用户的会话都有一个唯一的键,通常是一个会话ID,与之关联的值则是用户的会话数据。

排行榜/计数器

排行榜作为一种广泛运用于社交媒体、游戏娱乐及电商平台等领域的核心功能,其核心价值在于直观呈现用户活跃度、商品热度、话题讨论度等关键信息的排名序列。

Sorted Set

Sorted Set(有序集合)的一种底层实现方式是通过跳表(Skip List)来完成的。跳表是一种可以替代平衡树的数据结构,它通过增加多级索引来提高数据查找的效率。以图是对Sorted Set中跳表的实现和数据结构的详细说明:

Spring Cloud 微服务总体架构图.png

跳表是在链表的基础上增加了多级索引(或称为“层”),每一层都是链表的一个子集,但是层的数量会随着数据量的增加而增加。跳表通过索引位置的跳转,实现了数据的快速定位。

跳表中插入一个元素的时间复杂度为O(logn),与二分查找的时间复杂度相同,因此跳表也被称为实现了二分查找的链表。

特点说明

Redis的有序集合不仅能够存储元素,还能为每个元素关联一个浮点数分数(score),这一特性使得它成为实现排行榜的理想选择。

  • 唯一性:Sorted Set中的元素是唯一的,不允许重复。
  • 有序性:通过分数(score)对元素进行排序,支持从小到大和从大到小两种排序方式。
  • 动态性:支持在运行时动态地添加、删除和更新元素及其分数。
排行榜场景

Redis的有序集合(Sorted Set)构成了一种独特而强大的数据结构,其精妙之处在于每个成员元素均紧密绑定了一个浮点数分数(score),这一特性直接驱动了集合内部元素根据分数自动排序的机制。

Spring Cloud 微服务总体架构图.png

Sorted Set不仅支持传统的正序排名,即按照元素的分数从低到高进行排序,还能轻松实现倒序排名,即分数从高到低排序,这种双向排序的能力,为开发者提供了构建多样化排行榜的广阔舞台。

Spring Cloud 微服务总体架构图.png

ZADD 命令通过指定有序集合的键名、一系列元素及其相应的分数,实现了一次性批量处理的能力。若集合中尚未包含某元素,则直接将其与分数一起添加至集合中;若元素已存在于集合中,则将其分数更新为指定的新值,这一过程既快捷又高效。

排行榜操作处理

在这里插入图片描述

添加元素和分数:

ZADD 命令在 Redis 中扮演着核心角色,它允许开发者高效地向有序集合(Sorted Set)中插入或更新元素及其对应的分数值。

Spring Cloud 微服务总体架构图.png

我们对Sorted Set的底层结构进行了精细化的三层划分,其中前两层被设计为索引层,专门用于高效地圈定数据插入或查询的范围。随后,通过执行ZADD命令,我们向这个有序集合中插入了一个分数为12的元素。

Spring Cloud 微服务总体架构图.png

在插入过程中,系统充分利用了前两层索引层来快速定位合适的插入位置。这一机制确保了新元素能够按照既定的有序规则(即根据元素的分数值)被精确地放置到集合中的适当位置,而无需对整个集合进行耗时的重新排序操作。

获取排行榜

在Redis数据库中,巧妙地运用ZRANGEZREVRANGE命令,能够实现对排行榜数据的高效检索与排序。这两个命令分别允许用户根据元素的分数(score),灵活地以升序或降序方式检索存储在有序集合(sorted set)中的元素。

Spring Cloud 微服务总体架构图.png

  • ZRANGE命令:此命令用于从有序集合中按照分数从低到高的顺序(即升序)获取指定范围内的元素。通过指定起始索引和结束索引,用户可以精确地控制返回的元素列表,从而轻松实现如“获取排行榜前N名”等功能。
  • ZREVRANGE命令:与ZRANGE相对,ZREVRANGE命令则是按照分数从高到低的顺序(即降序)检索元素。这一特性特别适用于需要展示“热门榜”、“高分榜”等场景,其中元素的排名基于其分数的高低。
更新元素分数

通过ZINCRBY命令,不仅简化了排行榜更新的逻辑复杂度,还极大地提升了系统的响应速度和性能。

Spring Cloud 微服务总体架构图.png

它避免了传统方法中可能因并发更新导致的竞态条件和数据不一致问题,因为Redis内部通过其高效的原子操作机制来保证每次分数调整的独立性和完整性。

计数器场景

计数器常用于记录某种事件的发生次数,如用户访问量、点击量、点赞数等。Redis提供了对整数的原子操作,使得计数器功能的实现变得非常简单和高效。

在这里插入图片描述

初始化计数器

将计数器初始化为0,意味着从零开始计数,这在统计用户访问量、记录事件发生次数等场景中尤为常见。

SET命令的使用不仅限于计数器的初始化,其原子性保证了在高并发环境下操作的安全性,避免了数据不一致的问题。然而,在专门处理计数任务的场景中,Redis还提供了如INCRDECR等专门的计数器命令,它们能够在不指定初始值的情况下,直接对存储在字符串中的整数值进行原子性的增加或减少操作。

增加计数器

通过执行INCR命令,可以确保在多线程或多进程环境中,计数器的值能够安全地增加1,避免了并发更新时可能产生的数据竞争和不一致问题。

当业务需求不仅仅是简单地递增1,而是需要按指定数量增加计数器的值时,INCRBY命令便显得尤为重要。INCRBY允许开发者指定一个增量值,该值将被原子地加到计数器的当前值上,从而实现了更加灵活的计数逻辑。

INCRINCRBY命令的原子性保证了操作的独立性和完整性,即使在高并发的环境下,也能确保计数器的准确更新。这种设计不仅简化了并发控制的复杂性,还提升了系统的性能和响应速度。

获取计数器值

通过执行GET命令并指定计数器的键名,系统能够以原子且高效的方式返回该计数器当前的数值,确保了数据的即时性和准确性。

这一操作不仅简化了数据获取的流程,还保证了在多线程或多进程环境中数据访问的一致性。由于Redis内部采用了高效的存储和检索机制,GET命令能够迅速响应查询请求,即使在处理大规模数据集时也能保持出色的性能表现。

消息队列

Redis提供了多种数据结构来实现消息队列,其中最常用的包括List结构、PubSub(发布/订阅)和Stream结构。

消息队列.png

List列表实现方案

Redis的List数据结构,作为一种高效且灵活的容器,其核心实现基于双向链表,这一设计赋予了List从头部和尾部进行高效数据操作的能力。

Spring Cloud 微服务总体架构图.png

具体而言,这种双向链表的特性允许开发者在列表的两端轻松实现数据的插入(push)和移除(pop)操作,极大地提升了数据处理的灵活性和效率。

生产者-消费者模型

在生产者-消费者模型中,Redis的List被广泛用作消息队列。

  • 生产者(数据生产者)可以通过执行LPUSH命令,将新生成的消息高效地添加到队列的左侧(即列表的头部),这种从头部插入数据的方式,保证了消息能够按照它们被发送的顺序进行排队。

Spring Cloud 微服务总体架构图.png

  • 消费者(数据处理者)则可以利用RPOPBRPOP命令从队列的右侧(即列表的尾部)移除并获取消息。RPOP命令执行后即移除并返回列表的最后一个元素,若列表为空,则返回nil。

Spring Cloud 微服务总体架构图.png

BRPOP(Blocking List Pop)命令则提供了一种更为高级的阻塞式消费模式,它允许消费者在列表为空时进入等待状态,直至有新的元素被添加到列表中,从而避免了消费者因频繁轮询列表而浪费资源。

Redis消息队列系统(基于List)

使用Redis的List数据结构来实现一个简单的消息队列系统。这个系统包括生产者(Producer)和消费者(Consumer)两部分,它们通过Redis的LPUSHBRPOP命令进行消息的发布和接收。

生产者(Producer)

生产者负责将消息发布到Redis的List中。这里使用LPUSH命令,因为它允许我们从列表的左侧(即头部)插入元素,这样可以保证消息的顺序。

import redis.clients.jedis.Jedis;

public class Producer {
    private static final String QUEUE_KEY = "messageQueue";

    public void sendMessage(String message) {
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            jedis.lpush(QUEUE_KEY, message);
            System.out.println("Message sent: " + message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Producer producer = new Producer();
        producer.sendMessage("Hello, Redis Queue!");
        producer.sendMessage("Another message for the queue.");
    }
}
消费者(Consumer)

消费者负责从Redis的List中接收消息。这里使用BRPOP命令,它是一个阻塞式的列表弹出原语,可以阻塞连接直到有元素可弹出为止,或者直到超时。

import redis.clients.jedis.Jedis;

public class Consumer {
    private static final String QUEUE_KEY = "messageQueue";
    private static final long TIMEOUT = 0; // 0 表示无限等待

    public void receiveMessage() {
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            List<String> result = jedis.brpop(TIMEOUT, QUEUE_KEY);
            if (result != null && !result.isEmpty()) {
                String message = result.get(1); // 索引1是消息内容,索引0是key
                System.out.println("Received message: " + message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        while (true) { // 持续监听消息
            consumer.receiveMessage();
        }
    }
}

注意:在实际应用中,消费者通常不会在一个无限循环中持续调用receiveMessage,因为这会导致CPU资源的浪费。你可以考虑使用线程池、定时任务或其他并发控制机制来优化消费者的行为。

PubSub(发布/订阅)实现方案

PubSub(Publish-Subscribe)机制是一种高效且解耦的通信范式,它依托于频道(Channel)作为消息传递的中介。在这一模型中,消息的生产者(发布者)不再直接将信息发送给特定的消费者,而是将消息广播至特定的频道。消费者则通过订阅这些频道来接收并处理感兴趣的消息,实现了生产与消费之间的松耦合。

基于频道的发布/订阅模式,不仅简化了消息传递的复杂性,还提高了系统的可扩展性和灵活性

Spring Cloud 微服务总体架构图.png

  • 发布者利用PubSub系统提供的接口,将包含数据或事件信息的消息推送到指定的频道上。这一过程是异步的,意味着发布者无需等待消费者的响应即可完成消息的发送。

  • 消费者通过订阅操作,向PubSub系统表达自己对特定频道消息的兴趣。一旦有消息被发布到该频道,系统便会自动将这些消息分发给所有已订阅的消费者。

Java代码案例

下面是一个简单的Java代码案例,展示了如何使用Jedis库来实现Redis的Pub/Sub(发布/订阅)模式。这个案例包括一个发布者(Publisher)类和一个订阅者(Subscriber)类。

发布者(Publisher)类
import redis.clients.jedis.Jedis;  
  
public class Publisher {  
  
    private static final String CHANNEL = "mychannel";  
  
    public void publishMessage(String message) {  
        try (Jedis jedis = new Jedis("localhost", 6379)) {  
            System.out.println("Sending message: " + message);  
            jedis.publish(CHANNEL, message);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    public static void main(String[] args) {  
        Publisher publisher = new Publisher();  
        publisher.publishMessage("Hello, Redis Pub/Sub!");  
        publisher.publishMessage("Another message for Pub/Sub.");  
    }  
}
订阅者(Subscriber)类

订阅者通常会在一个单独的线程或进程中运行,因为它需要不断地监听来自Redis的消息。为了简化示例,我们将在一个单独的main方法中启动订阅者,但在实际应用中,你可能希望将订阅者代码放在服务或应用程序的某个持久化组件中。

import redis.clients.jedis.Jedis;  
import redis.clients.jedis.JedisPubSub;  
  
public class Subscriber {  
  
    private static final String CHANNEL = "mychannel";  
  
    public static void main(String[] args) {  
        Jedis jedis = new Jedis("localhost", 6379);  
        JedisPubSub jedisPubSub = new JedisPubSub() {  
            @Override  
            public void onMessage(String channel, String message) {  
                System.out.println("Received message on channel '" + channel + "': " + message);  
            }  
  
            @Override  
            public void onPMessage(String pattern, String channel, String message) {  
                // 可以处理模式匹配的消息,但本例中不使用  
            }  
  
            @Override  
            public void onSubscribe(String channel, int subscribedChannels) {  
                System.out.println("Subscribed to channel '" + channel + "' with " + subscribedChannels + " total subscriptions.");  
            }  
  
            @Override  
            public void onUnsubscribe(String channel, int subscribedChannels) {  
                System.out.println("Unsubscribed from channel '" + channel + "' with " + subscribedChannels + " total subscriptions left.");  
            }  
  
            @Override  
            public void onPUnsubscribe(String pattern, int subscribedChannels) {  
                // 可以处理取消模式匹配订阅的情况,但本例中不使用  
            }  
  
            @Override  
            public void onPSubscribe(String pattern, int subscribedChannels) {  
                // 可以处理模式匹配订阅的情况,但本例中不使用  
            }  
        };  
  
        jedis.psubscribe(jedisPubSub, CHANNEL);  
  
        // 在实际应用中,你应该使用更优雅的方式来处理订阅者线程的结束,例如使用守护线程或等待某个条件。  
        try {  
            Thread.sleep(Long.MAX_VALUE);  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
            System.out.println("Subscriber interrupted and will exit.");  
        } finally {  
            // 取消订阅(可选,因为程序即将退出)  
            jedis.punsubscribe();  
            jedis.close();  
        }  
    }  
}
Stream结构(Redis 5.0及以后版本)

Redis自5.0版本起,引入了Stream这一创新的数据结构,专为高效存储与灵活处理消息流而设计。Stream不仅继承了传统消息队列的核心优势,如消息持久化、严格的有序性保障,还独创性地引入了消息的唯一ID机制以及消费者组模型,极大地丰富了消息处理的场景与灵活性。

生产端

在生产端,开发者通过利用Redis的XADD命令,能够轻松地将消息发布至指定的Stream中。

  
public class RedisStreamProducer {  
  
    private static final String STREAM_KEY = "mystream";  
  
    public static void main(String[] args) {  
        try (Jedis jedis = new Jedis("localhost", 6379)) {  
            // 构造消息ID,通常可以使用 * 来让Redis自动生成唯一的ID  
            String messageId = "*";  
            // 准备消息内容,这里使用Map来模拟键值对消息  
            Map<String, String> messageContent = new HashMap<>();  
            messageContent.put("field1", "value1");  
            messageContent.put("field2", "value2");  
  
            // 使用XADD命令发布消息到Stream  
            // 第一个参数是Stream的key,第二个参数是消息ID(* 表示自动生成),后面跟着字段和值  
            StreamEntryID entryId = jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, messageContent);  
  
            System.out.println("Message published with ID: " + entryId.getId());  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

这一过程不仅简便快捷,而且确保了每条消息都能获得一个全局唯一的ID,这对于追踪消息状态、实现消息去重等高级功能至关重要。

消费端

而在消费端,Redis的XREAD命令则为消费者提供了从Stream中读取消息的强大能力。消费者可以根据自身需求,灵活选择读取方式,如阻塞读取、按ID范围读取等,以实现对消息流的精准控制与高效处理。

import redis.clients.jedis.Jedis;  
import redis.clients.jedis.StreamEntryID;  
import redis.clients.jedis.StreamEntry;  
  
import java.util.List;  
  
public class RedisStreamConsumer {  
  
    private static final String STREAM_KEY = "mystream";  
    private static final String CONSUMER_GROUP = "mygroup";  
    private static final String CONSUMER_NAME = "myconsumer";  
  
    public static void main(String[] args) {  
        try (Jedis jedis = new Jedis("localhost", 6379)) {  
        
            List<StreamEntry> entries = jedis.xreadgroup(CONSUMER_GROUP, CONSUMER_NAME,  
                    java.util.Collections.singletonList(new Entry(STREAM_KEY, StreamEntryID.create("$"))), 0);  
  
            if (!entries.isEmpty()) {  
                for (StreamEntry entry : entries) {  
                    // 处理消息  
                    System.out.println("Received message ID: " + entry.getId());  
                    entry.getFields().forEach((field, value) -> System.out.println(field + ": " + value));  
  
                }  
            } else {  
                System.out.println("No messages available.");  
            }  
  
            // 并使用更合适的阻塞超时时间或逻辑来处理没有消息可读的情况。  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
    
    // 假设的Entry类(实际代码中不需要,仅用于说明)  
    static class Entry {  
        String streamKey;  
        StreamEntryID id;  
  
        Entry(String streamKey, StreamEntryID id) {  
            this.streamKey = streamKey;  
            this.id = id;  
        }  
    }  
 
}  
  

此外,通过结合消费者组(Consumer Group)的使用,Redis Stream还支持了消息的负载均衡与故障恢复机制,确保即使在分布式环境下,消息也能被可靠地分发给各个消费者进行处理。


Redis安装指引

Redis的下载地址

$ wget http://download.redis.io/releases/redis-x.y.z.tar.gz
$ tar xzf redis-x.y.z.tar.gz
$ cd redis-x.y.z
$ make$cd..
$ ln -s redis-x.y.z redis

配置后台运行

Redis 默认并不以守护进程(即后台服务)的方式运行,这可能会影响到其在服务器上的持续运行和稳定性。为了改变这一默认设置,您可以通过编辑 Redis 的配置文件来启用守护进程模式。以下是详细的步骤说明:

Redis 的配置文件。假设您的 Redis 配置文件位于 /data/redis/redis.conf,您可以运行以下命令来编辑它:

sudo vim /data/redis/redis.conf

在打开的配置文件中,您需要找到与守护进程相关的配置项。通常,这个配置项叫做 daemonize。将其值从 no 修改为 yes,以启用守护进程模式。

daemonize yes

注意,在修改配置文件并保存更改后,您可能需要重新启动 Redis 服务以使更改生效。具体的重启命令取决于您的操作系统和 Redis 的安装方式,但通常类似于以下命令:

sudo systemctl restart redis

或者

sudo /etc/init.d/redis restart

或者如果您是直接使用 redis-server 命令启动的,您可能需要先停止当前运行的 Redis 实例,然后再使用修改后的配置文件重新启动它。

配置日志地址

在配置日志(log)文件的存储位置时,默认情况下,日志信息会实时显示在命令行终端的窗口中,这对于即时监控和调试非常有用。然而,为了满足不同的需求,您还可以选择将日志信息重定向至特定的文件路径,编辑Redis配置文件vim /data/redis/redis.conf

logfile "/data/redis/logs/redis.log" 

配置pid

Redis 作为守护进程运行时,默认会将进程ID文件(pid file)保存在 /var/run/redis.pid。然而,在复杂的系统环境中,特别是当运行多个 Redis 实例时,为了避免冲突,管理员可能需要自定义 pid 文件的存储位置。这可以通过修改 Redis 配置文件中的 pidfile 指令来实现,确保每个 Redis 实例都有其独特的 pid 文件和监听端口。

例如,如果您想将 pid 文件保存在 /data/redis/redis1.pid,您可以在 Redis 配置文件中设置:

pidfile /data/redis/redis1.pid

配置端口和接收请求IP

在配置Redis服务器时,我们需要首先确定它将监听哪个端口以便接收客户端的连接请求。默认情况下,Redis使用端口6379,这是一个广为人知的默认端口,但为了安全起见,您也可以在配置文件中使用port指令来指定其他端口。

# 设置Redis监听的端口  
port 6379  
  
# 在生产环境中,建议将Redis绑定到本地回环地址,以限制访问  
bind 127.0.0.1

除了端口外,我们还需要考虑Redis服务器应该绑定到哪些IP地址上。bind指令用于指定Redis应该监听哪些IP地址上的连接请求。如果不设置bind指令或将其设置为0.0.0.0,Redis将会监听所有可用的网络接口,这意味着它将接受来自任何IP地址的连接请求。

在生产环境中,出于安全考虑,我们通常建议将Redis绑定到本地回环地址127.0.0.1(也被称为localhost),这样Redis就只会接受来自同一台机器上的连接请求。

连接Redis建立通信桥梁

当Redis服务器和客户端位于同一台机器上时,你可以直接使用redis-cli命令来连接服务器,而无需任何额外的参数。这是连接到本地Redis服务器的最简单方法:

redis-cli

执行上述命令后,你将进入Redis的交互式终端,可以在其中执行各种Redis命令。

连接到远程Redis服务器

如果Redis服务器和客户端位于不同的机器上,你需要提供Redis服务器的地址和端口(默认为6379)来建立连接。这可以通过在redis-cli命令后添加-h(主机名)和-p(端口号)参数来实现:

redis-cli -h <hostname> -p <port>

其中,<hostname>是Redis服务器的IP地址或主机名,<port>是Redis服务器的端口号。

修改Redis配置文件(redis.conf)

如果客户端无法直接连接到Redis服务器,除了检查网络连接和防火墙设置外,有时还需要修改Redis的配置文件(通常是redis.conf)。以下是一些可能需要关注的配置项:

绑定地址(bind):默认情况下,Redis可能只监听本地地址(如127.0.0.1),这意味着只有本地客户端可以连接。要允许远程连接,你可以将bind指令更改为监听所有可用的网络接口(使用0.0.0.0)或特定的IP地址。

bind 0.0.0.0

或者,如果你只想让某些特定的IP地址或网络接口可以连接,可以列出它们:

bind 192.168.1.100 10.0.0.1

密码认证(requirepass):如果Redis服务器启用了密码认证,你需要在连接时提供密码。这可以通过在redis-cli命令后添加-a参数来实现:

redis-cli -h <hostname> -p <port> -a <password>

或者,在redis.conf文件中设置密码:

requirepass yourpassword

总结一下 — Redis启动过程

在这里插入图片描述

服务器配置初始化

在启动服务器流程的第一步,我们将执行全局服务器配置的初始化。这一步是为了确保服务器能够按照预设的规范进行运作,包括但不限于端口设置、日志级别、安全协议等关键参数。

加载定制配置文件

随后,系统会尝试加载用户指定的配置文件。若用户已明确指定了配置文件路径,系统将优先读取并使用该文件中的设置。若用户未指定,系统将自动采用预设的默认配置,以保证服务的顺利启动和稳定运行。

服务器环境准备与初始化

在加载配置文件后,我们会对服务器环境进行准备和初始化。这一步骤涉及到服务器资源的分配、内部状态的设置以及服务依赖的加载等关键任务,确保服务器能够在最佳状态下接受和处理来自客户端的请求。

数据库连接与数据加载

系统将尝试与数据库建立连接,并加载所需的数据。这一步骤对于依赖数据库的应用至关重要,因为它确保了服务器能够实时访问和更新数据库中的数据,从而为用户提供最新、最准确的信息。

网络监听与请求处理

服务器将开始进入网络监听状态,等待并处理来自客户端的请求。在这一阶段,服务器会持续监听指定的网络端口,一旦接收到请求,就会立即启动相应的处理逻辑,确保客户端的请求能够得到快速、准确的响应。