redis的基本原理:Redis本质上是一个基于内存的数据结构存储系统,使用多种高效的数据结构,如字符串、列表、集合、哈希等。这种设计使得Redis能够以极高的速度处理数据,特别适合用于缓存和高速数据访问场景
redis缓存测试点
1、性能测试角度
缓存增加/更新功能是否正确,查看缓存数据是否正确
增加相关日志,查看日志
使用命令行,memcached和reids可以登录后,直接查看
缓存删除:在Redis中,删除缓存的常用命令是 DEL。这个命令可以从Redis中删除一个或多个键及其关联的值。
缓存有效,验证相关业务功能
缓存被删除,验证相关业务功能
缓存过期失效,memcached 和redis 可以设置失效时间,查看失效时间有没有,对不对
超量淘汰机制:缓存达到上限怎么处理
缓存穿透:当查询的数据在缓存和数据库中都不存在时,每次请求都会直接访问数据库,导致数据库压力增大。
缓存雪崩:因为某些原因导致缓存中大量的数据同时失效或过期,导致后续请求都落到后端存储上,从而引起系统负载暴增、性能下降甚至瘫痪。
缓存击穿:在高并发访问下,热点数据失效后,大量请求同时涌入后端存储,导致后端存储负载增大。
redis缓存服务停掉
缓存超时
缓存数据被误修改后,快速恢复到指定版本
缓存数据被误删除后,快速恢复数据
2、Redis功能测试角度redis测试
- redis数据生效时,读取是否正确
- redis数据不存在,能否正常从db中读取到正确的值,并正确写入Redis和返回给上层
- 数据在redis和db中都不存在时的表现是否正常
- 删除数据时,redis和db的数据是否一致【删除redis数据之后,接口调用从db中读取到正确的数值之后正确写入Redis和返回给上层】
测试点
有些测试朋友来问我,redis要怎么测试?首先我们需要知道,redis是什么?它能做什么?
redis是一个key-value类型的高速存储数据库。
redis常被用做:缓存、队列、发布订阅等。
所以,“redis要怎么测试?”这个问题就可以转化为:
缓存怎么测试?
队列怎么测试?
订阅怎么测试?
在我所接触的技术栈中,发布订阅很少用redis的,我们主要说一说缓存和队列。
01 缓存的分类
缓存有几种类型:文件缓存、数据库缓存、内存缓存、浏览器缓存。
浏览器缓存指的是浏览器自身的缓存能力。现代浏览器为了加快页面加载速度,往往会把css、js等资源文件下载一次之后缓存一段时间,直到缓存失效或者请求明确告知需要更新。
通过后端语言直接渲染、smarty等模板渲染方式输出界面的,一般都会选择文件类型缓存。
[图片]
图–文件缓存
随着大前端技术迅速发展,前后端分离越来越流行,smarty渲染的方式使用越来越少,对后端服务的接口响应时间要求也越来越高,文件缓存不再适用这种场景,数据库缓存越来越流行。
数据库缓存目前最常见的:redis和memcached。它们都是分布式的key-value高速缓存系统。
[图片]
上图–redis缓存
内存缓存跟数据库缓存也是类似的,但受技术栈限制,比如Java可以使用,并且Java中使用非常频繁,但PHP无法使用。内存缓存比数据库缓存更快,但因为内存不可能一直增加,所以限制更多,稍不注意就会出现内存泄漏等问题。
在实际的使用过程中,Java接口往往会将部分高频数据塞到内存缓存中作为一级缓存,次高频数据塞到redis中作为二级缓存,最后再从db查询数据。
[图片]
上图–组合使用
缓存的作用:
从上面的内容你可能已经知道,缓存最重要的两个作用:加快访问速度、减少服务器和db压力[数据库压力]。
02 缓存的使用场景
你可能会问,上面这些跟测试没啥关系啊?不,我认为了解上面的内容对测试还是有帮助的。你知道在技术实现上,什么时候应该加缓存,什么时候不应该加缓存吗?这就是对一个接口的技术把控,不光开发需要知道,测试人员也一样。
如果一个应该添加缓存的接口没有添加缓存在压测之前被你提前发现了,你不觉得自豪吗?其实跟缓存的作用一一对应,当接口的qps较高(比如超过100)或者对响应速度有要求,或者服务器性能、db性能较差的,都可以尝试使用缓存解决问题。
我举几个例子说明:
1、微信新版本中,个人中心多了一个“状态”功能。
微信的用户体量非常庞大,访问qps非常高,几十万人在同一秒访问,不可能每次都去查询数据库。
类似这种需求,一般会是这样的做法:先把用户的状态数据缓存在app中(浏览器缓存),在某个时间段通过主动推或者被动拉的方式调用后端接口请求“状态”数据;接口从redis/memcached的缓存中读取数据并返回;如果数据量不那么庞大,接口可以直接从内存缓存中读取数据并返回;数据返回后,再把用户app中的缓存更新。
[图片]
上图–示例1
2、有一个小型电商的商品管理后台列表页面,访问人数不多,sku改动频次很低,可能3天才被访问几十次。这种场景一不需要使用缓存,二在商品信息被更新之后需要立即看到更新后的数据,不适合使用缓存,所以不建议使用缓存。
3、同样的电商管理后台,这次是一个统计页面,统计昨天/今天/近一周的商品销售情况。
这个场景可以分情况来看,有多种不同的解决方案。
(我们抛开大数据统计的各类技术方案,简单实现一个系统的统计功能)
- 不需要实时统计
只需要定时统计一次即可,比如只看昨天一天统计数据:可以由定时脚本统计之后直接存储在db,需要查看统计数据时直接查询db即可 - 需要查询实时统计数据
但需要查询的各个统计sql执行效率满足预期:每次查看数据直接查询db即可,此时db压力不大 - 需要查询实时统计数据
且因业务数据庞大,各个统计sql执行效率非常低或无法直接统计:可以汇总各个指标,将统计值维护在缓存中,比如需要销量信息,每售出一件商品,销量统计值缓存+1,查看统计数据时查询此时的缓存即可
03 缓存的生成方式
了解到缓存的使用场景之后,我们来说说缓存的生成方式。
一般来说缓存有两种使用方式,我简单概括为:外面和里面。
先来说说一个接口的请求到了程序里,是怎么处理的:
[图片]
上图–程序处理流程
这是一个典型的MVC,由Controller接收和处理请求数据,由Service处理Model中获取的数据,再由View输出。
对不同场景,我们可以采取多种方式,在多个节点增加不同的缓存,来解决不同的问题。
比如,针对请求参数多变,返回的数据如果跟请求参数强相关,适合在“外面”(请求参数过滤之后)缓存查询到的数据。这类数据一般缓存时间短,比如缓存5分钟。主要应对相同请求参数在短时间内的重复请求。如果遇到请求攻击,即使这个缓存有效期只有1秒,也是很有效的,能挡住大量的请求。
[图片]
上图–在“外面”缓存
比如,针对请求参数变化不大,返回的数据跟db中存储的数据很接近的情况,适合在“里面”缓存数据,也就是在更新db的同时更新缓存,这种情况最优的状态下,只需要读缓存就够了,不需要跟db直接交互,能大大缓解db压力。这种缓存有效期可以设置很长。
[图片]
上图–在“里面”缓存
04 缓存的更新方式
说完生成,再来说说缓存的更新。缓存在生成之后,正常都不会一成不变,所以需要对缓存进行更新。
有几种更新方式:
过期后自动更新:这是最懒的更新方式。通过设置缓存有效期,让缓存失效后通过新的请求自动创建新的缓存。
删除缓存:在更新db数据后,直接删除缓存,通过新的请求自动创建新的缓存。
重新设置缓存:在更新db数据后,直接重新设置缓存。
05 缓存的持久化
"Redis持久化策略"指的是Redis数据库中用于将内存中的数据保存到硬盘上,以便在数据库重启后能够恢复数据的几种不同方法。Redis是一个基于内存的数据库,因此持久化机制对于防止数据丢失至关重要。Redis提供了两种主要的持久化方式:
- RDB(Redis Database)持久化:这种持久化策略会在指定的时间间隔内生成数据集的时间点快照。RDB持久化通过创建数据集的快照来工作,这些快照被保存在磁盘上的一个文件中,通常称为dump.rdb文件。如果Redis服务器发生故障,可以使用这个文件来恢复数据。
- AOF(Append Only File)持久化:这种持久化策略记录每次写操作命令,并将其追加到一个日志文件中。当Redis服务器重启时,会重新执行这些命令来重建原始数据集。AOF持久化提供了更好的数据完整性,因为它可以更频繁地写入磁盘,从而减少数据丢失的风险。
Redis用户可以根据需求选择使用RDB、AOF或者两者结合的方式来实现数据的持久化。每种方式都有其优缺点,例如RDB恢复速度快,但是可能会丢失最近的数据;AOF数据安全性高,但是文件可能会比RDB大,且恢复速度可能慢一些。
redis简单操作
key类型
- none (key 不存在)
- string 字符串
- list 列表
- set 集合
- zset 有序集
- hash 哈希表
String 型 Value 操作命令【一般来说都是string类型】
Redis 存储数据的 Value 可以是 一个 String 类型数据。 String 类型的 Value 是 Redis 中最基本,最常见的类型。 String 类型的 Value 中可以存放任意数据, 包括 数值型,甚至是二进制的 图片、音频、视频 、序列化对象等。一个 String 类型的 Value 最大是 512M 大小。
⭐️ 3.3.1、set
格式: SET key value [EX seconds | PX milliseconds] [NX|XX]
功能:SET 除了可以直接将 key 的值设为 value 外,还可以指定一些参数。
EX seconds :为当前 key 设置过期时间,单位 秒。等价于 SETEX 命令。
PX milliseconds :为当前 key 设置过期时间,单位 毫秒。等价于 PSETEX 命令。
NX :(No eXist) 指定的 key 不存在才会设置成功,用于添加指定的 key 。等价于 SETNX 命令。
XX :指定的 key 必须存在才会设置成功,用于更新指定 key 的 value 。
说明:如果 value 字符串中带有空格,则该字符串需要使用双引号或单引号引起来,否则会认为 set 命令的参数数量不正确,报错。
- 使用 SET 命令创建键并设置值。例如,如果你想创建一个键名为 mykey,值为 myvalue 的字符串类型的键,你可以执行:
- SET mykey “myvalue”
⭐️ 3.3.2、setex 与 psetex
格式:SETEX / PSETEX key seconds value
功能:set expire ,其不仅为 key 指定了 value ,还为其设置了生存时间。 setex 的单位为秒, psetex 的单位为毫秒。
说明:如果 key 已经存在, 则覆写旧值。该命令类似于以下两个命令,不同之处是,SETEX 是一个原子性操作,关联值 和 设置生存时间两个动作会在同一时间内完成,该命令在 Redis 用作缓存时,非常实用。
SET key value
EXPIRE key seconds #设置生存时间
⭐️ 3.3.3、setnx
格式:SETNX key value
功能:SET if Not eXists ,将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key已经存在,则 SETNX 不做任何动作。 成功,返回 1 ,否则 ,返回 0 。
说明:该命令等价于 set key value nx
⭐️ 3.3.4、getset
格式:GETSET key value
功能:将给定 key 的值设为 value ,并返回 key 的旧值。
说明:当 key 存在但不是字符串类型时,返回一个错误; 当 key 不存在时,返回 nil 。
⭐️ 3.3.5、mset 与 msetnx
格式:MSET / MSETNX key value [key value …]
功能:同时设置一个或多个 key-value 对。
说明:如果某个给定 key 已经存在,那么 MSET 会用 新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX命令:它只会在所有给定 key 都不存在的情况下进行设置操作。 MSET/MSETNX是一个原子性 (atomic) 操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况不可能发生。该命令永不失败。
⭐️ 3.3.6、mget[查]
格式: MGET key [key …]
功能:返回所有 (一个或多个) 给定 key 的值。
说明:说明:如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
⭐️ 3.3.7、append
格式:APPEND key value
功能:如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。
说明:追加 value 之后, key 中字符串的长度。
⭐️ 3.3.8、incr 与 decr
格式: INCR key 或 DECR key
功能:increment ,自动递增。 将 key 中存储的数字值增一。decrement ,自动递减。将 key 中存储的数字值减一。
说明:如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 增一/减一 操作。如果值不能表示为数字,那么返回一个错误提示。如果执行正确,则返回增一/减一 后的值。
⭐️ 3.3.9、incrby 与 decrby
格式:INCRBY key increment 或 DECRBY key decrement
功能:将 key 中存储的数字值 增加/减少 指定的数值,这个数值只能是整数,可以是负数,但不能是小数。
说明:如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 增/减 操作。如果值不能表示为数字,那么返回一个错误提示。如果执行正确,则返回 增/减 后的值。
⭐️ 3.3.10、incrbyfloat
格式:INCRBYFLOAT key increment
功能:为 key 中所储存的值加上 浮点数增量 increment 。
说明:与之前的说明相同。没有 decrbyfloat 命令,但 increment 为负数可以实现减操作效果。
⭐️ 3.3.11、strlen
格式:STRLEN key
功能:返回 key 所储存的字符串值的长度。
说明:当 key 储存的不是字符串值时,返回一个错误,当 key 不存在时,返回 0 。
⭐️ 3.3.12、getrange
格式:GETRANGE key start end
功能:返回 key 中字符串值的 子字符串,字符串的截取范围由 start 和 end 两个偏移量决定 包括 start 和 end 在内。
说明:end 必须要比 start 大。支持 负数偏移量 表示从字符串最后开始计数, -1 表示最后一个字符, -2 表示倒数第二个,以此类推。
⭐️ 3.3.13、setrange
格式:SETRANGE key offset value
功能:用 value 参数替换给定 key 所储存的字符串值 str ,从偏移量 offset 开始。
说明:当 offset 值大于 str 长度时,中间使用零字节 \x00 填充,即 0000 0000 字节填充;对于不存在的 key 当作空串处理。
⭐️ 3.3.14、位运算命令
名称中包含 BIT 的命令,都是对二进制位的操作命令,例如, setbit 、 getbit 、 bitcount 、bittop 、 bitfield ,这些命令不常用。
⭐️ 3.3.15、典型应用场景
Value 为 String 类型的 应用场景很多,这里仅举这种 典型应用场景 的例子:
1)、数据缓存
Redis 作为数据缓存层, MySQL 作为 数据存储层。应用服务器首先从 Redis 中获取数据,如果缓存层中没有,则从 MySQL 中获取后先存入缓存层再返回给应用服务器。
2)、计数器
在 Redis 中写入一个 value 为数值型的 key 作为平台计数器、视频播放计数器等。每个有效客户端访问一次,或视频每播放一次,都是直接修改 Redis 中的计数器,然后再以 异步方式 持久化 到其它数据源中,例如持久化到 MySQL 。
3)、共享 Session
对于一个分布式应用系统,如果将类似用户登录信息这样的 Session 数据保存在提供登录服务的服务器中,那么如果用户再次提交像收藏、支付等请求时可能会出现问题:在提供收藏、支付等服务的服务器中并没有该用户的 Session 数据,从而导致该用户需要重新登录。对于用户来说,这是不能接受的。
此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从 Redis 中查找相应的 Session 数据,如果存在,则再进行相关操作,否则跳转到 登录页面。这样就不会引发“重新登录”问题。
4)、限速器
现在很多平台为了 防止DoS (Denial of Service,拒绝服务) 攻击,一般都会限制一个 IP 不能在一秒内访问超过 n 次。而 Redis 可以可以结合 key 的 过期时间 与 incr 命令来完成 限速功能,充当限速器。
注意,其无法 防止 DDoS (Distributed Denial of Service ,分布式拒绝服务)攻击。( 多个ip同时访问 )
//客户端每提交一次请求,都会执行下面的代码
//指定新 ip 作为 key 的缓存过期时间为 60 秒
boolean isExists = redis.set(ip, 1, “EX 60”, “NX”); //等价于 set 192.168.192.55 1 ex 60 nx
if(isExists != null || redis.incr(ip) <= 5) {
// 通过
} else {
// 限流
}