赞
踩
redis有String、List、Set、Hash、zSet这五种数据结构。
String是redis中最基本的一种数据结构,redis中String的底层实现是SDS(simple dynamic string),SDS与普通C语言字符串的区别:
List是元素的集合,其底层由ziplist(压缩列表)或linkedList(双向链表)实现。当每个节点的大小小于64字节,就用ziplist,如果超过,直接升级为linkedList。
hash保存的是键值对,无序。其底层由ziplist或hashtable实现。当每个键和值的长度小于64字节,那就用ziplist,反之则升级为hashtable。
一个redis的字典中保存有一个长度为2的hash表数组,其中一个用来保存键值对,另一个只有扩容时使用。
redis的hash表的关键字段:
table:用于存储键值对
size:表示哈希表的数组大小
used:表示哈希表中已经存储的键值对的个数
sizemask:大小永远为 size - 1,该属性用于计算哈希值
解决hash冲突:链地址法
计算负载因子:load_factor = ht[0].used / ht[0].size
Redis 哈希表不仅提供了扩容还提供了收缩机制,扩容与收缩都是通过 rehash 完成的。与 HashMap一样,Redis 中的哈希表想要执行 rehash 扩容操作也是需要一定条件的,主要为以下 2 个:
服务器目前没有执行BGREWRITEAOF 或者 BGSAVE 命令,切哈希表的负载因子大于等于 1 ;
服务器目前正在执行 BGSAVE 或者BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于5,因为redis开启子进程,使用cow进行写入,如果cow途中rehash,那会导致父进程重新开辟一块儿内存空间来rehash,浪费内存空间;
下面是收缩 rehash 的条件: 哈希表的负载因子小于 0.1 时, 程序自动开始对哈希表执行收缩操作字典的rehashidx用于保存当前的rehash进度
如果一次性完成rehash,那么会消耗太久的时间,redis是单进程单线程的,会造成服务器被阻塞,暂时不可用。
set保存不重复的元素,无序;底层由intset或hashtable结合实现。
sortedset同时会由两种数据结构支持,ziplist和skiplist.
只有同时满足如下条件是,使用的是ziplist,其他时候则是使用skiplist:
有序集合保存的元素数量小于128个
有序集合保存的所有元素的长度小于64字节。
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速;
- Redis 设计了一套自己的数据结构;
- 采用单线程来负责存取、交互,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路 I/O 复用模型,非阻塞 IO;
redis内存数据数据集大小升到一定大的时候,就会实行数据淘汰策略(回收策略)。
volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;
allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;
volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放;
volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
noeviction:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。
有RDB和AOF两种持久化机制:
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件。
有两种方式:SAVE和BGSAVE
SAVE:服务器进程会忙于持久化,而不能处理来自客户端的请求。
下面谈谈BGSAVE:
优点:
使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能 ;
数据紧凑、适合做备份
缺点:
一是持久化的过程很耗时;
二是RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候;
AOF是将更新、删除、添加这些命令写入一个文件当中,恢复数据就从这个文件逐行读出命令并执行即可。
AOF持久化的三种方式:
always:每当有命令被执行,都要添加到AOF文件
everysec:每秒写一次(每秒将缓存区中的命令写入文件)
no:由操作系统决定何时写入文件
因为 AOF 的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF 文件的体积也会变得越来越大。
重写的实现:对于数据库中全部的key,获得其值,并用一条命令来记录这对键值对,来代替之前记录这对键值对的多对键值对。
优点:
有多种持久化策略供选择,出现故障时,丢失的数据量少;
AOF文件过大时,可以重写AOF文件;
缺点:
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
性能:memcache使用多核、redis使用单核。多核可以充分利用闲置cpu;
数据类型:memcache仅支持简单kv键值对,redis支持多种数据结构;redis还可以做一些简单的逻辑运算。
可靠性:memcache不支持持久化,如果突然断电,那么内存中的数据是不可复原的,而redis支持AOF和RDB两种持久化;
限制:Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB。
一种是在访问key的时候,检查该key是否已过期,过期则删除;
另一种是定期检查key,在所有key里面随机抽取一些key来检查,若过期则删除。
缓存穿透:
用户不断请求redis中没有且数据库中也没有的数据(例如id =
-1)。因为数据库中也没有该条数据,那么查询那些不存在的数据是不会写入缓存的,所以每次都会重新区数据库查。流量大的话,数据库就挂了。
解决方法:
1、接口校验。在正常业务流程中可能会存在少量访问不存在 key
的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。2、缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。
3、布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。
缓存击穿:
某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
解决方案:
设置热点数据永不过期,然后由定时任务去异步加载数据,更新缓存;
加互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。最理想的互斥锁是根据key的值来加锁。
缓存雪崩:
大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。
解决方案:
1、过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
2、热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
3、加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。
只在master上写,然后同步到slave上,slave负责读。这种读写分离模型只适用于读多写少的场景。
缺点:如果写很多的话,同步数据的压力会很大。因为每个节点都保存的是全部的数据。
数据分片模型可以解决读写分离模型的痛点:每个节点只保存数据的子集,每个节点都是一个master。减小了单个节点的存储压力,也能充分利用每个节点的存储能力和计算能力。
避免了线程切换的资源消耗
单线程不存在资源共享与竞争,不用考虑锁的问题
基于内存的,内存的读写速度非常快
使用非阻塞的 IO 多路复用机制
数据存储进行了压缩优化
使用了高性能数据结构,如 Hash、跳表等
Redis 通过同步(sync)和指令传播(command propagate)两个操作完成同步
同步(sync):将从节点的数据库状态更新至与主节点的数据库状态一致。
步骤:
从节点向主节点发送 SYNC 指令;
收到 SYNC 指令,主节点执行 BGSAVE 指令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从现在开始执行的所有写指令;
主节点 BGSAVE 指令执行后,会将生成的 RDB 文件发送给从节点;
从节点接收、载入 RDB 文件,将数据库状态更新至主节点执行 BGSAVE 指令时的数据库状态;
从节点加载完 RDB 文件,通知主节点将记录在缓冲区里面的所有写指令发送给从节点,从节点执行这些写指令,将数据库状态更新至主节点当前数据库状态。
指令传播(command propagate):主节点数据被修改,会主动向从节点发送执行的写指令,从节点执行之后,两个节点数据状态又保持一致
当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这时候可以用哨兵模式。
哨兵是一个独立的进程,作为进程,它会独立运行。
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵挂了怎么办?答:哨兵也弄个集群。
Redis存储在内存中的数据升到配置大小时,就进行数据淘汰
使用 allkeys-lru 策略,从数据集(server.db[i].dict)中挑选最近最少使用的数据优先淘汰,即可满足保存热点数据
使用 keys 指令可以查找指定模式的 key 列表
如果在线上使用,keys 指令会导致线程阻塞,直到执行结束。可以 使用 scan 指令,无阻塞的提取出指定模式的 key 列表,但会有一定的重复概率,需要在客户端做一次去重,整体耗时比直接用 keys 指令长
点击查看
cluster集群的master选举机制:
点击查看
重点:
epoll_create(int size)//size是对大小的建议,并非真实大小
epoll_create:创建一个epoll文件描述符;创建eventpoll实例,其中包含红黑树cache和双向链表。
int epollctl(int epfd, int op, int fd, struct epollevent *event);
epollctl:可以对红黑树进行三种操作:增删改;如果是将新的fd加入红黑树,那么还会为该fd注册回调函数;op操作类型,用三个宏EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD,来分别表示增删改对fd的监听。
int epollwait(int epfd, struct epollevent *events, int maxevents, int timeout);
epollwait:读取就绪队列
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。