redis复习总结

我的redis

1. redis集群

主从集群哨兵集群】:主从集群是指中,存在一个master节点和多个slave节点。master节点负责接收客户端的读写,slave节点负责读操作。主节点一旦接收到数据的变更,就会将数据同步至slave节点。

但这样的模式下,如果master节点下线,集群不会自动选取主节点。因此redis提供了哨兵机制,用于监控redis集群。如果哨兵发现master节点下线,则会自行从slave节点中选出新的主节点。同样,哨兵也可以组成集群,用于哨兵间的相互监督。

在这里插入图片描述

但是主从集群无法解决动态扩容问题,因此就有了redis cluster

redis cluster:redis cluster实现了redis数据的分布式存储。每个节点存储不同的数据,实现数据分片的功能。

redis cluster提供了slot槽,每个节点都会分配一个slot槽。但存储数据时,redis会根据key进行计算,得到一个slot值。根据slot值从不同的redis节点中查询或存储数据。

对于每个节点来说,节点本身也可以实现主从复制的模式。
在这里插入图片描述

二者区别

  1. 哨兵集群实现了主从复制,实现读写分离;而cluster集群的slave节点只是个冷备节点,当master挂了才会进行读写操作
  2. 哨兵集群没法动态扩容;cluster集群通过slot槽实现数据分片的功能,能够动态扩容
  3. 从集群方式来看,哨兵是一主多从,而cluster是多主多从

2. redis内存淘汰策略

  1. Random算法

  2. TTL算法

  3. LRU算法:Least Recently Used,最近最少使用。redis会维护一个候选池,池中数据会根据时间进行排序。每次抽取5个key,存入候选池中。当候选池满了之后,会将访问时间最大的key删除。

    在这里插入图片描述

  4. LFU算法:Least Frequently Used,最近最小频率使用。LFU算法是一个二维的双向链表,一个是维护访问频率,另一个是维护相同访问频率的不同key。当key被访问后,会改变它的访问频数,移动该节点。通过维护访问频次来实现低频使用key的数据淘汰。但使用频率也有缺点,比如一个key一开始访问频率高,但后续访问频率低,这样的话就没办法很好的淘汰这个key。因此,LFU算法也会参考key的上次访问时间,来标记key是否为热点数据。

在这里插入图片描述

3. redis 6.0 多线程

redis多线程不是多指令的多线程,而是对网络io的多线程。对于redis来说,性能瓶颈主要集中在网络,cpu,内存,而网络这方面是最值得优化的地方。以前redis是使用一个线程处理socket连接都是一个线程处理,现在采用多线程的方式加快网络处理速度。

4. redis 主从复制原理

redis主从复制提供了两种方案,分别是全量复制,增量复制

全量复制主要原理如下:slave节点连接master节点,发送同步请求。master节点会执行BGSAVE生成数据快照。然后将数据发送给slave节点。master节点同时还会缓存同步期间变更的数据,再于slave节点进行同步。

在这里插入图片描述

增量复制:master节点会将变更的数据同步给slave节点。增量复制是通过维护offset这样一个偏移量来实现数据的同步。

5. redis缓存穿透

客户端请求的数据,数据库不存在。redis始终不会生效

  • 缓存空对象
    • 请求为null,直接缓存空对象。但可能会缓存大量垃圾数据。所以需要设置TTL。并且可能存在短期不一致性。
  • 布隆过滤器
    • 在客户端请求redis前,增加过滤层。

6. 缓存雪崩

短时间内,大量缓存key同时失效,或者redis宕机,导致请求直接到达数据库

  • 给TTL添加随机值
  • 添加多级缓存

7. 缓存击穿

短期内,热点key失效,导致大量请求访问数据库

【防止多个线程同时查询数据库,重构redis】

  • 互斥锁
    • 当一个线程更新redis时,加锁防止其余线程同时更新redis
  • 逻辑过期
    • 当监测发现redis的key过期时,对redis加锁。同时开启新的线程,同步redis数据。然后返回旧的过期数据。只有当开启的线程结束,redis锁才会被释放。再次期间内,其余请求均获取锁失败,返回旧数据。

在这里插入图片描述

8. 秒杀存在的问题

  • 超买超卖
    • CAS解决:
      • 执行逻辑:查询当前库存,假设为x。执行update减少库存时,判断库存是否任然为x。如果是,则表明没有被其它线程干扰,执行sql;否则执行失败
      • 存在问题:假设同一时间有100个线程查询库存,且皆为x。此时只有一个线程能够成功扣减库存。此时库存为x - 1。而其它99个线程发现库存已经不为x,那么剩余99个线程均执行失败。
  • 一人一单:
    • 查询资格
      • 库存是否足够
      • 是否购买过
    • 下单
      • 查询资格 + 下单应为原子操作。也就是说,两部操作需要加锁
  • 秒杀优化
    • 数据库异步下单

    • redis扣减库存 + redis下单

    • 在这里插入图片描述

    • 在这里插入图片描述

9.intSet集合底层源码

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;
  • encoding:编码格式,用于确定存储数据的编码格式
  • length:标识存储数据个数
  • contents:存储数据地址(指针)

添加元素

/* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    /* 判断插入数据的编码类型, 如果过大, 需要重新修改is的encoding */
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;

    /* Upgrade encoding if necessary. If we need to upgrade, we know that
     * this value should be either appended (if > 0) or prepended (if < 0),
     * because it lies outside the range of existing values. */
    // 如果当前插入元素的编码大于is的现有编码, 进行数据编码升级
    if (valenc > intrev32ifbe(is->encoding)) {
        /* This always succeeds, so we don't need to curry *success. */
        return intsetUpgradeAndAdd(is,value);
    } else {
        /* Abort if the value is already present in the set.
         * This call will populate "pos" with the right position to insert
         * the value when it cannot be found. */
        // 二分查找, 确定新元素因该插入的位置, 同时保证元素的唯一性
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }
		// 扩容
        is = intsetResize(is,intrev32ifbe(is->length)+1);
        // 移动pos+1后面元素
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }

    _intsetSet(is,pos,value);
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

is数据结构升级

/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    uint8_t curenc = intrev32ifbe(is->encoding);
    uint8_t newenc = _intsetValueEncoding(value);
    int length = intrev32ifbe(is->length);
    int prepend = value < 0 ? 1 : 0;

    /* First set new encoding and resize */
    // 设置新的编码
    is->encoding = intrev32ifbe(newenc);
    // 数组扩容
    is = intsetResize(is,intrev32ifbe(is->length)+1);

    /* Upgrade back-to-front so we don't overwrite values.
     * Note that the "prepend" variable is used to make sure we have an empty
     * space at either the beginning or the end of the intset. */
    // 倒序重拍元素
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    /* Set the value at the beginning or the end. */
    // 赋值新元素
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    // 重置长度
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

10. dict结构底层源码

struct dict {
    dictType *type;
	// 数组, 存储hash表
    dictEntry **ht_table[2];
    unsigned long ht_used[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
};

struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    // 解决hash冲突
    struct dictEntry *next;     /* Next entry in the same hash bucket. */
};

hash扩容时机

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
 	// 如果正在rehash, 则直接返回
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (!dictTypeExpandAllowed(d))
        return DICT_OK;
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht_used[0] / DICTHT_SIZE(d->ht_size_exp[0]) > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht_used[0] + 1);
    }
    return DICT_OK;
}
  • rehash:
    • 已经在rehash
    • ht_used[0] >= 1 << ht_size_exp[0] 【存储的节点个数 >= hash数组长度】
    • ht_used[0] / (1 << ht_size_exp[0]) > 5 【必须rehash】
  • 不进行rehash
    • size == 0
    • dict_can_resize 不能进行rehash

rehash操作: 延迟rehash

申请新的hash表,将其赋值给dict的另外一个hash表用于存储。未来数据的增删改查,需要在旧hash,新hash中判断。如果数据在旧hash表中,则需要进行数据迁移。直到rehash结束。rehash动作完成后,更换新旧hash表在dict中存储位置。0号hash表为新表,1号hash表置为null

11.zipList结构底层源码

zipList:是一块特殊的双端列表。原有的使用链表方式创建的双端链表需要存储pre,next指针,需要占用大量的内存,而zipList则不用存储指针。zipList向内存申请一块连续的空间,通过对内存空间字节数划分的方式来确定每个元素所在位置

在这里插入图片描述

12.quickList结构

为了解决zipList存在的如下问题:

  • 需要连续空间,内存占用较多
  • 数据量大,超出ZipList上限
    在这里插入图片描述

quickList,采用数据分片的一种思想。通过双向链表,将多个zipList连接到一起,通过这样的方式实现长数据存储,内存连续的问题。此外,quickList多用于链表双端的操作。因此中间的zipList可以采用算法进行数据压缩,进一步减少内存占用。

13.skipList

普通的链表,遍历时指针每次只能移动一位,时间复杂度是O(N)。如果遍历的时候,能够增加指针跳跃的跨度,就能提高遍历的速度。跳表所采取的解决方案是,向上建立索引结构。约上层的索引,指针遍历跨度越大。这其实是一种二分的思想,每次跨度跳跃当前区间范围的一半,通过这样的方式略去不可能的半个区间。以此达到提高检索效率的目的

为了尽量保持二分,每层节点个数应为下层的1/2。在添加节点的时候,由随机函数确定应该插入多少层节点。

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

14.redisObject

redis的数据类型都被封装为redisObject,

15.List结构

quickList,包装redisObject
在这里插入图片描述

16.Set结构

intSet / dict
在这里插入图片描述

17.ZSet

按照score排序 + key唯一 + kv存储

  • skipList 【按照score排序】

  • dict 【按照key查询,且唯一】

  • quickList 【entry1 + entry2 分别存储key,value;每次添加元素时,手动排序】

18.RDB 【redis data dump】

  • bgsave 后台数据备份

底层fork一个子进程,读取内存数据,将内存数据写入RDB文件。在备份数据时,会对数据开放读取权限,如果主进程需要修改内存数据,那么底层则会对进行拷贝。

19.AOP 【append only file】

  • aof记录命令,将命令写入aof文件中
  • 写入频率,每秒钟进行数据写入。指令命令先写入内存的aof缓冲区,每间隔一秒钟,将内存数据刷入磁盘中。

20.生产消费者模型

1. List模型

采用list存储数据,生产者从左端压入数据;消费者从右端消费数据。

缺点:只支持一对生产消费者;数据没有持久化;如果List数据为空,消费者会直接返回。如果要持续监测List集合中是否存在数据,则需要while监控。但while监控又会造成cpu大量空转,浪费性能。

2. pub / subscribe

消费者订阅队列,生产者将数据投放至队列中,实现数据实时转发。如果消费者端一直没有接收到数据,消费者则会阻塞,让出cpu。

缺点:数据是实时转发,没有做数据的持久化操作。如果消费者挂了,曾经生产的数据会全部丢失。

3. redis的Stream

stream提供订阅,发布模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/316833.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

集合(二)Collection集合Set

一、Set介绍&#xff1a; 是一个散列的集合&#xff0c;数据会按照散列值存储的&#xff0c;如两个hello的散列值相同&#xff0c;会存储在同一个地址中&#xff0c;所以看到的就是只有一个hello在集合中了。 1、Set集合有两个主要的实现子类&#xff1a;Hashset和Treeset。ha…

ZooKeeper 实战(四) Curator Watch事件监听

文章目录 ZooKeeper 实战(四) Curator Watch事件监听0.前言1.Watch 事件监听概念2.NodeCache2.1.全参构造器参数2.2.代码DEMO2.3.日志输出 3.PathChildrenCache3.1.全参构造器参数3.2.子节点监听时间类型3.2.代码DEMO 4.TreeCache4.1.构造器参数4.2.代码DEMO4.3.日志输出 ZooKe…

代码随想录算法训练营第四天 |链表总结

1、每次先加判断&#xff1a; if (head null) {return head;} 2、ListNode dummy new ListNode(-1, head);和ListNode dummy new ListNode(-1);区别&#xff1a; 在Java中&#xff0c;ListNode dummy new ListNode(-1, head); 和 ListNode dummy new ListNode(-1); 的主…

软考学习笔记--操作系统-进程管理

进程管理是一个具有独立功能的程序关于数据集合的一次可以并发执行的运行活动&#xff0c;是系统进行资源分配和调度的基本单位。相对于程序&#xff0c;进程是动态的概念&#xff0c;而程序是静态的概念&#xff0c;是指令的集合。进程具有动态性和并发性&#xff0c;需要一定…

LeetCode讲解篇之39. 组合总和

文章目录 题目描述题解思路题解代码 题目描述 题解思路 首先排序数组&#xff0c;然后开始选择数字&#xff0c;当选择数字num后&#xff0c;在去选择大于等于num的合法数字&#xff0c;计算过程中的数字和&#xff0c;直到选数字和等于target, 加入结果集&#xff0c;若数字和…

爬虫案例—表情党图片data-src抓取

爬虫案例—表情党图片data-src抓取 表情党网址&#xff1a;https://qq.yh31.com 抓取心情板块的图片data-src 由于此页面采用的是懒加载技术&#xff0c;为了节省网络带宽和减轻服务器压力。不浏览的图片&#xff0c;页面不加载&#xff0c;统一显示LOADING…。如下图&#x…

tkinter控件中文显示为unicode编码的解决办法

一、背景 最近使用python tkinter编写界面应用时&#xff0c;发现按钮的中文名称在windows上显示正常&#xff0c;但是在linux上显示为中文的unicode编码&#xff1b;文本输入框也是&#xff0c;输入中文输时&#xff0c;text控件上也显示为unicode编码&#xff0c;如下图所示…

【Python数据可视化】matplotlib之设置坐标:添加坐标轴名字、设置坐标范围、设置主次刻度、坐标轴文字旋转并标出坐标值

文章传送门 Python 数据可视化matplotlib之绘制常用图形&#xff1a;折线图、柱状图&#xff08;条形图&#xff09;、饼图和直方图matplotlib之设置坐标&#xff1a;添加坐标轴名字、设置坐标范围、设置主次刻度、坐标轴文字旋转并标出坐标值matplotlib之增加图形内容&#x…

02-Dapper

1.2&#xff1a;Dapper 1.2.1&#xff1a;设计要求 1、无处不在的部署&#xff1a; 任何服务都应该被监控到&#xff0c;任何服务出问题都要做到有据可查。2、持续的监控&#xff1a;做到7*24小时全天候监控&#xff0c;任何时候出了问题都要基于监控数据追踪问题根源。1.2.2…

基于SSM+JSP的订餐管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

WordPress企业模板

首页大图wordpress外贸企业模板 橙色的wordpress企业模板 演示 https://www.zhanyes.com/waimao/6250.html

如何领取腾讯云免费服务器?腾讯云服务器免费领取教程

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

SAP SQVI制作报表及SE93创建事务代码

在平时的项目中&#xff0c;财务想查询所有的凭证明细&#xff0c;SAP的查询凭证FB03不能满足需求&#xff0c;所以用SQVI制作一个简易的查询报表。 1、打开SQVI&#xff0c;填写自开发报表的名称“ZFB03”&#xff0c;点击“创建”&#xff0c;输入自开发报表的名称“凭证明细…

79LXX 三端负电源电压调节器,具有一系列固定电压输出,适用于小于100mA电源供给的场合

79LXX系列三端负电源电压调节器是单片双极型线性集成电路&#xff0c;采用TO92、SOT89-3的封装形式封装&#xff0c;有一系列固定的电压输出&#xff0c;适用于小于100mA电源供给的场合。 主要特点&#xff1a; 最大输出电流为100mA 固定输出电压分别为-5V、-6V、-8V、-9V、-1…

回归预测 | Matlab基于SO-GRU蛇群算法优化门控循环单元的数据多输入单输出回归预测

回归预测 | Matlab基于SO-GRU蛇群算法优化门控循环单元的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SO-GRU蛇群算法优化门控循环单元的数据多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SO-GRU蛇群算法优化门控循环单元的数…

7.11、Kali Linux中文版虚拟机安装运行教程

目录 一、资源下载准备工作 二、安装教程 三、kali linux换源 四、apt-get update 报错 一、资源下载准备工作 linux 中文版镜像历史版本下载:http://old.kali.org/kali-images/ 大家可以自行选择版本下载&#xff0c;本人下载的是2021版本 二、安装教程 打开vmvare wokst…

Canopen学习笔记——sync同步报文增加数据域(同步计数器)

1.Canfestival同步报文sync的设置 在OD表中的配置如下&#xff1a; 如果0x1006索引的同步报文循环周期时间设置为0则禁用同步报文&#xff0c;这里要注意的就是&#xff0c;上面第一张图也提到了&#xff0c;时间单位是us。第二张图&#xff0c;我的0x1006就设置为0xF4240,也就…

docker compose安装gitlab

环境 查看GitLab镜像 docker search gitlab 拉取GitLab镜像 docker pull gitlab/gitlab-ce 准备gitlab-docker.yml文件 version: 3.1 services:gitlab:image: gitlab/gitlab-ce:latestcontainer_name: gitlabrestart: alwaysenvironment:GITLAB_OMNIBUS_CONFIG: |external_url…

HarmonyOS开发FA应用模型下多个页面的声明方式

目录 方式1 方式2 HarmonyOS配套的IDE是DevEco Studio&#xff0c;目前的版本是3.1。官网可以直接下载 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 ​ 方式1 ​在DevEco Studio如果是在pages目录通过右键New->ArkTS File生成的文件&#xff0c;需要注意&…

PHP如何拆分中文名字(包括少数民族名字)

/*** param string|null $name* return array|null*/ function splitName($name) {if (empty($name) || empty(trim($name))) {return null;}//该正则是用来提取$name参数里面的中文字符的。preg_match_all(/[\x{4e00}-\x{9fff}]/u, $name, $matchers);$matchersCount isset($…