打开我的收藏夹 -- redis篇

2021-09-18 10:32:25 浏览数 (1)

  • redis的通讯协议是什么
  • redis究竟有没有ACID事务
  • redis的乐观锁watch是怎么实现的?
  • redis是如何持久化的
  • redis在内存使用上是如何开源节流?
  • redis是如何实现主从复制
  • redis是怎么制定过期删除策略的

去我的收藏夹吃灰吧

总之应该是这样一种心态吧:我觉得这个内容还不错,或许我早晚要拿出来装逼用,不收藏到时候找不到了多可惜。

我希望收藏了我就会看,我就能学到什么,我就比别人多一些知识。

但是我又无比清楚地知道我不会再看了。

还好啊,我没有这个坏习惯,我也希望关注收藏我博客的朋友们没有这个坏习惯。

我呢,是个极简主义者,我的收藏夹啊,来来去去得有上千的流水了。可以说,铁打的收藏夹,流水的博客。不信你去数数,不会超过两百篇。加上最近我又在大力整顿收藏夹了,所以估计就更少了。

为什么我提倡大家要经常性的,不定时的去整顿收藏夹呢?原因只有一个:你收藏,坑定是因为当时的你觉得这部分内容,好,或许是你不会的,或许是你觉得不错的。如果是你不会的内容,放收藏夹吃一天灰,你就受一天的损失。这点我深有体会(本来该装进口袋的钱没装进口袋了,感触自然就很深了)。

好,不多废话,之后也不多废话,这是第一篇嘛。我知道,这段话写完,我的数据坑定会受影响有波动的。


“redis”收藏夹内容分布

写这篇之前,我的收藏夹里有16篇redis的收藏(我记得之前挺多啊,怎么被腰斩了,不管了)。看我写完之后还能剩几篇。

根据我多次整顿收藏夹的经验,我们需要分这么几步来进行。

1、确定要搞哪个收藏夹。这个需要多方面考量,要从收藏数量、预期价值、预期效果、期望、心情等多方面来进行综合考量。我呢,主要看心情。(如果只有一个“默认收藏夹”的朋友一定要抓紧将自己的收藏分门别类,不然数量一旦大到看都不想看的地步,那你的收藏真的没什么意义了。)

2、将该分类收藏夹下文章全部打开。只要你的电脑hold的住。我曾经一次性打开了四十二个收藏(“Linux服务器”收藏夹),加上其他几个界面,大概有四十五个界面。还行,hold的住。

为什么要全部打开呢?就怕你看到一半想开溜。当然,如果真的想溜,浏览器一关照样溜。这只是一个心理作用啦,而且这么高负荷,如果你心疼你的电脑,也会合理的加快处理速度(有问题后面再说)。

3、合理的加速。对于确定已经不要的,或者说404收藏,直接咔咔删掉就好了。对于感觉依旧学不明白的,直接关闭界面,等着下一轮再说。对于当前能力范围之内能够消化掉的,开始下一步,用自己的语言,整理成自己的东西。也就是我这篇,以及这个系列,将要做的事情。这个系列可能不会专门开一个专栏。

4、Action。先将各篇主题提取出来,做成一份目录。CSDN在目录这一块儿做的不错。目录做好之后,就可以对着目录开始总结了。


Redis概念与介绍

对于说过的内容,我想直接一链带过:

redis(1)

redis(2)

注意,这两个是系列,不是单篇,里面东西很多,包括了安装、集群、主从等手动操作,也包括了缓存击穿、缓存穿透、持久化等一系列概念,还有对redis.conf的翻译。

所以,该点进去还是点进去吧,后面基本不会再提这些。

NoSQL

我一向认为,技术的存在即合理。可能是曾经合理,可能将来会合理。在这个数据井喷的时代,出现了NoSQL,为什么不继续坚定不移的走SQL道路呢?那肯定是SQL不够用了嘛,所以NoSQL数据库出来填补空白了。

那么SQL数据库在哪些方面有所不足呢?(看NoSQL的优势,有时候可以从它的对立面去看)

  1. 数据量太大了,一台机器硬盘都放不下。
  2. 索引太大了,一个机器的运行内存放不下了。(索引放不下那问题就大了)
  3. 读写性能瓶颈。

于是呢,就出现了:

Memcached(缓存) MySql 垂直拆分

说实话,我写过的小项目数据量还没大到那一步,但是我也一直想试试这种玩法,不难玩的啦。

再往后,主从复制,读写分离,分库分表啥的我就不说了。

以上这些,哦,出现了缓存。缓存是为了减小数据库的压力而生,在前面的链接中有(“缓存穿透、缓存雪崩、缓存击穿”那一篇)。


redis命令参考

这里分享一个腾讯大佬分享的redis命令参考

自个儿去看吧,我就不吱声儿。


redis为什么快?

  1. 纯内存操作。
  2. 单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题。

其实吧,严格来说,redis还是有一些线程执行其他任务的,单线程的意思是:执行命令是单线程顺序执行。

使用redis时,几乎不会被CPU卡脖子,主要受限于内存和网络。多线程任务可以分摊 Redis 同步 IO 读写负荷。

  1. 采用了非阻塞I/O多路复用机制。Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll的read、write、close等都转换成事件,不在网络I/O上浪费过多的时间。

redis数据结构

String

动态字符串sds。

redis构建了一种简单的动态字符串抽象类型。也有C风格字符串,不过不是出现在重要场合。

看一点源码啊:

代码语言:javascript复制
struct sdshdr
{
    int len;//buf已使用字节数量(保存的字符串长度)
    int free;//未使用的字节数量
    char buf[];//用来保存字符串的字节数组
};

好处:

  1. 减少原先繁琐的内存扩增问题。
  2. 减少内存重分配问题。
  3. 具体的空间预分配原则是:当修改字符串后的长度len小于1MB,就会预分配和len一样长度的空间,即len=free;若是len大于1MB,free分配的空间大小就为1MB。

List

链表在redis也有一定的应用。

还是看一点源码吧。

代码语言:javascript复制
//redis的节点使用了双向链表结构
typedef struct listNode {
    // 前置节点
    struct listNode *prev;
    // 后置节点
    struct listNode *next;
    // 节点的值
    void *value;
} listNode;

双端、无环、带长度记录、

多态:使用 void* 指针来保存节点值, 可以通过 dup 、 free 、 match 为节点值设置类型特定函数, 可以保存不同类型的值。

字典

多说无益,放码过去。

代码语言:javascript复制
//redis的字典使用哈希表作为底层实现
typedef struct dictht {
    // 哈希表数组
    dictEntry **table;	//每个 dictEntry 结构保存着一个键值对。
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;
 
    // 该哈希表已有节点的数量
    unsigned long used;
 
} dictht;
代码语言:javascript复制
typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
 
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;	//next就是解决键冲突问题的,冲突了就挂后面
} dictEntry;

字典实现:

代码语言:javascript复制
typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    int rehashidx; //* rehashing not in progress if rehashidx == -1 
} dict;

跳表

为实习准备的数据结构(9)-- 跳表

跳表很重要,很重要,很重要。


这些数据结构后续会再出一些博客来专门写。现在有心也无力。


Redis开发规范

禁用命令:

禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。

使用批量命操作提高效率:

代码语言:javascript复制
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率。

但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。

代码语言:javascript复制
1. 原生是原子操作,pipeline是非原子操作。
2. pipeline可以打包不同的命令,原生做不到
3. pipeline需要客户端和服务端同时支持。

Redis事务功能较弱,不建议过多使用

不支持回滚。

Redis集群版本在使用Lua上有特殊要求:

代码语言:javascript复制
 1.所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array"
    2.所有key,必须在1个slot上,否则直接返回error, “-ERR eval/evalsha command keys must in same slot”

其他建议:

  1. 冷热数据分离,不要将所有数据全部都放到Redis中
  2. 不同的业务数据要分开存储
  3. 规范Key的格式
  4. 存储的Key一定要设置超时时间
  5. 对于必须要存储的大文本数据一定要压缩后存储
  6. 线上Redis禁止使用Keys正则匹配操作
  7. 可靠的消息队列服务
  8. 谨慎全量操作Hash、Set等集合结构
  9. 根据业务场景合理使用不同的数据结构类型

Redis-HyperLogLog

什么是HyperLogLog

HyperLogLog(HLL)是一种用于基数计算的概率数据结构,通俗的说就是支持集合中不重复元素的统计。

常规基数计算需要准备一块内存空间用于存储已经计数的元素,避免某些元素被重复统计。Redis提供了一种用精度来换取内存空间的算法,标准误差低于1%。仅需要12K 就能完成统计(再加上HLL自身所需的一点bytes),如果HyperLogLog中的元素较少,所需内存空间更小。HyperLogLogs的标准误差是0.81%。

输入元素数量或体积非常大时,HLL所需空间固定且很小。12kb内存可计算接近 2^64 个不同元素的基数。

HyperLogLog虽然技术实现是一种不同的数据结构,但底层依旧是Redis strings,所以可以使用GET命令获取序列化后的数据,使用SET命令反序列化数据存储到Redis。

HyperLogLog命令

具体操作后面再出吧,在redis的数据结构系列将会出现hyperloglog。


Redis 面试七连问

redis的通讯协议是什么

redis的通讯协议是文本协议,是的,Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信,没错,文本协议确实是会浪费流量,不过它的优点在于直观,非常的简单,解析性能及其的好,我们不需要一个特殊的redis客户端仅靠telnet或者是文本流就可以跟redis进行通讯。

redis究竟有没有ACID事务

代码语言:javascript复制
redis具备了一定的原子性,但不支持回滚
redis不具备ACID中一致性的概念(或者说redis在设计就无视这点)
redis具备隔离性
redis通过一定策略可以保证持久性

redis的乐观锁watch是怎么实现的?

在redis的事务中使用watch实现,watch 会在事务开始之前盯住 1 个或多个关键变量,当事务执行时 也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时, Redis 会检查关键变量自 watch 之后,是否被修改了。

(我清晰的记得老师讲过,但是我更清楚我不知道老师是怎么讲的了)

redis是如何持久化的

这篇开头链接里面有,但是我这里长话短说一下。

redis的持久化有两种机制,一个是RDB,也就是快照,快照就是一次全量的备份,会把所有redis的内存数据进行二进制的序列化存储到磁盘。另一种是aof日记,aof日志记录的是数据操作修改的指令记录日志,可以类比mysql的binlog,aof日期随着时间的推移只会无限增量。

在对redis进行恢复时,rdb快照直接读取磁盘既可以恢复,而aof需要对所有的操作指令进行重放进行恢复,这个过程有可能非常漫长。

好,我们选AOF。(开玩笑的)

aof不需要是全量日志,只需要保存前一次rdb存储开始到这段时间增量aof日志即可,一般来说,这个日志量是非常小的。

redis在内存使用上是如何开源节流?

ziplist、quicklist、对象共享等机制。

redis是如何实现主从复制

有完整重同步和部分重同步两种模式:

其中完整同步用于处理初次复制情况:完整重同步的执行步骤和sync命令执行步骤一致,都是通过让主服务器创建并发送rdb文件,以及向从服务器发送保存在缓冲区的写命令来进行同步。 部分重同步是用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,主服务可以讲主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以讲数据库更新至主服务器当前所处的状态。

完整重同步:

slave发送psync给master,由于是第一次发送,不带上runid和offset master接收到请求,发送master的runid和offset给从节点 master生成保存rdb文件 master发送rdb文件给slave 在发送rdb这个操作的同时,写操作会复制到缓冲区replication backlog buffer中,并从buffer区发送到slave slave将rdb文件的数据装载,并更新自身数据

部分重同步:

网络发生错误,master和slave失去连接 master依然向buffer缓冲区写入数据 slave重新连接上master slave向master发送自己目前的runid和offset master会判断slave发送给自己的offset是否存在buffer队列中,如果存在,则发送continue给slave,如果不存在,意味着可能错误了太多的数据,缓冲区已经被清空,这个时候就需要重新进行全量的复制 master发送从offset偏移后的缓冲区数据给slave slave获取数据更新自身数据

redis是怎么制定过期删除策略的

这个前面有提到过。LRU。

代码语言:javascript复制
定时删除: 在设置键过去的时间同时,创建一个定时器,让定时器在键过期时间来临,立即执行对键的删除操作。
惰性删除: 放任键过期不管,但是每次从键空间获取键时,都会检查该键是否过期,如果过期的话,就删除该键。
定期删除: 每隔一段时间,程序都要对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,由算法而定。

今天先到这里啦。

0 人点赞