唯美网站模板,做php网站用什么软件开发,php网站开发专业是做什么的,美篇制作app下载官网免费初识 Redis 1 认识NoSQL1.1 结构化与非结构化1.2 关联和非关联1.3 查询方式1.4. 事务1.5 总结 2 Redis 概述2.1 应用场景2.2 特性 3 Resis 全局命令4 Redis 基本数据类型4.1 String4.1.1 常用命令4.1.2 命令的时间复杂度4.1.3 使用场景 4.2 Hash4.2.1 常用命令4.2.2 命令的时间… 初识 Redis 1 认识NoSQL1.1 结构化与非结构化1.2 关联和非关联1.3 查询方式1.4. 事务1.5 总结 2 Redis 概述2.1 应用场景2.2 特性 3 Resis 全局命令4 Redis 基本数据类型4.1 String4.1.1 常用命令4.1.2 命令的时间复杂度4.1.3 使用场景 4.2 Hash4.2.1 常用命令4.2.2 命令的时间复杂度4.2.3 使用场景 4.3 List4.3.1 常用命令4.3.2 命令的时间复杂度4.3.3 使用场景 4.4 Set4.4.1 常用命令4.4.2 命令的时间复杂度4.4.3 使用场景 4.5 ZSet(SortedSet)4.5.1 常用命令4.5.2 命令的时间复杂度4.5.3 使用场景 5 Redis 缓存5.1 认识缓存5.1.1 缓存的概念5.1.2 缓存的作用及成本5.1.3 缓存的使用 5.2 Redis 缓存5.2.1 Redis 缓存更新策略5.2.2 主动更新方案5.2.3 Cache Aside 的模式选择 5.3 Redis 缓存相关问题5.3.1 缓存穿透5.3.2 缓存雪崩5.3.3 缓存击穿 6 Redis 持久化6.1 RDB6.1.1 执行时机6.1.2 RDB原理6.1.3 小结 6.2 AOF6.2.1 AOF原理6.2.2 AOF配置6.2.3 AOF文件重写 6.3 RDB与AOF对比 1 认识NoSQL
NoSql 可以翻译做Not Only Sql不仅仅是SQL或者是No Sql非Sql的数据库。是相对于传统关系型数据库而言有很大差异的一种特殊的数据库因此也称之为非关系型数据库。
1.1 结构化与非结构化
传统关系型数据库是结构化数据每一张表都有严格的约束信息字段名、字段数据类型、字段约束等等信息插入的数据必须遵守这些约束 而NoSql则对数据库格式没有严格约束往往形式松散自由。
可以是键值型 可以是文档型 也可以是列族型 甚至可以是图格式 1.2 关联和非关联
传统数据库的表与表之间往往存在关联例如外键 而非关系型数据库不存在关联关系要维护关系要么靠代码中的业务逻辑要么靠数据之间的耦合
{id: 1,name: 张三,orders: [{id: 1,item: {id: 10, title: 荣耀6, price: 4999}},{id: 2,item: {id: 20, title: 小米11, price: 3999}}]
}此处要维护“张三”的订单与商品“荣耀”和“小米11”的关系不得不冗余的将这两个商品保存在张三的订单文档中不够优雅。还是建议用业务来维护关联关系。
1.3 查询方式
传统关系型数据库会基于Sql语句做查询语法有统一标准
而不同的非关系数据库查询语法差异极大五花八门各种各样。 1.4. 事务
传统关系型数据库能满足事务ACID的原则。
而非关系型数据库往往不支持事务或者不能严格保证ACID的特性只能实现基本的一致性。
1.5 总结
除了上述四点以外在存储方式、扩展性、查询性能上关系型与非关系型也都有着显著差异总结如下 存储方式 关系型数据库基于磁盘进行存储会有大量的磁盘IO对性能有一定影响非关系型数据库他们的操作更多的是依赖于内存来操作内存的读写速度会非常快性能自然会好一些
扩展性 关系型数据库集群模式一般是主从主从数据一致起到数据备份的作用称为垂直扩展。非关系型数据库可以将数据拆分存储在不同机器上可以保存海量数据解决内存大小有限的问题。称为水平扩展。关系型数据库因为表之间存在关联关系如果做水平扩展会给数据查询带来很多麻烦
2 Redis 概述 Redis 一个开源的基于键值对Key-ValueNoSQL 数据库。使用 ANSIC 语言编写、支持网络、基于内存但支持持久化。性能优秀并提供多种语言的 API。
我们要首先理解一点我们把 Redis 称为 KV 数据库键值对数据库那就可以把 Redis 内部的存储视为存在着一个巨大的 Map对 Map 的操作无非就是get 和 put然后通过 key 操作这个 key 所对应的 value而这个 value 的类型可以多种多样也就是 Redis 为我们提供的那些数据结构比如字符串String、哈希(Hash)等等。
Redis 会将所有数据都存放在内存中所以它的读写性能非常惊人。不仅如此Redis 还可以将内存的数据利用快照和日志的形式保存到硬盘上这样在发生类似断电或者机器故障的时候内存中的数据不会丢失。
除了上述功能以外Redis 还提供了键过期、发布订阅、事务、流水线、Lua 脚本等附加功能。
2.1 应用场景
缓存
缓存机制几乎在所有的大型网站都有使用合理地使用缓存不仅可以加快数据的访问速度而且能够有效地降低后端数据源的压力。Redis 提供了键值过期时间设置并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说一个合理的缓存设计能够为一个网站的稳定保驾护航。
排行榜系统
排行榜系统几乎存在于所有的网站例如按照热度排名的排行榜按照发布时间的排行榜按照各种复杂维度计算出的排行榜Redis 提供了列表和有序集合数据结构合理地使用这些数据结构可以很方便地构建各种排行榜系统。
计数器应用
计数器在网站中的作用至关重要例如视频网站有播放数、电商网站有浏览数为了保证数据的实时性每一次播放和浏览都要做1 的操作如果并发量很大对于传统关系型数据的性能是一种挑战。Redis 天然支持计数功能而且计数的性能也非常好可以说是计数器系统的重要选择。
社交网络
赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能由于社交网站访问量通常比较大而且传统的关系型数据不太适合保存这种类型的数据Redis 提供的数据结构可以相对比较容易地实现这些功能。
消息队列系统
消息队列系统可以说是一个大型网站的必备基础组件因为其具有业务解耦、 非实时业务削峰等特性。Redis 提供了发布订阅功能和阻塞队列的功能虽然和专业的消息队列比还不够足够强大但是对于一般的消息队列功能基本可以满足。
2.2 特性
速度快
正常情况下Redis 执行命令的速度非常快官方给出的数字是读写性能可以达到 10 万/秒。
基于键值对的数据结构服务器
几乎所有的编程语言都提供了类似字典的功能例如 Java 里的 map类似于这种组织数据的方式叫作基于键值的方式与很多键值对数据库不同的是Redis 中的值不仅可以是字符串而且还可以是具体的数据结构这样不仅能便于在许多应用场景的开发同时也能够提高开发效率。
Redis 的全称是 Remote Dictionary Server它主要提供了 5 种数据结构字符串、哈希、列表、集合、有序集合同时在字符串的基础之上演变出了位图Bitmaps)和 HyperLogLog 两种数据结构并且随着 LBS (Location BasedService基于位置服务)的不断发展Redis 中加入有关 GEO地理信息定位的功能。
丰富的功能
除了 5 种数据结构Redis 还提供了许多额外的功能:提供了键过期功能可以用来实现缓存。
提供了发布订阅功能可以用来实现消息系统。支持 Lua 脚本功能可以利用 Lua 创造出新的 Redis 命令。提供了简单的事务功能能在一定程度上保证事务特性。提供了流水线Pipeline功能这样客户端能将一批命令一次性传到 Redis减少了网络的开销。
简单稳定
Redis 的简单主要表现在三个方面。
首先Redis 的源码很少早期版本的代码只有 2 万行左右3.0 版本以后由于添加了集群特性代码增至 5 万行左右。
其次Redis 使用单线程模型这样不仅使得 Redis 服务端处理模型变得简单而且也使得客户端开发变得简单。
最后Redis 不需要依赖于操作系统中的类库。
Redis 虽然很简单但是不代表它不稳定。实际的运行中很少出现因为 Redis 自身 bug 而宕掉的情况。
客户端语言多
Redis 提供了简单的 TCP 通信协议很多编程语言可以很方便地接人到 Redis。
持久化
通常看将数据放在内存中是不安全的一旦发生断电或者机器故障重要的数据可能就会丢失因此Redis提供了两种持久化方式RDB 和 AOF即可以用两种策略将内存的数据保存到硬盘中这样就保证了数据的可持久性。
主从复制
Redis 提供了复制功能,实现了多个相同数据的 Redis 副本复制功能是分布式Redis 的基础。
高可用和分布式
Redis Sentinel它能够保证 Redis 节点的故障发现和故障自动转移。Redis 从 3.0 版本正式提供了分布式实现 Redis Cluster它是 Redis 真正的分布式实现提供了高可用、读写和容量的扩展性。
3 Resis 全局命令
在了解 Redis 的数据结构之前先了解 Redis 的一些全局命令。
命令说明keys *查看所有键同时也支持通配符如 keys n*dbsize返回当前数据库中键的总数exists检查键是否存在存在返回 1不存在返回 0如 exists namedel删除键无论值是什么数据结构类型del 命令都可以将其删除。返回删除键个数删除不存在键返回 0。同时 del 命令可以支持删除多个键如 del name ageexpireRedis 支持对键添加过期时间当超过过期时间后会自动删除键时间单位秒如 expire name 10ttlttl 命令会返回键的剩余过期时间若返回 -1 则表示键没设置过期时间-2 键不存在type返回键的数据结构类型randomkey随机返回一个键rename键重命名为了防止被强行 renameRedis 提供了 renamenx 命令确保只有 newKey 不存在时候才被覆盖。由于重命名键期间会执行 del 命令删除旧的键如果键对应的值比较大会存在阻塞 Redis 的可能性
注意
dbsize 命令在计算键总数时不会遍历所有键,而是直接获取 Redis 内置的键总数变量,所以 dbsize 命令的时间复杂度是 O(1)。而 keys 命令会遍历所有键所以它的时间复杂度是 o(n)当 Redis 保存了大量键时线上环境禁止使用 keys 命令除了 expire、ttl 命令以外Redis 还提供了 expireat、pexpire,pexpireat、pttl、persist 等一系列命令可自行查验。
4 Redis 基本数据类型
Redis是典型的key-value数据库key一般是字符串而value包含很多不同的数据类型 4.1 String
字符串类型的数据结构可以理解为MapString,String
字符串类型是 Redis 最基础的数据结构。首先键都是字符串类型而且其他几种数据结构都是在字符串类型基础上构建的所以字符串类型能为其他四种数据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如 JSON、XML))、数字(整数、浮点数)甚至是二进制(图片、音频、视频)但是值最大不能超过 512 MB。
4.1.1 常用命令
设置值 set
set key value [ex seconds] [px milliseconds] [nxlxx]ex seconds为键设置秒级过期时间。px milliseconds为键设置毫秒级过期时间。nx键必须不存在才可以设置成功用于添加。xx与 nx 相反键必须存在才可以设置成功用于更新。
其中ex 参数和 expire 命令基本一样。还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间然后你调用了 set 方法修改了它它的过期时间会消失。
除了 set 选项Redis 还提供了 setex 和 setnx 两个命令
setex key seconds valuesetnx key value
setex 和 setnx 的作用和 ex 和 nx 选项是一样的。也就是setex 为键设置秒级过期时间setnx 设置时键必须不存在才可以设置成功。
有什么应用场景吗?
以 setnx 命令为例子由于 Redis 的单线程命令处理机制如果有多个客户端同时执行 setnx key value根据 setnx 的特性只有一个客户端能设置成功setnx 可以作为分布式锁的一种实现方案。
获取值 get
get key如果要获取的键不存在则返回 nil。
另外除了单个设置和获取键值Redis 还支持批量操作。
批量设置值 mset
mset name ayue age 20 sex 男批量获取值 mget
mget name age sex如果有些键不存在那么它的值为 nil结果是按照传入键的顺序返回。
批量操作命令可以有效提高效率假如没有 mget 这样的命令要执行 n 次 get 命令具体耗时如下
n 次 get 时间 n 次网络时间 n 次命令时间使用 mget 命令后要执行 n 次 get 命令操作具体耗时如下
n 次 get 时间 1 次网络时间 n 次命令时间Redis 可以支撑每秒数万的读写操作但是这指的是 Redis 服务端的处理能力对于客户端来说一次命令除了命令时间还是有网络时间假设网络时间为 1 毫秒命令时间为 0.1 毫秒按照每秒处理 1 万条命令算那么执行 1000 次 get 命令需要 1.1 秒1000*11000*0.11100ms1 次 mget 命令的需要 0.101 秒 1*11000*0.1101ms。
数字运算 incr
incr 命令用于对值做自增操作返回结果分为三种情况
值不是整数返回错误值是整数返回自增后的结果键不存在按照值为 0 自增,返回结果为 1。
incr key除了 incr 命令Redis 提供了 decr自减、 incrby自增指定数字、decrby自减指定数字、incrbyfloat自增浮点数。
追加指令 append
append 可以向字符串尾部追加值。
append key valuestrlen
返回字符串长度。
strlen key截取字符串 getrange
getrange 截取字符串中的一部分形成一个子串需要指明开始和结束的偏移量截取的范围是个闭区间。 命令说明时间复杂度get key获取值O(1)del key [key …]删除keyO(N)(N是键的个数)mset key [key value …]批量设置值O(N)(N是键的个数)mget key [key …]批量获取值O(N)(N是键的个数)incr key将 key 中储存的数字值增一O(1)decr key将 key 中储存的数字值减一O(1)incrby key increment将 key 所储存的值加上给定的增量值incrementO(1)decrby key incrementkey 所储存的值减去给定的减量值decrementO(1)incrbyfloat key increment将 key 所储存的值加上给定的浮点增量值incrementO(1)append key value如果 key 已经存在并且是一个字符串 APPEND 命令将指定的 value 追加到该 key 原来值value的末尾O(1)strlen key返回 key 所储存的字符串值的长度。O(1)setrange key offset value用 value 参数覆写给定 key 所储存的字符串值从偏移量 offset 开始O(1)getrange key start end返回 key 中字符串值的子字符O(N)(N是字符串的长度)
4.1.2 命令的时间复杂度
字符串这些命令中除了 del 、mset、 mget 支持多个键的批量操作时间复杂度和键的个数相关为 O(n)getrange 和字符串长度相关也是 O(n)其余的命令基本上都是 O(1)的时间复杂度在速度上是非常快的。
4.1.3 使用场景
字符串类型的使用场景很广泛如下
1、缓存功能
Redis 作为缓存层MySQL 作为存储层绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性所以缓存通常能起到加速读写和降低 后端压力的作用。
2、计数
使用 Redis 作为计数的基础工具它可以实现快速计数、查询缓存的功能同时数据可以异步落地到其他数据源。
3、共享 Session
一个分布式 Web 服务将用户的 Session 信息例如用户登录信息保存在各 自服务器中这样会造成一个问题出于负载均衡的考虑分布式服务会将用户的访问均衡到不同服务器上用户刷新一次访问可能会发现需要重新登录这个问题是用户无法容忍的。
为了解决这个问题 可以使用 Redis 将用户的 Session 进行集中管理在这种模式下只要保证 Redis 是高可用和扩展性的每次用户更新或者查询登录信息都直接从 Redis 中集中获取。
4、限时
很多应用出于安全的考虑会在每次进行登录时让用户输入手机验证码从而确定是否是用户本人。但是为了短信接口不被频繁访问会限制用户每分钟获取验证码的频率例如一分钟不能超过 5 次。一些网站限制一个 IP 地址不能在一秒钟之内访问超过 n 次。或者同一 IP 在短时间内多次浏览谋篇文章浏览次数不会一直增加。点赞次数在短时间内不能重复点赞。
4.2 Hash
hash类型的数据结构可以理解为MapString,HashMapString,String
Redis hash 是一个 string 类型的 field字段 和 value值 的映射表hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32 - 1 键值对40多亿。
4.2.1 常用命令
基本上哈希的操作命令和字符串的操作命令很类似很多命令在字符串类型的命令前面加上了 h 字母代表是操作哈希类型同时还要指明要操作的 field 的值。
hset
hset key field value如果设置成功会返回 1反之会返回 0。此外 Redis 提供了 hsetnx 命令它们的关系就像 set 和 setnx 命令一样只不过作用域由键变为 field。
127.0.0.1:6379 hset hash:test name ayue
(integer) 1
127.0.0.1:6379 hget
hget key field获取值
127.0.0.1:6379 hget hash:test name
ayue
127.0.0.1:6379 其他命令
命令说明时间复杂度HDEL key field [field]删除一个或多个Hash的fieldO(N) N是被删除的字段数量HEXISTS key field判断field是否存在于Hash中O(1)HGET key field获取Hash中field的值O(1)HGETALL key从Hash中读取全部的域和值O(N) N是Hash的长度HINCRBY key field increment将Hash中指定域的值增加给定的数字O(1)HINCRBYFLOAT key field increment将Hash中指定域的值增加给定的浮点数O(1)HKEYS key获取Hash的所有字段O(N) N是Hash的长度HLEN key获取Hash里所有字段的数量O(1)HMGET key field field获取Hash里面指定字段的值O(N) N是请求的字段数HMSET key field value [field value …]批量设置Hash字段值O(N) N是设置的字段数HSET key field value设置Hash里面一个字段的值O(1)HSETNX key field value设置Hash的一个字段只有当这个字段不存在时有效O(1)HSTRLEN key field获取Hash里面指定field的长度O(1)HVALS key获得 Hash 的所有值O(N) N是Hash的长度HSCAN key cursor [MATCH pattern] [COUNT count]迭代 Hash 里面的元素
4.2.2 命令的时间复杂度
哈希类型的操作命令中hdelhmgethmset 的时间复杂度和命令所带的 field 的个数相关 O(k)hkeyshgetallhvals 和存储的 field 的总数相关O(N)。其余的命令时间复杂度都是 O(1)。
4.2.3 使用场景
1、存储对象
Redis哈希对象常常用来缓存一些对象信息如用户信息、商品信息、配置信息等。
我们以用户信息为例它在关系型数据库中的结构是这样的
idnameage1Tom152Jerry13
而使用Redis Hash存储其结构如下图
hmset user:1 name Tom age 15
hmset user:2 name Jerry age 13相比较于使用Redis字符串存储其有以下几个优缺点: 原生字符串每个属性一个键。 set user:1:name Tom
set user:1:age 15优点简单直观每个属性都支持更新操作。 缺点占用过多的键内存占用量较大同时用户信息内聚性比较差所以此种方案一般不会在生产环境使用。 序列化字符串后将用户信息序列化后用一个键保存 set user:1 serialize(userInfo)优点简化编程如果合理的使用序列化可以提高内存的使用效率。 缺点序列化和反序列化有一定的开销同时每次更新属性都需要把全部数据取出进行反序列化更新后再序列化到Redis中。 序列化字符串后将用户信息序列化后用一个键保存 hmset user:1 name Tom age 15 优点简单直观如果使用合理可以减少内存空间的使用。 缺点要控制哈希在ziplist和hashtable两种内部编码的转换hashtable会消耗更多内存。
2、购物车
购物车主要功能是临时存放欲购买的商品然后在结算或下订单时把购物里面的数据全部移除。其数据结构主要包含的字段有用户ID、商品ID、商品数量等等。通常我们需要实现以下几个功能
全选功能获取所有该用户的所有购物车商品商品数量购物车图标上要显示购物车里商品的总数删除要能移除购物车里某个商品增加或减少某个商品的数量。
在之前很多电商网站通过 cookie 实现购物车功能也就是将整个购物车都存储到 cookie里面。
优点无须对数据库进行写入就可以实现购物车功能,这种方式大大提高了购物车的性能。缺点程序需要重新解析和验证( validate) cookie确保 cookie 的格式正确并且包含的商品都是真正可购买的商品。另外因为浏览器每次发送请求都会连 cookie 一起发送所以如果购物车 cookie 的体积比较大那么请求发送和处理的速度可能会有所降低。
而通过 Redis 定义购物车非常简单当前登录用户 ID 号做为key商品 ID 号为 field加入购物车数量为 value如下
hmset cart:001 prod:01 1 prod:02 1| | || | || | |key field value而对于上述功能可以通过 Hash 的相关命令来操作。
4.3 List
list类型的数据结构可以理解为MapString,ListString
列表( list)类型是用来存储多个有序的字符串a、b、c、d、e 五个元素从左到右组成了一个有序的列表列表中的每个字符串称为元素(element)一个列表最多可以存储 2 ^ 32 - 1 个元素。
在 Redis 中可以对列表两端插入( push)和弹出(pop)还可以获取指定范围的元素列表、获取指定索引下标的元素等。
列表是一种比较灵活的数据结构它可以充当栈和队列的角色在实际开发上有很多应用场景。 列表类型有两个特点
列表中的元素是有序的这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。列表中的元素可以是重复的。
4.3.1 常用命令
Redis列表对象常用命令如下表点击命令可查看命令详细说明
命令说明时间复杂度BLPOP key [key …] timeout删除并获得该列表中的第一元素或阻塞直到有一个可用O(1)BRPOP key [key …] timeout删除并获得该列表中的最后一个元素或阻塞直到有一个可用O(1)BRPOPLPUSH source destination timeout弹出一个列表的值将它推到另一个列表并返回它;或阻塞直到有一个可用O(1)LINDEX key index获取一个元素通过其索引列表O(N)LINSERT key BEFOREAFTER pivot value在列表中的另一个元素之前或之后插入一个元素O(N)LLEN key获得队列(List)的长度O(1)LPOP key从队列的左边出队一个元素O(1)LPUSH key value [value …]从队列的左边入队一个或多个元素O(1)LPUSHX key value当队列存在时从队到左边入队一个元素O(1)LRANGE key start stop从列表中获取指定返回的元素O(SN)LREM key count value从列表中删除元素O(N)LSET key index value设置队列里面一个元素的值O(N)LTRIM key start stop修剪到指定范围内的清单O(N)RPOP key从队列的右边出队一个元O(1)RPOPLPUSH source destination删除列表中的最后一个元素将其追加到另一个列表O(1)RPUSH key value [value …]从队列的右边入队一个元素O(1)RPUSHX key value从队列的右边入队一个元素仅队列存在时有效O(1)
4.3.2 命令的时间复杂度
列表类型的操作命令中llenlpoprpopblpop 和 brpop 命令时间复杂度都是 O(1)其余的命令的时间复杂度都是 O(n)只不过 n 的值根据命令不同而不同比如 lsetlindex 时间复杂度和命令后的索引值大小相关rpush 和 lpush 和插入元素的个数相关等等。
4.3.3 使用场景
1、消息队列
但使用 Redis 做消息队列存在很多问题如消息确认 ACK消息丢失等所以一般来说还是用比较专业的 MQ 中间件。
2、文章列表
如下面这样的文章列表当用户和文章都越来越多时为了加快程序的响应速度我们可以把用户自己的文章存入到 List 中因为 List 是有序的结构所以这样又可以完美的实现分页功能从而加速了程序的响应速度。 上图可表示为
# 深圳卫健委发布一条消息消息ID为 99
lpush mes:001 99
# 武汉本地宝发布一条消息消息ID为 100
lpush mes:001 100
# 获取消息列表‘
lrange mes:001 0 54.4 Set
set类型的数据结构可以理解为MapString,SetString
集合( set类型也是用来保存多个的字符串元素,但和列表类型不一样的是集合中不允许有重复元素并且集合中的元素是无序的不能通过索引下标获取元素。
一个集合最多可以存储 2 ^ 32 - 1 个元素。Redis 除了支持集合内的增删改查同时还支持多个集合取交集、并集、差集合理地使用好集合类型能在实际开发中解决很多实际问题。
4.4.1 常用命令
Redis Set 对象常用命令如下表点击命令可查看命令详细说明
命令说明时间复杂度SADD key member [member …]添加一个或者多个元素到集合(set)里O(N)SCARD key获取集合里面的元素数量O(1)SDIFF key [key …]获得队列不存在的元素O(N)SDIFFSTORE destination key [key …]获得队列不存在的元素并存储在一个关键的结果集O(N)SINTER key [key …]获得两个集合的交集O(N*M)SINTERSTORE destination key [key …]获得两个集合的交集并存储在一个关键的结果集O(N*M)SISMEMBER key member确定一个给定的值是一个集合的成员O(1)SMEMBERS key获取集合里面的所有元素O(N)SMOVE source destination member移动集合里面的一个元素到另一个集合O(1)SPOP key [count]删除并获取一个集合里面的元素O(1)SRANDMEMBER key [count]从集合里面随机获取一个元素SREM key member [member …]从集合里删除一个或多个元素O(N)SUNION key [key …]添加多个set元素O(N)SUNIONSTORE destination key [key …]合并set元素并将结果存入新的set里面O(N)SSCAN key cursor [MATCH pattern] [COUNT count]迭代set里面的元素O(1)
4.4.2 命令的时间复杂度
scardsismember 时间复杂度为 O(1)其余的命令时间复杂度为 O(n)其中 saddsrem 和命令后所带的元素个数相关spopsrandmember 和命令后所带 count 值相关交集运算 O(m*k)k 是多个集合中元素最少的个数m 是键个数并集、差集和所有集合的元素个数和相关。
4.4.3 使用场景
1、抽奖活动
常见的抽奖活动比如基于 Redis 实现抽奖功能。
SPOP(随机移除并返回集合中一个或多个元素)和 SRANDMEMBER(随机返回集合中一个或多个元素)命令可以帮助我们实现一个抽奖系统如果允许重复中奖可以使用SRANDMEMBER 命令。
活动 ID 为 001则
# Tom userID:01 参加活动
sadd action:001 01
# Jerry userID:02 参加活动
sadd action:001 02
# 开始抽奖1名中奖者
srandmember action:001 1 或 spop action:001 1
# 查看有多少用户参加了本次抽奖
smembers action:0012、点赞功能
比如设计一个微信点赞功能。
# 张三用户ID 为userId:01
# 张三对消息 ID008点赞啦
sadd zan:008 userId:01# 张三取消了对消息008的点赞
srem zan:008 userId:01# 检查用户是否点过赞
sismember zan:008 userId:01# 获取消息ID008所有的点赞用户列表
smembers zan:008# 消息ID008的点赞数计算
scard zan:0083、关系设计
如我们要设计一个微博的共同关注或者可能认识的人。设计如下
① A 关注的人
sadd A:cares B C D E② B 关注的人
sadd B:cares A C D F③ C 关注的人
sadd C:cares A F按照以上条件
④ A 和 B 共同关注的人
# D,C
sinter A:cares B:cares⑤ 我关注的人也关注他
# A 关注的 B 也关注了 F返回 1 否则返回 0
sismember B:cares F⑥ 可能认识的人
# C 可能认识的人 C,D
sdiff B:cares C:cares4、集合操作
setA{A,B,C} setB{B, C}① 集合与集合之间的交集
sinter setA setB得到集合{B,C}② 集合与集合之间的并集
sunion setA setB 得到集合{A,B,C}③ 集合与集合之间的差集
sdiff setA setB得到集合{A}
127.0.0.1:6379 SADD setA A B C
(integer) 3
127.0.0.1:6379 SADD setB B C
(integer) 2
127.0.0.1:6379 SINTER setA setB
1) C
2) B
127.0.0.1:6379 SUNION setA setB
1) A
2) B
3) C
127.0.0.1:6379 SDIFF setA setB
1) A
127.0.0.1:6379 4.5 ZSet(SortedSet)
ZSet有序集合相对于哈希、列表、集合来说会有一点点陌生但既然叫有序集合那么它和集合必然有着联系它保留了集合不能有重复成员的特性但不同的是有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是它给每个元素设置一个分数( score)作为排序的依据。
有序集合中的元素不能重复但是 score 可以重复就和一个班里的同学学号不能重复但是考试成绩可以相同。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能合理的利用有序集合能帮助我们在实际开发中解决很多问题。
4.5.1 常用命令
zadd
向有序集合 top:20211221 添加话题和点击量。
zadd hot:20211220 10 薇娅逃税zadd 命令还有四个选项 nx、xx、ch、incr 四个选项
nxmember 必须不存在才可以设置成功用于添加xxmember 必须存在才可以设置成功用于更新ch返回此次操作后有序集合元素和分数发生变化的个数incr对 score 做增加相当于 zincrby 。
Redis列表对象常用命令如下表
命令说明时间复杂度BZPOPMAX key [key …] timeout从一个或多个排序集中删除并返回得分最高的成员或阻塞直到其中一个可用为止O(log(N))BZPOPMIN key [key …] timeout从一个或多个排序集中删除并返回得分最低的成员或阻塞直到其中一个可用为止O(log(N))ZADD key [NXXX] [CH] [INCR] score member [score member …]添加到有序set的一个或多个成员或更新的分数如果它已经存在O(log(N))ZCARD keyZCOUNT key min max返回分数范围内的成员数量O(log(N))ZINCRBY key increment member增量的一名成员在排序设置的评分O(log(N))ZINTERSTORE相交多个排序集导致排序的设置存储在一个新的关键O(NK)O(Mlog(M))ZLEXCOUNT key min max返回成员之间的成员数量O(log(N))ZPOPMAX key [count]删除并返回排序集中得分最高的成员O(log(N)*M)ZPOPMIN key [count]删除并返回排序集中得分最低的成员O(log(N)*M)ZRANGE key start stop [WITHSCORES]根据指定的index返回返回sorted set的成员列表O(log(N)M)ZRANGEBYLEX key min max [LIMIT offset count]返回指定成员区间内的成员按字典正序排列, 分数必须相同。O(log(N)M)ZREVRANGEBYLEX key max min [LIMIT offset count]返回指定成员区间内的成员按字典倒序排列分数必须相同O(log(N)M)ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]返回有序集合中指定分数区间内的成员分数由低到高排序。O(log(N)M)ZRANK key member确定在排序集合成员的索引O(log(N))ZREM key member [member …]从排序的集合中删除一个或多个成员O(M*log(N))ZREMRANGEBYLEX key min max删除名称按字典由低到高排序成员之间所有成员。O(log(N)M)ZREMRANGEBYRANK key start stop在排序设置的所有成员在给定的索引中删除O(log(N)M)ZREMRANGEBYSCORE key min max删除一个排序的设置在给定的分数所有成员O(log(N)M)ZREVRANGE key start stop [WITHSCORES]在排序的设置返回的成员范围通过索引下令从分数高到低O(log(N)M)ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]返回有序集合中指定分数区间内的成员分数由高到低排序。O(log(N)M)ZREVRANK key member确定指数在排序集的成员下令从分数高到低O(log(N))ZSCORE key member获取成员在排序设置相关的比分O(1)ZUNIONSTORE添加多个排序集和导致排序的设置存储在一个新的键O(N)O(M log(M))ZSCAN key cursor [MATCH pattern] [COUNT count]迭代sorted sets里面的元素O(1)
4.5.2 命令的时间复杂度
参考上表。
4.5.3 使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜榜单的维度可能是多个方面的按照时间、按照播放数量、按照获得的赞数。 如上热搜榜以日期为 key
① 点击热搜每次加 1
zincrby hot:20211220 1 薇娅逃税② 右侧排行实现展示今日前 50 排名
# zrange 是从低到高返回,zrevrange 反之
zrevrange hot:20211221 0 49 withscores5 Redis 缓存
5.1 认识缓存
什么是缓存?
就像自行车,越野车的避震器!
举个例子:越野车,山地自行车,都拥有避震器,防止车体加速后因惯性,在酷似U字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样;
同样,实际开发中,系统也需要避震器,防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪;
这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术
5.1.1 缓存的概念
缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码(例如:
// 例1:
Static final ConcurrentHashMapK,V map new ConcurrentHashMap(); // 本地用于高并发// 例2:
static final CacheK,V USER_CACHE CacheBuilder.newBuilder().build(); // 用于redis等缓存// 例3:
Static final MapK,V map new HashMap(); // 本地缓存由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被final修饰,所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的,不能改变,因此不用担心赋值()导致缓存失效;
5.1.2 缓存的作用及成本
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力
实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存来作为避震器,系统是几乎撑不住的,所以企业会大量运用到缓存技术;
但是缓存也会增加代码复杂度和运营的成本: 5.1.3 缓存的使用
实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与redis中的缓存并发使用
浏览器缓存主要是存在于浏览器端的缓存
**应用层缓存**可以分为tomcat本地缓存比如之前提到的map或者是使用redis作为缓存
**数据库缓存**在数据库中有一片空间是 buffer pool增改查数据都会先加载到mysql的缓存中
**CPU缓存**当代计算机最大的问题是 cpu性能提升了但内存读写速度没有跟上所以为了适应当下的情况增加了cpu的L1L2L3级的缓存 5.2 Redis 缓存
5.2.1 Redis 缓存更新策略
缓存更新是redis为了节约内存而设计出来的一个东西主要是因为内存数据宝贵当我们向redis插入太多数据此时就可能会导致缓存中的数据过多所以redis会对部分数据进行更新或者把他叫为淘汰更合适。
内存淘汰 redis自动进行当redis内存达到咱们设定的max-memery的时候会自动触发淘汰机制淘汰掉一些不重要的数据(可以自己设置策略方式)
过期淘汰 当我们给redis设置了过期时间ttl之后redis会将过期的数据进行删除方便咱们继续使用缓存
主动更新 我们可以手动调用方法把缓存删掉通常用于解决缓存和数据库不一致问题 注意缓存更新策略的选择要基于业务场景来看
低一致性需求使用内存淘汰或过期淘汰。高一致性需求主动更新为主过期淘汰兜底。
5.2.2 主动更新方案
由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是:
用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢有如下几种方案
Cache Aside Pattern 缓存调用者在更新完数据库后再去更新缓存也称之为双写方案
Read/Write Through Pattern : 由系统本身完成数据库与缓存的问题交由系统本身去处理
Write Behind Caching Pattern 调用者只操作缓存其他线程去异步处理数据库实现最终一致 5.2.3 Cache Aside 的模式选择
操作缓存和数据库时有三个问题需要考虑
如果采用 Cache Aside Pattern 方案那么假设我们每次操作数据库后都操作缓存但是中间如果没有人查询那么这个更新动作实际上只有最后一次生效中间的更新动作意义并不大我们可以把缓存删除等待再次查询时将缓存中的数据加载出来 删除缓存还是更新缓存 更新缓存每次更新数据库都更新缓存无效写操作较多删除缓存更新数据库时让缓存失效查询时再更新缓存 如何保证缓存与数据库的操作的同时成功或失败 单体系统将缓存与数据库操作放在一个事务分布式系统利用TCC等分布式事务方案
应该具体操作缓存还是操作数据库我们应当是先操作数据库再删除缓存原因在于如果你选择第一种方案在两个线程并发来访问时假设线程1先来他先把缓存删了此时线程2过来他查询缓存数据并不存在此时他写入缓存当他写入缓存后线程1再执行更新动作时实际上写入的就是旧的数据新的数据被旧数据覆盖了。 先操作缓存还是先操作数据库 先删除缓存再操作数据库 操作前 正常情况 异常情况 概率较高 先操作数据库再删除缓存 操作前 正常情况 异常情况概率较低
5.3 Redis 缓存相关问题
5.3.1 缓存穿透
缓存穿透 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在这样缓存永远不会生效这些请求都会打到数据库。
常见的解决方案有两种
缓存空对象 优点实现简单维护方便缺点 额外的内存消耗可能造成短期的不一致 布隆过滤 优点内存占用较少没有多余key缺点 实现复杂存在误判可能
缓存空对象思路分析 当我们客户端访问不存在的数据时先请求redis但是此时redis中没有数据此时会访问到数据库但是数据库中也没有数据这个数据穿透了缓存直击数据库我们都知道数据库能够承载的并发不如redis这么高如果大量的请求同时过来访问这种不存在的数据这些请求就都会访问到数据库简单的解决方案就是哪怕这个数据在数据库中也不存在我们也把这个数据存入到redis中去这样下次用户过来访问这个不存在的数据那么在redis中也能找到这个数据就不会进入到缓存了
布隆过滤 布隆过滤器其实采用的是哈希思想来解决这个问题通过一个庞大的二进制数组走哈希思想去判断当前这个要查询的这个数据是否存在如果布隆过滤器判断存在则放行这个请求会去访问redis哪怕此时redis中的数据过期了但是数据库中一定存在这个数据在数据库中查询出来这个数据后再将其放入到redis中
假设布隆过滤器判断这个数据不存在则直接返回
这种方式优点在于节约内存空间存在误判误判原因在于布隆过滤器走的是哈希思想只要哈希思想就可能存在哈希冲突 5.3.2 缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机导致大量请求到达数据库带来巨大压力。
解决方案
给不同的Key的TTL添加随机值利用Redis集群提高服务的可用性给缓存业务添加降级限流策略给业务添加多级缓存 5.3.3 缓存击穿
缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种
互斥锁逻辑过期
逻辑分析假设线程1在查询缓存之后本来应该去查询数据库然后把这个数据重新加载到缓存的此时只要线程1走完这个逻辑其他线程就都能从缓存中加载这些数据了但是假设在线程1没有走完的时候后续的线程2线程3线程4同时过来访问当前这个方法 那么这些线程都不能从缓存中查询到数据那么他们就会同一时刻来访问查询缓存都没查到接着同一时间去访问数据库同时的去执行数据库代码对数据库访问压力过大 解决方案一 使用锁
因为锁能实现互斥性。假设线程过来只能一个人一个人的来访问数据库从而避免对于数据库访问压力过大但这也会影响查询的性能因为此时会让查询的性能从并行变成了串行我们可以采用tryLock方法 double check来解决这样的问题。
假设现在线程1过来访问他查询缓存没有命中但是此时他获得到了锁的资源那么线程1就会一个人去执行逻辑假设现在线程2过来线程2在执行过程中并没有获得到锁那么线程2就可以进行到休眠直到线程1把锁释放后线程2获得到锁然后再来执行逻辑此时就能够从缓存中拿到数据了。 解决方案二 逻辑过期方案
方案分析我们之所以会出现这个缓存击穿问题主要原因是在于我们对key设置了过期时间假设我们不设置过期时间其实就不会有缓存击穿的问题但是不设置过期时间这样数据不就一直占用我们内存了吗我们可以采用逻辑过期方案。
我们把过期时间设置在 redis的value中注意这个过期时间并不会直接作用于redis而是我们后续通过逻辑去处理。假设线程1去查询缓存然后从value中判断出来当前的数据已经过期了此时线程1去获得互斥锁那么其他线程会进行阻塞获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑直到新开的线程完成这个逻辑后才释放锁 而线程1直接进行返回假设现在线程3过来访问由于线程线程2持有着锁所以线程3无法获得锁线程3也直接返回数据只有等到新开的线程2把重建数据构建完后其他线程才能走返回正确的数据。
这种方案巧妙在于异步的构建缓存缺点在于在构建完缓存之前返回的都是脏数据。 进行对比
互斥锁方案 由于保证了互斥性所以数据一致且实现简单因为仅仅只需要加一把锁而已也没其他的事情需要操心所以没有额外的内存消耗缺点在于有锁就有死锁问题的发生且只能串行执行性能肯定受到影响
逻辑过期方案 线程读取过程中不需要等待性能好有一个额外的线程持有锁去进行重构数据但是在重构数据完成前其他的线程只能返回之前的数据且实现起来麻烦 6 Redis 持久化
Redis有两种持久化方案
RDB持久化AOF持久化
6.1 RDB
RDB全称Redis Database Backup fileRedis数据备份文件也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件恢复数据。快照文件称为RDB文件默认是保存在当前运行目录。
6.1.1 执行时机
RDB持久化在四种情况下会执行
执行save命令执行bgsave命令Redis停机时触发RDB条件时
1save命令
执行下面的命令可以立即执行一次RDB save命令会导致主进程执行RDB这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。
2bgsave命令
下面的命令可以异步执行RDB 这个命令执行后会开启独立进程完成RDB主进程可以持续处理用户请求不受影响。
3停机时
Redis停机时会执行一次save命令实现RDB持久化。
4触发RDB条件
Redis内部有触发RDB的机制可以在redis.conf文件中找到格式如下
# 900秒内如果至少有1个key被修改则执行bgsave 如果是save 则表示禁用RDB
save 900 1
save 300 10
save 60 10000 RDB的其它配置也可以在redis.conf文件中设置
# 是否压缩 ,建议不开启压缩也会消耗cpu磁盘的话不值钱
rdbcompression yes# RDB文件名称
dbfilename dump.rdb # 文件保存的路径目录
dir ./ 6.1.2 RDB原理
Redis会单独创建fork一个子进程来进行持久化。
bgsave开始时会fork主进程得到子进程子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术
当主进程执行读操作时访问共享内存当主进程执行写操作时则会拷贝一份数据执行写操作。 6.1.3 小结
RDB方式bgsave的基本流程
fork主进程得到一个子进程共享内存空间子进程读取内存数据并写入新的RDB文件用新RDB文件替换旧的RDB文件
RDB会在什么时候执行save 60 1000代表什么含义
默认是服务停止时代表60秒内至少执行1000次修改则触发RDB
RDB的缺点
RDB执行间隔时间长两次RDB之间写入数据有丢失的风险fork子进程、压缩、写出RDB文件都比较耗时
6.2 AOF
6.2.1 AOF原理
AOF全称为Append Only File追加文件。Redis处理的每一个写命令都会记录在AOF文件可以看做是命令日志文件。 6.2.2 AOF配置
AOF默认是关闭的需要修改redis.conf配置文件来开启AOF
# 是否开启AOF功能默认是no
appendonly yes
# AOF文件的名称
appendfilename appendonly.aofAOF的命令记录的频率也可以通过redis.conf文件来配
# 表示每执行一次写命令立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区然后表示每隔1秒将缓冲区数据写到AOF文件是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no三种策略对比 6.2.3 AOF文件重写
因为是记录命令AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作但只有最后一次写操作才有意义。通过执行bgrewriteaof命令可以让AOF文件执行重写功能用最少的命令达到相同效果。 如图AOF原本有三个命令但是set num 123 和 set num 666都是对num的操作第二次会覆盖第一次的值因此第一个命令记录下来没有意义。
所以重写命令后AOF文件内容就是mset name jack num 666
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb 6.3 RDB与AOF对比 RDB和AOF各有自己的优缺点如果对数据安全性要求较高在实际开发中往往会结合两者来使用。 RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储。 AOF 持久化方式记录每次对服务器写的操作当服务器重启的时候会重新执行这些命令来恢复原始的数据AO F命令以 Redis 协议追加保存每次写的操作到文件末尾Redis 还能对 AOF 文件进行后台重写使得 AOF 文件的体积不至于过大。
只做缓存如果你只希望你的数据在服务器运行的时候存在你也可以不使用任何持久化。
同时开启两种持久化方式
在这种情况下当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
RDB 的数据不实时同时使用两者时服务器重启也只会找 AOF 文件那要不要只使用AOF呢作者建议不要因为 RDB 更适合用于备份数据库AOF 在不断变化不好备份快速重启而且不会有 AOF 可能潜在的 Bug留着作为一个万一的手段。
性能建议
因为 RDB 文件只用作后备用途建议只在 Slave从节点 上持久化 RDB 文件而且只要 15 分钟备份一次就够了只保留 save 900 1 这条规则。
如果开启 AOF 好处是在最恶劣情况下也只会丢失不超过两秒数据启动脚本较简单只 load 自己的AOF文件就可以了代价一是带来了持续的 IO二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可应该尽量减少 AOF rewrite 的频率AOF重写的基础大小默认值 64M 太小了可以设到 5G 以上默认超过原大小 100% 大小重写可以改到适当的数值。
如果不开启 AOF 仅靠 Master-Slave Repllcation主从复制 实现高可用性也可以能省掉一大笔IO也减少了 rewrite 时带来的系统波动。代价是如果 Master/Slave 同时挂掉会丢失十几分钟的数据启动脚本也要比较两个 Master/Slave 中的 RDB 文件载入较新的那个微博就是这种架构。
参考资料
探索数据库 Redis常用数据结构及应用场景 Redis的RDB与AOF详解