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

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

nanshan 2024-10-20 07:36 22 浏览 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源码之后,评价道“这是我看过最舒服的源码”。
源码注释详细不说,各种变量、函数名称也是十分标准。
由此可见一斑

相关推荐

删库之后不要着急跑路,教你神不知鬼不觉找回数据

在工作中,我们误删数据或者数据库,我们一定需要跑路吗?我看未必,程序员一定要学会自救,神不知鬼不觉的将数据找回。在mysql数据库中,我们知道binlog日志记录了我们对数据库的所有操作,所以...

数据库告警不可用,增删改受阻(数据库限制删除)

前言:昨晚,突然出现服务不可用告警,查看日志上线报文入库到数据库很慢并受阻,出现数据不同步问题。排查问题查看发现服务都是在执行update、insert这些DML命令的时候,报的数据库执行超时。经过一...

Binlog实现MySQL复制,5个关键步骤,务必掌握!

复制是MySQL最重要的功能之一,MySQL集群的高可用、负载均衡和读写分离都是基于复制来实现的。Binlog就是实现主从复制的关键,主数据库将修改操作记录到Binlog中,从数据库通过解...

MySQL数据实时增量同步到Elasticsearch

Mysql到Elasticsearch的数据同步,一般用ETL来实现,但性能并不理想,目前大部分的ETL是定时查询Mysql数据库有没有新增数据或者修改数据,如果数据量小影响不大,但如果几百万上千万的...

MySQL 数据库恢复:如何执行时间点恢复(PITR)以挽救受损数据?

天津鸿萌科贸发展有限公司从事数据安全服务二十余年,致力于为各领域客户提供专业的数据恢复、数据备份、数据取证、数据迁移、网络安全、数据清除等解决方案,并针对企业面临的数据安全风险,提供专业的相关数据安全...

阿里面试:MySQL Binlog有哪些格式?底层原理?优缺点?

binlog的格式也有三种:STATEMENT、ROW、MIXED,下面我详解binlog三种模式@mikechenStatement模式Statement模式:是基于SQL语句的复制(statem...

快速带你读懂MySQL的binlog写入机制

深入讲解MySQL中的重要日志binlog的写入机制以及影响IO性能的关键配置,并且介绍了如何利用binlog去恢复数据,保证MySQL的可靠性。Q:binlog写入时机binlog的写入逻辑并...

MySQL 误删除数据恢复全攻略:基于 Binlog 的实战指南

在MySQL的世界里,二进制日志(Binlog)就是我们的"时光机"。它默默记录着数据库的每一个重要变更,就像一位忠实的史官,为我们在数据灾难中提供最后的救命稻草。本文将带您深入掌握如...

一文了解MySQL Binlog(一文了解肝脏有益和有害的食物)

MySQL的Binlog日志是一种二进制格式的日志,Binlog记录所有的DDL和DML语句(除了数据查询语句SELECT、SHOW等),以Event的形式记录,同时记录语句执行时...

数据丢失?别慌!MySQL备份恢复攻略

想象一下,某个晴朗的午后,你正享受着咖啡,突然接到紧急电话:你的网站或APP彻底挂了!系统崩溃,界面全白。虽然心头一紧,但你或许还能安慰自己:系统崩溃只是暂停服务,数据还在,修复修复就好了。然而,如果...

Mysql中的bin log、redo log、undo log的区别

最近在整理面试题,在看mvcc的时候看到了undolog,今天索性把这三个log都记录一遍。MySQL的逻辑架构说之前先说一下MySQL的基本架构,MySQL主要分为两层:Server层和存储引...

binlog日志定时清理(binlog清理规则)

binlog日志binlog是MySQL数据库的一种日志文件,用于记录所有对数据的修改操作。binlog全称为binarylog,它以二进制格式记录MySQL服务器上所有的修改操作,包括对哪个数据库...

茶水间炸锅了!菜鸟误删用户表,运维老张的MySQL救命三招!

(公司茶水间,运维老张、开发小王和新人小李围着咖啡机)小李:(紧张兮兮)张哥!我...我好像把测试库的用户表删了!下午演示咋办啊?老张:(淡定喝咖啡)慌啥?昨晚的备份是吃干饭的?走,教你恢复!一、基础...

解决运维痛点,提高运维安全性-雷池 SafeLine WAF新功能身份认证

雷池介绍使用雷池SafeLineWAF已经两年多了,在1.5.x版本时就已经开始测试使用,并在推出LTS版本后转入LTS分支。近期雷池SafeLineWAF重点更新了身份认证功能,并提供了SS...

【Docker 新手入门指南】第十五章:常见故障排除

一、前期准备:收集关键信息在排查问题前,建议先获取以下系统数据,便于精准定位故障:1.系统基础信息#查看Docker版本(确认是否为最新稳定版)dockerversion#查看...

取消回复欢迎 发表评论: