百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Redis流行的原因(redis为什么用)

nanshan 2024-10-20 07:36 12 浏览 0 评论

1. Redis是什么

Redis官方这样解释

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

简言之Redis(全称:Remote Dictionary Server 远程字典服务)是一个使用ANSI C编写的开源、支持网络、基于内存,并提供多种语言API的可持久化的键值对存储数据库。


2. Redis由来

2008年,意大利的一家创业公司Merzia推出了一款基于MySQL的网站实时统计系统LLOOGG,然而没过多久该公司的创始人Salvatore Sanfilippo(网名Antirez)便开始对MySQL的性能感到失望,于是他决定亲自为LLOOGG量身定做一个数据库,并于2009年开发完成,这个数据库就是Redis。

不过Salvatore Sanfilippo并不满足只将Redis用于LLOOGG这一款产品,而是希望让更多的人使用它,于是在同一年Salvatore Sanfilippo将Redis开源发布,并开始和Redis的另一名主要的代码贡献者Pieter Noordhuis一起继续着Redis的开发,直到今天。

Antirez今年已经四十多岁,依旧在孜孜不倦地写代码,为Redis的开源事业持续贡献力量。

Redis端口为什么是6379?

6379 是 "MERZ " 九宫格输入法对应的数字。
Alessia Merz 是一位意大利舞女、女演员。Redis 作者 Antirez 早年看电视节目,觉得 Merz 在节目中的一些话愚蠢可笑,Antirez 喜欢造“梗”用于平时和朋友们交流,于是造了一个词 “MERZ”,形容愚蠢,与 “stupid” 含义相同。
后来 Antirez 重新定义了 “MERZ” ,形容”具有很高的技术价值,包含技艺、耐心和劳动,但仍然保持简单本质“。
到了给 Redis 选择一个数字作为默认端口号时,Antirez 没有多想,把 “MERZ” 在手机键盘上对应的数字 6379 拿来用了。

3. Redis为什么快?

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速
  2. 数据结构简单(数据结构是专门设计的),对数据操作也简单
  3. 采用单线程(6.x之前),避免了不必要的上下文切换和竞争条件- 不存在多进程/线程切换消耗 CPU- 不存在加锁/释放锁操作,没有因为可能出现死锁而导致的性能消耗
  4. 非阻塞I/O多路复用模型

4. RESP协议

RESP全称:REdis Serialization Protocol

  • 实现简单
  • 快速解析
  • 可读性强

Redis协议将传输的结构数据分为5种最小单元类型,单元结束时统一加上回车换行符号\r\n。

  1. 单行字符串 以 + 符号开头。
  2. 多行字符串 以 $ 符号开头,后跟字符串长度。
  3. 整数值 以 : 符号开头,后跟整数的字符串形式。
  4. 错误消息 以 - 符号开头。
  5. 数组 以 * 号开头,后跟数组的长度。
  • 单行字符串 hello world
    +hello world\r\n
  • 多行字符串 hello world
    $11\r\nhello world\r\n
    多行字符串当然也可以表示单行字符串。
  • 整数 1024
    :1024\r\n
  • 错误 参数类型错误
    -WRONGTYPE Operation against a key holding the wrong kind of value\r\n
  • 数组 [1,2,3]
    *3\r\n:1\r\n:2\r\n:3\r\n
  • NULL 用多行字符串表示,不过长度要写成-1。
    $-1\r\n
  • 空串 用多行字符串表示,长度填 0。
    $0\r\n\r\n

注意这里有两个\r\n。为什么是两个?因为两个\r\n之间,隔的是空串.


5. 丰富的数据类型

常用的几种数据类型

  • string
  • list
  • set
  • zset
  • hash
  • HyperLogLog
  • bitmap
  • Geo

6. 数据过期淘汰策略

懒性删除

  • 触发机制
    当访问redis中键值对时会判断这个键值对是否过期,如果过期的话就会删除这个键值对并返回nil

优点:对CPU友好,不用执行与当前命令无关的操作
缺点:对内存不友好,当大量过期的键值对不被访问时会浪费大量内存空间

定期删除

为了弥补惰性删除对于内存的不友好,redis中还有一种过期策略即定期删除。

  • 触发机制
    当一个键值对设置expire后,redis中会维护一个过期字典。这个过期字典在redis中会使用serverCron时间事件轮询,轮询过期键值对进行释放。(redis.conf配置文件中hz配置项配置,serverCron每秒执行次数, 默认10表示每100ms执行一次serverCron)

redis中限制每次过期key清理时间不超过CPU时间的25%,这段时间内会执行如下步骤操作:

  1. 随机选取过期字典中的100个key
  2. 淘汰所有的过期key
  3. 如果过期key超过25个则重复步骤1

主动删除

物理机的内存空间是有限的,当所有内存被占满以后redis接收到写操作命令应该怎么处理?此时就会触发主动删除

  • 触发机制
    redis.conf配置文件中maxmemory参数设置redis占用内存的大小,当超过这个值限定以后将会根据maxmemory-policy设置清理redis内存对象
    有关这个maxmemory提醒一点:集群环境下适当调低maxmemory配置,给output buffer预留空间。因为output buffer空间并不包括在maxmemory中
  • 清理策略
    清理策略划分可以分为两个维度与三个方面
    两个维度分别是过期键中筛选、所有键中筛选
    三个方面分别是 lru、ttl、random
  • volatile-lru:过期键中最长时间未调用的键值对
  • volatile-ttl:过期键中即将过期的键值对
  • volatile-random:过期键中随机删除
  • allkeys-lru:所有键中最长时间未调用的键值对
  • allkeys-random:所有键中随机删除
  • noevication:不清理,返回异常

7. 持久化

目前,分为3种持久化方式

  1. RDB
  2. AOF
  3. 混合模式(RDB+AOF)

RDB(Redis DataBase)

  • 开启方式
    redis.conf配置save
#   save ""    # 关闭RDB
save 900 1         # after 900 sec (15 min) if at least 1 key changed
save 300 10       # after 300 sec (5 min) if at least 10 keys changed
save 60 10000  # after 60 sec if at least 10000 keys changed

功能核心函数:rdbSave() / rdbLoad()

  • RDB触发场景:
  1. 执行 SHUTDOWN 命令(未配置AOF)

2. 执行 SAVE/BGSAVE 命令

127.0.0.1:6379> BGSAVE
21499:M 08 Jun 2020 17:53:18.596 * Background saving started by pid 21528
Background saving started
127.0.0.1:6379> 21528:C 08 Jun 2020 17:53:18.601 * DB saved on disk
21528:C 08 Jun 2020 17:53:18.601 * RDB: 0 MB of memory used by copy-on-write
21499:M 08 Jun 2020 17:53:18.681 * Background saving terminated with success

SAVE/BGSAVE区别
SAVE :使用主进程进行RDB快照数据的持久化(会阻塞Redis其他操作)
BGSAVE :fork()出来子进程进行RDB快照数据的持久化(推荐)

优点:
1.RDB是一个非常紧凑的文件,保存了Redis在某个时间点上的数据集,非常适合用于进行备份
2.RDB在恢复大数据集时的速度比 AOF 的恢复速度要快
缺点:
1.数据集比较庞大时,fork()可能会非常耗时,造成服务器在某段时间内停止处理客户端
2.每隔一段时间才保存一次RDB文件,在这种情况下,一旦发生故障停机,你就可能会丢失好这段时间的数据


AOF(Append-only file)

  • 开启方式
    redis.conf配置
appendonly yes

# appendfsync always
# appendfsync no
appendfsync everysec

核心函数:flushAppendOnlyFile()
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile函数都会被调用,这个函数执行以下两个工作
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中

优点:
1.如不小心执行flushall命令, 只要AOF文件未被重写,停止服务器, 移除AOF文件末尾的FLUSHALL命令并重启Redis,就可以将数据集恢复到flushall执行之前的状态
2.可读性高
3.默认为每秒钟fsync一次,也最多只会丢失一秒钟的数据

缺点:
1.AOF文件比较大
2.加载入内存,耗时比RDB慢


混合模式

  • 开启方式
# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
#   [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes
  • 显式的查看混合模式的数据
  1. 执行BGREWRITEAOF命令
? software redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name ivansli
OK
127.0.0.1:6379> set today 20200608
OK
127.0.0.1:6379> keys *
1) "today"
2) "name"
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started

127.0.0.1:6379> set time 19:15:55
OK

2. 查看appendonly.aof文件

REDIS0009?      redis-ver5.0.7?
redis-bits?@?ctime?
?^used-mem??
aof-preamble???today <4nameivansli??4?wq???*2
$6
SELECT
$1
0
*3
$3
set
$4
time
$8
19:15:55

混合模式的AOF文件数据,相当于:
某刻的RDB格式全量数据 + 此刻之后的RESP格式增量数据

为什么使用混合模式(RDB优点+AOF优点):

  1. RDB格式数据加载快速
  2. AOF追加的RESP数据,可以减少数据的丢失
  3. 既能保证Redis重启时的速度,又能降低数据丢失的风险


重启之后数据加载

源码(V5.08)追踪
server.c
main() -> loadDataFromDisk()

/* Function called at startup to load RDB or AOF file in memory. */
void loadDataFromDisk(void) {
    long long start = ustime();
    if (server.aof_state == AOF_ON) {
        if (loadAppendOnlyFile(server.aof_filename) == C_OK)
            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    } else {
        rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
        if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
            serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
                (float)(ustime()-start)/1000000);

            /* Restore the replication ID / offset from the RDB file. */
            if ((server.masterhost ||
                (server.cluster_enabled &&
                nodeIsSlave(server.cluster->myself))) &&
                rsi.repl_id_is_set &&
                rsi.repl_offset != -1 &&
                /* Note that older implementations may save a repl_stream_db
                 * of -1 inside the RDB file in a wrong way, see more
                 * information in function rdbPopulateSaveInfo. */
                rsi.repl_stream_db != -1)
            {
                memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
                server.master_repl_offset = rsi.repl_offset;
                /* If we are a slave, create a cached master from this
                 * information, in order to allow partial resynchronizations
                 * with masters. */
                replicationCacheMasterUsingMyself();
                selectDb(server.cached_master,rsi.repl_stream_db);
            }
        } else if (errno != ENOENT) {
            serverLog(LL_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
            exit(1);
        }
    }
}

判断是否开启了AOF

  • 是:则通过loadAppendOnlyFile() 加载AOF文件
  • 否:通过rdbLoad() 加载RDB文件

loadAppendOnlyFile()

/* Replay the append log file. On success C_OK is returned. On non fatal
 * error (the append only file is zero-length) C_ERR is returned. On
 * fatal error an error message is logged and the program exists. */
int loadAppendOnlyFile(char *filename) {
    struct client *fakeClient;
    FILE *fp = fopen(filename,"r");
    ......

    /* Check if this AOF file has an RDB preamble. In that case we need to
     * load the RDB file and later continue loading the AOF tail. */
    char sig[5]; /* "REDIS" */
    if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
        /* No RDB preamble, seek back at 0 offset. */
        if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
    } else {
        /* RDB preamble. Pass loading the RDB functions. */
        rio rdb;

        serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
        if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
        rioInitWithFile(&rdb,fp);
        if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
            serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
            goto readerr;
        } else {
            serverLog(LL_NOTICE,"Reading the remaining AOF tail...");
        }
    }

    /* Read the actual AOF file, in REPL format, command by command. */
    while(1) {
        int argc, j;
        unsigned long len;
        robj **argv;
        char buf[128];
        sds argsds;
        struct redisCommand *cmd;

        /* Serve the clients from time to time */
        if (!(loops++ % 1000)) {
            loadingProgress(ftello(fp));
            processEventsWhileBlocked();
        }

        if (fgets(buf,sizeof(buf),fp) == NULL) {
            if (feof(fp))
                break;
            else
                goto readerr;
        }
        ......
    }
    ......
}

通过读取AOF文件的前5个字符来判断是否是RDB+AOF混合模式

  • 是:则先加载RDB数据(二进制数据),再一条一条的加载AOF数据(RESP协议格式数据)
  • 否:一条一条的加载AOF数据(RESP协议格式数据)

大致的重启数据加载流程为:

小结

至于Redis为什么是最流行的键值对存储数据库,仁者见仁智者见智。

个人认为,总结几点:

  1. 是开源的(节约企业自研成本)
  2. 数据类型丰富(不仅仅是单纯的k-v)
  3. 处理速度快(单机读写10W+左右)
  4. 支持数据持久化
  5. 多种语言API支持

附:
曾有一同事在追溯Redis源码之后,评价道“这是我看过最舒服的源码”。
源码注释详细不说,各种变量、函数名称也是十分标准。
由此可见一斑

相关推荐

实战派 | Java项目中玩转Redis6.0客户端缓存

铺垫首先介绍一下今天要使用到的工具Lettuce,它是一个可伸缩线程安全的redis客户端。多个线程可以共享同一个RedisConnection,利用nio框架Netty来高效地管理多个连接。放眼望向...

轻松掌握redis缓存穿透、击穿、雪崩问题解决方案(20230529版)

1、缓存穿透所谓缓存穿透就是非法传输了一个在数据库中不存在的条件,导致查询redis和数据库中都没有,并且有大量的请求进来,就会导致对数据库产生压力,解决这一问题的方法如下:1、使用空缓存解决对查询到...

Redis与本地缓存联手:多级缓存架构的奥秘

多级缓存(如Redis+本地缓存)是一种在系统架构中广泛应用的提高系统性能和响应速度的技术手段,它综合利用了不同类型缓存的优势,以下为你详细介绍:基本概念本地缓存:指的是在应用程序所在的服务器内...

腾讯云国际站:腾讯云服务器如何配置Redis缓存?

本文由【云老大】TG@yunlaoda360撰写一、安装Redis使用包管理器安装(推荐)在CentOS系统中,可以通过yum包管理器安装Redis:sudoyumupdate-...

Spring Boot3 整合 Redis 实现数据缓存,你做对了吗?

你是否在开发互联网大厂后端项目时,遇到过系统响应速度慢的问题?当高并发请求涌入,数据库压力剧增,响应时间拉长,用户体验直线下降。相信不少后端开发同行都被这个问题困扰过。其实,通过在SpringBo...

【Redis】Redis应用问题-缓存穿透缓存击穿、缓存雪崩及解决方案

在我们使用redis时,也会存在一些问题,导致请求直接打到数据库上,导致数据库挂掉。下面我们来说说这些问题及解决方案。1、缓存穿透1.1场景一个请求进来后,先去redis进行查找,redis存在,则...

Spring boot 整合Redis缓存你了解多少

在前一篇里面讲到了Redis缓存击穿、缓存穿透、缓存雪崩这三者区别,接下来我们讲解Springboot整合Redis中的一些知识点:之前遇到过,有的了四五年,甚至更长时间的后端Java开发,并且...

揭秘!Redis 缓存与数据库一致性问题的终极解决方案

在现代软件开发中,Redis作为一款高性能的缓存数据库,被广泛应用于提升系统的响应速度和吞吐量。然而,缓存与数据库之间的数据一致性问题,一直是开发者们面临的一大挑战。本文将深入探讨Redis缓存...

高并发下Spring Cache缓存穿透?我用Caffeine+Redis破局

一、什么是缓存穿透?缓存穿透是指查询一个根本不存在的数据,导致请求直接穿透缓存层到达数据库,可能压垮数据库的现象。在高并发场景下,这尤其危险。典型场景:恶意攻击:故意查询不存在的ID(如负数或超大数值...

Redis缓存三剑客:穿透、雪崩、击穿—手把手教你解决

缓存穿透菜小弟:我先问问什么是缓存穿透?我听说是缓存查不到,直接去查数据库了。表哥:没错。缓存穿透是指查询一个缓存中不存在且数据库中也不存在的数据,导致每次请求都直接访问数据库的行为。这种行为会让缓存...

Redis中缓存穿透问题与解决方法

缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...

Redis客户端缓存的几种实现方式

前言:Redis作为当今最流行的内存数据库和缓存系统,被广泛应用于各类应用场景。然而,即使Redis本身性能卓越,在高并发场景下,应用于Redis服务器之间的网络通信仍可能成为性能瓶颈。所以客户端缓存...

Nginx合集-常用功能指导

1)启动、重启以及停止nginx进入sbin目录之后,输入以下命令#启动nginx./nginx#指定配置文件启动nginx./nginx-c/usr/local/nginx/conf/n...

腾讯云国际站:腾讯云怎么提升服务器速度?

本文由【云老大】TG@yunlaoda360撰写升级服务器规格选择更高性能的CPU、内存和带宽,以提供更好的处理能力和网络性能。优化网络配置调整网络接口卡(NIC)驱动,优化TCP/IP参数...

雷霆一击服务器管理员教程

本文转载莱卡云游戏服务器雷霆一击管理员教程(搜索莱卡云面版可搜到)首先你需要给服务器设置管理员密码,默认是空的管理员密码在启动页面进行设置设置完成后你需要重启服务器才可生效加入游戏后,点击键盘左上角E...

取消回复欢迎 发表评论: