Redis学习小总结

Redis

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。Redis 也经常用来做分布式锁。Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

优点

  • 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,适合较小数据量。
  • 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  • 较难支持在线扩容

为什么要用 Redis /为什么要用缓存

高性能:硬盘读取数据慢,操作缓存就是直接操作内存,所以速度相当快。

高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

为什么要用 Redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

Redis为什么这么快

1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。

2.数据结构简单,对数据操作也简单,

3.采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

基于内存而且使用多路复用技术,单线程速度很快,又保证了多线程的特点。

4.使用多路 I/O 复用模型,非阻塞 IO;

5.Redis 直接自己构建了 VM 机制 。

引入多线程

Redis 的多线程部分只是用来处理网络数据的读写和协议解析执行命令仍然是单线程。这么设计是不想 Redis 因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。

Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上网络 IO 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。

之前用单线程是因为基于内存速度快,而且多路复用有类似多线程的作用,也就是足够了,现在引入多线程是因为在某些操作要优化,比如删除操作,因此引入了多线程。

Redis的应用场景

计数器、缓存、消息队列(发布/订阅功能)(List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。不过最好使用 Kafka、RabbitMQ 等消息中间件。)、分布式锁实现

Redis持久化

把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制

RDB:RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。(bin目录恢复)

优点:

1、只有一个文件 dump.rdb,方便持久化。(flushall\shudown 也生成)
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点:

1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)

AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令(读操作不记录)记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。appendonly.aof

优点:

1.数据安全,每次命令都会被记录。更新频率高。通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof (--fix)工具解决数据一致性问题。

2.AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前,可以删除其中的某些命令。>64M时创建新的aof文件替代现有的,新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。通过fork一个子进程,将写命令追加到AOF重写缓存中。

缺点:

1.AOF 文件比 RDB 文件大,且恢复速度慢。**

2.数据集大的时候,比 rdb 启动效率低。

Redis持久化数据和缓存怎么做扩容?

  • 如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。

  • 如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

过期键的删除策略

我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

过期策略通常有以下三种:

定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。

Redis key的过期时间和永久有效分别怎么设置?
EXPIRE和PERSIST命令

我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

定时去清理过期的缓存;

当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

主从复制

是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

主从复制的作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:可以实现读写分离(延迟与不一致问题,数据过期),可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

建立方法:配置文件中加入:slaveof 客户端执行命令:slaveof redis-server启动命令后加入 --slaveof

【数据同步阶段】全量复制和增量复制
  1. 全量复制用于初次复制或其他无法进行增量复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。执行完毕后,将RDB文件发给从节点,从节点清楚旧数据,执行写命令更新数据。

哨兵机制

redis的哨兵机制是来实现自动化的故障恢复,不过只能实现主节点的故障切换恢复,无法对从节点实现故障恢复。进一步提高了Redis的高可用性。

原理:

主节点会被多个哨兵节点监控,当主节点发生故障宕机挂掉了,哨兵节点通过心跳检测判断其出现故障下线了(主观下线),如果判断下线的哨兵节点超过一定数量,则判断为客观下线,通过Raft算法(先到先得)来协商出一个领导哨兵节点进行故障转移操作,选出一个从节点升级为主节点,原来的主节点会变成新主节点的从节点。
缺点:哨兵无法对从节点进行自动故障转移

怎么选择从节点的?
1、先过滤掉那些不健康的从节点
2、根据优先级选择(slave-priority值越小越好,设为0则没有机会)
3、无法区分优先级,根据复制偏移量大的来(offset)
4、无法区分复制偏移量,则选择运行ID最小的

配置哨兵节点需要注意的地方?
1、配多几个哨兵节点,并且部署在不同物理机上,防止误判主节点故障
2、配置个数最好为奇数,容易‘决策’

Redis集群(cluster)

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。Redis Cluster并没有使用一致性hash,而是采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。分区的一种实现。

方案说明

1.通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
2.每份数据分片会存储在多个互为主从的多节点上
3.数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
4.同一分片多个节点间的数据不保持一致性
5.读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
6.扩容时时需要需要把旧节点的数据迁移一部分到新节点
每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

redis cluster 节点间采用 gossip 协议进行通信。

优点

1.无中心架构,支持动态扩容

2.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

缺点:只能使用0号数据库,数据迁移需要人工干预

分区:

是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

分区实现方案:客户端分区,代理分区,查询路由分区请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)

案例:设置name=p7+,失效时长100s,不存在时设置
1.1.1.1:6379> set name p7+ ex 100 nx
OK
1.1.1.1:6379> get name
"p7+"
1.1.1.1:6379> ttl name
(integer) 94
del key 解锁

什么是 RedLock
基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:安全特性:互斥访问,即永远只有一个 client 能拿到锁
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性:只要大部分 Redis 节点存活就可以正常提供服务

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  1. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力img

2.缓存空对象

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;(空白值键浪费内存)

缓存击穿

缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

  1. 设置热点数据永远不过期
  2. 加互斥锁,互斥锁SETNX

缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.一般并发量不是特别多的时候,使用最多的解决方案是加锁排队
3.给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

方法:定时刷新缓存/数据量不大,可以在项目启动的时候自动进行加载/直接写个缓存刷新页面,上线时手工操作一下

缓存降级

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。比如,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。防止发生雪崩。

Redis支持的Java客户端都有哪些?官方推荐用哪个?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

如何保证缓存与数据库双写时的数据一致性?

读请求和写请求串行化,串到一个内存队列里去。但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上请求。

为什么是删除缓存,而不是更新缓存?

缓存更新有时候要进行复杂运算,每次修改数据库,都要更新缓存,而且这个数据可能还不会频繁被访问。删除缓存,用到的时候再计算。

Redis 异步队列:使用list rpush lpop 当lpop没有消息时,可以sleep

Redis 延时队列 使用zset ZADD example score(代表时间戳) value(代表时间戳)

Redis回收进程如何工作的?

  1. 一个客户端运行了新的命令,添加了新的数据。在redis.conf 里面有个配置策略 maxmemory-policy
  2. Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  3. 一个新的命令被执行,等等。
  4. 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

回收算法LRU 选择最近最少使用的数据全部淘汰掉,剩下的就是经常访问的数据,都是热点数据。

random 随机淘汰 不淘汰,超过最大,报错

posted @ 2020-08-08 18:20  零度以外的风景  阅读(204)  评论(0)    收藏  举报