Redis是一中基于 key-value 对的非关系型数据库(nosql),redis 全部数据都存在内存中,因此读写速度很是快,所以 redis 被普遍应用于缓存方向。另外,redis 也常常用来作分布式锁。redis 提供了多种数据类型来支持不一样的业务场景。node
由于是纯内存操做,Redis的性能很是出色,每秒能够处理超过 10万次读写操做,是已知性能最快的Key-Value DB。面试
Redis的出色之处不只仅是性能,Redis最大的魅力是支持保存多种数据结构。redis
Redis的主要缺点是数据库容量受到物理内存的限制,不能用做海量数据的高性能读写,所以Redis适合的场景主要局限在较小数据量的高性能操做和运算上。算法
主要从“高性能”和“高并发”这两点来看待这个问题。sql
高性能:数据库
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,由于是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就能够直接从缓存中获取了。操做缓存就是直接操做内存,因此速度至关快。若是数据库中的对应数据改变的以后,同步改变缓存中相应的数据便可!segmentfault
高并发:缓存
直接操做缓存可以承受的请求是远远大于直接访问数据库的,因此咱们能够考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用通过数据库。服务器
(1) 速度快,由于数据存在内存中。相似于HashMap,HashMap的优点就是查找和操做的时间复杂度都是O(1)网络
(2) 支持丰富数据类型,支持string(字符串类型),list(列表类型),set(集合类型),sorted set(有序集合类型),hash(散列类型)
(3) 支持事务,操做都是原子性,所谓的原子性就是对数据的更改要么所有执行,要么所有不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过时时间,过时后将会自动删除
缓存分为本地缓存和分布式缓存。以 Java 为例,
使用自带的 map 或者 guava 实现的是本地缓存,最主要的特色是轻量以及快速,生命周期随着 jvm 的销毁而结束,而且在多实例的状况下,每一个实例都须要各自保存一份缓存,缓存不具备一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的状况下,各实例共用一份缓存数据,缓存具备一致性。缺点是须要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
(1) redis支持更丰富的数据类型(支持更复杂的应用场景):Redis支持Srting、list,set,zset,hash等数据结构的存储。memcache只支持字符串String数据类型。
(2)Redis支持数据的持久化,能够将内存中的数据保持在磁盘中,重启的时候能够再次加载进行使用;而Memecache把数据所有存在内存之中。
(3)Redis使用单线程的多路 IO 复用模型;Memcached是多线程、非阻塞IO复用的网络模型。(redis的速度比memcached快不少)
(4)集群模式:memcached没有原生的集群模式,须要依靠客户端来实现往集群中分片写入数据;但redis 目前是原生支持 cluster 模式的。
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
(Redis 数据都放在内存中。若是机器挂掉,内存的数据就不存在。因此须要作持久化,将内存中的数据保存在磁盘,下一次启动的时候就能够恢复数据到内存中。)
Redis 提供了两种持久化方式:RDB(默认) 和AOF 。
Redis能够经过建立快照来 得到存储在内存里面的数据在某个时间点上的副本。Redis建立快照以后,能够对快照进行备份,能够将快照复制到其余服务器从而建立具备相同数据的服务器副本(Redis主从结构,主要用来提升Redis性能),还能够将快照留在原地以便重启服务器的时候使用。
快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)以后,若是至少有1个key发生变化,Redis就会自动触发BGSAVE命令建立快照。
save 300 10 #在300秒(5分钟)以后,若是至少有10个key发生变化,Redis就会自动触发BGSAVE命令建立快照。
save 60 10000 #在60秒(1分钟)以后,若是至少有10000个key发生变化,Redis就会自动触发BGSAVE命令建立快照。
与快照持久化相比,AOF持久化的实时性更好,所以已成为主流的持久化方案。默认状况下Redis没有开启AOF(append only file)方式的持久化,能够经过appendonly参数开启:appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是经过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不一样的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重下降Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操做系统决定什么时候进行同步
为了兼顾数据和写入性能,用户能够考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。并且这样即便出现系统崩溃,用户最多只会丢失一秒以内产生的数据。当硬盘忙于执行写入操做的时候,Redis还会优雅的放慢本身的速度以便适应硬盘的最大写入速度。
- RDB (快照):快照形式 ,按期将当前时刻的数据保存磁盘中。会产生一个dump.rdb文件
- 特色:性能较好,数据备份。但可能会存在数据丢失。
- AOF(只追加文件) :append only file (全部对redis的操做命令记录在aof文件中),恢复数据,从新执行一遍便可。
- 特色:每秒保存,数据比较完整。但耗费性能。
【注】若是两个都配了优先加载AOF。(同时开启两个持久化方案,则按照 AOF的持久化放案恢复数据。)
Redis中有个设置时间过时的功能,即对存储在 redis 数据库中的值能够设置一个过时时间。做为一个缓存数据库,这是很是实用的。如咱们通常项目中的 token 或者一些登陆信息,尤为是短信验证码都是有时间限制的,按照传统的数据库处理方式,通常都是本身判断过时,这样无疑会严重影响项目性能。
咱们 set key 的时候,均可以给一个过时时间(expire time),经过过时时间咱们能够指定这个 key 能够存活的时间。
按期删除+惰性删除。
可是仅仅经过设置过时时间仍是有问题的。咱们想一下:若是按期删除漏掉了不少过时 key,而后你也没及时去查,也就没走惰性删除,此时会怎么样?若是大量过时key堆积在内存里,致使redis内存块耗尽了。怎么解决这个问题呢? redis 内存淘汰机制(数据淘汰策略)。
问题:MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
redis内存数据集大小上升到必定大小的时候,就会施行数据淘汰策略。
redis 提供 6种数据淘汰策略:
在Redis当中,有生存期的key被称为volatile。在建立缓存时,要为给定的key设置生存期,当key过时的时候(生存期为0),它可能会被删除。
更新生存时间:能够对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间
最大缓存配置:在 redis 中,容许用户设置最大使用内存大小 server.maxmemory,默认为0,没有指定最大缓存,若是有新的数据添加,超过最大内存,则会使redis崩溃,因此必定要设置。redis 内存数据集大小上升到必定大小的时候,就会实行数据淘汰策略。
优势:
缺点:
优势:
缺点:
在这个图中,每个蓝色的圈都表明着一个redis的服务器节点。它们任何两个节点之间都是相互连通的。客户端能够与任何一个节点相链接,而后就能够访问集群中的任何一个节点。对其进行存取和其余操做。
通常集群建议搭建三主三从架构,三主提供服务,三从提供备份功能。
每个节点都存有这个集群全部主节点以及从节点的信息。
它们之间经过互相的ping-pong判断是否节点能够链接上。若是有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,而后去链接它的备用节点。若是某个节点和全部从节点所有挂掉,咱们集群就进入faill状态。还有就是若是有一半以上的主节点宕机,那么咱们集群一样进入faill状态。这就是咱们的redis的投票机制,具体原理以下图所示:
优势:
缺点:
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用一种叫作哈希槽 (hash slot)
的方式来分配数据的。redis cluster 默认分配了 16384 个slot,当须要在 Redis 集群中放置一个 key-value 时,redis先对key使用crc16算法算出一个结果,而后把结果对16384求余数,这样每一个key都会对应一个编号在 0-16383之间的哈希槽,redis会根据节点数量大体均等的将哈希槽映射到不一样的节点。
Redis 集群会把数据存在一个 master节点,而后在这个 master 和其对应的salve之间进行数据同步。当读取数据时,也根据一致性哈希算法到对应的 master 节点获取数据。只有当一个master 挂掉以后,才会启动一个对应的 salve 节点,充当 master 。
须要注意的是:必需要3个或以上
的主节点,不然在建立集群时会失败,而且当存活的主节点数小于总节点数的一半时,整个集群就没法提供服务了。
缓存穿透:通常缓存系统都是按照key去查询,若是不存在对应的value再去数据库中查找。一些恶意的请求会故意查询不存在的key,请求量很大,会对数据库形成很大的压力。
解决:
1)接口层增长校验,如对id作基础校验,id<=0的直接拦截。
2)从缓存中取不到的数据在数据库中也没有取到,这时能够将key-value对携程key-null,缓存有效时间设置短点。这样能够防止攻击用户反复用同一个id暴力攻击。
缓存击穿:指缓存中没有但数据库中有的数据(通常是缓存时间到期),这时因为并发用户特别多,同时缓存没读到数据,就同时去数据库去取数据,引发数据库压力瞬间增大。
解决:
1)设置热点数据永不过时。
2)加互斥锁。缓存中若是没数据,就首先去获取锁,获取锁成功后(if(relock.tryLock()))后再去数据库取数据,而后更新缓存数据。其余并行进入的线程等待100ms再从新去缓存取数据。
缓存雪崩:指缓存中大批量数据到过时时间,而查询数量巨大,引发数据库压力过大甚至宕机。 与缓存击穿不一样的是,缓存击穿指并发查同一条数据,缓存血崩是不一样数据。
解决:
1)对查询结果为空的数据也进行缓存(key-value写成key-null),而后将它的缓存时间设置短一点,这样能够防止攻击用户反复用一个id暴力攻击。
2)对key进行过滤。将全部可能存在的数据哈希到一个足够大的bitmap中,必定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
3)缓存失效后,经过加锁或者队列来控制读数据库写缓存的线程数量。好比对某个key只容许一个线程查询数据和写缓存,其余线程等待。
所谓 Redis 的并发竞争Key 的问题也就是:多个系统同时对一个 key 进行操做,可是最后执行的顺序和咱们指望的顺序不一样,这样也就致使告终果的不一样!
推荐一种方案:分布式锁(zookeeper 和 redis 均可以实现分布式锁)。(若是不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点能够实现的分布式锁。大体思想为:每一个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个惟一的瞬时有序节点。 判断是否获取锁的方式很简单,只须要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除便可。同时,其能够避免服务宕机致使的锁没法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,固然是从以可靠性为主。因此首推Zookeeper。
采用以下策略:
为何不先删除缓存再更新数据库?
若是有两个并发操做:一个更新、一个查询。更新操做删除删除缓存后,查询操做没有从redis中查到数据,就从数据库中取,而此时数据库中仍是老数据。查询操做从数据库中取得老数据后写入缓存中,而后更新操做更新了数据库。这样数据库与缓存中数据不一致,缓存中仍是老数据。
先更新数据库再删除缓存就没问题吗?
仍是有的,例如一个读操做没有从缓存中读到数据就去数据库里读,此时忽然来了一个写操做,它先更新了数据库又删除了缓存,以后那个读操做再把老数据放进去,这样这样数据库与缓存中数据不一致,缓存中仍是老数据。
解决:能够为缓存设置过时时间。
为何删除缓存,而不是把更新的数据写入缓存里?
若是不删除缓存而将更新的数据写入缓存,这么作引起的问题是,若是A,B两个线程同时作数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。
(1)若是集群任意master挂掉,且当前master没有slave,集群进入fail状态。
(2)若是集群超过半数以上master挂掉,不管是否有slave集群,都会进入fail状态.multi:开启事务
Redis提供了一个 multi 命令开启事务,exec 命令提交事务,在它们之间的命令是在一个事务内的,能保证原子性。Redis在事务没提交以前不会执行事务中的命令,会等到事务提交的那一刻再执行事务中的全部命令。
multi 开始到 exec结束前,中间全部的命令都被加入到一个命令队列中;当执行 exec命令后,将queue中全部的命令执行。
Redis执行命令的错误主要分为两种:
Redis事务老是具备原子性、一致性和隔离性,当 Redis 运行在某种特定的持久化模式下(appendfsync always)时,事务也具备持久性。
Redis为了达到最快的读写速度将数据都读到内存中,并经过异步的方式将数据写入磁盘。因此redis具备快速和数据持久化的特征。若是不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存愈来愈便宜的今天,redis将会愈来愈受欢迎。
若是设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
参考 redis面试题