资讯详情

资讯详情

【Redis】初识Redis

文章目录一、Redis基础1.1 认识Redis1.2 Redis 特性1.3 Redis的适用场景典型适用业务场景不适合的场景1.4 Redis版本规则与特性1.5 Redis基础命令行Redis重要程序文件命令行客户端1.6 Redis客户端与服务器交互过程二、全局通用命令2.1 get/set2.2 keys2.3 exists2.4 del2.5 expire/pexpire2.6 ttl/pttlRedis中key的过期删除策略2.7 type2.8 scan/sscan/hscan/zscan三、数据类型与内部编码3.1 数据类型3.2 内部编码四、单线程架构4.1 单线程模型的工作过程4.2 单线程为什么这么快五、数据库管理5.1 select 切换数据库5.2 flushdb/flushall 清除数据库一、Redis基础1.1 认识RedisRedis 全称REmote Dictionary Server远程字典服务器是当下互联网行业使用最广泛的内存 NoSQL 数据库。不同于普通的键值数据库Redis 的 value 并非只能存储简单字符串而是支持字符串、哈希、列表、集合、有序集合等丰富的数据结构同时拓展了位图、HyperLogLog、GEO 地理位置、Stream 流等高级能力。Redis 将绝大部分数据存放于内存带来极高的读写性能官方标称可达10 万 QPS。同时提供 RDB、AOF 两套持久化方案可以把内存数据落地磁盘规避断电、机器宕机带来的数据丢失风险。除此之外还具备键过期、发布订阅、事务、流水线 Pipeline、Lua 脚本、主从复制、哨兵、集群等全套能力。Redis 官方原生并不支持 Windows 操作系统Windows 版本是微软团队维护的分支。生产环境一律使用 Linux 操作系统运行 Redis。1.2 Redis 特性性能极高读写速度快全部数据存储在内存中内存访问速度远高于磁盘这是最根本原因。谷歌硬件性能统计内存访问约100ns磁盘寻道需要10ms级别相差十万倍。Redis核心功能就是操作内存中的数据结构都是比较简单的逻辑网络上Redis使用了IO多路复用的方式epoll也就是使用一个线程管理多个socket。单线程执行命令。Redis 6.0版本引入多线程但多线程仅仅负责网络IO读写、协议解析所有数据命令依旧是单线程执行。单线程规避多线程锁竞争、线程上下文切换的巨大开销。Redis使用C语言编写代码贴近操作系统底层没有高级语言额外开销。基于键值对的数据结构服务器普通KV数据库value只能存字符串Redis value支持多种复杂数据结构string、hash、list、set、zset衍生Bitmaps、HyperLogLog、GEO、Stream。开发者可以直接使用内置数据结构完成业务不需要自己做二次封装大幅提升开发效率。功能丰富除基础数据结构外配套大量实用能力键过期key设置TTL过期时间天然适合缓存场景发布订阅实现简单消息广播Lua脚本把多条命令打包在脚本中执行保证原子执行简易事务Pipeline流水线把多条命令打包一次性发给服务端减少网络往返RTT。简单稳定代码量小早期版本代码约2万行3.0加入集群后约5万行对比其他NoSQL体量很小开发者可以通读理解源码。单线程模型服务端、客户端逻辑都更简单。不依赖第三方系统库事件驱动模型全部自己实现。生产环境稳定性很高极少因为Redis自身bug发生宕机。支持多语言客户端Redis使用简单的TCP通信协议Java、C、Python、PHP、NodeJS、Go等主流编程语言都拥有成熟客户端持久化内存数据易失断电数据丢失。Redis提供RDB、AOF两种持久化把内存数据保存硬盘相当于把内存中的数据备份重启可以加载文件恢复数据。主从复制支持主从复制一台主节点的数据自动同步到多台从节点生成多份数据副本。主从复制是哨兵、集群的底层基础。可以实现数据备份、读写分离。高可用与分布式Redis Sentinel哨兵实现故障自动发现、故障转移解决主节点宕机人工切换实现高可用Redis Cluster集群官方分布式分片方案实现横向扩容提升存储容量与读写吞吐量。1.3 Redis的适用场景典型适用业务场景缓存Cache缓存是Redis最经典场景。把热点数据放到Redis请求优先读取Redis缓存未命中才查询后端MySQL数据库。既加速接口响应又大幅降低数据库压力。同时Redis支持key过期、内存淘汰策略完美适配缓存业务。计数器应用视频播放量、文章浏览数、点赞数。高并发场景数据库做自增压力巨大Redis的INCR系列命令可以高性能完成计数。计数数据可以异步落地到数据库。社交网络业务点赞、踩、粉丝关系、共同好友、消息推送。社交业务访问量大关系型数据库处理关系类数据成本高Redis的数据结构可以轻松实现。简易消息队列利用List的阻塞弹出brpop/blpop实现阻塞队列也可以使用发布订阅完成消息广播。但它不是专业MQ没有完备的消息确认、死信队列适合简单业务。不适合的场景Redis数据主要放在内存内存硬件成本远高于磁盘。海量冷数据不适合存入Redis。像冷数据这样访问频率极低的数据全部放在Redis会带来极高硬件成本冷数据应该存MySQL、HDFS这类磁盘存储。原则Redis优先存放热数据。冷热分离热数据Redis加速冷数据磁盘存储。1.4 Redis版本规则与特性Redis版本命名借鉴Linux版本第二位偶数代表稳定版本奇数代表开发测试版本。生产环境务必选用偶数稳定版。2.6支持Lua脚本、毫秒级key过期、位图命令bitcount2.8部分复制PSYNC雏形Sentinel哨兵正式生产可用3.0正式发布Redis Cluster集群3.2新增GEO地理位置list底层使用quicklist4.0模块扩展能力、PSYNC2.0完整部分复制、LFU淘汰算法、unlink异步删除、RDB‑AOF混合持久化5.0新增Stream流数据类型6.0IO多线程、ACL权限管控、SSL加密、客户端缓存7.0AOF改为多文件存储RDB格式升级。1.5 Redis基础命令行Redis官方不支持WindowsWindows版本由微软开源小组维护生产环境全部部署Linux。Redis重要程序文件文件功能redis‑serverRedis服务主程序redis‑cliRedis命令行交互客户端redis‑benchmark性能压测工具redis‑check‑aof修复损坏AOF持久化文件redis‑check‑rdb修复损坏RDB快照文件redis‑sentinel哨兵程序实际是redis‑server软链接配置文件/etc/redis.conf持久化文件目录默认/var/lib/redis/日志目录默认/var/log/redis/命令行客户端redis‑cli两种使用模式交互式进入客户端之后逐行输入命令redis-cli-h127.0.0.1-p6379127.0.0.1:6379pingPONG127.0.0.1:6379setkey hello OK127.0.0.1:6379get keyhello命令模式直接执行命令输出结果适合脚本调用redis-cli -h 127.0.0.1 -p 6379 get key本地默认端口6379不写‑h、‑p默认连接本机6379。1.6 Redis客户端与服务器交互过程Redis 使用TCP 协议进行通信默认端口 6379通信协议是RESPRedis 序列化协议建立 TCP 连接客户端向 Redis 服务端发起 TCP 三次握手建立 socket 连接。一个客户端对应一条 socket 通道多个客户端可以同时连服务器。客户端构造 RESP 协议请求客户端Java 的 Jedis/Lettuce 等把我们调用的命令例如set key value封装成RESP 格式字符串通过 socket 发送给 Redis 服务器。Redis 服务器读取请求Redis 是单线程事件模型IO 多路复用监听所有 socket 事件。读到客户端发来 RESP 请求解析出真实 Redis 命令。执行命令解析完成调用内部函数执行这条命令操作内存数据。同一时刻只执行一条命令命令串行执行。封装 RESP 响应结果把执行后的返回值成功、字符串、nil、数字等包装成 RESP 响应报文。回写给客户端通过 socket 把 RESP 响应发送回客户端。客户端解析 RESP 响应客户端解析 RESP 报文转换成编程语言的数据对象给到业务代码使用。连接维持 / 关闭如果是长连接socket 保持复用来反复收发命令连接池就是利用长连接如果短连接交互完成后关闭 TCP 连接四次挥手断开。二、全局通用命令redis按照键值对的形式存储数据key是字符串类型或可以加上也可以省略。value可以有多种类型包括字符串哈希表列表集合和有序集合等。全局命令就是指可以操作任意数据类型的命令。redis中命令不区分大小写。2.1 get/setget/set用于存取键值对语法setkey value get key注意get命令只能用于value为String类型的key2.2 keys通过通配符描述一个key符合描述的key就可以被查询出来语法keys pattern通配符?匹配任意一个字符*匹配任意数量的任意字符[字符1 字符2...]给出固定选项指定匹配其中一个字符[^字符]排除字符其余的字符都可以匹配[字符1-字符2]匹配字符1到字符2之间任意一个字符时间复杂度O(N)注意如果使用keys *就相当于查询所有的key生产环境中key的数量非常多而redis是单线程的服务器会阻塞整个Redis。因此生产环境中禁止使用keys命令2.3 exists判断key是否存在一次查询多个key返回存在key的数量。语法EXISTS key[key...]示例redisSET key1HelloOKredisEXISTS key1(integer)1redisEXISTS nosuchkey(integer)0redisSET key2WorldOKredisEXISTS key1 key2 nosuchkey(integer)2时间复杂度redis是按照哈希表来组织键值对的对于单个key时间复杂度是O(1)官方文档给出的时间复杂度是O(N)N是指待查询key的数量。为什么redis要设计一个命令可以操作多个keyredis是客户端-服务器结构的程序客户端和服务器之间使用网络通信。如果分开操作就会产生多个轮次的通信。而网络通信与内存相比效率低成本高每一次网络通信都涉及封装和分用。因此redis很多命令都支持多种操作从而减少客户端和服务器的交互次数。2.4 del删除一个或多个key返回成功删除个数语法DEL key[key...]时间复杂度O(1)当Redis作为缓存与MySQL中的drop或delete命令相比Redis中的del命令危险程度并不大。Redis主要应用场景就是缓存此时Redis中存储热点数据全量数据在MySql中随时间推移Redis中可能存在没用的key因此删除少量的key影响并不大可以再从MySQL中读取数据但是如果大批量删除数据大量的请求就直接打给MySQL此时问题就比较严重了。当Redis作为数据库Redis中就保存全量数据此时与MySQL一样del命令就要谨慎操作2.5 expire/pexpireexpire可以为key设置过期时间超出时间key就会自动删除语法expire key seconds可以使用pexpire设置过期时间单位是毫秒。返回值1设置成功0设置失败只能为已经存在的key设置过期时间如果key不存在则返回0时间复杂度O(1)基于Redis实现的分布式锁为了防止出现不能正确解锁现象通常会在加锁的时候设置过期时间。所谓的使用Redis作为分布式锁就是给Redis里写一个特殊的key-value超出时间后删除特殊的key2.6 ttl/pttlttl可以获取key剩余存活秒数返回-1表示永不过期返回-2表示key不存在语法ttl keypttl可以获取key剩余存活时间单位是毫秒时间复杂度O(1)TYPE key # 查看key对外数据类型 SCAN 0 # 渐进式遍历key分批返回不会阻塞Redis生产替代KEYSKEYS会扫描全部key实例key数量几十万上百万的时候会阻塞Redis全部命令。线上遍历key必须用SCAN。Redis中key的过期删除策略Redis采用惰性删除定期删除两种策略搭配使用。惰性删除Redis在访问或修改key时都会先检查key是否过期如果已经过期就会直接删除key返回nil给客户端如果没有过期就会把数据返回给客户端。定期删除每隔一段时间随机抽取一部分key进行检查删除其中过期的key。定期删除中抽取检查的过程是非常快的因为Redis是单线程程序处理命令和扫描过期key都在同一个线程中如果一次扫描了大批量的key就会导致正常的处理请求命令被阻塞。2.7 typetype命令用于返回key对应的value的数据类型。语法typekey时间复杂度O(1)返回值nonekey不存在stringlistsetzsetstream当redis作为消息队列时使用这个类型的value使用示例redisSET key1valueOKredisLPUSH key2value(integer)1redisSADD key3value(integer)1redisTYPE key1stringredisTYPE key2listredisTYPE key3set2.8 scan/sscan/hscan/zscan用游标cursor分批遍历避免服务端一次返回海量元素造成阻塞和内存尖峰。每轮返回新的游标 本批元素下一次调用scan时输入上次返回的游标。返回游标为0表示遍历结束客户端循环调用直到游标回到0。四个命令分别遍历整个键空间、Set、Hash、ZSetZScan 返回“成员, 分数”交替。语法scan cursor[match pattern][count count][type type]sscan key cursor[match pattern][count count]hscan key cursor[match pattern][count count]zscan key cursor[match pattern][count count]match pattern与keys的语法规则一样可以过滤出符合要求的key。count n给服务端的每次扫描量提示非精确条数可能返回 0 条或多于 n 条。scan支持type按数据结构过滤键如type string。示例时间复杂度每次调用 O(1) 均摊、整轮遍历 O(N)N 为该结构或键空间中的元素总数返回值二元组[新游标, 元素数组]新游标为0时结束。sscan/hscan返回成员hscan 为 field/value 交替zscan返回 member/score 交替注意游标的概念与下标不同游标可以理解为字符串它不连续递增数字大小没有含义。不保证快照一致性只保证“不会因为扩容而漏掉一直存在的元素”。从遍历开始到结束都存在的元素一定会被返回遍历过程中被删除的元素不保证出现因此要避免遍历过程中增加、修改或删除元素。同一个元素可能被返回多次业务侧需要去重。COUNT越大越接近一次扫完越小则每轮延迟越低但网络往返更多默认 10。不允许在遍历中途用同一个游标并发执行其他扫描游标本身无状态、不绑客户端。补充KEYS *一次性返回全部键O(N) 且会阻塞主线程生产环境禁止用SCAN替代。SSCAN支持HSCAN key 0 COUNT 1000这类“大 key 分页巡检”。需要精确元素个数需要用SCARD/HLEN/ZCARDSCAN只负责枚举。三、数据类型与内部编码我们用type key看到的是对外暴露的数据类型Redis底层有一套内部编码Redis会根据数据的数量、大小自动切换底层编码兼顾内存占用与读写性能。数据类型 vs 数据结构这里的数据类型和数据结构与Java语言中的概念不一样。Redis 的数据类型是对外提供给用户使用的是业务层面概念底层数据结构是 Redis 内部实现存储的工具。一个 Redis 数据类型在不同条件下可以切换不同底层数据结构切换过程对使用者透明用户无感知。底层数据结构对应用户是不可见的没有操作命令。3.1 数据类型Redis内置了很多数据类型其中最常用的有五种String 字符串Hash 哈希List 列表Set 集合ZSet 有序集合内部存储了score表示权重3.2 内部编码针对上述的数据类型源码层面会进行特定的优化来达到节省时间/空间的效果。这些优化就要依靠内部具体实现的数据结构也就是编码方式。举个例子对于外部来说hash的作用就是存储键值对保证O(1)的时间复杂度就可以找到但是对于内部来说不一定使用标准的哈希表在某些特定场景下可能使用不同的数据结构来实现功能用户感知不到这些变化。object encoding key命令可以查看key的底层内部编码127.0.0.1:6379sethello world OK127.0.0.1:6379lpush mylist a b c(integer)3127.0.0.1:6379object encoding helloembstr127.0.0.1:6379object encoding mylistquicklist四、单线程架构4.1 单线程模型的工作过程假设有三个客户端同时向redis服务器发送请求宏观上这三个请求是并发的而redis服务器是单线程模型6.0引入IO多线程命令执行依旧是单线程所有请求到达服务器就要在队列里排队再等待redis服务器取出命令执行因此微观上来讲redis服务器就是串行执多个命令的。4.2 单线程为什么这么快redis主要是访问内存而MySQL主要是访问硬盘内存访问速度远高于磁盘这是最根本原因。谷歌硬件性能统计内存访问约100ns磁盘寻道需要10ms级别相差十万倍。逻辑简单Redis核心功能就是操作内存中的数据结构都是比较简单的逻辑而MySQL的增删改查涉及到各种约束也提供了丰富的功能多表联查排序视图等这些操作都需要更复杂的功能支撑就会导致更多的开销。单线程执行命令。Redis 6.0版本引入多线程但多线程仅仅负责网络IO读写、协议解析所有数据命令依旧是单线程执行。单线程规避多线程锁竞争、线程上下文切换的巨大开销。使用epoll这样的IO多路复用机制一个线程可以管理多个socket。一个服务器对应多个客户端就有多个socket而服务器和客户端之间的通信不一定都很频繁大部分的socket都是静默的没有数据需要传输。也就是说同一时刻只有少数的socket是活跃的。可以通过多线程为每一个客户端分配一个线程但多个线程的创建调度以及销毁的开销都很大并且大部分线程是空闲的因此通过IO多路复用让一个线程来处理多个socket。IO多路复用也是操作系统实现的操作系统向外提供一套API。Redis采用了epoll相当于事件通知/回调机制。epoll适合在事件交互不频繁的场景下使用。注意这里“快”的概念是与MySQL这样的关系型数据库做比较但是与直接在内存中操作变量相比redis就慢得多。因为redis是先通过网络再操作内存。在学习多线程时我们认为多线程可以一定程度上提高效率但是这里却说单线程是Redis高效率的原因。这是因为多线程可以在CPU密集型的任务中发挥优势使用多个线程可以充分利用CPU多核资源。但是对于Redis来说核心任务主要就是操作内存中的数据结构这些操作不会吃很多CPU也就是说单个核心就可以解决问题了如果这个时候引入多线程并不会明显的提高效率反而会因为线程安全锁竞争线程阻塞等问题降低效率。单线程的风险是一个慢命令会阻塞全部请求。 如果一次性读取几十万元素的集合、大量数据的hgetall会造成整个服务卡顿。对策避免大key用scan/hscan/sscan/zscan渐进式分批遍历业务拆分不要把海量数据放到同一个key。五、数据库管理5.1 select 切换数据库redis默认由有16个数据库编号0-15.使用select 编号可以切换到对应的数据库默认情况下使用0号数据库。这16个数据库之间数据不冲突也就是说操作一个数据库中的数据不会影响另一个。但是随着版本升级不建议频繁使用多数据库的特性。如果真的需要隔离两套键值对最好维护多个redis实例而不是在一个redis实例中维护多个数据库。这是因为redis本身没有为多数据库提供太多特性其次无论维护多少个数据库redis还是单线程模型所以彼此之间需要排队等待命令执行。同时多数据库还会让开发、测试、运维工作变得复杂。5.2 flushdb/flushall 清除数据库flushdb / flushall 命令⽤于清除数据库区别在于 flushdb 只清除当前数据库flushall 会清除所有数据库。flushdb[async|sync]async异步清除主线程把数据库中的哈希字典指针换掉旧字典脱离主线程立即返回OK主线程可以马上处理其他客户端的命令不会阻塞后台独立子线程慢慢遍历旧字典逐个回收内存。sync同步删除主线程自己遍历所有key逐个删除内存全部释放后才返回OK在此期间主线程被卡住其他redis命令全部阻塞无法执行。时间复杂度O(n)这两个命令只适合学习阶段练习线上环境属于高危行为
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →