目录
九:MySQL里有2000w数据,redis中只存20w的数据,如何 保证redis中的数据都是热点数据
(1)select, poll, epoll 都是I/O多路复用的具体的实现
1.关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
优点:
(1)易于维护:都是使用表结构,结构一致
(2)使用方便:SQL语言使得我们操作数据库更加方便
(3)支持SQL:可以通过SQL语句进行复杂查询
缺点
(1)读写性能比较差,尤其是海量数据的高效率读写
(2)固定的表的结构,灵活性差
(3)硬盘I/O要求高:网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈
2.非关系型数据库不是数据库,而是数据结构化存储方法的集合,可以是文档或键值对等
优点
(1)格式灵活:存储数据格式可以是key,value格式,文档格式,图片格式等等,使用灵活,应用场景广泛,而关系型数据库只支持基础的数据类型
(2)速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
(3)高扩展性;Nosql基于键值对,数据之间没有耦合性,所以非常容易水平扩展
缺点
(1)不支持事务的ACID特性
(2)不提供SQL支持,学习和使用成本较高
(3)只适合存储一些较为简单的数据,需要进行较复杂查询的数据,关系型数据库显的更为合适
Redis是用C语言编写的,开源的高性能非关系型数据库(Nosql),是一个高性能的key--value数据库;
Redis可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值得类型可以为字符串,List列表,Set集合,Sorted set有序集合,Hash散列表。
与传统数据库不同的是Redis的数据库是存储与内存中的,所以读写速度非常快,所以Redis被广泛应用与缓存方向,每秒可以处理超过10w次读写操作,是已执性能最快的Key--value DB;
此外,Redis也经常用来做分布式锁。而且Redis还支持事务,持久化,LUA脚本,LRU驱动事件,多种集群方案。
优点:
(1)读写性能优异
(2)支持数据持久化---AOF持久化和RDB持久化
(3)支持事务,Redis的所有操作都是原子性的
(4)数据结构丰富,除了支持String类型之外,还支持hash,set,zset,list等数据结构
(5)支持主从复制,主机会自动将数据同步到从机,可进行读写分离
缺点
(1)数据库容量容易受到物理内存的限制,不能用作高性能海量数据读写,因此Redis主要适用于较小数据量的高性能操作和运算上
(2)Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写需求失败,需要等待机器重启或者手动切换前端的IP才能恢复
(3)主机宕机,宕机前有部分数据未能同步到从机,切换IP后会引入数据不一致的问题
(4)Redis较难支持在线扩容,在集群容量达到上限时,扩容会变得很复杂;所有运维人员在系统上线时必须确保足够的空间,这对资源造成很大的浪费
RDB持久化可以在指定时间间隔内生成数据集的时间点快照
在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。
以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次 数据集: # save 60 1000 //关闭RDB只需要将所有的save保存策略注释掉即可
AOF持久化是记录服务器执行的所有读写操作命令,并在服务器启动时,通过执行这些命令来还原数据集。AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾;
你可以通过修改配置文件来打开 AOF 功能:
1 # appendonly yes
你可以配置 Redis 多久才将数据 fsync 到磁盘一次。 有三个选项:
1 appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
2 appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
3 appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
Redis还可以在后台对AOF文件进行重写,使AOF文件的体积不会超出保存数据集状态所需的实际大小,。
Redis还可以同时使用RDB和AOF持久化。在这种情况下,当Redis重启时,他会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。
主要从高性能和高并发这两个角度来看待问题
高性能:
假如用户第一次访问数据库中的某些数据,这个过程时比较慢的,因为使用硬盘上直接读取的。将该用户访问的数据存储在缓存中,这样下次再次访问这些数据的时候,就可以直接去缓存中拿了。操作缓存就是直接操作内存,所以速度特别快。如果数据库中的数据改变后,同步改变缓存中相对应得数据即可。
高并发:
直接操作缓存所能承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户中的一部分请求会直接到缓存中,而不是数据库。
(1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存 中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1);
(2)数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的
(3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者 多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁 操作,没有因为可能出现死锁而导致的性能消耗;
(4)使用多路 I/O 复用模型,非阻塞 IO
Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分 的使用要求
常用命令 :set/get/decr/incr/mget等;
应用场景 :String是最常用的一种数据类型,普通的key/value存储都可以归为此类;
实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。
常用命令 :lpush/rpush/lpop/rpop/lrange等;
应用场景 :Redis list的应用场景 非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;
实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
常用命令 :sadd/spop/smembers/sunion等;
应用场景 :Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;
实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
常用命令 :zadd/zrange/zrem/zcard等;
应用场景 :Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
常用命令 :hget/hset/hgetall等
应用场景 :我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;
实现方式:Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。如图所示,Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时redisObject的encoding字段为int。
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过 期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种
(1)定时过期:每个过期时间的Key都需要一个定时器,到过期期间就会立即清楚。该策略可以立即清楚过期的key,对内存很友好;但是会占用大量CPU去处理过期的数据,从而影响缓存的响应时间和吞吐量。
(2)惰性过期:只有当访问一个key时,才会去判断这个Key是否已经过期,过期则清除;该策略可以最大化的节省CPU资源,但对内存十分不友好,极端情况可能出现大量的 过期key没有再次被访问,从而不会被清除,占用大量内存
(3)定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数 量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定 时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达 到最优的平衡效果。
Redis内存数据集大小上升到一定大小时,就会实施数据淘汰策略
no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用 的key。 (这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中, 移除最近最少使用的key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空 间中,随机移除某个key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除将要过期的key,ttl的值越大优先被移除。
Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于 处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。
Redis是单线程的;通常说的单线程,主要是指Redis对外提供的键值存储服务的主要流程是单线程,也就是网络I/O和数据读写是由单个线程来完成的。
除此之外Redis的其他功能,例如持久化,异步删除,集群数据同步等,是由额外线程来执行的。
因此,严格来说Redis并不是全面单线程。
首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流。
之所以有这三个鬼存在,其实是他们出现是有先后顺序的。
I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。
一、select 被实现以后,很快就暴露出了很多问题。
二、于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如
但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。
于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.
三、epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
可是epoll 有个致命的缺点,只有linux支持。
虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。
IO多路复用模型使用了Reactor设计模式实现了这一机制。
通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。
注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的 Redis会将一个事务中的所有命令序列化,然后按顺序执行。
(1)redis不支持回滚:““Redis 在事务失败时不进行回滚,而是继续执行 余下的命令”
(2)如果一个事务的命令出现错误,那么所有的命令都不会再执行
(3)如果在一个事务中出现运行错误,那么正确的命令会被执行
Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服 务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务 也具有耐久性。
Redis事务支持隔离性吗
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可 以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有 隔离性的。
Redis事务保证原子性吗,支持回滚吗
Redis中单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务 中任意命令执行失败,其余的命令仍会被执行。
Redis事务其他实现
基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行, 其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是 会继续运行完
基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时 先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC 命令给master请求复制数据。
master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期 间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。
当持久化进行完 毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后 再加载到内存中。
然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多 个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave。
数据部分复制
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支 持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
有以下功能:
(1)集群监控:负责监控 redis master 和 slave 进程是否正常工作。
(2)消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给 管理员。 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
(3)配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
(4)哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去 运行,互相协同工作。 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可 用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
哨兵的核心知识
(1)哨兵至少需要 3 个实例,来保证自己的健壮性。
(2)哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群 的高可用性。
(3)对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进 行充足的测试和演练。
理 解
重在穿透
吧,也就是访问透过redis直接经过mysql,通常是一个不存在的key
,在数据库查询为null
。每次请求落在数据库、并且高并发。数据库扛不住会挂掉。
解决方案
可以将查到的null设成该key的缓存对象。
当然,也可以根据明显错误的key在逻辑层就就行验证
。
同时,你也可以分析用户行为,是否为故意请求或者爬虫、攻击者。针对用户访问做限制。
其他等等,比如采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层 存储系统的查询压力
是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库 上,造成数据库短时间内承受大量请求而崩掉。
解决方案
通常的解决方案是将key的过期时间后面加上一个随机数
,让key均匀的失效。
考虑用队列或者锁让程序执行在压力范围之内,当然这种方案可能会影响并发量。
热点数据可以考虑不失效
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,好像蛮力击穿一样。
击穿和穿透不同,穿透的意思是想法绕过
redis去使得数据库崩掉。而击穿你可以理解为正面刚
击穿,这种通常为大量并发对一个key进行大规模的读写操作。这个key在缓存失效期间大量请求数据库,对数据库造成太大压力使得数据库崩掉。就比如
在秒杀场景下10000块钱的mac和100块的mac这个100块的那个订单肯定会被抢到爆,不断的请求(当然具体秒杀有自己处理方式这里只是举个例子)。所以缓存击穿就是针对某个常用key大量请求导致数据库崩溃。
解决方法
可以使用互斥锁避免大量请求同时落到db。
可以将缓存设置永不过期(适合部分情况)