Redis全面讲解

Redis全面讲解


说起redis(Remote Dictionary Server),大家想到的就是key-value关系型数据库,具有高性能,高可用性,支持多种数据结构等优势。整个数据库的操作都是需要在内存中完成,所以redis每秒可以处理超过 10万次读写操作,此外redis提供了丰富的数据结构。下面将会从一下几个方面系统的介绍redis,希望能带给大家帮助。

  • redis的数据类型
  • redis的应用
  • redis的数据安全和性能保障
  • 高性能、高可用

一. redis的数据结构

  redis支持五种数据结构,包括字符串,列表,散列(hash),集合,有序集合。

1.1 字符串

  1. 字符串的底层数据结构是简单动态字符串(simple dynamic string)SDS。

在这里插入图片描述

  2. 相比较C的字符串,SDS的优点如下:

C字符串SDS
获取字符串长度的复杂度为O(N)获取字符串长度的复杂度为O(1)
API 是不安全的,可能会造成缓冲区溢出API 是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配修改字符串长度N次最多执行N次内存重分配

  3.字符串的API主要如下
在这里插入图片描述

  4.字符串主要应用在:单值缓存,对象转换成Json字符串的缓存。

1.2 列表

  1.列表的优点在于,它可以包含多个字符串,使得用户的数据可以集中在同一个地方。redis使用压缩列表(ZIPLIST)和双端链表(LINKEDLIST)实现列表对象,根据存储的内容决定使用哪种数据结构。(顺序保存

  • 双端无环链表:带头尾指针,带链表长度计数器,链表可以保存不同的数据类型具有多态。
    在这里插入图片描述

  • 压缩列表: 是一种节约内存的顺序型数据结构,当一个列表键只包含少量列表项,每个列表项只是最小整数值或较短的字符串,就会使用压缩列表。
    在这里插入图片描述

  2. 主要API:
在这里插入图片描述

   **4. 列表主要应用:**可以利用列表实现简单的数据类型来满足实际的需要,例如:stack(LPUSH+LPOP), Queue(LPUSH+RPOP), Blocking MQ(LPUSH,BRPOP列表中没有元素则阻塞)。

1.3 散列

  1. 散列可以让用户将多个健值对存储到一个redis健里面,适用于将一些相关的数据存储在一起(保存对象)。底层使用压缩列表,字典实现的哈希对象。

  • 字典:字典使用哈希表作为底层实现,一个哈希表有多个哈希表节点,每个节点保存字典的key-value值
    在这里插入图片描述

  2.主要的API如下

在这里插入图片描述

  **3.散列主要应用:**将其看作关系型数据库保存对象类型的数据

1.4 集合

  1. 以无序的方式存储多个各自不相同的元素,可以利用多个集合之间的交集,并集,差集等运算。整数集合是集合健的底层实现之一,当一个集合只包含整数值元素,并且这个集合元素数量不多时,redis就会使用整数集合作为集合健的底层实现。(编码类型有int16和int64)
数据结构如下:
在这里插入图片描述

在这里插入图片描述

  2. set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

  3. 集合主要API如下:
在这里插入图片描述

  4. 实际中应用:在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能。

1.5 有序集合

   1. zset是在set集合的基础上的一个集合,结合了集合的并交差的计算,又拥有排序的性质。
   2. zset的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

   3. 主要API
在这里插入图片描述


二. redis应用

  我们用redis主要在访问速度上拥有显著优势外,其本身支持的多种数据类型也非常有用,能覆盖系统开发中的很多应用场景。

2.1 缓存

  • 必须保证不同对象的 key 不会重复。
  • 选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。
  • 缓存内容与数据库的一致性。

2.2 消息队列

  1. Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。
  2. 如果需要实现带有优先级的消息队列也可以选择sorted set,或者设置多个list,标注优先级。
  3. 而pub/sub功能也可以用作发布者 / 订阅者模型的消息(没用过,消息可能丢失),重启会丢失消息

2.3 时间轴(Timeline)

  list作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴(有序)。当用户发完微博后,都通过lpush将它存放在一个list中,之后便可以通过lrange取出当前最新的微博。

2.4 排行榜/计数器

  1. 使用sorted set和一个计算热度的算法便可以轻松打造一个热度排行榜,zrevrangebyscore可以得到以分数倒序排列的序列,zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。
  2. string、hash和sorted set都提供了incr方法用于原子性的自增操作。

2.5 分布式锁

  1. setnx(set if not exists)如果这个操作返回false,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回true,则说明得了锁,便可以继续进行操作,并且在操作后通过del命令释放掉锁。

  2. 并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。

  3. 如果在setnx和 expire之间服务器突然挂掉, 就会导致expire永远执行不到, 造成死锁, 解决办法, 指令setnx和expire组合在一起原子指令
    在这里插入图片描述

  4. 思考, 如果在加锁和解锁之前的逻辑执行的时间超出了设置的过期时间, 就会出问题, 解决办法, set指令的value参数设置一个随机数, 释放锁的时候先匹配随机数是否一致, 然后再删除key,
    在这里插入图片描述

  5. 锁冲突,加锁失败
    解决一:直接抛出异常,通知用户稍后重试
    解决二:sleep 一会稍后重试,注:此刻线程会一直占用
    解决三:将请求转移到延时队列,过一会重试(见下)

2.5 延时队列

平时开发中大部分情况用kafka作为消息中间件,客户端使用难度大,然而我们大部分情况下使用的只是保存消费的队列,Redis的消息队列不是专业的消息中间件,没有ack的保证,如果对消息的可靠性要求十分高时,redis显然不适合。

  1. 异步消息队列
    在这里插入图片描述

Redis的list常用异步消息队列,使用rpush/lpush入队列,rpop/lpop来出队列。但是,如果队列为空的时,不停的pop造成浪费生命的空轮询。
解决一:sleep一会
解决二:blpop/brpop 阻塞读,阻塞读在队列没有数据的时候,会立刻进去休眠状态,一旦数据来的时候,立即醒过来,消息的延迟几乎为零。但是如果线程一直阻塞,服务器一般会自动断开,blpop/brpop就会抛出异常
2. 延时队列的实现
可以用个redis的zset来实现,消息序列化value,到期时间作为score,多个线程轮询的获取当前到期的任务

def delay(msg):
    msg.id = str(uuid.uuid4()) # 保证 value 值唯一 value = json.dumps(msg)
    retry_ts = time.time() + 5 # 5 秒后重试 redis.zadd("delay-queue", retry_ts, value)
def loop(): 
    while True:
        # 最多取 1 条
        values = redis.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1) 
        if not values:
            time.sleep(1) # 延时队列空的,休息 1s
        continue
    value = values[0] # 拿第一条,也只有一条
    success = redis.zrem("delay-queue", value) # 从消息队列中移除该消息
    if success: # 因为有多进程并发的可能,最终只会有一个进程可以抢到消息
        msg = json.loads(value) 
        handle_msg(msg)

2.6 HyperLogLog(统计去重)

  redis提供HyperLogLog数据结构来解决统计问题,HyperLogLog提供了不精确的去重统计方案,HyperLogLog提供了pfadd 和 pfcount,根据字面意义很好理解,一个是增加 计数,一个是获取计数。pfadd 用法和 set 集合的 sadd 是一样的,来一个用户 ID,就将用 户 ID 塞进去就是。pfcount 和 scard 用法是一样的,直接获取计数值。HyperLogLog的底层算法不了解。

2.7 布隆过滤器(不精确的set结构)

  案例,新闻推荐,每次推荐时需要去重,如果,历史记录存储到关系型数据库里,去重需要频繁的对数据库进行exists查询,访问量大的时候数据库当然压力大。但是如果把把历史数据全部缓存起来,会浪费大量的存储空间,解决方法布隆过滤器。布隆过滤器有二个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在, 原理不知道

2.8扩展

  1. 使用redis3.2以后增加了地理位置 GEO 模块来实现摩拜单车「附近的 Mobike」、美团和饿了么「附近的餐馆」这样的功能了
  2. Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。该模块也使用了漏斗算法,并 提供了原子的限流指令。
  3. Redis 2.8之后增加了scan指令,在海量的数据遍历

三. redis 数据安全与性能保障

  Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能。

3.1 数据持久化

  持久化让用户的数据存储到硬盘上,防止系统故障而将数据备份呆远程的地方,或者保存最终计算结果。redis提供了两种不同的持久化方法将数据保存到磁盘上。分别是快照(存在于某一时刻的所有数据都写到磁盘里,指定时间间隔),另外一种是文件追加(执行命令时,将别执行的命令复制在磁盘上)

3.1.1 快照持久化(RDB)

  • 向Redis发送一个BGSAVE的命令来创建一个快照。(fork一个子进程,用来处理将快照写入到磁盘中,父进程则继续处理命令请求)
  • 向Redis发送一个SAVE命令来创建一个快照。在创建快照完毕之前不再响应其他命令(不常用,一般在没有内存的情况下才使用)
  • 在新的快照文件创建完毕之前,如果系统,redis或者硬件中任何一个崩溃,将丢失最近一次生成快照之后更改的所有数据,因此,快照持久化只适用于即使丢失一部分数据也不会造成问题的应用程序。

3.1.2 AOF持久化

  AOF 持久化会将被执行的写命令写到AOF文件的末尾,以此来记录数据发生的变化。因为redis只要从头到尾重新执行一次AOF文件所包含的写命令,就可以恢复AOF文件所记录的数据集。

AOF同步的频率

  • always:性能差,数据不会丢失
  • everysec:不影响性能,数据只会丢1s内的
  • no:数据会丢失

  除了对数据进行持久化,用户还必须对持久化文件进行备份,最好是备份到不同的地方或者不同的服务器上,尽量避免数据丢失事故的发生。
  通过AOF持久化或者快照持久化,用户可以在系统重启或者崩溃的情况下仍保留数据。随着负载量的上升,或者数据完整性变的越来越重要时,用户可能需要使用复制特性。

3.2 复制

  复制可以让其它服务器拥有一个不断更新的数据副本,,从而使得拥有数据副本的服务器可以用于处理客户端的读请求。redis同样也采用主服务器(master)向多个从服务器发送更新,并使用从服务器(slave)来处理所有读请求。
具体如何复制此处省略。。。。。。

  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内。
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…(这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以 立刻启用Slave1做Master,其他不变。)
  • redis sentinel 可以监视指定的redis主服务器及下属从服务器,并在主服务器下线时自动的进行故障转移。

3.3 redis 事务

  redis的事务很特殊,将一组操作放在队列中,批量的执行,若中间执行错了,也不会滚。(假事务)以命令MULTI为开始,之后跟着用户传入多个命令,最后以EXEC结束。但是由于这种简单的事务在EXEC命令被调用之前不会执行任何实际操作,所以用户将没办法根据读取到的数据决定。除了要使用MULIT命令和EXEC命令外,还需要配合使用WATCH命令。
  watch 会在事务开始之前盯住 1 个或多个关键变量,当事务执行时,也就是服务器收到 了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后,是否被 修改了 (包括当前事务所在的客户端)。如果关键变量被人动过了,exec 指令就会返回 null

  • 关系型数据库会对被访问的数据加锁,直到事务提交或者回滚为止。如果有其它的客户端试图对被加锁的数据进行写入,那么改客户端会被阻塞,直到事务执行完成为止。那么持有锁的客户端运行越慢,等待解锁的客户端被阻塞的时间就越长。(悲观锁
  • redis为了减少客户端的等待时间,并不会在执行WATCH命令时对数据进行枷锁。但是,redis会在数据被其它客户端抢先修改的情况下,通知执行WATCH命令的客户端(乐观锁

在实际应用中,很少利用redis事务来确保数据的一致性,主要利用redis的事务来提高性能。一次性发送多个命令,然后等待所有回复,这样可以减少客户端与redis服务器之间的网络通信请求的次数来提升性能。


四 redis高性能

Redis的处理速度之快的主要原因如下:

  1. 线程IO模型,REDIS 单线程
  2. RESP(Redis Serialization Protocol)通信协议(文本协议实现简单,解析性能好,所以很多开源的项目使用RESP作为通信协议)
  3. 内存的读取
  4. 小对象利用压缩的数据结构,zipList

4.1 线程IO模型

redis是单线程的,但是redis如何处理那么多并发客户端的连接呢?->select系列的事件轮询API,非阻塞IO
实际问题,10000个客户端需要连上一个服务器并保持TCP连接,客户端会不定时的发送给服务器。

  • 使用一个线程监听,当新的客户端发起连接时,建立连接并new一个子线程来处理

在这里插入图片描述

缺点:当客户端的请求数量多,服务器的线程数就过多,服务器,cpu的性能会断崖式的下降

  • 使用一个线程监听,当新的客户端发起连接时,建立连接并用线程池来解决
    在这里插入图片描述

服务端的处理能力受限于线程池的线程数影响,如果客户端的连接大部分是处于空闲的状态的化,线程池中的线程资源会别浪费掉。

  • 非阻塞

上面的主要问题是一个线程处理一个客户端的连接。因此解决方案,一个线程处理多个客户端的连接,该线程伦询每个客户端的连接,如果某个连接需要处理则交给内核处理。
在这里插入图片描述

缺点:内核态到用户态不断的切换,导致资源性能不断的消耗

  • 多路复用IO之select

多个需要处理的连接打包交个内核

在这里插入图片描述

缺点:打包的大小受限,最大不能超过1024

  • 多路复用IO之poll
    poll 跟 select 相似对其进行了部分优化,比如单进程能打开的文件描述符不受限制,底层是采用的链表实现
  • 多路复用 IO 终极 epoll

五. 高可用性

Redis的高可用技术主要包括持久化(RDB,AOF)、复制、哨兵和集群。

  • 持久化 持久化是最简单的高可用方法,主要作用是数据备份,即数据存储到硬盘上,保证数据不会丢失
  • 复制 高可用的基础,哨兵、集群都是再复制基础上出现的。复制主要实现了数据的多机备份以及读写操作的负载均衡和简单的故障恢复。
  • 哨兵 选取leader
  • 集群

下图展示了redis的主从复制模式和哨兵(sentinel)高可用架构
在这里插入图片描述

redis 支持三种集群方案:1.主从复制;2.sentinel模式;3.Cluster模式。下面将一一介绍。

##5.1 主从复制
Redis主从复制,将主节点的数据同步给从节点,因此从节点起到了两个作用。1:如果主节点宕机,从节点代替主节点立即顶上去。2:扩展主节点的读能力,分担主节点的压力。

主要的缺点:

  1. 需要人工干预,一旦主节点宕机,从节点晋升为主节点,需要通知应用方修改的主节点地址(不具备自动容错和恢复功能)
  2. 主节点的写能力、存储能力受到单机的限制

5.2 Redis Sentinel的主要功能(节点与节点之间)

在这里插入图片描述

  • 监控:不断的检查主从服务器是否正常允许
  • 通知:某个服务器异常时Sentinel通过API脚本向管理员或者其他应用程序发送消息
  • 自动故障转移:当主节点异常,自动选取主节点转移
  • 配置提供者:Redis Sentinel 模式下,客户端应用 在初始化时连接的是 Sentinel 节点集合,从中获取 主节点 的信息。
    缺点:较难支持在线扩容

5.3 集群(Cluster)

5.3.1 客户端分区方案

客户端已经决定数据存储再哪个redis节点,主要是采用hash算法将Redis数据的key映射到不同的redis上
在这里插入图片描述

缺点:客户端无法动态的增删服务节点。

5.3.2 代理分区方案

在这里插入图片描述

代理分区主要的方案:Twemproxy 和 Codis
缺点:增加了一层Proxy,加重了架构的复杂度和性能损耗

5.3.3 查询路由方案

在这里插入图片描述

优点:无中心节点,可以平滑的进行节点的扩容/缩容,支持高可用和自动故障转移,成本低
缺点:不如中心节点zooKeeper处理即使

5.3.4 理论(一致性哈希分区)

在这里插入图片描述

参考:

  1. https://segmentfault.com/a/1190000020211283?utm_source=tag-newest
  2. https://segmentfault.com/a/1190000011370510
  3. https://blog.csdn.net/u014590757/article/details/79860766
  4. https://juejin.cn/post/6844903670572646413
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值