查漏补缺第一期(Redis相关)
前言
目前正在出一个查漏补缺专题
系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~
本专题主要以Java
语言为主, 好了, 废话不多说直接开整吧~
Q1 & 请介绍一下redis的架构
Redis
是一种开源的高性能键值存储系统,通常被用作内存数据库、缓存和消息中间件。它具有简单、灵活、高效的特点,以及丰富的数据结构和功能,使其成为许多应用程序的首选。
Redis的架构主要由以下几个关键组件组成:
-
客户端:应用程序通过
Redis
客户端与Redis
服务器进行通信。客户端可以使用多种编程语言和协议与Redis
进行交互,如Redis
的官方客户端库、REST API
或其他第三方客户端。 -
服务器:
Redis
服务器是Redis
的核心组件,负责处理客户端请求、执行命令和存储数据。服务器使用单线程事件循环模型,通过监听网络连接和处理事件来实现高并发和低延迟的性能。 -
数据存储:
Redis
支持多种数据结构,包括字符串、哈希表、列表、集合和有序集合等。这些数据结构都可以通过唯一的键来进行访问和操作。Redis的数据存储是基于内存的,但也可以通过持久化机制将数据写入磁盘,以便在服务器重启时进行恢复。 -
内存管理:
Redis
通过使用自定义的内存分配器来管理内存,以提高性能并减少内存碎片
。它使用了多种技术,如对象共享、对象池和内存压缩,来有效地管理内存使用。 -
高可用性:为了实现高可用性和数据冗余,
Redis
提供了主从复制和哨兵机制。主从复制通过将数据从主服务器复制到多个从服务器,实现数据的冗余备份和读写分离。哨兵机制用于监控和管理Redis
服务器集群,当主服务器发生故障时,自动选举新的主服务器。 -
集群模式:
Redis
还提供了集群模式,可以将数据分片存储在多个节点上,实现水平扩展和负载均衡。集群模式使用哈希槽来划分数据,每个节点负责管理一部分哈希槽的数据。
总体而言,Redis
的架构设计简单而灵活,适用于各种场景。它的高性能、丰富的数据结构和功能,使其成为处理大规模数据、高并发访问和实时应用的理想选择。
Q2 & 请详细讲一下redis的线程模型
Redis
的线程模型采用的是单线程的事件循环模型,也称为I/O多路复用
模型。
-
单线程模型:
Redis
使用单线程
来处理所有的客户端请求和数据库操作。这意味着Redis在任何给定的时间只能处理一个请求,但通过事件循环机制,它能够高效地处理大量并发请求。 -
事件循环:
Redis
使用事件循环来实现高并发和低延迟的性能。事件循环是通过I/O多路复用技术实现的,通常使用select、poll或epoll
等系统调用。Redis
通过监听套接字上的事件,如可读、可写或异常事件,来感知和处理客户端请求。 -
非阻塞
I/O
:Redis
使用非阻塞I/O
来实现事件循环。在接收到客户端请求时,Redis不会阻塞等待请求完成,而是立即返回到事件循环并继续处理其他请求。当请求的I/O
操作完成时,Redis会通过回调函数来处理响应数据。 -
文件事件:
Redis
使用文件事件
来表示与客户端或其他网络连接相关的事件。每当有新的客户端连接或数据到达时,Redis
会生成相应的文件事件并将其加入到事件队列中。事件循环会从队列中获取文件事件并处理它们。 -
时间事件:
Redis
还支持时间事件,用于执行定时任务。时间事件可以是一次性的,也可以是周期性的。通过时间事件,Redis
可以执行诸如过期键的清理、统计信息的更新等后台任务。 -
非阻塞操作:
Redis
的数据存储是基于内存的,因此读写操作通常是非阻塞的。这使得Redis
能够在快速的内存访问下提供高性能的读写操作。 -
多个数据库:
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
和多路复用
技术来实现高性能的事件驱动模型。
-
非阻塞式
I/O
: 非阻塞式I/O
是一种I/O
操作的模式,在进行读取或写入操作时,不会阻塞线程等待操作完成。Redis使用非阻塞I/O
来实现事件驱动模型,它的实现原理如下:-
设置套接字为非阻塞模式:
Redis
在接受客户端连接或创建套接字时,将套接字设置为非阻塞模式。这样,在进行读取或写入操作时,不会阻塞线程,而是立即返回。 -
非阻塞的读取操作:当
Redis
执行读取操作时,它会检查套接字上是否有数据可读。如果没有数据可读,读取操作会立即返回,避免线程阻塞。Redis
可以使用非阻塞的方式读取数据,以便处理其他事件。 -
非阻塞的写入操作:当
Redis
执行写入操作时,它会检查套接字是否可写。如果套接字不可写,写入操作会立即返回,避免线程阻塞。Redis
可以使用非阻塞的方式写入数据,以便处理其他事件。
-
-
多路复用:
多路复用
是一种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 专题学习
- 一起来学kafka之Kafka集群搭建
- 一起来学kafka之整合SpringBoot基本使用
- 一起来学kafka之整合SpringBoot深入使用(一)
- 一起来学kafka之整合SpringBoot深入使用(二)
- 一起来学kafka之整合SpringBoot深入使用(三)
项目源码(源码已更新 欢迎star⭐️)
ElasticSearch 专题学习
项目源码(源码已更新 欢迎star⭐️)
往期并发编程内容推荐
- Java多线程专题之线程与进程概述
- Java多线程专题之线程类和接口入门
- Java多线程专题之进阶学习Thread(含源码分析)
- Java多线程专题之Callable、Future与FutureTask(含源码分析)
- 面试官: 有了解过线程组和线程优先级吗
- 面试官: 说一下线程的生命周期过程
- 面试官: 说一下线程间的通信
- 面试官: 说一下Java的共享内存模型
- 面试官: 有了解过指令重排吗,什么是happens-before
- 面试官: 有了解过volatile关键字吗 说说看
- 面试官: 有了解过Synchronized吗 说说看
- Java多线程专题之Lock锁的使用
- 面试官: 有了解过ReentrantLock的底层实现吗?说说看
- 面试官: 有了解过CAS和原子操作吗?说说看
- Java多线程专题之线程池的基本使用
- 面试官: 有了解过线程池的工作原理吗?说说看
- 面试官: 线程池是如何做到线程复用的?有了解过吗,说说看
- 面试官: 阻塞队列有了解过吗?说说看
- 面试官: 阻塞队列的底层实现有了解过吗? 说说看
- 面试官: 同步容器和并发容器有用过吗? 说说看
- 面试官: CopyOnWrite容器有了解过吗? 说说看
- 面试官: Semaphore在项目中有使用过吗?说说看(源码剖析)
- 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)
- 面试官: CountDownLatch有了解过吗?说说看(源码剖析)
- 面试官: CyclicBarrier有了解过吗?说说看(源码剖析)
- 面试官: Phaser有了解过吗?说说看
- 面试官: Fork/Join 有了解过吗?说说看(含源码分析)
- 面试官: Stream并行流有了解过吗?说说看
推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)
博客(阅读体验较佳)
转载自:https://juejin.cn/post/7233951543115415607