redis

官方文档也针布错http://redis.cn/documentation.html

这个博客针布错https://blog.csdn.net/qq_38261137/article/details/106949963

https://thinkwon.blog.csdn.net/article/details/103522351

不能用的命令

keys、flushdb、flushall

Redis的应用场景

总结一

计数器

可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

缓存

将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

会话缓存

可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

全页缓存(FPC)

除基本的会话token之外,Redis还提供很简便的FPC平台。以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

查找表

例如 DNS 记录就很适合使用 Redis 进行存储。查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。

消息队列(发布/订阅功能)

List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。不过最好使用 Kafka、RabbitMQ 等消息中间件。

分布式锁实现

在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

其它

Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能。

总结二

Redis相比其他缓存,有一个非常大的优势,就是支持多种数据类型。

数据类型说明string字符串,最简单的k-v存储hashhash格式,value为field和value,适合ID-Detail这样的场景。list简单的list,顺序列表,支持首位或者末尾插入数据set无序list,查找速度快,适合交集、并集、差集处理sorted set有序的set

其实,通过上面的数据类型的特性,基本就能想到合适的应用场景了。

string——适合最简单的k-v存储,类似于memcached的存储结构,短信验证码,配置信息等,就用这种类型来存储。

hash——一般key为ID或者唯一标示,value对应的就是详情了。如商品详情,个人信息详情,新闻详情等。

list——因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等。因为list是有序的,适合根据写入的时间来排序,如:最新的***,消息队列等。

set——可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:查找两个人共同的好友等。

Sorted Set——是set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。

如上所述,虽然Redis不像关系数据库那么复杂的数据结构,但是,也能适合很多场景,比一般的缓存数据结构要多。了解每种数据结构适合的业务场景,不仅有利于提升开发效率,也能有效利用Redis的性能。

过期数据的删除策略

定时 使用定时器,时间到了就删除过期的key

惰性+定期 每隔一段时间抽取一些key进行检查,然后删除;惰性的话就是用到该key时发现数据过期了就删除该key返回空。

这样的话还是有很多没有删除,这就需要内存淘汰策略。

内存淘汰策略*6 内存不够用时,会删除没有过期的

设置过期时间的key*3 最近最少未使用、最快要过期的、随机

所有key*2 随机、最近最少未使用

不淘汰,内存不足新写入的时候报错。

  1. 0 引入volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。

手写一下LRU算法实现?

继承于LinkedHashMap

通过HashMap实现LinkedHashMap

两种持久化方案

RDB AOF

RDB 每隔一段时间将数据存储到硬盘上,宕机可能会丢失部分数据;如果数据量很大保存时间过长。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期

AOF 将命令追加到日志记录中,可以选择逐条追加/每秒追加/让操作系统选择

  • redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

AOF安全,但是占的空间大,恢复也会比较慢。

redis默认使用RDB,性能最大化,fork子进程来完成写操作,让主进程继续处理命令,IO最大化,保证了Redis的高性能,数据集大的时候AOF启动效率更高。缺点是数据安全性比较低。

AOF数据安全,但是占用空间更大,恢复的时候启动效率低。

什么是事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

Redis事务

multi 命令入队 exec

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。

Redis和Memchace的区别

redis 支持复杂的数据结构

redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。

redis 原生支持集群模式。在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

性能对比。由于 redis 只使用单核,而 memcached 可以使用多核,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。

redis 的线程模型

redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。

文件事件处理器的结构包含 4 个部分:

多个 socket IO 多路复用程序 文件事件分派器 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) 多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。

哪里使用了缓存?

快递网点的一些统计信息,总包裹数量等;取件消息进入缓存,通常一个人看到取件消息后至少还要再看一次取件;

当前仓库和快递柜的包裹信息。

在抢购优惠券功能上将优惠券剩余量放到了redis中,防止超卖。

缓存和数据库双写一致性问题

只能实现最终一致性不能实现强一致性。

首先是通过设置过期时间,在过期时间内缓存的值都可能不是干净的。
然后是不设置过期时间的三种更新策略:
  1. 先写数据库,在更新缓存(最不好的)

    因为是更新缓存,存在并发问题,两个线程写数据库的顺序和删除缓存的顺序不一致会导致错误的缓存一直使用

  2. 先删除缓存在更新数据库,B的查询在A的删除缓存之后A的写入数据库之前,同样会出现很久的错误缓存

    可以采用延迟双删的策略,写入数据库1s后再次删除缓存

  3. 先更新数据库,在删缓存

    有很小的几率会产生问题,A读取缓存失效,然后读取数据库,之后卡住了,知道B写入数据库之后,写入缓存之后A再写入缓存导致旧的缓存。

每种方法都有删除缓存失败的情况使用失败重试机制,使用消息队列,将需要删除的key放入MQ中。

就有了通过订阅mysql的binlog来删缓存的操作。

只更缓存,不更MySQL,MySQL由缓存异步的更新

使用canal解析binlog Mysql通过binlog同步redis 基于zookeeper临时有序节点可以实现的分布式锁

Redis为什么快

  • 纯内存操作。
  • 核心是基于非阻塞的 IO 多路复用机制。
  • 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题

讲一下IO多路复用

image-20210414192259605

有一个线程监听socket,每来一个事件将其放入到事件队列,然后事件分派器将事件分派到不同的处理器进行处理。

https://blog.csdn.net/mashaokang1314/article/details/88636371

https://www.cnblogs.com/zwt1990/p/8821185.html

redis如何保证高可用的

哨兵模式 和主从模式

redis主从模式

一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点,同时每个从节点也可以是别的从节点的父节点,即主从节点连接形成树结构。

redis主从模式如何复制的?

http://kaito-kidd.com/2020/06/30/redis-replication/

主从结构中数据的复制是单向的,只能由主节点到从节点,所有的内存变更,即数据的增删改都只能在主节点上进行,从节点通过同步的方式完成修改。默认情况下,从节点对非Master节点客户端是只读的。

主从复制分为全量复制和增量复制+命令传播

全量复制就是将主节点生成RDB文件发给从节点,从节点清除内存中的数据并删除之前RDB,然后将接受到的RDB放入内存。(第一次复制)

增量复制:master复制的时候可能会有新的数据产生,master会把数据放入缓冲区,等slave加载RDB完成后再将这些增量数据发送给slave。

命令传播就是每处理完一个命令都会把命令广播给所有的子节点,每个子结点收到广播后会继续广播给子节点,然后各个子结点会执行这个命令

如果主从断开连接,可能会发生全量复制,也可能只是增量复制。在slave第一次全量同步的时候记录了master的runid,然后slave知道自己的offset,这时候只需要把offset发送给master,master会先检查runid是否相同,然后检查offset中的命令是否再缓冲区中,因为缓冲区是有大小的,时间就的就会被删除,如果找得到offset之后的数据就可以只发送这些命令,否则就要进行全量复制。

心跳机制

redis哨兵模式

单点模式就是一个单机

主从 1个主N个从 主机挂了之后 不能提供服务

哨兵模式 单哨兵和多哨兵 单哨兵是一个哨兵线程不断向redis服务器发送命令等待响应,没有响应表示挂掉,然后从其余的服务器选出一个主服务器;多哨兵是要所有的哨兵都认为该服务器挂掉,然后投票选举。

使用哨兵是,客户端直接连哨兵服务器

【集群模式】

多个主从节点,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容

并发竞争的问题

五个类型 的底层数据结构

字符串

https://www.cnblogs.com/reecelin/p/13352694.html

char数组+len长度+free长度

Simple Dynamic String SDS来存储

1
2
3
4
5
6
struct sdsshr<T>{
T len;//数组长度
T alloc;//数组容量
unsigned flags;//sdshdr类型
char buf[];//数组内容
}
  • int:当存储的字符串全是数字时,此时使用int方式来存储;
  • embstr:当存储的字符串长度小于44个字符时,此时使用embstr方式来存储;
  • raw:当存储的字符串长度大于44个字符时,此时使用raw方式来存储;
链表

https://www.cnblogs.com/reecelin/p/13358432.html

通过linkedListzipListquickList三种方式实现

分别是双向链表(与Java中的一样)、压缩列表、快表

zipList

通过Entry数组实现,每个Entry中存着前一个Entry的长度,编码,内容,节省了linkedList的前后指针。

quickList

qucikList是由zipList和双向链表linkedList组成的混合体。

哈希Map

https://www.cnblogs.com/reecelin/p/13362104.html

ziplist
hash

数组+链表 dictEntry **table 每一个dictentry有key,val

​ rehash hashmap中有两个table 一次使用一个,rehash的时候将一个rehash到另一个上;如果数据量特别大会渐进式哈希,一次弄不完。

压缩列表:

集合

https://www.cnblogs.com/reecelin/p/13364089.html

intset
hashmap
有序集合

https://www.cnblogs.com/reecelin/p/13368374.html

压缩列表zipList
跳表

跳表实现,解决了有序链表查询慢的问题,将查询时间降低到logN

将一些点提出来做索引,然后在从这些点中提出来做二级索引。

插入的时候采用随机的方式决定他最多要插到几层。

1
2
while (random(0,1))  
K++;

hash底层结构

https://www.jianshu.com/p/7f53f5e683cf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//哈希表的table指向的数组存放这dictEntry类型的地址。定义在dict.h/dictEntryt中
typedef struct dictEntry {//字典的节点
void *key;
union {//使用的联合体
void *val;
uint64_t u64;//这两个参数很有用
int64_t s64;
} v;
struct dictEntry *next;//指向下一个hash节点,用来解决hash键冲突(collision)
} dictEntry;

//dictType类型保存着 操作字典不同类型key和value的方法 的指针
typedef struct dictType {
unsigned int (*hashFunction)(const void *key); //计算hash值的函数
void *(*keyDup)(void *privdata, const void *key); //复制key的函数
void *(*valDup)(void *privdata, const void *obj); //复制value的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2); //比较key的函数
void (*keyDestructor)(void *privdata, void *key); //销毁key的析构函数
void (*valDestructor)(void *privdata, void *obj); //销毁val的析构函数
} dictType;

//redis中哈希表定义dict.h/dictht
typedef struct dictht { //哈希表
dictEntry **table; //存放一个数组的地址,数组存放着哈希表节点dictEntry的地址。
unsigned long size; //哈希表table的大小,初始化大小为4
unsigned long sizemask; //用于将哈希值映射到table的位置索引。它的值总是等于(size-1)。
unsigned long used; //记录哈希表已有的节点(键值对)数量。
} dictht;

//字典结构定义在dict.h/dict
typedef struct dict {
dictType *type; //指向dictType结构,dictType结构中包含自定义的函数,这些函数使得key和value能够存储任何类型的数据。
void *privdata; //私有数据,保存着dictType结构中函数的参数。
dictht ht[2]; //两张哈希表。
long rehashidx; //rehash的标记,rehashidx==-1,表示没在进行rehash
int iterators; //正在迭代的迭代器数量
} dict;

dict内部有两个dicthashtable 平常只有一个有内容,另一个的table为null,在扩容的时候,将一个重新映射到另一个上。

拉链法解决哈希冲突 ,数组中存储的是第二维链表的第一个指针

扩容时机是数组元素个数等于数组大小的时候就会扩容,但如果正在bgsave不会扩容,当entry个数为数组大小的五倍的时候就会强制扩容。而且它的扩容并不是一次完成的,可以渐进式的扩容rehash。

Redis中的Rehash机制

https://blog.csdn.net/cqk0100/article/details/80400811

你是如何查询