明天你会感谢今天奋力拼搏的你。
ヾ(o◕∀◕)ノヾ
Redis 作为一款开源的内存型数据结构存储系统,在当今的分布式系统架构中扮演着极为关键的角色,凭借其出色的读写速度、丰富的数据类型和强大的功能,在缓存、消息队列、分布式系统等众多场景中得到了广泛应用。本系列文章,将从基本概念入手,逐步深入到核心功能与底层原理,将为你全面剖析 Redis 的各项特性。
TIP:Redis的安装配置这里就不做多讲,网上有很多,这里贴一个讲解Redis比较全面的网站:《Redis 教程 | 菜鸟教程》
Redis,即 Remote Dictionary Server(远程字典服务器)的缩写,它采用键值对(key - value)的存储形式(通俗点理解其实就是一个巨大的HashMap),其中每个键都具有唯一性,通过键能够快速定位并获取对应的值。由于数据完全存储在内存中,Redis 拥有卓越的读写性能,能完美应对高并发场景下对数据的快速访问需求,极大提升系统的响应速度。
Redis作为一个巨大的HashMap,能存哪些数据,如何进行操作?
Redis支持多种数据类型,每种数据类型都有其独特的用途,如下列举一些常用的数据类型和基础操作命令。
SET key value [EX seconds] [PX milliseconds] [NX|XX]。其中,EX seconds表示设置键的过期时间为seconds秒;PX milliseconds表示设置键的过期时间为milliseconds毫秒;NX表示只有当键不存在时才设置键值,XX表示只有当键已存在时才设置键值。例如,SET mykey "Hello Redis" EX 60,将键mykey的值设置为Hello Redis,并在 60 秒后过期。GET key。如GET mykey,将返回mykey对应的值。INCR key。若键不存在,则默认初始值为 0,然后进行自增。例如,SET num 5,再执行INCR num,num的值将变为 6。INCRBY key incrementINCR相反,用于对存储的整数进行自减操作,语法为DECR key。DECRBY key decrementMSET key1 value1 key2 value2 ...。该命令是原子性操作,要么所有键值对都设置成功,要么都失败。例如,在用户注册场景中,需要同时设置用户的姓名、年龄和邮箱,可执行MSET user:1001:name "CYX" user:1001:age 25 user:1001:email "``cyx@example.com``",一次性完成多个键值对的设置。MGET key1 key2 ...。比如,在展示用户信息页面时,需要获取用户的姓名、年龄和邮箱,执行MGET user:1001:name user:1001:age user:1001:email,即可一次性获取这三个键对应的值,提高数据获取效率。APPEND key valueLPUSH key value [value ...]。例如,LPUSH mylist "apple" "banana",会将banana和apple依次插入到列表mylist的头部。RPUSH key value [value ...]。LPOP key。RPOP key。BLPOP|BRPOP key1 [key2] timeout,超时时间设置为0表示不设置等待超时时间,会一直等待到有数据可弹出为止。这个命令可以实现一个简单的消息中间件的功能。 LINDEX key indexLRANGE key start stop。其中,start和stop为元素的索引,索引从 0 开始,-1表示最后一个元素。例如,LRANGE mylist 0 -1将返回列表mylist的所有元素。LREM key count value其中key表示列表的键名。count表示移除元素的方式,分为三种情况:1、正数表示从列表头部开始,移除前count个等于value的元素。2、负数表示从列表尾部开始,移除前count个等于value的元素。3、零表示移除所有等于value的元素。命令返回被移除元素的数量。若列表不存在,返回 0。LTRIM key start stop,其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。LSET key index valueSADD key member [member ...]。例如,SADD myset "red" "green",将red和green添加到集合myset中。SMEMBERS key。SISMEMBER key member。如果成员存在,返回 1;否则返回 0。SINTER key [key ...]。例如,SINTER set1 set2将返回set1和set2的交集。SUNION key [key ...]。SDIFF key [key ...]。ZADD key [NX|XX] [CH] [INCR] score member [score member ...]。其中,NX表示只有当成员不存在时才添加;XX表示只有当成员已存在时才更新;CH表示返回此次操作中分数发生变化的成员及其分数变化情况;INCR表示对成员的分数进行递增操作。例如,ZADD myzset 1 "one" 2 "two",将one和two添加到有序集合myzset中,分数分别为 1 和 2。ZRANGE key start stop [WITHSCORES]。WITHSCORES参数表示同时返回成员及其分数。例如,ZRANGE myzset 0 -1 WITHSCORES将返回myzset中的所有成员及其分数。ZRANK key member。例如,ZRANK myzset "one"将返回one在myzset中的排名。HSET key field value。例如,HSET myhash name "John" age 30,将在哈希表myhash中设置name字段为John,age字段为 30。HSETNX key field valueHGET key field。如HGET myhash name将返回John。HMGET key field1 [field2] 和 HMSET key field1 value1 [field2 value2 ]HGETALL key。HDEL key field [field ...]。TIP:企业中key的命名可以参考如下格式,业务名:对象名:id:[属性]
更多命令可以参考其它网站的汇总:Redis命令
Redis 还提供了一些高级数据类型,以满足更复杂的业务需求。
位图其本质上是基于字符串类型实现的,但它可以按位进行操作,适合需要大规模布尔值存储、高效统计(如计数、交集)的场景。对数据稀疏、需要复杂查询逻辑或频繁删除操作的场景不适合。
相关命令:
SETBIT key offset value,其中value只能是 0 或 1。例如,SETBIT user_login_status 100 1,表示将user_login_status位图中偏移量为 100 的位设置为 1。GETBIT key offset 。BITCOUNT key [start end],start和end参数可选,用于指定统计的字节范围。应用的场景:
优点
缺点
Redis HyperLogLog 是一种概率性数据结构,用于估算集合的基数(去重元素数量)。它的核心优势是只需极小的内存(固定 12KB)就能统计极大的数据量(如 2^64 个元素),误差率约为 0.81%。
相关命令介绍:
PFADD key element [element ...]。例如,PFADD myhll "a" "b" "c",将a、b、c添加到myhll中。PFCOUNT key [key ...]。如果传入多个键,将返回这些键对应的 HyperLogLog 合并后的估算数量。PFMERGE destkey sourcekey [sourcekey ...]。例如,PFMERGE newhll hll1 hll2,将hll1和hll2合并到newhll中。应用场景
优缺点:
原理:HyperLogLog基于概率论中伯努利试验,并结合了极大似然估算方法,并做了分桶优化。
Redis3.2版本提供了GEO(地理信息定位)功能,用于存储地理位置信息,并提供了一系列用于计算距离、查找附近位置等操作的命令。可以用来实现诸如:附近位置、摇一摇等类似功能。
相关命令介绍:
GEOADD key longitude latitude member [longitude latitude member ...]。例如,GEOADD cities 116.40 39.90 beijing 121.47 31.23 shanghai 。GEODIST key member1 member2 [unit],unit参数可选,用于指定距离单位,如m(米)、km(千米)等。GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] 。KEYS pattern。例如,KEYS my*将返回所有以my开头的键。但在生产环境中,由于可能会阻塞 Redis 服务器,不建议使用,可使用SCAN命令替代。SCAN命令采用游标方式渐进式查找,不会阻塞服务器。EXISTS key [key ...]。如果存在,返回 1;否则返回 0。DEL key [key ...]。RENAME key newkey。如果newkey已存在,将被覆盖。SELECT index。例如,SELECT 2将切换到编号为 2 的数据库。FLUSHDB。FLUSHALL。PUBLISH channel message。例如,PUBLISH news_channel "Breaking news!" 。SUBSCRIBE channel [channel ...]。客户端执行该命令后,将进入订阅模式,等待接收消息。UNSUBSCRIBE [channel [channel ...]]。XADD key * field value [field value ...],其中*表示自动生成唯一的消息 ID。例如,XADD event_stream * event "user_login" user_id 123 。XREAD STREAMS key [key ...] $,$表示从流的末尾开始读取,也可以指定具体的消息 ID 进行读取。XGROUP CREATE event_stream group1 $ MKSTREAM,创建一个名为group1的消费者组,并在流不存在时创建流。注意:生产环境尽量避免使用keys命令,生产环境数据量大的情况(几万、几十万+),如果直接使用keys * 命令,会刷出大量数据可能导致Redis阻塞。
Redis 最常用的功能之一就是作为缓存。在传统的应用架构中,数据库往往是性能瓶颈,因为磁盘 I/O 操作相对较慢。而 Redis 基于内存存储数据,读写速度极快,可以将一些经常被访问但更新频率较低的数据存储在 Redis 中,当应用程序需要这些数据时,优先从 Redis 中获取,只有在 Redis 中不存在时,才去数据库查询,并将查询结果存入 Redis,以便后续使用。
例如,在一个新闻网站中,新闻详情页面的数据更新频率较低,但访问量很大。可以将新闻详情数据存储在 Redis 中,每次用户请求新闻详情时,先从 Redis 中获取数据,如果获取到则直接返回给用户;如果 Redis 中没有,则从数据库中查询,然后将查询结果存入 Redis,并设置一个合理的过期时间(如几分钟或几小时),以保证数据的时效性。这样可以大大减轻数据库的压力,提高应用的响应速度。
在分布式系统中,多个进程或线程可能同时访问共享资源,为了避免数据不一致等问题,需要使用锁机制。Redis 可以实现分布式锁,其原理是利用 Redis 的原子性操作。
常见的实现方式是使用SET命令的NX参数。当一个进程想要获取锁时,使用SET lock_key "lock_value" NX EX lock_timeout命令尝试设置一个键值对,其中lock_key是锁的名称,lock_value可以是任意唯一标识该进程的值,NX表示只有当键不存在时才设置成功,即获取锁成功;EX lock_timeout设置锁的过期时间,防止进程在获取锁后因异常未释放锁而导致死锁。如果设置成功,该进程就获得了锁,可以执行相关操作;操作完成后,使用DEL lock_key命令释放锁。
例如,在一个分布式任务调度系统中,多个节点可能同时尝试执行同一个任务,通过 Redis 分布式锁可以保证同一时间只有一个节点执行该任务,避免任务重复执行或数据冲突。
Redis 可以作为简单的消息队列使用,主要利用列表(List)数据类型的LPUSH和RPOP命令。生产者使用LPUSH命令将消息添加到列表头部,消费者使用RPOP命令从列表尾部获取消息,从而实现消息的生产和消费。
为了实现更可靠的消息队列,还可以结合BRPOP命令(阻塞式获取)。当列表中没有消息时,BRPOP命令会阻塞消费者,直到有新消息加入列表,这样可以避免消费者频繁轮询,提高效率。
例如,在一个电商系统中,订单生成后,需要进行一系列后续操作,如库存扣减、发送邮件通知等。可以将订单相关信息作为消息发送到 Redis 消息队列中,不同的消费者(如库存处理模块、邮件发送模块)从队列中获取消息并进行相应处理,实现系统的解耦和异步处理。
Redis 是基于内存的数据库,为了保证数据在服务器重启后不丢失,提供了两种持久化方式:RDB(Redis Database)和 AOF(Append Only File)。
RDB:RDB 是一种快照式的持久化方式,它将 Redis 在某一时刻的内存数据以二进制文件的形式保存到磁盘上。Redis 会按照配置的规则(如多久一次、数据集大小达到多少时)自动触发 RDB 快照,也可以使用SAVE或BGSAVE命令手动触发。SAVE命令会阻塞 Redis 服务器,直到快照完成;而BGSAVE命令会 fork 一个子进程,由子进程进行快照操作,不会阻塞主线程。RDB 的优点是恢复速度快,因为直接加载二进制文件到内存即可;缺点是数据安全性相对较低,因为两次快照之间的数据变化可能会丢失。
AOF:AOF 是一种日志式的持久化方式,它会将 Redis 执行的每一个写命令追加到 AOF 文件中。当 Redis 服务器重启时,会重新执行 AOF 文件中的命令来恢复数据。AOF 可以配置不同的同步策略,如always(每个写命令都同步到磁盘)、everysec(每秒同步一次)、no(由操作系统决定何时同步)。always策略数据安全性最高,但性能最差;everysec策略在保证较高数据安全性的同时,对性能影响较小,是默认的同步策略。AOF 的优点是数据安全性高,基本可以保证不丢失数据;缺点是文件体积较大,恢复速度相对较慢。
Redis 采用了一种混合的内存管理方式。一方面,它使用了 jemalloc 内存分配器,jemalloc 是一个高效的内存分配库,能够减少内存碎片,提高内存分配和释放的效率。例如,在频繁分配和释放小内存块的场景下,jemalloc 可以避免内存碎片的产生,使得内存的使用更加紧凑和高效。
另一方面,Redis 对不同数据类型的内存使用进行了优化。对于字符串类型,当字符串长度较小时,采用紧凑的存储方式,直接在数据结构中存储字符串内容;当字符串长度较大时,则会使用动态分配的内存来存储。对于列表、集合等复杂数据类型,通过合理的数据结构设计,减少内存浪费。同时,Redis 还支持设置最大内存限制,当内存使用达到限制时,可以根据配置的淘汰策略(如volatile - lru、allkeys - lru等)自动删除一些数据,以释放内存空间。
Redis 的数据类型在底层是通过多种数据结构实现的。
字符串:Redis底层是用的C语言编写的,但其没有直接使用C语言的String类型,而是自己去实现了字符串类型,其底层实现主要是简单动态字符串(SDS),它克服了传统 C 语言字符串在长度计算、内存分配等方面的不足。SDS 不仅记录了字符串的内容,还记录了字符串的长度等信息,在进行字符串操作(如追加、截取)时,能够更高效地管理内存,避免缓冲区溢出等问题。
列表:在底层,列表可以使用双向链表或压缩列表(ziplist)来实现。当列表元素较少且每个元素长度较短时,使用压缩列表可以节省内存空间;当列表元素较多或元素长度较大时,会转换为双向链表,以提高操作效率,如在列表头部或尾部插入、删除元素等操作的时间复杂度为 O (1)。
集合:集合的底层实现有两种方式,当集合中的元素都是整数且元素数量较少时,使用整数集合(intset);当元素不是整数或元素数量较多时,使用哈希表(dict)。整数集合在存储整数时,能够根据元素的范围自动调整存储方式,以节省内存;哈希表则提供了高效的查找、插入和删除操作,时间复杂度平均为 O (1)。
哈希:哈希表是 Redis 哈希类型的底层实现。它采用链式哈希(拉链法)来解决哈希冲突,当多个键的哈希值相同时,会将这些键值对存储在同一个哈希桶的链表中。同时,Redis 会根据哈希表的负载因子自动进行扩容和缩容操作,以保证哈希表的性能。
有序集合:有序集合的底层实现是跳跃表(skiplist)和哈希表的结合。跳跃表用于按照分数对元素进行排序,提供了高效的范围查询操作;哈希表则用于快速根据成员查找其分数,使得插入、删除和查找操作的时间复杂度都能保持在较低水平。
Redis 采用事件驱动模型来处理客户端请求和内部事件。Redis 的事件主要分为文件事件(网络 I/O 事件)和时间事件。
文件事件:Redis 使用 I/O 多路复用技术(如 epoll、kqueue 等)来监听多个客户端的套接字。当有客户端连接请求、数据发送或接收等操作时,会产生文件事件。Redis 将这些事件与相应的事件处理器关联起来,当事件发生时,调用对应的处理器进行处理。例如,当有新的客户端连接到 Redis 服务器时,会触发连接事件处理器,接受客户端连接,并将客户端套接字加入到事件监听列表中;当客户端发送命令请求时,会触发读事件处理器,读取命令并进行解析和执行。
时间事件:时间事件主要用于实现 Redis 的定时任务,如过期键的删除、RDB 和 AOF 的持久化操作等。Redis 将时间事件存储在一个有序链表中,按照事件的到期时间排序。在每次事件循环中,Redis 会检查时间事件链表,执行到期的时间事件。同时,Redis 的事件循环是单线程的,这保证了操作的原子性,避免了多线程编程中的锁竞争等问题,使得 Redis 在处理高并发请求时也能保持高效和稳定。
全部评论