第一章:初识 Redis
1.1盛赞 Redis
Redis 是⼀种基于键值对(key-value)的 NoSQL 数据库,与许多键值对数据库不同的是,Redis 中的值可以是由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、 Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成,因此 Redis可以满⾜许多的应⽤场景,⽽且因为 Redis 会将所有数据都存放再内存中,所以它的读写性能⾮常惊⼈。不仅云云,Redis 还可以将内存的数据利⽤快照和⽇志的形式生存到硬盘上,如许在发⽣雷同断电或者呆板故障的时候,内存中的数据不会“丢失”。除了上述功能以外,Redis 还提供了键逾期、发布订阅、事件、流⽔线、Lua 脚本等附加功能。总之,如果在合适的场景使⽤号 Redis,它就会像⼀把瑞⼠军⼑⼀样势如破竹。 2008 年,Redis 的作者 Salvatore Sanfilippo 在开发⼀个叫 LLOOGG 的⽹站时,须要实现⼀个⾼ 性能的队列功能,最开始是使⽤ MySQL 来实现的,但厥后发现⽆论怎么优化 SQL 语句等都不能使⽹站的性能提⾼上去,再加上⾃⼰囊中羞涩,于是他决定⾃⼰做⼀个专属于 LLOOGG 的数据库,这个就是 Redis 的前⾝。厥后,Salvatore Sanfilippo 将 Redis 1.0 的源码发布到 Github 上,大概连他⾃⼰都没想到,Redis 厥后云云受接待。 如果现在有⼈问 Redis 的作者都有谁在使⽤ Redis,我想他可以开句玩笑的答复:还有谁不使⽤ Redis,当然这只是开玩笑,但是从 Redis 的官⽅公司统计来看,有许多重量级的公司都在使⽤ Redis,如国外的 Twitter、Instagram、Stack Overflow、Github 等,国内就更多了,如果单单从体量来统计,新浪微博可以说是环球最⼤的 Redis 使⽤者,除了新浪微博,还有像阿⾥巴巴、腾讯、搜狐、优酷⼟⾖、美团、⼩⽶、唯品会等公司都是 Redis 的使⽤者。除此之外,许多开源技术像 ELK 等 已经把 Redis 作为它们组件中的紧张⼀环,⽽且 Redis 还提供了模块系统让第三⽅⼈员实现功能扩展,让 Redis 发挥出更⼤的威⼒。所以,可以这么说,纯熟使⽤和运维 Redis 已经成为开发运维⼈员的⼀个必备技能。 1.2 Redis 特性
Redis 之所以受到云云多公司的⻘睐,必然有之过⼈之处,下⾯是关于 Redis 的 8 个紧张特性。 1. 速率快
正常环境下,Redis 执⾏下令的速率⾮常快,官⽅给出的数字是读写性能可以到达 10 万 / 秒,当 然这也取决于呆板的性能,但这⾥先不讨论呆板性能上的差异,只分析⼀下是什么作育了 Redis 云云之快,可以⼤概归纳为以下四点: • Redis 的所有数据都是存放在内存中的,下表是⾕歌公司 2009 年给出的各层级硬件执⾏速率,所以把数据放在内存中是 Redis 速率快的最主要原因。 2. 基于键值对的数据结构服务器
⼏乎所有的编程语⾔都提供了雷同字典的功能,比方 C++ ⾥的 map、Java ⾥的 map、Python ⾥ 的 dict 等,雷同于这种构造数据的⽅式叫做基于键值对的⽅式,与许多键值对数据库不同的是, Redis 中的值不仅可以是字符串,⽽且还可以是具体的数据结构,如许不仅能便于在许多应⽤场景的开发,同时也能提⾼开发效率。Redis 的全程是 REmote Dictionary Server,它主要提供了 5 种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(ordered set / zset),同时在字符串的底子之上演变出了位图(Bitmaps)和 HyperLogLog 两种神奇的 ”数据结构“,而且随着 LBS(Location Based Service,基于位置服务)的不断发展,Redis 3.2. 版本种加⼊有关 GEO(地理信息定位)的功能,总之在这些数据结构的帮助下,开发者可以开发出各种 “有意思” 的应⽤。 3. 丰富的功能
除了 5 种数据结构,Redis 还提供了许多额外的功能: • 提供了键逾期功能,可以⽤来实现缓存。 • 提供了发布订阅功能,可以⽤来实现消息系统。 • ⽀持 Lua 脚本功能,可以利⽤ Lua 创造出新的 Redis 下令。 • 提供了简单的事件功能,能在⼀定程度上保证事件特性。 • 提供了流⽔线(Pipeline)功能,如许客⼾端能将⼀批下令⼀次性传到 Redis,减少了⽹络的开 销。 4. 简单稳固
Redis 的简单主要表现在三个⽅⾯。⾸先,Redis 的源码很少,早期版本的代码只有 2 万⾏左右, 3.0 版本以后由于添加了集群特性,代码增⾄ 5 万⾏左右,相对于许多 NoSQL 数据库来说代码量相对要少许多,也就意味着普通的开发和运维⼈员完全可以 “吃透” 它。其次, Redis 使⽤单线程模型 , 如许不仅使得 Redis 服务端处置惩罚模型变得简单,⽽且也使得客⼾端开发变得简单。末了,Redis 不须要依靠于操作系统中的类库(比方 Memcache 须要依靠 libevent 如许的系统类库),Redis ⾃⼰实现了事件处置惩罚的相关功能。 但与简单相对的是 Redis 具备相当的稳固性,在⼤量使⽤过程中,很少出现因为 Redis ⾃⾝ BUG ⽽导致宕掉的环境。 5. 客⼾端语⾔多
Redis 提供了简单的 TCP 通信协议,许多编程语⾔可以很⽅便地接⼊到 Redis,而且由于 Redis 受到社区和各⼤公司的⼴泛承认,所以⽀持 Redis 的客⼾端语⾔也⾮常多,⼏乎涵盖了主流的编程语⾔,比方 C、C++、Java、PHP、Python、NodeJS 等,后续我们会对 Redis 的客⼾端使⽤做具体阐明。 6. 持久化(Persistence)
通常看,将数据放在内存中是不安全的,⼀旦发⽣断电或者呆板故障,紧张的数据大概就会丢 失,因此 Redis 提供了两种持久化⽅式:RDB 和 AOF,即可以⽤两种策略将内存的数据生存到硬盘中 (如图 1-1 所⽰),如许就保证了数据的可持久性,后续我们将对 Redis 的持久化进⾏具体阐明。 7. 主从复制(Replication)
Redis 提供了复制功能,实现了多个相同数据的 Redis 副本(Replica)(如图 1- 2 所⽰),复制 功能是分布式 Redis 的底子。后续我们会对 Redis 的复制功能进⾏具体演⽰。 8. ⾼可⽤(High Availability)和分布式(Distributed)
Redis 提供了⾼可⽤实现的 Redis 哨兵(Redis Sentinel),可以或许保证 Redis 结点的故障发现和故 障⾃动转移。也提供了 Redis 集群(Redis Cluster),是真正的分布式实现,提供了⾼可⽤、读写和容量的扩展性 1.3 Redis 使⽤场景
1.3.1 Redis 可以做什么
1. 缓存(Cache)
缓存机制⼏乎在所有⼤型⽹站都有使⽤,合理地使⽤缓存不仅可以加速数据的访问速率,⽽且能 够有效地低落后端数据源的压⼒。Redis 提供了键值逾期时间设置,而且也提供了灵活控制最⼤内存和内存溢出后的淘汰策略。可以这么说,⼀个合理的缓存筹划可以或许为⼀个⽹站的稳固保驾护航。 2. 排⾏榜系统
排⾏榜系统⼏乎存在于所有的⽹站,比方按照热度排名的排⾏榜,按照发布时间的排⾏榜,按照 各种复杂维度计算出的排⾏榜,Redis 提供了列表和有序集合的结构,合理地使⽤这些数据结构可以很⽅便地构建各种排⾏榜系统。 3. 计数器应⽤
计数器在⽹站中的作⽤⾄关紧张,比方视频⽹站有播放数、电商⽹站有欣赏数,为了保证数据的 实时性,每⼀次播放和欣赏都要做加 1 的操作,如果并发量很⼤对于传统关系型数据的性能是⼀种挑战。Redis 自然⽀持计数功能⽽且计数的性能也⾮常好,可以说是计数器系统的紧张选择。 4. 交际⽹络
赞 / 踩、粉丝、共同好友 / 喜好、推送、下拉刷新等是交际⽹站的必备功能,由于交际⽹站访问量 通常⽐较⼤,⽽且传统的关系型数据不太合适生存这种范例的数据,Redis 提供的数据结构可以相对⽐较容易地实现这些功能。 5. 消息队列系统
消息队列系统可以说是⼀个⼤型⽹站的必备底子组件,因为其具有业务解耦、⾮实时业务削峰等 特性。Redis 提供了发布订阅功能和壅闭队列的功能,虽然和专业的消息队列⽐还不够⾜够强⼤,但是对于⼀般的消息队列功能根本可以满⾜ 1.3.2 Redis 不可以做什么
现实上和任何⼀⻔技术⼀样,每个技术都有⾃⼰的应⽤场景和边界,也就是说 Redis 并不是万⾦ 油,有许多合适它解决的题目,但是也有许多不合适它解决的题目。 我们可以站在数据规模和数据冷热的⻆度来进⾏分析。 站在数据规模的⻆度看,数据可以分为⼤规模数据和⼩规模数据,我们知道 Redis 的数据是存放 在内存中的,虽然现在内存已经⾜够便宜,但是如果数据量⾮常⼤,比方每天有⼏亿的⽤⼾⾏为数 据,使⽤ Redis 来存储的话,根本上是个⽆底洞,经济本钱相当⾼。 站在数据冷热的⻆度,数据分为热数据和冷数据,热数据通常是指须要频繁操作的数据,反之为 冷数据 ,比方对于视频⽹站来说,视频根本信息根本上在各个业务线都是经常要操作的数据,⽽⽤⼾的观看记录不⼀定是经常须要访问的数据,这⾥临时不讨论两者数据规模的差异,单纯站在数据冷热的⻆度上看,视频信息属于热数据,⽤⼾观看记录属于冷数据。如果将这些冷数据放在 Redis 上,根本上是对于内存的⼀种浪费,但是对于⼀些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍。 所以,Redis 并不是万⾦油,相信随着我们对 Redis 的逐步学习,可以或许清晰 Redis 真正的使⽤场 景。 第⼆章 Redis 常⻅数据范例
2.1.1 根本全局下令
Redis 有 5 种数据结构,但它们都是键值对种的值,对于键来说有⼀些通⽤的下令。 KEYS
返回所有满⾜样式(pattern)的 key。⽀持如下统配样式。 • h?llo 匹配 hello , hallo 和 hxllo • h*llo 匹配 hllo 和 heeeello • h[ae]llo 匹配 hello 和 hallo 但不匹配 hillo • h[^e]llo 匹配 hallo , hbllo , ... 但不匹配 hello • h[a-b]llo 匹配 hallo 和 hbllo 返回值:匹配 pattern 的所有 key。 EXISTS
判定某个 key 是否存在。 DEL
删除指定的 key。 返回值:删撤除的 key 的个数 EXPIRE
为指定的 key 添加秒级的逾期时间(Time To Live TTL) 返回值:1 表⽰设置成功。0 表⽰设置失败 TTL
获取指定 key 的逾期时间,秒级 剩余逾期时间。-1 表⽰没有关联逾期时间,-2 表⽰ key 不存在。 关于键逾期机制,可以参考图 2-1 所⽰。 TYPE
返回 key 对应的数据范例。 返回值: none , string , list , set , zset , hash and stream 2.1.2 数据结构和内部编码
type 下令现实返回的就是当前键的数据结构范例,它们分别是:string(字符串)、list(列 表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构。 Redis 的 5 种数据范例 现实上 Redis 针对每种数据结构都有⾃⼰的底层内部编码实现,⽽且是多种实现,如许 Redis 会 在合适的场景选择合适的内部编码,如表 2-1 所⽰。 可以看到每种数据结构都有⾄少两种以上的内部编码实现,比方 list 数据结构包含了 linkedlist 和 ziplist 两种内部编码。同时有些内部编码,比方 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 下令查询内部编码 可以看到 hello 对应值的内部编码是 embstr,键 mylist 对应值的内部编码是 ziplist。 Redis 如许筹划有两个好处: 1)可以改进内部编码,⽽对外的数据结构和下令没有任何影响,如许⼀旦开发出更优秀的内部编码,⽆需改动外部数据结构和下令,比方 Redis 3.2 提供了 quicklist,联合了 ziplist 和 linkedlist 两者的优势,为列表范例提供了⼀种更为优秀的内部编码实现,⽽对⽤⼾来说根本⽆感知。 2)多种内部编码实现可以在不同场景下发挥各⾃的优势,比方 ziplist ⽐较节省内存,但是在列表元素⽐较多的环境下,性能会降落,这时候 Redis 会根据设置选项将列表范例的内部实现转换为 linkedlist,整个过程⽤⼾同样⽆感知。 2.1.3 单线程架构
Redis 使⽤了单线程架构来实现⾼性能的内存数据库服务,本节⾸先通过多个客⼾端下令调⽤的例 ⼦阐明 Redis 单线程下令处置惩罚机制,接着分析 Redis 单线程模型为什么性能云云之⾼,最终给出为什么明白单线程模型是使⽤和运维 Redis 的关键。 1. 引出单线程模型
redis单线程,只有一个线程去吸取处置惩罚所有的请求,并不是服务器进程内部只有一个线程,其实也有许多线程,这些线程都在处置惩罚网络IO
上面这个环境,大概会有线程安全题目
其实redis服务器并不会有这种环境,因为redis服务器是单线程服务器,把吸取到的请求串行实行,多个请求到达redis队列中,也是须要进行列队期待的.
redis可以或许使用单线程模型,很好的工作,是因为redis处置惩罚的业务逻辑是短平快,不斲丧CPU资源,如果一个操作实行的时间太长,就会影响其他的请求的实行
2.为什么redis是单线程,速率还能这么快,效率这么高?
他的速率快和效率高是与关系性数据库(MySQL,Orcale)进行对比
1.redis直接访问的是内存,而关系性数据库访问的硬盘
2.redis的核心功能,比关系性数据库的核心功能要简单,数据库对于数据的插入查询,都有更复杂的功能支持,
3.单线程模型,减少了不须要的线程竞争开销,redis每个操作都是短平快,不涉及特别斲丧cpu的操作
4.处置惩罚网络IO的时候,使用了epoll如许的IO多路复用机制,IO多路复用机制,一个线程就可以处置惩罚多个socket,,epoll事件关照/回调机制,一个线程可以完成好多使命,前提是这些使命的交互不频繁,大部分时间都在等,可以采取epoll多路复用机制
1.String 字符串
在redis中,所以的key都是string范例,value的数据范例才有差异
字符串范例是 Redis 最底子的数据范例,直接按照二进制数据的方式进行存储,不会做任何的编码转化,存的是啥,取出来就是啥,关于字符串须要特别注意:1)⾸先 Redis 中所有的键的 范例都是字符串范例,⽽且其他⼏种数据结构也都是在字符串雷同底子上构建的,比方列表和集合的元素范例是字符串范例,所以字符串范例能为其他 4 种数据结构的学习奠定底子。2)其次,如图 2-7所⽰,字符串范例的值现实可以是字符串,包含⼀般格式的字符串或者雷同 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚⾄是⼆进制流数据,比方图⽚、⾳频、视频等。不过⼀个字符串的最⼤值不能超过 512 MB。
常见下令
SET
将 string 范例的 value 设置到 key 中。如果 key 之前存在,则覆盖,⽆论原来的数据范例是什么。之前关于此 key 的 TTL 也全部失效。 语法: 1 SET key value [expiration EX seconds|PX milliseconds] [NX|XX] 下令有效版本:1.0.0 之后 时间复杂度:O(1)
SET 下令⽀持多种选项来影响它的⾏为: • EX seconds⸺使⽤秒作为单位设置 key 的逾期时间。 • PX milliseconds ⸺使⽤毫秒作为单位设置 key 的逾期时间。 • NX ⸺只在 key 不存在时才进⾏设置,即如果 key 之前已经存在,设置不执⾏。 • XX ⸺只在 key 存在时才进⾏设置,即如果 key 之前不存在,设置不执⾏。 返回值: • 如果设置成功,返回 OK。 • 如果由于 SET 指定了 NX 或者 XX 但条件不满⾜,SET 不会执⾏,并返回 (nil)。 GET
获取 key 对应的 value。如果 key 不存在,返回 nil。如 果 value 的数据范例不是 string,会报错 。 语法: 1 GET key 下令有效版本:1.0.0 之后 时间复杂度:O(1) 返回值:key 对应的 value,或者 nil 当 key 不存在。 MGET
⼀次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据范例不是 string,返回 nil。 语法: 1 MGET key [key ...] 下令有效版本:1.0.0 之后 时间复杂度:O(N) N 是 key 数目 返回值:对应 value 的列表 MSET
⼀次性设置多个 key 的值。 语法: 1 MSET key value [key value ...] 下令有效版本:1.0.1 之后 时间复杂度:O(N) N 是 key 数目 返回值:永远是 OK 学会使⽤批量操作,可以有效提⾼业务处置惩罚效率,但是要注意,每次批量操作所发送的键的数目也不是⽆控制的,否则大概造成单⼀下令执⾏时间过⻓,导致 Redis 壅闭。 SETNX
设置 key-value 但只答应在 key 之前不存在的环境下。 语法: 1 SETNX key value 下令有效版本:1.0.0 之后 时间复杂度:O(1) 返回值:1 表⽰设置成功。0 表⽰没有设置。 SET、SET NX、SET XX 执⾏流程 计数下令
INCR
将 key 对应的 string 表⽰的数字加⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。 INCRBY
将 key 对应的 string 表⽰的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。 返回值:integer 范例的加完后的数值。 DECR
将 key 对应的 string 表⽰的数字减⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错 DECYBY
将 key 对应的 string 表⽰的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。 INCRBYFLOAT
将 key 对应的 string 表⽰的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是⼀个浮点数,则报错。答应采⽤科学计数法表⽰浮点数。 许多存储系统和编程语⾔内部使⽤ CAS 机制实现计数功能,会有⼀定的 CPU 开销,但在 Redis 中完全不存在这个题目,因为 Redis 是单线程架构,任何下令到了 Redis 服务端都要顺序执⾏。 APPEND
如果 key 已经存在而且是⼀个 string,下令会将 value 追加到原有 string 的后边。如果 key 不存在,则效果等同于 SET 下令。 语法: 1 APPEND KEY VALUE 时间复杂度:O(1). 追加的字符串⼀般⻓度较短, 可以视为 O(1). 返回值:追加完成之后 string 的⻓度。 GETRANGE
返回 key 对应的 string 的⼦串,由 start 和 end 确定(左闭右闭)。可以使⽤负数表⽰倒数。-1 代表倒数第⼀个字符,-2 代表倒数第⼆个,其他的与此雷同。超过范围的偏移量会根据 string 的⻓度调解成正确的值。 语法: 1 GETRANGE key start end 时间复杂度:O(N). N 为 [start, end] 区间的⻓度. 由于 string 通常⽐较短, 可以视为是 O(1) 返回值:string 范例的⼦串 SETRANGE
覆盖字符串的⼀部分,从指定的偏移开始。 语法: 1 SETRANGE key offset value 时间复杂度:O(N), N 为 value 的⻓度. 由于⼀般给的 value ⽐较短, 通常视为 O(1). 返回值:更换后的 string 的⻓度。 STRLEN
获取 key 对应的 string 的⻓度。当 key 存放的雷同不是 string 时,报错。 语法: 1 STRLEN key 时间复杂度:O(1) 返回值:string 的⻓度。或者当 key 不存在时,返回 0 下令⼩结
字符串范例下令的效果、时间复杂度,可以参考此表,联合业务需求和数据⼤⼩选择合适的下令。 内部编码
字符串范例的内部编码有 3 种:使用object encoding来查询当前编码 int:8 个字节的⻓整型。 embstr:⼩于即是 39 个字节的字符串。 raw:⼤于 39 个字节的字符串
典范使⽤场景
缓存(Cache)功能
图 2-10 是⽐较典范的缓存使⽤场景,此中 Redis 作为缓冲层,MySQL 作为存储层,绝⼤部分请 求的数据都是从 Redis 中获取。由于 Redis 具有⽀撑⾼并发的特性,所以缓存通常能起到加速读写和低落后端压⼒的作⽤。
举一个例子来明白一下缓存
这是一段伪代码
1)假设业务是根据⽤⼾ uid 获取⽤⼾信息 UserInfo getUserInfo(long uid) { ... } 2)⾸先从 Redis 获取⽤⼾信息,我们假设⽤⼾信息生存在 "user:info:<uid>" 对应的键中: // 根据 uid 得到 Redis 的键 String key = "user:info:" + uid; // 尝试从 Redis 中获取对应的值 String value = Redis 执⾏下令: get key; // 如果缓存掷中( hit ) if (value != null) { // 假设我们的⽤⼾信息按照 JSON 格式存储 UserInfo userInfo = JSON 反序列化 (value); return userInfo; } 如果没有从 Redis 中得到⽤⼾信息,及缓存 miss,则进⼀步从 MySQL 中获取对应的信息,随后写⼊缓存并返回: // 如果缓存未掷中( miss ) if (value == null) { // 从数据库中,根据 uid 获取⽤⼾信息 UserInfo userInfo = MySQL 执⾏ SQL : select * from user_info where uid = <uid> // 如果表中没有 uid 对应的⽤⼾信息 if (userInfo == null) { 响应 404 return null; } // 将⽤⼾信息序列化成 JSON 格式 String value = JSON 序列化 (userInfo); // 写⼊缓存,为了防⽌数据腐烂( rot ),设置逾期时间为 1 ⼩时( 3600 秒) Redis 执⾏下令: set key value ex 3600 // 返回⽤⼾信息 return userInfo; } redis如许的缓存,经常存储"热门"数据(被高频使用的数据),最近一段时间都会反复用到的数据,
计数(Counter)功能
许多应⽤都会使⽤ Redis 作为计数的底子⼯具,它可以实现快速计数、查询缓存的功能,同时数 据可以异步处置惩罚或者落地到其他数据源。如图 2-11 所⽰,比方视频⽹站的视频播放次数可以使⽤ Redis 来完成:⽤⼾每播放⼀次视频,相应的视频播放数就会⾃增 1。 // 在 Redis 中统计某视频的播放次数 long incrVideoCounter(long vid) { key = "video:" + vid; long count = Redis 执⾏下令: incr key return count; } 共享会话(Session)
如图 2-12 所⽰,⼀个分布式 Web 服务将⽤⼾的 Session 信息(比方⽤⼾登录信息)生存在各⾃ 的服务器中,但如许会造成⼀个题目:出于负载均衡的考虑,分布式服务会将⽤⼾的访问请求均衡到不同的服务器上,而且通常⽆法保证⽤⼾每次请求都会被均衡到同⼀台服务器上,如许当⽤⼾刷新⼀次访问是大概会发现须要重新登录,这个题目是⽤⼾⽆法容忍的。 为了解决这个题目,可以使⽤ Redis 将⽤⼾的 Session 信息进⾏集中管理,如图 2-13 所⽰,在这种模式下,只要保证 Redis 是⾼可⽤和可扩展性的,⽆论⽤⼾被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新 Session 信息。 ⼿机验证码
许多应⽤出于安全考虑,会在每次进⾏登录时,让⽤⼾输⼊⼿机号而且共同给⼿机发送验证码, 然后让⽤⼾再次输⼊收到的验证码并进⾏验证,从⽽确定是否是⽤⼾本⼈。为了短信接⼝不会频繁访问,会限定⽤⼾每分钟获取验证码的频率,比方⼀分钟不能超过 5 次 此功能可以⽤以下伪代码阐明根本实现思绪: 2.Hash 哈希
⼏乎所有的主流编程语⾔都提供了哈希(hash)范例,它们的叫法大概是哈希、字典、关联数 组、映射。在 Redis 中,哈希范例是指值本⾝⼜是⼀个键值对结构,形如 key = "key",value = { { field1, value1 }, ..., {fieldN, valueN } },Redis 键值对和哈希范例⼆者的关系可以⽤图 2-15 来表⽰。 常见下令
1.HSET
设置 hash 中指定的字段(field)的值(value)。 语法: HSET key field value [field value ...] 时间复杂度:插⼊⼀组 field 为 O(1), 插⼊ N 组 field 为 O(N) 返回值:添加的字段的个数。 2.HGET
获取 hash 中指定字段的值。 语法: 1 HGET key field 时间复杂度:O(1) 返回值:字段对应的值或者 nil。 3.HEXISTS
判定 hash 中是否有指定的字段。 语法: HEXISTS key field 时间复杂度:O(1) 返回值:1 表⽰存在,0 表⽰不存在。 4.HDEL
删除 hash 中指定的字段。 语法: 1 HDEL key field [field ...] 时间复杂度:删除⼀个元素为 O(1). 删除 N 个元素为 O(N). 返回值:本次操作删除的字段个数。 5.HKEYS
获取 hash 中的所有字段。 语法: HKEYS key 时间复杂度:O(N), N 为 field 的个数. 返回值:字段列表。 6.HVALS 获取 hash 中的所有的值。 语法: HVALS key 时间复杂度:O(N), N 为 field 的个数. 返回值:所有的值。 6.HGETALL
获取 hash 中的所有字段以及对应的值。 语法: HGETALL key 时间复杂度:O(N), N 为 field 的个数. 返回值:字段和对应的值。 7.HMGET
⼀次获取 hash 中多个字段的值。 语法: HMGET key field [field ...] 时间复杂度:只查询⼀个元素为 O(1), 查询多个元素为 O(N), N 为查询元素个数. 返回值:字段对应的值或者 nil。 在使⽤ HGETALL 时,如果哈希元素个数⽐较多,会存在壅闭 Redis 的大概。如果开发⼈员只 须要获取部分 field,可以使⽤ HMGET,如果⼀定要获取全部 field,可以尝试使⽤ HSCAN 下令,该下令采⽤渐进式遍历哈希范例, 8.HLEN
获取 hash 中的所有字段的个数。 语法: HLEN key 时间复杂度:O(1) 返回值:字段个数。 9.HSETNX
在字段不存在的环境下,设置 hash 中的字段和值。 语法: HSETNX key field value 时间复杂度:O(1) 返回值:1 表⽰设置成功,0 表⽰失败。 10.HINCRBY
将 hash 中字段对应的数值添加指定的值。 语法: HINCRBY key field increment 时间复杂度:O(1) 返回值:该字段变化之后的值 HINCRBYFLOAT HINCRBY 的浮点数版本。 语法: HINCRBYFLOAT key field increment 时间复杂度:O(1) 返回值:该字段变化之后的值。 下令⼩结
表 2-4 是哈希范例下令的效果、时间复杂度,开发⼈员可以参考此表,联合⾃⾝业务需求和数据 ⼤⼩选择合适的下令。 内部编码
哈希的内部编码有两种: ziplist(压缩列表):
当哈希范例元素个数⼩于 hash-max-ziplist-entries 设置(默认 512 个)、 同时所有值都⼩于 hash-max-ziplist-value 设置(默认 64 字节)时,Redis 会使⽤ ziplist 作为哈 希的内部实现,ziplist 使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐ hashtable 更加优秀。 hashtable(哈希表):
当哈希范例⽆法满⾜ ziplist 的条件时,Redis 会使⽤ hashtable 作为哈希 的内部实现,因为此时 ziplist 的读写效率会降落,⽽ hashtable 的读写时间复杂度为 O(1)。 下⾯的⽰例演⽰了哈希范例的内部编码,以及响应的变化 1)当 field 个数⽐较少且没有⼤的 value 时,内部编码为 ziplist:
2)当有 value ⼤于 64 字节时,内部编码会转换为 hashtable:
3)当 field 个数超过 512 时,内部编码也会转换为 hashtable:
使⽤场景
图 2-16 为关系型数据表记录的两条⽤⼾信息,⽤⼾的属性表现为表的列,每条⽤⼾信息表现为 ⾏。 。如果映射关系表⽰这两个⽤⼾信息,则如图 2-17 所⽰。 相⽐于使⽤ JSON 格式的字符串缓存⽤⼾信息,哈希范例变得更加直观,而且在更新操作上变得 更灵活。可以将每个⽤⼾的 id 界说为键后缀,多对 field-value 对应⽤⼾的各个属性 注意
哈希范例和关系型数据库有两点不同之处: • 哈希范例是希罕的,⽽关系型数据库是完全结构化的,比方哈希范例每个键可以有不同的 field,⽽ 关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为 null,如图 2-18 所⽰。 • 关系数据库可以做复杂的关系查询,⽽ Redis 去模仿关系型复杂查询,比方联表查询、聚合查询等 根本不大概,维护本钱⾼。 缓存⽅式对⽐
截⾄⽬前为⽌,我们已经可以或许⽤三种⽅法缓存⽤⼾信息,下⾯给出三种⽅案的实现⽅法和优缺点 分析。 1. 原⽣字符串范例
使⽤字符串范例,每个属性⼀个键。 set user:1:name James set user:1:age 23 set user:1:city Beijing 优点:实现简单,针对个别属性变更也很灵活。 缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在 Redis 中⽐较分散,缺少内聚性,所以这种⽅案根本没有实⽤性。 2. 序列化字符串范例,比方 JSON 格式
set user:1 颠末序列化后的⽤⼾对象字符串 优点:针对总是以团体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼。 缺点:本⾝序列化和反序列须要⼀定开销,同时如果总是操作个别属性则⾮常不灵活。 3. 哈希范例
hmset user:1 name James age 23 city Beijing 优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。 缺点:须要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,大概会造成内存的较⼤斲丧。 关于内聚和耦合
高内聚:有关联的代码紧密联系在一起
低耦合:代码的各个模块之间影响不大
3.List列表
列表范例是⽤来存储多个有序的字符串,如图 2-19 所⽰,a、b、c、d、e 五个元素从左到右组成 了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储 个元 素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等(如图 2-19 和图 2-20 所⽰)。列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊,在现实开发上有许多应⽤场景 列表范例的特点:
第⼀:
列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表, 比方要获取图 2-20 的第 5 个元素,可以执⾏ lindex user:1:messages 4 或者倒数第 1 个元素,lindex user:1:messages -1 就可以得到元素 e。 第⼆
区分获取和删除的区别,比方图 2-20 中的 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删 除,这个操作会导致列表的⻓度从 5 变成 4;但是执⾏ lindex 4 只会获取元素,但列表⻓度是不会变化的。 第三
列表中的元素是答应重复的,比方图 2-21 中的列表中是包含了两个 a 元素的。 常见下令
LPUSH(头插)
将⼀个或者多个元素从左侧放⼊(头插)到 list 中。 语法: LPUSH key element [element ...] 时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数. 返回值:插⼊后 list 的⻓度 LPUSHX
在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回0 语法: 1 LPUSHX key element [element ...] 时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数. 返回值:插⼊后 list 的⻓度。 RPUSH(尾插)
将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。 语法: RPUSH key element [element ...] 时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数. 返回值:插⼊后 list 的⻓度。 RPUSHX
在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。 语法: RPUSHX key element [element ...] 时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数. 返回值:插⼊后 list 的⻓度。 LRANGE
获取从 start 到 end 区间的所有元素,左闭右闭。 语法: LRANGE key start stop 时间复杂度:O(N) 返回值:指定区间的元素。 LPOP(头删)
从 list 左侧取出元素(即头删)。 语法: LPOP key 时间复杂度:O(1) 返回值:取出的元素或者 nil。 RPOP(尾删)
从 list 右侧取出元素(即尾删)。 语法: RPOP key 时间复杂度:O(1) 返回值:取出的元素或者 nil。 LINDEX
获取从左数第 index 位置的元素。 语法: LINDEX key index 时间复杂度:O(N) 返回值:取出的元素或者 nil LINSERT
在特定位置插⼊元素。 语法: LINSERT key <BEFORE | AFTER> pivot element 时间复杂度:O(N) 返回值:插⼊后的 list ⻓度。 LLEN
获取 list ⻓度。 语法: LLEN key 时间复杂度:O(1) 返回值:list 的⻓度。 LREM
指定元素精准删除
1.当count>0时,从头开始删除指定元素的次数
2.当count<0时,从尾开始删除指定元素的次数
3.当count=0时,删除全部指定的元素
LTRIM
只保留范围内的元素
LSET
根据下标修改元素
壅闭版本下令
blpop 和 brpop 是 lpop 和 rpop 的壅闭版本,和对应⾮壅闭版本的作⽤根本⼀致,除了: • 在列表中有元素的环境下,壅闭和⾮壅闭表现是⼀致的。但如果列表中没有元素,⾮壅闭版本会理 解返回 nil,但壅闭版本会根据 timeout,壅闭⼀段时间,期间 Redis 可以执⾏其他下令,但要求执 ⾏该下令的客⼾端会表现为壅闭状态(如图 2-22 所⽰)。 • 下令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元 素,下令⽴即返回。 • 如果多个客⼾端同时多⼀个键执⾏ pop,则开始执⾏下令的客⼾端会得到弹出的元素。 BLPOP
LPOP 的壅闭版本。 语法: BLPOP key [key ...] timeout 时间复杂度:O(1) 返回值:取出的元素或者 nil。 BRPOP
RPOP 的壅闭版本。 语法: BRPOP key [key ...] timeout 时间复杂度:O(1) 返回值:取出的元素或者 nil。 内部编码
列表范例的内部编码有两种: • ziplist(压缩列表):
当列表的元素个数⼩于 list-max-ziplist-entries 设置(默认 512 个),同时 列表中每个元素的⻓度都⼩于 list-max-ziplist-value 设置(默认 64 字节)时,Redis 会选⽤ ziplist 来作为列表的内部编码实现来减少内存斲丧。 linkedlist(链表):
当列表范例⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。 quicklist
每个节点都是一个压缩列表,以链表的形式连接起来
使⽤场景
作为数组
消息队列
如图 2-22 所⽰,Redis 可以使⽤ lpush + brpop 下令组合实现经典的壅闭式⽣产者-斲丧者模型队列,⽣产者客⼾端使⽤ lpush 从列表左侧插⼊元素,多个斲丧者客⼾端使⽤ brpop 下令壅闭式地从队列中"争抢" 队⾸元素。通过多个客⼾端来保证斲丧的负载均衡和⾼可⽤性。 分频道的消息队列 如图 2-23 所⽰,Redis 同样使⽤ lpush + brpop 下令,但通过不同的键模仿频道的概念,不同的斲丧者可以通过 brpop 不同的键值,实现订阅不同频道的理念。 微博 Timeline
每个⽤⼾都有属于⾃⼰的 Timeline(微博列表),现须要分⻚展⽰⽂章列表。此时可以考虑使⽤ 列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素。 1)每篇微博使⽤哈希结构存储,比方微博中 3 个属性:title、timestamp、content: hmset mblog:1 title xx timestamp 1476536196 content xxxxx ... hmset mblog:n title xx timestamp 1476536196 content xxxxx 2)向⽤⼾ Timeline 添加微博,user:<uid>:mblogs 作为微博的键: lpush user:1:mblogs mblog:1 mblog:3 ... lpush user:k:mblogs mblog:9 3)分⻚获取⽤⼾的 Timeline,比方获取⽤⼾ 1 的前 10 篇微博: keylist = lrange user:1:mblogs 0 9 for key in keylist { hgetall key } 此⽅案在现实中大概存在两个题目: 1. 1 + n 题目。即如果每次分⻚获取的微博个数较多,须要执⾏多次 hgetall 操作,此时可以考虑使⽤pipeline(流⽔线)模式批量提交下令,或者微博不采⽤哈希范例,⽽是使⽤序列化的字符串范例,使⽤ mget 获取。 2. 分裂获取⽂章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。 4.set集合
集合范例也是生存多个字符串范例的元素的,但和列表范例不同的是,集合中 1)元素之间是⽆序 的 2)元素不答应重复,如图 2-24 所⽰。⼀个集合中最多可以存储 个元素。Redis 除了⽀持 集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集,合理地使⽤好集合范例,能在现实开发中解决许多题目。 set这个术语有不同的含义,如果是集合的话,就是把有关联的数据联系在一起,集合中的元素是无序的,集合中的元素是唯一的,集合中的每个元素都是string范例的 根本下令
SADD
将⼀个或者多个元素添加到 set 中。注意,重复的元素⽆法添加到 set 中。 语法: SADD key member [member ...] 时间复杂度:O(1) 返回值:本次添加成功的元素个数 SMEMBERS
获取⼀个 set 中的所有元素,注意,元素间的顺序是⽆序的。 语法: SMEMBERS key 时间复杂度:O(N) 返回值:所有元素的列表。 SISMEMBER
判定⼀个元素在不在 set 中。 语法: SISMEMBER key member 时间复杂度:O(1) 返回值:1 表⽰元素在 set 中。0 表⽰元素不在 set 中或者 key 不存在。 SCARD
获取⼀个 set 的基数(cardinality),即 set 中的元素个数。 语法: SCARD key 时间复杂度:O(1) 返回值:set 内的元素个数。 SPOP
从 set 中删除并返回⼀个或者多个元素。注意,由于 set 内的元素是⽆序的,所以取出哪个元素现实是未界说⾏为,即可以看作随机的。 语法: SPOP key [count] 时间复杂度:O(N), n 是 count 返回值:取出的元素。 srandmember 从set中随机获取一个元素,但是不会移除该元素 SMOVE
将⼀个元素从源 set 取出并放⼊⽬标 set 中。 语法: SMOVE source destination member 时间复杂度:O(1) 返回值:1 表⽰移动成功,0 表⽰失败 SREM
将指定的元素从 set 中删除。 语法: SREM key member [member ...] 时间复杂度:O(N), N 是要删除的元素个数. 返回值:本次操作删除的元素个数。 集合间操作
交集(inter)、并集(union)、差集(diff)的概念如图 2-25 所⽰。 SINTER
获取给定 set 的交集中的元素。 语法: SINTER key [key ...] 时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数. 返回值:交集的元素。 SINTERSTORE
获取给定 set 的交集中的元素并生存到⽬标 set 中。 语法: SINTERSTORE destination key [key ...] 时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数. 返回值:交集的元素个数。
SUNION
获取给定 set 的并集中的元素。 语法: SUNION key [key ...] 时间复杂度:O(N), N 给定的所有集合的总的元素个数. 返回值:并集的元素。 SUNIONSTORE
获取给定 set 的并集中的元素并生存到⽬标 set 中。 语法: 1 SUNIONSTORE destination key [key ...] 时间复杂度:O(N), N 给定的所有集合的总的元素个数. 返回值:并集的元素个数。 SDIFF
获取给定 set 的差集中的元素。 语法: SDIFF key [key ...] 时间复杂度:O(N), N 给定的所有集合的总的元素个数. 返回值:差集的元素。 SDIFFSTORE
获取给定 set 的差集中的元素并生存到⽬标 set 中。 语法: SDIFFSTORE destination key [key ...] 时间复杂度:O(N), N 给定的所有集合的总的元素个数. 返回值:差集的元素个数 下令小结
内部编码
集合范例的内部编码有两种: • intset(整数集合):
当集合中的元素都是整数而且元素的个数⼩于 set-max-intset-entries 设置 (默认 512 个)时,Redis 会选⽤ intset 来作为集合的内部实现,从⽽减少内存的使⽤。 • hashtable(哈希表):
当集合范例⽆法满⾜ intset 的条件时,Redis 会使⽤ hashtable 作为集合 的内部实现。 使⽤场景
集合范例⽐较典范的使⽤场景是标签(tag)。比方 A ⽤⼾对娱乐、体育板块⽐较感爱好,B ⽤⼾ 对历史、新闻⽐较感爱好,这些爱好点可以被抽象为标签。有了这些数据就可以得到喜欢同⼀个标签的⼈,以及⽤⼾的共同喜好的标签,这些数据对于增强⽤⼾体验和⽤⼾黏度都⾮常有帮助。 比方⼀个电⼦商务⽹站会对不同标签的⽤⼾做不同的产品保举。 1.使用set来生存用户的标签
2.使用set来计算共同好友,基于集合求交集
3.使用set来计算uv Zset 有序集合
有序集合相对于字符串、列表、哈希、集合来说会有⼀些陌⽣。它保留了集合不能有重复成员的 特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点范例的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是⽤下标作为排序依据⽽是⽤这个分数。如图 2-26 所⽰,该有序集合显⽰了三国中的武将的武⼒ 有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利⽤有序集合,可 以帮助我们在现实开发中解决许多题目。 常见下令
zadd
添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 范例,+inf/-inf 作为正负 极限也是合法的。 ZADD 的相关选项: XX:仅仅⽤于更新已经存在的元素,不会添加新元素。 NX:仅⽤于添加新元素,不会更新已经存在的元素。 CH:默认环境下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。 INCR:此时下令雷同 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。 ZCARD
获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。 ZCOUNT
返回分数在 min 和 max 之间的元素个数,默认环境下,min 和 max 都是包含的,可以通过 (,表示开区间 ZRANGE
返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。 ZREVRANGE
返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。 备注:这个下令大概在 6.2.0 之后废弃,而且功能归并到 ZRANGE 中。 ZRANGEBYSCORE
返回分数在 min 和 max 之间的元素,默认环境下,min 和 max 都是包含的,可以通过 ( 清除。 备注:这个下令大概在 6.2.0 之后废弃,而且功能归并到 ZRANGE 中。 ZPOPMAX
删除并返回分数最⾼的 count 个元素。 BZPOPMAX
ZPOPMAX 的壅闭版本。 ZPOPMIN
删除并返回分数最低的 count 个元素。 BZPOPMIN
ZPOPMIN 的壅闭版本。 ZRANK
返回指定元素的排名,升序。 ZREVRANK
返回指定元素的排名,降序。 ZSCORE
返回指定元素的分数。 ZREM
删除指定的元素。 ZREMRANGEBYRANK
按照排序,升序删除指定范围的元素,左闭右闭。 ZREMRANGEBYSCORE
按照分数删除指定范围的元素,左闭右闭。 ZINCRBY
为指定的元素的关联分数添加指定的分数值 集合间操作
ZINTERSTORE(交集)
求出给定有序集合中元素的交集并生存进⽬标有序集合中,在归并过程中以元素为单位进⾏归并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。 时间复杂度:O(N*K)+O(M*log(M)) N 是输⼊的有序集合中, 最⼩的有序集合的元素个数; K 是输⼊了⼏个有序集合; M 是最闭幕果的有序集合的元素个数. ZUNIONSTORE并集
求出给定有序集合中元素的并集并生存进⽬标有序集合中,在归并过程中以元素为单位进⾏归并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。 语法: ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>] 时间复杂度:O(N)+O(M*log(M)) N 是输⼊的有序集合总的元素个数; M 是最闭幕果的有序集合的元素个数. 返回值:⽬标集合中的元素个数
下令⼩结
内部编码
有序集合范例的内部编码有两种: ziplist(压缩列表):
当有序集合的元素个数⼩于 zset-max-ziplist-entries 设置(默认 128 个), 同时每个元素的值都⼩于 zset-max-ziplist-value 设置(默认 64 字节)时,Redis 会⽤ ziplist 来作
为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。 skiplist(跳表):
当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时ziplist 的操作效率会降落 1)当元素个数较少且每个元素较⼩时,内部编码为 ziplist: 2)当元素个数超过 128 个,内部编码 skiplist: 3)当某个元素⼤于 64 字节时,内部编码 skiplist: 使⽤场景
有序集合⽐较典范的使⽤场景就是排⾏榜系统。比方常⻅的⽹站上的热榜信息,榜单的维度大概 是多⽅⾯的:按照时间、按照阅读量、按照点赞量。本例中我们使⽤点赞数这个维度,维护每天的热榜:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |