Advertisement

Redis相关知识

阅读量:

目录

一、数据类型及使用场景

(2)QuickList

3、Hash

4、set

5、zset

二、Redis

1、redis线程模型

2、redis事务

4、持久化机制

三、部署方案


一、数据类型及使用场景

redis是kv存储系统,key为字符串类型,值为redis对象。

由5种常用数据类型:String,List,Hash,Set,ZSet。

1、String

底层编码格式:

  • int:在数据存储为64位长整型时,默认情况下会将其转换为双精度浮点数并进行存储;如果遇到浮点型数据,则Redis会将其转换为字符串形式以避免精度丢失。
  • 简单动态字符串表示用于表示非固定长度的数据块。

底层避免了对C字符串类型的直接引用。其主要区别体现在以下三点:第一点:该方案具有二进制安全特性,在运行期间不会因\0而终止,并且能够存储图像数据和序列化对象;第二点:结构中包含表示字符串长度的字段,在计算长度时时间复杂度维持在O(1)水平;第三点:当内存空间不足时会自动扩展内存容量,在内存不足的情况下将自动扩展机制细化为两种情况:当空间大小小于1M时每次扩展一倍;当空间大小达到或超过1M时则每次扩展增加1M的空间容量。此外该方案还实现了惰性内存管理机制:每当修改数据导致存储长度减少时系统会将超出需求的空间大小记录到free字段中;只有在内存不足的情况下才会优先利用预留的多余空间进行填充以减少内存分配开销。

2、List

在处理小规模数据时采用ziplist结构以降低内存占用;当ziplist节点数量达到或超过512个、单个节点大小达到或超过64字节时,则会转而采用QuikList结构。

(1)ziplist

img

ziplist将各个节点存储在一个连续的内存块中,并记录了每个节点的前一个节点大小以及当前节点的大小信息。不采用双链表形式的原因在于:(1)单链表中的前驱和后继指针占用额外的内存空间,在数据量较小时,实际数据所占内存空间反而不如指针占用的空间少;(2)每个单链表中的节点单独分配内存可能导致内存碎片问题;(3)根据局域性原理,在连续内存布局下更容易提高CPU缓存利用率。

操作时间复杂度:

  • 在队列操作中,“在队列操作中”替换原句中的“头尾压入/弹出操作”,并调整语序使其更加清晰。
    • “获取队列首尾元素的时间复杂度”替换原句中的“查询头尾元素时间复杂度”,使表述更加详细。
    • “中间位置的元素查找需要”替换原句中的“对于中间元素查找”,并通过增加细节描述使句子更加完整。
    • “独立地保留了其存储内容的长度信息”替换原句中的“都有单独的字段保存该数据长度”,使表述更加准确。
    • “获取长度时的时间复杂度为”替换原句中的“因此获取长度是”,通过增加逻辑连接词使句子更加连贯。

ziplist连锁更新:

每个节点均包含一个字段prevlen用于表示上一节点占用的空间大小。这些字段属于变长编码格式,在数据量较小的情况下采用单字节编码,在数据量较大的情况下则采用五字节编码方式来优化空间利用效率。因此,在插入新的节点到当前两个相邻节点之间时…

(2)QuickList

QuickList是由Ziplist元素构成的一个双向链表结构,在每个节点中采用Ziplist来存储数据项。

3、Hash

较小规模的数据采用ziplist进行存储。当且仅当ziplist中的节点数量达到512个以上或单个节点的数据量达到64字节以上时,则采用字典(dict)进行存储。之前已经介绍了ziplist的基本概念和操作方法;而本节将详细阐述字典实现的具体方式。

复制代码
 typedf struct dict{

    
     // 两张hash表 
    
     dictht ht[2];
    
     // rehash索引,字典没有进行rehash时,此值为-1
    
     int rehashidx;
    
     ...
    
     
    
 }dict;

在字典中定义了两个哈希表,在数据量增长过程中发生碰撞现象。为了处理这种冲突情况,在存储结构上采用数组与链表结合的方式进行处理。随着数据量的持续增长,在达到特定阈值时启动扩容机制以提高存储效率;而当系统负载控制在低于0.1的比例时则会实施收缩策略以优化资源使用情况

在扩容或缩容的过程中(即容量发生变化时),由于哈希表大小的变化使得存储的位置既是哈希值除以长度的结果(即单位容量),因此会导致发生重哈希(rehash)操作。在dict中采用渐进式的方式进行重哈希:每当执行操作时(即每进行一次操作),会将第一张表中对应位置的键值从该表重新计算哈希并迁移到另一张表上。直到所有键值都被迁移完成为止,在此期间重哈希过程完成,并将重哈希索引设为-1。

  • 在查询过程中首先访问第一个哈希表。如果当前正执行再哈希操作,则转而查询另一张哈希表;
    • 当发生再哈希操作时,在插入数据时不使用第一张哈希表;
    • 在删除操作中移除第一张哈希表中的元素。

优缺点:通过单次处理较小的rehash任务避免一次性处理过大的计算量而导致Redis内存溢出,并且通过每次操作单独处理较小的rehash任务来降低资源消耗;同时保持两个数据表以确保完整性,在正常情况下能够有效管理内存占用规模较大但当机器运行至满载状态时进行rehash可能导致大量原始键数据丢失

4、set

在数据均为整数的前提下(即其数量少于512),采用intset进行存储可显著减少内存占用;反之,则采用哈希表(hashtable)进行存储。

整数集合intset

基于数组实现的整数集合按顺序且不重复地存储整形数据;插入和查找操作的时间复杂度均为O(n)。

5、zset

在较小规模的数据处理中,我们主要采用ziplist结构。如果单个ziplist节点的数量超过128或单个节点的大小超过64字节时,则以skiplist跳表作为存储结构。

跳表:

二、Redis

1、redis线程模型

redis采用单线程Reactor架构,并通过支持select、epoll的多路复用机制监听并捕获读写相关事件。在完成所有读写事件的收集后,系统会定期在内存中处理相应的读写请求。

为什么这么快?

  • 单线程模型减少了上下文切换、同步机制以及互斥锁的开销,并且更有效地提升了CPU缓存利用率;
  • 纯内存操作中所有读写都在内存完成而不涉及磁盘访问。在Redis 6.0版本中,默认情况下CPU并非瓶颈,在处理网络I/O时采用了多线程策略;
  • 高效的数据结构包括字符串集合(String)、列表(List)、哈希表(Hash)、集合(Set)和有序集合(ZSet)。

redis应用场景有哪些?

  • 缓存热点数据,减轻数据库压力;
  • 分布式锁
  • 分布式限流器

2、redis事务

(1)使用方式:

通过调用MULTI函数启动一个事务;后续的所有操作会被自动加入队列序列中执行;为了提交当前操作至数据库可以采用EXEC指令;如果需要则可以选择丢弃当前队列并终止事务

在组队阶段会对命令的语法进行检查,在出现语法错误时则会触发报错信息;当遇到业务逻辑问题(如程序逻辑错误)时,在组队阶段不会立即报错;而是在提交后才可能出现错误情况,并且在此时发生时也不会影响到其他命令的执行。

特点:

  • Redis采用了单线程技术设计,在其事务队列中,默认情况下各条指令将严格按照顺序执行而不会受到其他事务的干扰,在这种情况下确保每条指令都能被执行并最终实现隔离。
  • 在Redis的事务处理机制中,在一条指令发生错误的情况下,并不影响其余指令的正常处理;这一特点表明Redis并未实现严格的原子性特征;因此无法实现有效的回滚机制。
  • 数据持久性的保障方面需要注意的是:
    • 当采用RDB模式时,在完成当前交易后切换至下一个RDB快照之前出现程序崩溃的情况会导致整个交易无法保存数据;
    • 若选择AOF模式,在其配置参数设置为no或eveerysec时将导致数据在某些特定情况下出现丢失问题;而当参数设置为always时虽然能够保证数据持久性但会导致性能显著下降。

(2)lua脚本

lua脚本拥有隔离性但没有原子性,并不能实现回滚。Redis的所有命令都是原子性的这一特性是因为Redis程序本身是单线程的,在任何时刻都不会受到其他操作的影响。采用eval命令进行操作的Lua脚本所具有的特性是,在发生错误时其之前的运算不会被回滚所影响。

3、watch命令实现乐观锁

该命令可对一个或多个键进行监控,在任何一个键发生更改时将导致后续的所有事务均无法继续执行,并从而实现乐观锁机制

复制代码
 WATCH version;

    
 set version 1;
    
 MULTI;
    
 lpush mylist 123;
    
 ...
    
 set version 2;
    
 EXEC;
    
 //如果version等于2说明事务执行了,其他事务不会执行,否则说明当前事务执行失败了

4、持久化机制

RDB:其默认操作流程是,在设定的时间周期内定期生成内存数据的镜像并存储于存储设备上。这些镜像文件会被存储在dump目录下的rdb文件中。当系统重启时,程序会从存储设备中的dump.rdb文件读取并加载相应数据回至内存中。

采用自动与手动两种模式来完成rdb持久化。在使用save命令设置周期后,在定期同步rdb文件时需注意其操作期间可能会导致客户端请求被阻塞。bgsave命令则采用非阻塞模式并启动一个子进程来生成快照。

优缺点:

  • 占用了较少的空间,并且rdb数据仅仅是内存中的快照副本;
    • 运行速度更快,在重启过程中无需逐个执行命令。
    • 运行效率较高,并且确保了主进程不受子进程同步影响。
    • 发生故障时可能导致的数据丢失较多,在bgsave执行过程中涉及fork操作属于GIL(格林锁)操作,在频繁使用时计算成本显著提高。

(2)AOF(append only file)机制采用日志记录方式记录Redis的写操作,在系统重启时会读取并重新执行aof文件中的命令来恢复数据。在配置中启用appendonly开关为yes设置后,Redis每当执行一次write操作时,都会将该操作记录至内存中的aof buffer缓存区,随后根据特定策略将这些缓冲区中的操作追加至磁盘上的aof文件中。具体来说,该机制包括三种类型:1) everysec策略每隔一秒进行同步;2) no策略则由槽组系统自主决定何时进行同步;3) always策略则在每次发布一条消息后立即同步。

当aof文件大小大于阈值时会自动重写aof,对同一key的操作进行合并。

如果数据关键且能容忍几分钟的数据丢失(例如缓存等场景),则建议仅使用RDB即可。
如果是作为内存数据存储,则需要启用Redis的持久化功能,并建议同时开启RDB和AOF。
如果仅启用AOF,则推荐采用everysec的平衡性配置参数设置为1024和60秒。

三、部署方案

1、主从模式

单点负责管理请求,并将数据发送至从节点;所有读取操作由从节点完成以实现水平扩容。一旦主节点故障需由人工干预完成其职责。该架构的效率较低且实用性有限。

主从模式不具备故障自动切换的能力,在主节点发生故障时必须手动指定新的为主节点,并且会导致主机服务在该时段处于不可用状态。此外,在这一时间段内进行的数据变更也无法实现同步更新。

2、哨兵模式:

  • 每个哨兵通过周期性的1秒间隔向主节点、从节点以及所有其他哨兵节点发送PING请求。
  • 若在预设时间内未收到实例的有效响应,则该实例将被标记为主观下线状态。
  • 当被标记为主观下线的主节点存在时,哨兵将向其发送确认命令以核实其真实在线状态。
  • 一旦超过半数的哨兵一致确认主节点已处于下线状态,则该主节点将被标记为客观下线状态。
  • 采用投票机制进行选举后,在此次选举中胜出的哨兵将担任新的上任哨兵角色,并负责监督其他相关工作流程和资源分配情况。

3、Redis Cluster

采用主动模式(含哨兵模式)时,在分布式系统架构中存在以下局限性:单一 master 节点仅支持单点更新功能并不具备扩增能力,并且由于每台实例均存储完整的数据集合导致内存消耗较高且数据恢复速度较慢。Redis 集群采用分区技术将全局数据划分为多个独立的分区单元,在这种架构下集群内部分为 master 和 secondary nodes 两类角色。其中 master 节点负责读取与 writes 操作而 secondary nodes 从不参与读取或 writes 但会作为集群的备份存储单元存在。为了维持集群内各个分区的一致性 master 节点会定期与 secondary nodes 进行异步复制机制同步操作从而保障系统的整体稳定性

Redis集群采用哈希槽机制实现数据分区而非基于一致性的哈希方案。其内部配置了16384个哈希槽,并且键的位置由hash(key)取模16384确定。

四、常见问题:

1、过期键删除策略:

该系统采用自动失效机制,在获取键值时若检测到键已失效,则会将其移除以避免内存泄漏。

2、定期清除 。定时执行移除操作,在每次操作时会依次检查所有数据库(DB),从每个DB中随机抽取20个键(keys)进行检查。如果有超过期的键会被移除;如果其中如果有5个键已超期,则会继续对当前DB进行处理。否则,则开始处理下一个DB。

Redis存在最大内存限制。可以通过maxmemory参数设定最大可用内存。当实际占用的内存量超过设定的最大值时需要执行内存回收。在执行回收操作时会依据指定的淘汰策略来清理被释放的对象。

2、

全部评论 (0)

还没有任何评论哟~