共计 7409 个字符,预计需要花费 19 分钟才能阅读完成。
微信搜寻公众号“码路印记”,点关注不迷路!
从本文开始我将开启 Redis 专题,逐渐整顿一些 Redis 的常识要点并分享进去,目前整顿了以下要点。明天先分享第一篇,次要是 Redis 的一些根本知识点,也是通过这篇文章来开掘须要欠缺 Redis 的常识体系。
Redis 是什么?
Redis 是当初最受欢迎的 NoSQL 数据库之一,Redis 是一个应用 ANSI C 编写的开源、蕴含多种数据结构、反对网络、基于内存、可选持久性的键值对存储数据库,其具备如下个性:基于内存运行,性能高效;反对分布式,实践上能够有限扩大;key-value 存储系统;开源的应用 ANSI C 语言编写、恪守 BSD 协定、反对网络、可基于内存亦可长久化的日志型、Key-Value 数据库,并提供多种语言的 API。
相比于其余数据库类型,Redis 具备的特点是:C/ S 通信模型、单过程单线程模型、丰盛的数据类型、操作具备原子性、长久化、高并发读写、反对 lua 脚本。
因为 Redis 杰出的性能体现,全世界各大厂都在拥抱与应用,如 github、twitter、阿里巴巴、百度,并且一些大厂在开源版本的根底上持续深度开发与定制。当然,Redis 应用如此宽泛,它也成为了咱们开发者的必备技能,日常工作与面试曾经无奈忽视它的存在。
Redis 数据类型及操作指令
Redis 罕用的数据类型有 String、Hash、List、Set、SortedSet,一般来讲纯熟应用这些数据类型及操作指令能够满足咱们绝大多数的工作场景。除此之外,Redis 还反对一些高级的类型,比方 HyperLogLog、Geo、Pub/Sub 等。
本文对罕用的五种数据类型及指令进行简略总结,若有未列出的指令,大家能够到 https://redis.io/commands# 进行查问。
String
它是一个二进制平安的字符串,意味着它不仅可能存储字符串、还能存储图片、视频等多种类型, 最大长度反对 512M。以下列举了 String 类型罕用的指令及应用示例。
指令 | 用处 |
---|---|
SET key value | 设置指定 key 的值 |
GET key | 获取指定 key 的值。 |
GETSET key value | 将给定 key 的值设为 value,并返回 key 的旧值(old value)。 |
MGET key1 [key2..] | 获取所有 (一个或多个) 给定 key 的值。 |
SETEX key seconds value | 将值 value 关联到 key,并将 key 的过期工夫设为 seconds (以秒为单位)。 |
SETNX key value | 只有在 key 不存在时设置 key 的值。 |
MSET key value [key value…] | 同时设置一个或多个 key-value 对。 |
INCR key | 将 key 中贮存的数字值增一。 |
INCRBY key increment | 将 key 所贮存的值加上给定的增量值(increment)。 |
DECR key | 将 key 中贮存的数字值减一。 |
DECRBY key decrement | key 所贮存的值减去给定的减量值(decrement)。 |
DEL key [key1..] | 删除给定的 key,返回被删除的数量 |
String 类型是简洁的 key-value 构造,也是咱们开发中最罕用的一种类型,应用形式相似于 Java 中的 Map 类型。而且,Redis 提供了对数值类型的自增、自减指令,能够应用它来实现计数器、发号器等性能。
Hash
该类型是由 field 和关联的 value 组成的 map,其中,field 和 value 都是字符串类型的。Hash 类型与关系型数据库的表记录相似,key 为主键,field 为列名,value 为列对应的值,通过 Hash 咱们能够形容一个对象的多个特色。Hash 的操作命令如下:
指令 | 用处 |
---|---|
HDEL key field1 [field2] | 删除一个或多个哈希表字段 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
HGET key field | 获取存储在哈希表中指定字段的值。 |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment。 |
HINCRBYFLOAT key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment。 |
HKEYS key | 获取所有哈希表中的字段 |
HLEN key | 获取哈希表中字段的数量 |
HMGET key field1 [field2] | 获取所有给定字段的值 |
HMSET key field1 value1 [field2 value2] | 同时将多个 field-value (域 - 值)对设置到哈希表 key 中。 |
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value。 |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
HVALS key | 获取哈希表中所有值。 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对。 |
Hash 类型更加贴近于面向对象的概念,通过 key 确定存储的对象,而后以“field-value”键值对从多个维度来形容对象,每个 Hash 能够存储 2^32- 1 个键值对,大概有 40 多亿个。
List
该类型是一个插入程序排序的字符串元素汇合, 基于双链表实现。一个列表最多能够蕴含 2^32- 1 个元素。List 的操作命令如下:
指令 | 用处 |
---|---|
BLPOP key1 [key2] timeout | 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到期待超时或发现可弹出元素为止。 |
RPUSH key value1 [value2] | 在列表中增加一个或多个值 |
RPUSHX key value | 为已存在的列表增加值 |
BRPOP key1 [key2] timeout | 移出并获取列表的最初一个元素,如果列表没有元素会阻塞列表直到期待超时或发现可弹出元素为止。 |
LINDEX key index | 通过索引获取列表中的元素 |
LLEN key | 获取列表长度 |
LPOP key | 移出并获取列表的第一个元素 |
LINSERT key BEFORE/AFTER pivot value | 在列表的元素前或者后插入元素 |
LPUSH key value1 [value2] | 将一个或多个值插入到列表头部 |
LPUSHX key value | 将一个值插入到已存在的列表头部 |
LRANGE key start stop | 获取列表指定范畴内的元素 |
LREM key count value | 移除列表元素 |
LSET key index value | 通过索引设置列表元素的值 |
LTRIM key start stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
RPOP key | 移除列表的最初一个元素,返回值为移除的元素。 |
RPOPLPUSH source destination | 移除列表的最初一个元素,并将该元素增加到另一个列表并返回 |
BRPOPLPUSH source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到期待超时或发现可弹出元素为止。 |
List 应用双向链表来实现,相似队列构造,队头在右边,队尾在左边。所以,RPUSH 相当于在队尾增加元素,LPOP 是在对头取出元素。基于此个性,咱们能够应用 List 作为异步队列。如下图所示:
Set
Set 类型是一种无程序汇合, 它和 List 类型最大的区别是:汇合中的元素没有程序, 且元素是惟一的。Set 类型的底层是通过哈希表实现的,其操作命令为:
指令 | 用处 |
---|---|
SADD key member1 [member2] | 向汇合增加一个或多个成员 |
SCARD key | 获取汇合的成员数 |
SDIFF key1 [key2] | 返回第一个汇合与其余汇合之间的差别。 |
SDIFFSTORE destination key1 [key2] | 返回给定所有汇合的差集并存储在 destination 中 |
SINTER key1 [key2] | 返回给定所有汇合的交加 |
SINTERSTORE destination key1 [key2] | 返回给定所有汇合的交加并存储在 destination 中 |
SISMEMBER key member | 判断 member 元素是否是汇合 key 的成员 |
SMEMBERS key | 返回汇合中的所有成员 |
SMOVE source destination member | 将 member 元素从 source 汇合挪动到 destination 汇合 |
SPOP key | 移除并返回汇合中的一个随机元素 |
SRANDMEMBER key [count] | 返回汇合中一个或多个随机数 |
SREM key member1 [member2] | 移除汇合中一个或多个成员 |
SUNION key1 [key2] | 返回所有给定汇合的并集 |
SUNIONSTORE destination key1 [key2] | 所有给定汇合的并集存储在 destination 汇合中 |
SSCAN key cursor [MATCH pattern] [COUNT count] | 迭代汇合中的元素 |
Set 类型次要利用在某些场景,如社交场景中,通过交加、并集和差集运算,通过 Set 类型能够十分不便地查找独特好友、独特关注和独特偏好等社交关系。
SortedSet
SortedSet 是一种有序汇合类型,每个元素都会关联一个 double 类型的分数权值,通过这个权值来为汇合中的成员进行从小到大的排序。与 Set 类型一样,其底层也是通过哈希表实现的。Sorted Set 的罕用指令及阐明见下表:
指令 | 用处 |
---|---|
ZADD key score1 member1 [score2 member2] | 向有序汇合增加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序汇合的成员数 |
ZCOUNT key min max | 计算在有序汇合中指定区间分数的成员数 |
ZINCRBY key increment member | 有序汇合中对指定成员的分数加上增量 increment |
ZRANGE key start stop [WITHSCORES] | 通过索引区间返回有序汇合指定区间内的成员 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通过分数返回有序汇合指定区间内的成员 |
ZSCORE key member | 返回有序集中,成员的分数值 |
SortedSet 的特点是外部元素惟一、有序,通过以上指令对数据进行操作能够放弃这一个性,咱们能够应用它来实现排行榜、提早队列等性能。
Pub/Sub
Redis 公布订阅 (pub/sub) 是一种音讯通信模式:发送者 (pub) 发送音讯,订阅者 (sub) 接管音讯,能够实现 1:N 的音讯队列。如下图所示:
然而如果订阅者全副下线了,发布者公布的所有音讯都会失落,Pub/Sub 并没有提供音讯的长久化能力(Redis 5.0 提供了 Redis Stream 补救了这一毛病)。罕用的指令及阐明如下:
指令 | 作用 |
---|---|
PUBLISH channel message | 将信息发送到指定的频道。 |
SUBSCRIBE channel [channel …] | 订阅给定的一个或多个频道的信息。 |
UNSUBSCRIBE [channel [channel …]] | 指退订给定的频道。 |
Redis 为什么这么快?
这是一个常见的面试题,答复它之前咱们须要先晓得 Redis 到底有多块。Redis 官网文章《How fast is Redis?》给出了基准测试数据,由此可知 Redis 的性能受网络带宽、CPU、物理机 / 虚拟机、数据大小、客户端连接数等因素的影响。下图来自该文章,数据显示了在高端配置下 Redis QPS 受连接数影响的状况(横轴为连接数,纵轴为 QPS),在连接数小于 5000 时,QPS 能够达到 10 万 +。
再来看下 Redis 为什么这么快:
- Redis 是纯内存数据库,个别都是简略的存取操作,线程占用工夫短,工夫次要破费在 IO 上。
- 优良的数据结构:为了谋求高性能,Redis 的底层数据结构是专门设计的,构造简略,操作工夫复杂度多为 O(1)。
- 单线程模型:申请命令解决应用单线程,保障了每个操作的原子性,防止了不必要的线程间上下文切换与竞争;
- 应用非阻塞多路复用 I / O 技术,I/ O 申请解决高效。(对于多路复用当前独自介绍)
为什么说 Redis 是单线程的?
先来看下 Redis 官网是如何答复的:
It’s not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.
简略翻译下:CPU 成为 Redis 瓶颈的状况并不常见,Redis 通常是受内存或网络限度的。例如,在个别的 Linux 零碎上应用 Pipeline,Redis 每秒能够解决 100 万个申请,如果你的利用次要应用 O(N)或者 O(log(N))的命令,那么它简直不会占用太多的 CPU。
总结一下就是:单线程曾经够快了,为啥还要用多线程呢!哈哈哈。。。
其实说 Redis 是单线程并不谨严,所谓单线程只是 Redis 在解决网络申请时应用了单线程。因为应用了非阻塞、多路复用的 I / O 技术,简直纯内存无提早的操作,应用单个线程足以满足应用需要。然而,这并不是说 Redis 内只有一个线程,它还有其余线程来实现辅助工作,比方:数据过期策略、内存淘汰机制、主从复制等都有相干职责的线程。
“单线程机制”为 Redis 的快提供了强有力的撑持,这是它的一大劣势。然而当初咱们的服务器动不动就 8 核、16 核、32 核,Redis 只应用一个线程就没有方法充分利用硬件资源了。为了提供硬件资源利用率,咱们能够在一台机器部署多个 Redis 实例,或者应用虚拟化技术 /Docker 技术对物理资源再调配。
Redis 数据过期策略及内存淘汰机制
Redis 具备杰出的性能,很大一部分起因是它应用内存作为数据存储,以及 Redis 开发者对其外部数据结构的精益设计。Redis 可用内存的大小除了受物理内存空间限度外,还受零碎参数的影响。如下 maxmemory
代表了 Redis 最大占用内存字节数:
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
maxmemory 268435456
Redis 可用的内存资源是无限的,为了充分利用内存资源,同时提供高性能的服务,Redis 制订了一套欠缺的数据过期解决策略及内存淘汰机制。数据过期解决策略的指标是那些设置了过期工夫且曾经过期的数据;内存淘汰机制是指当 Redis 内存不够用时,为了保障可用性而采取的保障机制。
针对过期数据,Redis 并没有简略粗犷的应用定时删除形式,而是采纳了“定期清理 + 惰性删除策略”。来看下起因:
- 定时删除:用一个定时器来负责监督 key,过期则主动删除。尽管内存及时开释,然而非常耗费 CPU 资源。在大并发申请下,CPU 要将工夫利用在解决申请,而不是删除 key,因而没有采纳这一策略。
- 定期清理 + 惰性删除:Redis 默认每隔 100ms 查看,是否有过期的 key,有过期 key 则删除。须要阐明的是,Redis 不是每隔 100ms 将所有的 key 查看一次,而是随机抽取进行查看(如果每隔 100ms,全副 key 进行查看,Redis 岂不是卡死)。因而,如果只采纳定期删除策略,会导致很多 key 到工夫没有删除。于是,惰性删除派上用场。当咱们获取某个 key 的时候,Redis 会检查一下这个 key 如果设置了过期工夫是否过期了,如果过期了此时就会删除。
认真想下,“定期清理 + 惰性删除策略”并不是十拿九稳的。如果定期删除没删除 key,而后也没及时去申请 key,也就是说惰性删除也没失效。这样就会导致 Redis 的内存会越来越高,这时就到内存淘汰机制发挥作用了。Redis 提供了以下六种内存淘汰机制:
- volatile-lru:从已设置过期工夫的数据集(server.db[i].expires)中筛选最近起码应用的数据淘汰
- volatile-ttl:从已设置过期工夫的数据集(server.db[i].expires)中筛选将要过期的数据淘汰
- volatile-random:从已设置过期工夫的数据集(server.db[i].expires)中任意抉择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中筛选最近起码应用的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意抉择数据淘汰
- no-enviction(驱赶):禁止驱赶数据,新写入操作会报错
从下面的形容能够晓得:volatile-的策略针对的是那些设置了过期工夫的数据集,如果所有写入的数据都没有设置过期工夫,即便设置了这些策略没有什么用;all-的策略针对的是所有的数据集,如果删除了未过期的数据可能会影响生产平安;no-enviction 则会导致 Redis 无奈对外提供新的写入服务,十分不敌对。
所以,咱们在开发中须要认真评估是否为数据设置过期工夫,并抉择适合的内存淘汰策略。在咱们公司对 Redis 应用有着严格的标准:所有数据必须设置过期工夫,不能把 Redis 当成长久化数据库来应用,Redis 内所有数据必须能够重建。
咱们能够通过配置文件为 Redis 设置适合的内存淘汰机制:
# 淘汰策略:已设置过期中最近起码应用
maxmemory-policy volatile-lru
总结
本文内容多是理论性的基础知识,也有一些面试中的高频问题,其实 Redis 的根底远远不止这些,通过前面的文章缓缓补充!
如果感觉对你有用,请分享给须要的敌人!点关注不迷路,微信搜寻公众号“码路印记”!