查漏补缺第一期(Redis相关)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 前言目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~Q1 & 请介绍一下redis的架构Redis是一种开源的高性能键值存储系统,通常被用作内存数据库、缓存和消息中间件。它具有简单、灵活、高效的特点,以及丰富的数据结构和功能,使其成为许多应用程序的首选。

前言

目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~

Q1 & 请介绍一下redis的架构

Redis是一种开源的高性能键值存储系统,通常被用作内存数据库、缓存和消息中间件。它具有简单、灵活、高效的特点,以及丰富的数据结构和功能,使其成为许多应用程序的首选。

Redis的架构主要由以下几个关键组件组成:

  1. 客户端:应用程序通过Redis客户端与Redis服务器进行通信。客户端可以使用多种编程语言和协议与Redis进行交互,如Redis的官方客户端库、REST API或其他第三方客户端。
  2. 服务器:Redis服务器是Redis的核心组件,负责处理客户端请求、执行命令和存储数据。服务器使用单线程事件循环模型,通过监听网络连接和处理事件来实现高并发和低延迟的性能。
  3. 数据存储:Redis支持多种数据结构,包括字符串、哈希表、列表、集合和有序集合等。这些数据结构都可以通过唯一的键来进行访问和操作。Redis的数据存储是基于内存的,但也可以通过持久化机制将数据写入磁盘,以便在服务器重启时进行恢复。
  4. 内存管理:Redis通过使用自定义的内存分配器来管理内存,以提高性能并减少内存碎片。它使用了多种技术,如对象共享、对象池和内存压缩,来有效地管理内存使用。
  5. 高可用性:为了实现高可用性和数据冗余,Redis提供了主从复制和哨兵机制。主从复制通过将数据从主服务器复制到多个从服务器,实现数据的冗余备份和读写分离。哨兵机制用于监控和管理Redis服务器集群,当主服务器发生故障时,自动选举新的主服务器。
  6. 集群模式:Redis还提供了集群模式,可以将数据分片存储在多个节点上,实现水平扩展和负载均衡。集群模式使用哈希槽来划分数据,每个节点负责管理一部分哈希槽的数据。

总体而言,Redis的架构设计简单而灵活,适用于各种场景。它的高性能、丰富的数据结构和功能,使其成为处理大规模数据、高并发访问和实时应用的理想选择。

Q2 & 请详细讲一下redis的线程模型

Redis的线程模型采用的是单线程的事件循环模型,也称为I/O多路复用模型。

  1. 单线程模型:Redis使用单线程来处理所有的客户端请求和数据库操作。这意味着Redis在任何给定的时间只能处理一个请求,但通过事件循环机制,它能够高效地处理大量并发请求。
  2. 事件循环:Redis使用事件循环来实现高并发和低延迟的性能。事件循环是通过I/O多路复用技术实现的,通常使用select、poll或epoll等系统调用。Redis通过监听套接字上的事件,如可读、可写或异常事件,来感知和处理客户端请求。
  3. 非阻塞I/ORedis使用非阻塞I/O来实现事件循环。在接收到客户端请求时,Redis不会阻塞等待请求完成,而是立即返回到事件循环并继续处理其他请求。当请求的I/O操作完成时,Redis会通过回调函数来处理响应数据。
  4. 文件事件:Redis使用文件事件来表示与客户端或其他网络连接相关的事件。每当有新的客户端连接或数据到达时,Redis会生成相应的文件事件并将其加入到事件队列中。事件循环会从队列中获取文件事件并处理它们。
  5. 时间事件:Redis还支持时间事件,用于执行定时任务。时间事件可以是一次性的,也可以是周期性的。通过时间事件,Redis可以执行诸如过期键的清理、统计信息的更新等后台任务。
  6. 非阻塞操作:Redis的数据存储是基于内存的,因此读写操作通常是非阻塞的。这使得Redis能够在快速的内存访问下提供高性能的读写操作。
  7. 多个数据库:Redis支持多个数据库,每个数据库都有自己的键值空间。在单线程模型下,Redis使用字典来存储数据库,通过索引来快速访问不同的数据库。

通过以上的线程模型,Redis能够高效地处理大量的并发请求,而无需为每个请求创建线程或进程。单线程模型可以避免线程切换和同步开销,提供较低的延迟和较高的吞吐量。同时,Redis通过使用非阻塞I/O事件驱动的方式,实现了高效的事件处理和资源利用。

下面我们使用Java来简单的仿写一下单线程模型

public class Q2 {
    public static void main(String[] args) throws InterruptedException {
        RedisTaskQueue taskQueue = new RedisTaskQueue();
        RedisEventLoop eventLoop = new RedisEventLoop(taskQueue);
        RedisClient client = new RedisClient(taskQueue);
        // 启动事件处理器线程
        Thread eventLoopThread = new Thread(eventLoop);
        eventLoopThread.start();
        // 模拟客户端发送命令
        client.sendCommand("SET key1 value1");
        client.sendCommand("GET key1");
        client.sendCommand("DEL key1");
        // 等待所有命令执行完成
        eventLoopThread.join();
//        Executing Redis command: SET key1 value1
//        Executing Redis command: GET key1
//        Executing Redis command: DEL key1
    }
}
// 模拟Redis命令的执行任务
class RedisCommandTask {
    private String command;
    public RedisCommandTask(String command) {
        this.command = command;
    }
    public void execute() {
        // 模拟命令的执行
        System.out.println("Executing Redis command: " + command);
    }
}
// 模拟Redis的任务队列
class RedisTaskQueue {
    private Queue<RedisCommandTask> queue = new LinkedList<>();
    public synchronized void enqueue(RedisCommandTask task) {
        queue.add(task);
        notify(); // 通知等待中的线程有新任务
    }
    public synchronized RedisCommandTask dequeue() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 如果队列为空,则等待新任务的到来
        }
        return queue.poll();
    }
}
// 模拟Redis的事件处理器
class RedisEventLoop implements Runnable {
    private RedisTaskQueue taskQueue;
    public RedisEventLoop(RedisTaskQueue taskQueue) {
        this.taskQueue = taskQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                RedisCommandTask task = taskQueue.dequeue();
                task.execute();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}
// 模拟Redis的客户端
class RedisClient {
    private RedisTaskQueue taskQueue;
    public RedisClient(RedisTaskQueue taskQueue) {
        this.taskQueue = taskQueue;
    }
    public void sendCommand(String command) {
        RedisCommandTask task = new RedisCommandTask(command);
        taskQueue.enqueue(task);
    }
}

使用synchronized关键字实现了任务队列的线程安全性。enqueue()方法用于将任务添加到队列中,并使用notify()通知等待中的线程有新任务可执行。dequeue()方法则在队列为空时调用wait()进入等待状态,直到有新任务到来时被唤醒。

事件处理器线程在start()方法中通过循环不断从任务队列中取出任务并执行。如果队列为空,线程会调用wait()方法进入等待状态,直到有新任务到来。这样保证了事件处理器在没有任务时不会空转消耗CPU资源。

主程序中创建了一个RedisClient实例,并通过sendCommand()方法模拟了客户端发送命令的过程。每个命令都被封装成RedisCommandTask对象,并通过任务队列传递给事件处理器线程执行。这种方式模拟了Redis的非阻塞命令执行,即命令被添加到任务队列后,客户端可以继续发送其他命令而无需等待命令执行完成。

Q3 & 请详细讲一下redis的非阻塞式IO和多路复用

Redis使用非阻塞式I/O多路复用技术来实现高性能的事件驱动模型。

  1. 非阻塞式I/O: 非阻塞式I/O是一种I/O操作的模式,在进行读取或写入操作时,不会阻塞线程等待操作完成。Redis使用非阻塞I/O来实现事件驱动模型,它的实现原理如下:
  • 设置套接字为非阻塞模式:Redis在接受客户端连接或创建套接字时,将套接字设置为非阻塞模式。这样,在进行读取或写入操作时,不会阻塞线程,而是立即返回。
  • 非阻塞的读取操作:当Redis执行读取操作时,它会检查套接字上是否有数据可读。如果没有数据可读,读取操作会立即返回,避免线程阻塞。Redis可以使用非阻塞的方式读取数据,以便处理其他事件。
  • 非阻塞的写入操作:当Redis执行写入操作时,它会检查套接字是否可写。如果套接字不可写,写入操作会立即返回,避免线程阻塞。Redis可以使用非阻塞的方式写入数据,以便处理其他事件。
  1. 多路复用:多路复用是一种I/O多路复用技术,用于同时监视多个套接字上的事件。Redis使用多路复用技术来管理多个套接字上的事件,以实现高效的事件驱动模型。下面是多路复用的实现原理:
  • 注册事件套接字Redis将需要监听的套接字注册到多路复用器中,并指定事件类型,如可读、可写或异常。
  • 多路复用器监听事件:多路复用器负责监听所有已注册套接字上的事件。它使用系统调用(如select、poll或epoll)来监视套接字上的事件状态。
  • 事件就绪通知:当套接字上的事件状态发生变化时,多路复用器会通知Redis。这样,Redis可以及时处理事件。
  • 事件处理:一旦事件就绪,Redis会调用相应的事件处理函数来处理事件。例如,如果有可读事件就绪,Redis会调用读取数据的函数来处理请求。

通过非阻塞式I/O多路复用的组合,Redis实现了高效的事件驱动模型非阻塞式I/O使得Redis能够在进行读取或写入操作时不会阻塞线程,提高了并发处理能力。

多路复用技术允许Redis同时监听多个套接字上的事件,提供高效的事件管理和触发能力。这种组合使得Redis能够处理大量并发请求,提供高性能和低延迟的服务。

下面通过Java简单的演示一下非阻塞式I/O和多路复用

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NonBlockingServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(8888));
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = serverSocketChannel.accept();
                    clientChannel.configureBlocking(false);
                    clientChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = clientChannel.read(buffer);
                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String message = new String(data);
                        System.out.println("Received message: " + message);
                    }
                    clientChannel.close();
                }
            }
        }
    }
}

这个示例中,我们创建了一个非阻塞的服务器,使用ServerSocketChannel进行监听,并将其注册到Selector上。然后,在循环中,调用selector.select()等待事件发生。当有事件发生时,通过遍历selectedKeys来处理事件。如果事件是OP_ACCEPT,表示有新的客户端连接,我们接受连接并将客户端的SocketChannel注册到Selector上,监听可读事件OP_READ。如果事件是OP_READ,表示有数据可读,我们读取数据并处理。

结束语

大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

项目源码(源码已更新 欢迎star⭐️)

往期设计模式相关文章

设计模式项目源码(源码已更新 欢迎star⭐️)

Kafka 专题学习

项目源码(源码已更新 欢迎star⭐️)

ElasticSearch 专题学习

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

博客(阅读体验较佳)






















相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
缓存 NoSQL Java
吹爆!阿里最新开源的这份Redis全栈小册,涵盖了Redis的所有操作
前言 Redis这玩意不用多说,Java后端打工人就没有没接触过的,现在出去面试基本上是必问项;而且在工作中在项目中还能起很大的作用。它不仅能减少数据库的操作、并且你还可以利用redis的一些数据结构如set sorted set 解决一些特定的问题、利用单线程实现分布式锁、一些简单的订阅发布等等。 当然,这些你都会的话,那说明你只是停留在会用的阶段。如果你想要精通redis,并想在面试的中凭借Redis俘获面试官的芳心的话。除了会用以外,还要去掌握redis一些基本的原理如压缩表、跳跃表、哨兵模式、集群、高可用,这样可以让你更好的使用redis,保证缓存利用最大化、资源最优化。掌握这些你在
151 0
|
6月前
|
存储 缓存 NoSQL
实战:第十一篇:StringRedisTemplate获取redis信息,面试官突击一问
实战:第十一篇:StringRedisTemplate获取redis信息,面试官突击一问
|
6月前
|
存储 缓存 NoSQL
3w字深度好文|Redis面试全攻略
3w字深度好文|Redis面试全攻略
98 1
|
6月前
|
存储 缓存 NoSQL
Redis面试全攻略
Redis面试全攻略
87 0
|
11月前
|
存储 NoSQL 中间件
阿里技术专家亲码:满干货“Redis核心笔记”,全篇无尿点
Redis(Remote DictionaryService)是互联网技术领域使用最为广泛的存储中间件,也就是「远程字典服务」。Redis以其超高的性能、完美的文档、简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评。
|
NoSQL 网络协议 程序员
7年资深后端带你读懂Redis源码,共总结了这7点心得
7年资深后端带你读懂Redis源码,共总结了这7点心得
69782 2
7年资深后端带你读懂Redis源码,共总结了这7点心得
|
消息中间件 缓存 运维
不要小看一个Redis!从头到尾全是精华,阿里Redis速成笔记太香了
Redis想必大家都听说过,不管是面试还是工作上我们都能见到。但是Redis到底能干什么?又不能干什么呢?(如下图)
|
存储 SQL JSON
2022黑马Redis跟学笔记.基础篇(一)
2022黑马Redis跟学笔记.基础篇(一)
2022黑马Redis跟学笔记.基础篇(一)
|
存储 NoSQL 算法