7.Redis 的设计和实现详解

1. 数据结构和内部编码

Redis 支持多种数据结构,包括字符串(string)、哈希(hash)、列表(list)、集合(set)和有序集合(zset)。每种数据结构都有其对应的内部编码实现,这些内部编码在不同场景下发挥各自的优势。了解这些内部编码有助于优化内存使用和性能。

1.1 数据结构类型

Redis 的数据结构类型通过 type 命令返回,常见的类型有:

  • string: 字符串类型,最基本的数据类型。
  • hash: 哈希类型,适合存储对象的属性。
  • list: 列表类型,支持按顺序存储多个元素。
  • set: 集合类型,存储不重复的元素。
  • zset: 有序集合类型,支持按分数排序的集合。

1.2 内部编码

每种数据结构都有多种内部编码实现。例如,列表数据结构可以使用 linkedlist 和 ziplist 两种实现。Redis 会根据数据的特性选择合适的内部编码:

  • ziplist: 内存节省,适合小元素数量的列表,但在元素较多时性能下降。
  • linkedlist: 适合大量元素的操作,支持快速插入和删除。

Redis 3.2 版本引入了 quicklist,结合了 ziplist 和 linkedlist 的优点,提供更好的性能。

查询内部编码

可以使用 object encoding {key} 命令查询指定键的内部编码。

1.3 RedisObject 对象

Redis 中存储的所有值对象都封装在 redisObject 结构体中。该结构体包含以下字段:

  • type: 当前对象使用的数据类型。
  • encoding: 当前对象的内部编码类型。
  • lru: 记录对象最后访问时间,用于 LRU 算法。
  • refcount: 记录当前对象的引用次数,用于内存回收。
  • ptr: 指向对象的数据内容。
示例代码(Java)

以下是一个使用 Java 的 Redis 示例,展示如何设置和获取不同类型的数据:

java

import redis.clients.jedis.Jedis;public class RedisExample {public static void main(String[] args) {// 创建 Redis 连接try (Jedis jedis = new Jedis("localhost", 6379)) {// 设置字符串类型jedis.set("my_string", "Hello, Redis!");// 设置哈希类型jedis.hset("my_hash", "field1", "value1");jedis.hset("my_hash", "field2", "value2");// 设置列表类型jedis.rpush("my_list", "item1", "item2", "item3");// 设置集合类型jedis.sadd("my_set", "member1", "member2", "member3");// 设置有序集合类型jedis.zadd("my_zset", 1, "member1");jedis.zadd("my_zset", 2, "member2");// 获取数据System.out.println(jedis.get("my_string"));  // 输出: Hello, Redis!System.out.println(jedis.hgetAll("my_hash"));  // 输出: {field1=value1, field2=value2}System.out.println(jedis.lrange("my_list", 0, -1));  // 输出: [item1, item2, item3]System.out.println(jedis.smembers("my_set"));  // 输出: [member1, member2, member3]System.out.println(jedis.zrangeWithScores("my_zset", 0, -1));  // 输出: [member1=1.0, member2=2.0]}}
}

2. Redis 中的线程和 IO 模型

2.1 单线程模型

Redis 基于 Reactor 模式实现了一个文件事件处理器(File Event Handler, FEH),采用单线程模型处理客户端请求。虽然是单线程,但通过 I/O 多路复用可以同时监听多个 socket 连接。

文件事件处理器组成部分
  • socket: 对 socket 操作的抽象。
  • I/O 多路复用程序: 负责监听多个 socket。
  • 文件事件分派器: 根据 socket 事件类型调用相应的事件处理器。
  • 文件事件处理器: 定义事件发生时服务器应该执行的动作。

2.2 Redis 6 中的多线程

引入多线程的原因
  • 充分利用 CPU 资源: 单线程只能利用一个 CPU 核心,无法充分发挥多核 CPU 的性能。
  • 分摊 Redis 同步 IO 读写负荷: 提高处理性能,尤其在高并发场景下。
配置多线程

Redis 6.0 默认禁用多线程,开启多线程需要在 redis.conf 中设置:

io-threads-do-reads yes

并设置线程数,例如:

io-threads 4
性能提升

Redis 6 引入多线程 I/O 特性后,性能提升至少是原来的两倍,尤其在高并发场景下表现优异。使用多线程可以大幅度提高请求的处理能力。

应用场景

在需要处理大量并发请求的应用中,例如电商平台的购物车系统,开启多线程可以显著提高系统的响应速度和用户体验。

3. 缓存淘汰算法

3.1 maxmemory

Redis 提供 maxmemory 配置参数限制最大使用内存。当内存超出限制时,Redis 会根据设置的淘汰策略(maxmemory-policy)决定如何腾出空间。

3.2 淘汰策略

  • noeviction: 不会继续服务写请求,确保数据不丢失,但会影响业务的持续性。
  • volatile-lru: 淘汰设置了过期时间的 key,优先淘汰最少使用的 key,确保重要数据不会被意外删除。
  • allkeys-lru: 淘汰所有 key,优先淘汰最少使用的 key,适合作为缓存使用的场景。

3.3 LRU 算法

Redis 实现了一种近似 LRU 算法,通过随机采样法淘汰元素。每个 key 维护一个 24 位的时钟,用于记录最后一次被访问的时间。

近似 LRU 算法实现
  • 随机采样: 每次淘汰时随机选择若干个 key,淘汰最旧的 key。
  • 候选池: 维护一个候选池(大小为 16),根据访问时间排序,提升淘汰效果。
应用场景

在高并发访问的缓存系统中,使用 allkeys-lru 策略可以确保不再使用的数据被及时清理,从而释放内存给新数据。

4. 过期策略和惰性删除

4.1 过期

Redis 支持为所有数据结构设置过期时间。过期 key 会被 Redis 自动删除,采用定期删除和惰性删除相结合的策略。

4.2 定期扫描

Redis 默认每秒进行 10 次过期扫描,从过期字典中随机选择 20 个 key,删除已过期的 key。定期扫描可以有效地清理过期的数据,减轻内存压力。

4.3 惰性删除

当客户端访问某个 key 时,Redis 会检查其过期时间,如果已过期立即删除,不返回任何数据。惰性删除在某些情况下可以减少不必要的内存操作。

应用场景

在会话管理或限时优惠活动中,使用过期策略可以确保不再需要的数据被及时清理,避免占用过多的内存。

4.4 lazyfree

Redis 4.0 引入了 lazyfree 机制,支持在后台线程中异步删除大体积的 key,避免阻塞主线程。

UNLINK 命令

使用 UNLINK 命令可以对删除操作进行懒处理,将对象丢给后台线程去执行真正的删除。

UNLINK key
应用场景

在需要频繁删除大数据量的场景中,例如日志数据的清理,使用 UNLINK 可以避免因删除操作导致的系统性能下降。

5. 示例代码和应用

5.1 设置和获取数据

下面的 Java 示例展示了如何设置和获取不同类型的数据,并使用不同的删除策略:

java

import redis.clients.jedis.Jedis;public class RedisExample {public static void main(String[] args) {// 创建 Redis 连接try (Jedis jedis = new Jedis("localhost", 6379)) {// 设置字符串类型,设置过期时间为10秒jedis.setex("my_string", 10, "Hello, Redis!");// 设置哈希类型jedis.hset("my_hash", "field1", "value1");jedis.hset("my_hash", "field2", "value2");// 设置列表类型jedis.rpush("my_list", "item1", "item2", "item3");// 设置集合类型jedis.sadd("my_set", "member1", "member2", "member3");// 设置有序集合类型jedis.zadd("my_zset", 1, "member1");jedis.zadd("my_zset", 2, "member2");// 获取数据System.out.println(jedis.get("my_string"));  // 输出: Hello, Redis!System.out.println(jedis.hgetAll("my_hash"));  // 输出: {field1=value1, field2=value2}System.out.println(jedis.lrange("my_list", 0, -1));  // 输出: [item1, item2, item3]System.out.println(jedis.smembers("my_set"));  // 输出: [member1, member2, member3]System.out.println(jedis.zrangeWithScores("my_zset", 0, -1));  // 输出: [member1=1.0, member2=2.0]// 等待10秒后检查过期数据try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(jedis.get("my_string"));  // 输出: null,因为已过期// 使用 UNLINK 命令删除大数据// 假设我们有一个大对象String largeData = "x".repeat(10_000_000);  // 创建一个10MB大小的字符串jedis.set("large_key", largeData);// 使用 UNLINK 删除jedis.unlink("large_key");  // 异步删除大数据}}
}

结论

Redis 的设计理念和实现方式使其在高性能、高并发的场景中表现优异。通过深入理解 Redis 的数据结构、内部编码、IO 模型及缓存淘汰机制,开发者可以更好地优化 Redis 的使用,提高系统的性能和稳定性。

总结应用

  1. 选择合适的数据结构: 根据业务需求选择合适的 Redis 数据结构和内部编码,以优化内存使用和访问性能。
  2. 开启多线程: 在高并发场景下开启多线程功能,充分利用服务器的 CPU 资源。
  3. 合理设置淘汰策略: 根据业务场景选择合适的缓存淘汰策略,确保重要数据不被意外删除。
  4. 利用过期和惰性删除: 设置合理的过期时间,结合惰性删除策略,确保内存的有效利用。
  5. 异步删除大数据: 对于大数据的删除操作,使用 UNLINK 命令避免阻塞主线程,保持系统的高可用性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+SpringBoot+Vue的学生评奖评优管理系统的设计与实现

基于JavaSpringBootVue的学生评奖评优管理系统的设计与实现 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅 某信 gzh 搜索【智…

2024 年的 Web3 游戏:演变、趋势和市场动态

Web3 游戏行业在经历了多年的快速发展和变革之后,正在2024年迎来全新的阶段。这个行业从最初的边玩边赚(Play-to-Earn, P2E)模式出发,如今正在向更为平衡的“边玩边赚”模式转型。这种转型不仅解决了早期 P2E 模式下存在的可持续性…

EmguCV学习笔记 VB.Net 9.1 VideoCapture类

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

编译LineageOS模拟器镜像,导出到AndroidStudio

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 源码下载 LineageOS官网:https://lineageos.org/ LineageOS源码 github 地址:https://github.com/LineageOS/android LineageOS源码国…

编写一个每次随机生成 10个 0(包括) 到 100 之间的随机正整数。

编写一个每次随机生成 10个 0(包括) 到 100 之间的随机正整数。 package cn.itcast.example;import java.util.Iterator; import java.util.Random; public class example {public static void main (String[] arge) {System.out.println("Math.ra…

QNN:基于QNN+example重构之后的yolov8det部署

QNN是高通发布的神经网络推理引擎,是SNPE的升级版,其主要功能是: 完成从Pytorch/TensorFlow/Keras/Onnx等神经网络框架到高通计算平台的模型转换; 完成模型的低比特量化(int8),使其能够运行在高…

超长二进制利用Integer转换

1.Integer缺点 目前测试Integer只能一次性转4*7位二进制数,也就是7位16进制,故进行改进 2.改进 操作:每四位二进制一转换,以免到上限报错 注解格式:序号(代码顺序)解释 public class Main {…

《PCI Express体系结构导读》随记 —— 第II篇 第7章 PCIe总线的数据链路层与物理层(2)

接前一篇文章:《PCI Express体系结构导读》随记 —— 第II篇 第7章 PCIe总线的数据链路层与物理层(1) 7.1 数据链路层的组成结构 数据链路层使用ACK/NAK协议发送和接收TLP,由发送部件和接收部件组成。其中,发送部件由…

Java算法之堆排序(Heap Sort)

堆排序简介 堆排序是一种基于比较的排序算法,它使用二叉堆数据结构来实现。二叉堆是一种特殊的完全二叉树,其中每个父节点的键值都大于(或等于)其子节点的键值(大顶堆),或者小于(或…

docker在宿主机上最多可以创建多少个容器?

docker在宿主机上最多可以创建多少个容器? A. 1000 B. 和宿主机的cpu/memory 资源有关系 C. 不一定 选择C Docker 容器的数量受到宿主机的资源限制,包括CPU、内存和存储空间等。具体的容器数量取决于宿主机的硬件配置和资源使用情况。没有固定的数量限…

Springboot里集成Mybatis-plus、ClickHouse

🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 Springboot里集成Mybati…

基于Java+SpringBoot+Vue的汽车销售网站

基于JavaSpringBootVue的汽车销售网站 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅 某信 gzh 搜索【智能编程小助手】获取项…

【大模型】llama系列模型基础

前言:llama基于transformer架构,与GPT相似,只用了transformer的解码器部分。本文主要是关于llama,llama2和llama3的结构解读。 目录 1. llama1.1 整体结构1.2 RoPE1.3 SwiGLU 激活函数 2. llama22.2 GQA架构2.3 RLHF 3. llama3参考…

javascript用while语句计算1-100的和

while语句计算1-100的和的思路是 定义一个变量a1,变量b0 while(a<100) { bba或者ba a } 最后是输出b <html><head><meta charset"UTF-8"><title></title></head><body><script>let i1let a0while(i<1…

Springboot中使用Elasticsearch(部署+使用+讲解 最完整)

目录 引言 一、docker中安装Elasticsearch 1、创建es专有的网络 2、开放端口 3、在es-net网络上安装es和kibana 4、可能出现的问题 5、测试 6、安装IK分词器 7、测试IK分词器 二、结合业务实战 1、准备依赖 2、配置yml 3、读取yml配置 4、准备es配置类 5、编写测…

Leetcode面试经典150题-136.只出现一次的数字

解法都在代码里&#xff0c;不懂就留言或者私信 这个题不知道为啥会考&#xff0c;过于简单了&#xff0c;我解题写注释用了两分钟不到&#xff0c;5行代码。。。 class Solution {public int singleNumber(int[] nums) {/**这个题目确实时间的题&#xff0c;根据位运算法则我…

依赖倒置原则详细介绍

一.概念 依赖倒置原则(Dependency Inversion Principle, DIP)是SOLID五大设计原则之一,它是面向对象设计中非常重要的一个原则。它主要包含以下两个方面: 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。 这意味着高层模块(上层)不应该直接依赖于低层模块(下层)的实现…

创建表与删除表

创建表 使用DDL语句创建表 CREATE TABLE 表名(列名 类型,列名 类型......); 示例&#xff1a; 创建一个employees表包含雇员ID&#xff0c;雇员名字&#xff0c;雇员薪水。 create table employees(employee_id int,employee_name varchar(10),salary float(8,2)); 查看已…

斗破C++编程入门系列之十九:C++程序设计必知:多文件结构和编译预处理命令(九星斗者)

斗破C目录&#xff1a; 斗破C编程入门系列之前言&#xff08;斗之气三段&#xff09; 斗破C编程入门系列之二&#xff1a;Qt的使用介绍&#xff08;斗之气三段&#xff09; 斗破C编程入门系列之三&#xff1a;数据结构&#xff08;斗之气三段&#xff09; 斗破C编程入门系列之…

ctfshow之web55~web57(无字母的rce)

目录 web55 思路一&#xff1a; 思路二&#xff1a; web56 web57 本系列主要针对无字母rce或无字母无数字rce 声明&#xff1a;本章内容是引荐几位师傅的博客&#xff0c;然后根据自己的理解编写而成。 web55 if(isset($_GET[c])){$c$_GET[c];if(!preg_match("/\…