【Redis】Redis是什么?为什么这么快?
- 完全基于内存,将数据存储在内存中,读取的时候不需要进行磁盘的 IO
reids的瓶颈是内存以及网络,之前单线程实现是因为单线程实现简单 - 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU
这是历史遗留问题,以前服务器的配置低,单线程实现简单而且效果差不多,现在服务器的配置普遍提升,redis已经开始做多线程,性能提升了数倍 - 高效的数据结构,Redis 是用 C 语言开发完成的,但在 Redis 字符串中,并没有使用 C 语言中的字符串,而是用一种称为 SDS(Simple Dynamic String)的结构体来保存字符串
struct sdshdr{
int len;//已使用的字符串的长度
int free;//未使用的长度
char buf[];//具体数据,存储的字符数组
}
SDS 与 C 字符串的区别
①常数时间内获得字符串长度
C 字符串本身不记录长度信息,每次获取长度信息都需要遍历整个字符串,复杂度为 O(n);C 字符串遍历时遇到 ‘\0’ 时结束。
SDS 中 len 字段保存着字符串的长度,所以总能在常数时间内获取字符串长度,复杂度是 O(1)。
②避免缓冲区溢出
假设在内存中有两个紧挨着的两个字符串,s1=“xxxxx”和 s2=“yyyyy”。
由于在内存上紧紧相连,当我们对 s1 进行扩充的时候,将 s1=“xxxxxzzzzz”后,由于没有进行相应的内存重新分配,导致 s1 把 s2 覆盖掉,导致 s2 被莫名其妙的修改。
但 SDS 的 API 对 zfc 修改时首先会根据free检查空间是否足够,若不充足则会分配新空间,避免了缓冲区溢出问题。
③减少字符串修改时带来的内存重新分配的次
在 C 中,当我们频繁的对一个字符串进行修改(append 或 trim)操作的时候,需要频繁的进行内存重新分配的操作,十分影响性能。
如果不小心忘记,有可能会导致内存溢出或内存泄漏,对于 Redis 来说,本身就会很频繁的修改字符串,所以使用 C 字符串并不合适。而 SDS 实现了空间预分配和惰性空间释放两种优化策略:
**空间预分配:**当 SDS 的 API 对一个 SDS 修改后,并且对 SDS 空间扩充时,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间。
分配规则如下:如果对 SDS 修改后,len 的长度小于 1M,那么程序将分配和 len 相同长度的未使用空间。举个例子,如果 len=10,重新分配后,buf 的实际长度会变为 10(已使用空间)+10(额外空间)+1(空字符)=21。如果对 SDS 修改后 len 长度大于 1M,那么程序将分配 1M 的未使用空间。
**惰性空间释放:**当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。
④二进制安全
在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。
二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 ‘\0’,在 C 中遇到 ‘\0’ 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性。
在 Redis 中,常用的 5 种数据结构和应用场景如下:
- String: 缓存、计数器、分布式锁等。
- List: 链表、队列、微博关注人时间轴列表等。
- Hash: 用户信息、Hash 表等。
- Set: 去重、赞、踩、共同好友等。
- Zset: 访问量排行榜、点击量排行榜等。
Redis持久化
RDB 将某个时间点的所有数据都存放到硬盘上
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长
AOF 将写命令添加到 AOF 文件(Append Only File)的末尾
随着服务器写请求的增多,AOF 文件会越来越大
缓存穿透,缓存雪崩以及缓存击穿
缓存穿透:就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。
- 接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
- 缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿:就是一个很热门的数据,突然失效,大量请求到服务器数据库中
最好的办法就是设置热点数据永不过期
缓存雪崩:就是大量数据同一时间失效。
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。