Redis学习笔记
1、什么是Redis
Redis是开源的,性能极高的nosql数据库,key-value数据库,并且提供有多种语言的的API的非关系型数据库
- Redis读写速度快,读110000次/s,写81000次/s
- Redis支持数据持久化,可以将内存中的数据持久化到磁盘中,重启时候可以直接读取使用
- Redis支持备份,即master-slave模式的数据备份
- 丰富的数据类型:string、hash、set、list、zset
- Redis的所有操作都是原子性的,要你成功,要么失败不执行
- 还有丰富的特性:Redis支持publish/subscribe,通知,key过期
优势
- 性能极高,读写速度快
- 原子性-所有的操作都是原子性的
- 多个操作也支持事务,即原子性,通过MULTLI和EXEC指令包起来
- 丰富的特性
Redis与其它key-value存储有什么不同
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其它数据库的进化路径。
Redis运行在内存中,但是可以持久化到磁盘,所以在对不同数据集进行高速读写时候,需要权衡内存,因为数据量不能大雨硬盘内存。在内存数据库方面的另一个优点就是,相比在磁盘上香甜的复杂数据结构,在内存中操作起来更加简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
Redis是单进程单线程的?
Redis是单进程单线程的,redis利用队列技术将并发变为串行访问,消除了传统数据库串行控制的开销。
Redis的持久化机制是什么?各自的优缺点?
Reids提供有两种持久化机制RDB(默认)和AOF机制
RBD:是Redis DataBase缩写快照
RDB是redis默认持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件未dump.rbd。通过配置文件中的save参数来定义快照周期。
优点:
- 只有一个文件dump.rdb,方便持久化
- 容灾性好,一个文件可以保存到安全的磁盘。
- 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以IO最大化。使用单独子进程来进行持久化,主进程不会有任何IO操作,保证了redis的高性能
- 相对于数据集大时候,比AOF的启动效率更高
缺点:
- 数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
- AOF(Append-only file)持久化方式:是指所有的命令行记录以redis命令请求协议的格式完成持久化存储保存为aof文件。
AOF:持久化
AOF持久化(即Append-Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启redis会重新将持久化的日志文件回复数据。
当两种方式同时开启时候,数据恢复Redis会优先选择AOF恢复。
优点:
- 数据安全,aof持久化可以配置appendfsunc属性,有always,每进行一次命令操作就记录到aof文件中一次。
- 通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题
- AOF机制的rewrite模式。AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中某些命令(比如误操作的flushall)
缺点:
- AOF文件比RDB文件大,且恢复速度慢
- 数据集大的时候,比rdb启动效率低
Redis常见性能问题和解决方案:
-
master最好不要做任何持久化工作,如rdb内存快照和aof日志文件
-
如果数据比较重要,某个slave开启AOF备份数据,策略设置为每秒同步一次
-
为了主从复杂的速度和连接的稳定性,master和slave最好在同一个局域网内
-
避免在压力很大的主库上增加从库
-
主从复制不要用图状结构,用单向链表结构更为稳定,即:master->Slave1->Slave2->Slave3
这样的结构方面解决单电故障问题,实现Slave对master的替换。如果master挂了,可以离家启用Slave做master,其它的不变。
使用过Redis的分布式锁吗,它时怎么实现的?
先拿setnx来争抢锁,抢到之后,在用expire给锁加一个过期时间防止忘记了释放。
什么是缓存雪崩?
缓存雪崩我们可以简单理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库cpu和内存造成了巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
当缓存服务器重启或者大量缓存集中在某一个时间段实效,这样在实效的时候,会给后端带来很大压力。导致系统崩溃。
解决方案:
大多数系统设计者考虑用加锁(最多的解决方案)或者队列方式保证不会有大量的线程对数据库一次性进行读写,从而避免实效时大量的并发请求落到低层存储系统上。还有一个简单的方案就是将缓存失效时间分散开。
如何避免?
- 在缓存失效后,通过加锁或者队列来控制数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待
- 做二级缓存,A1为原始缓存,A2位拷贝缓存,A1失效时候,可以访问A2,A1缓存时间设置短期,A2设置长期
- 不同的key,设置不同的过期时间,让缓存失效时间点尽量均匀
什么是缓存穿透?
缓存穿透是指用户查询数据,在数据库没有,自然缓存中也不会有。每次都要去数据库再查询一遍,然后返回空,这样请求就绕过缓存直接查询数据库,如果恶意请求量很大,就会对后端系统造成很大压力。这就叫做缓存穿透。
解决方法:
- 最常见的则是采用隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免对底层存储系统的查询压力。
- 也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管实数据不存在还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库,这种方法最简单粗暴。
Redis缓存击穿(热点key)
缓存中的一个key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对着干key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据病回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:对缓存查询加锁,如果key不存在,就加锁,然后查DB入缓存,然后解锁;其它进行如果发现有锁就等待,然后等待解锁后返回数据或进入DB查询。
什么是缓存预热?
缓存预热是比较常见的概念,相信很多小伙伴都可以很容易理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,线查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。
解决思路:
- 直接写个缓存刷新页面,上线时候手动操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
什么是缓存降级?
当访问量剧增、服务出现问题(响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如购物车、结算)。
以参考日志级别设置预案:
- 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送警告。
- 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级
- 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略。例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
缓存热点数据和缓存冷数据
热点数据缓存才有意义有价值
对于冷数据而言,大部分数据可能害没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存,对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景。
对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效,那就没有太大的价值了。
Redis为什么是单线程的?
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存大小或者网络带宽。几人单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章的采用单线程方案了。
Redis利用队列技术将兵法访问变为串行访问
-
绝大部份请求都是存粹的内存操作(非常的快)
-
采用了单线程,避免了不必要的上下文切换和竞争条件
-
非阻塞IO优点:
- 速度快,因为数据存在内存中,类似于HashMap,hashMap的优势就是插座和操作的时间复杂度都是O(1)
- 支持丰富的数据类型,支持string、list、set、sorted set 、hash
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改,要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
同时有多个子系统去set一个key,这个时候要注意什么嗯?不推荐使用redis的事务机制。因为我们的生存环境,基本都是redis集群环境,做了数据分片操作。你一个事务中涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
- 如果对这个key操作,不要求顺序:准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可
- 如果对这个key操作,要求顺序:分布式锁加时间戳。假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推
- 利用队列,将set方法变成串行访问也可以在redis遇到高并发时候保证读写key的一致性。