文章目录

  • 面试系列
    • 一、Redis总结
      • 1、Redis数据结构
        • 1.1、Redis 基本特性
        • 1.2、Redis应用场景
        • 1.3、Redis有哪几种数据结构,原理,应用场景?
          • 1.3.1、String
            • 1.3.1.1、原理
            • 1.3.1.2、常用API
            • 1.3.1.3、应用场景
          • 1.3.2、List
            • 1.3.2.1、原理:
            • 1.3.2.2、常用API
            • 1.3.2.3、应用场景
          • 1.3.3、hash( 哈希):购物车
            • 1.3.3.1、原理:
            • 1.3.3.2、常用API
            • 1.3.3.3、应用场景:
          • 1.3.4、set
            • 1.3.4.1、原理
            • 1.3.4.2、常用API
            • 1.3.4.3、应用场景
          • 1.3.5、zset
            • 1.3.5.1、原理
            • 1.3.5.2、常用API
            • 1.3.5.3、应用场景:
          • 1.3.6、其他
            • 1.3.6.1、GeoHash(附近位置、摇一摇)
            • 1.3.6.2、BloomFilter
            • 1.3.6.3、其他
        • 1.2、zset用过吗,底层数据结构是什么?
        • 1.3、为什么使用跳表?跟B+树怎么比?查找的时间复杂度是多少?
        • 1.4、GEO是干嘛的?举个场景?
        • 1.5、HyperLogLog是干嘛的?
        • 1.6、Redis可以做消息队列吗?有什么缺点?
        • 1.7、可以实现延时队列吗?
      • 2、redis持久化
        • 2.1、redis持久化了解吗?有哪些方式?
        • 2.2、RDB和AOF的定义&生成方式&区别&各自的优缺点?
          • 2.2.1、RDB
            • 2.2.1.1、定义
            • 2.2.1.2、生成方式
            • 2.2.1.3、bgsave的写时复制(COW)机制
            • 2.2.1.4、优点
            • 2.2.1.5、缺点
          • 2.2.2、AOF
            • 2.2.2.1、定义
            • 2.2.2.2、AOF重写
            • 2.2.2.3、数据恢复方式
            • 2.2.2.4、优点:
            • 2.2.2.5、缺点:
          • 2.2.3、AOF和RDB两者比较:
        • 2.3、说一下redis持久化的过程?
        • 2.4、redis持久化方式你们是怎么选型的?
        • 2.5、了解过redis混合持久化吗?是一个文件还是多个文件?
        • 2.6、redis进行持久化的时候会阻塞主线程吗?
        • 2.7、Redis持久化数据和缓存怎么做扩容?
        • 2.8、Redis数据备份策略
      • 3、redis 实现分布式锁
        • 3.1、redis分布式锁了解吗?
        • 3.2、setnx的原理
        • 3.3、假如我的业务代码执行时间不是很稳定?使用redis做分布式锁会有什么问题呢?(锁失效)
        • 3.4、你怎么解决锁超时的问题?有什么方案吗?跟zk的分布式锁有什么区别?
          • 3.4.1、解决方案
          • 3.4.2、区别
        • 3.5、zk分布式锁实现原理
      • 4、redis集群部署
        • 4.1、redis集群部署模式有哪些,介绍一下
        • 4.2、介绍一下sentinel模式如何感知sever节点上下线的?
        • 4.3、哨兵leader选举流程
        • 4.4、sentinel的定时任务
        • 4.5、故障转移过程
        • 4.6、Redis集群原理分析
        • 4.7、Redis集群选举原理分析
        • 4.8、槽位定位算法
        • 4.9、跳转重定位
        • 4.9、Redis集群节点间的通信机制
        • 4.10、网络抖动
        • 4.11、集群脑裂数据丢失问题
        • 4.12、集群是否完整才能对外提供服务
        • 4.13、Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
        • 4.14、redis-server的ping/pong协议了解吗?怎么用的?
          • 4.14.1、ping
          • 4.14.2、pong
        • 4.15、讲一下redis主从复制(全量复制),数据部分复制,主从复制(部分复制,断点续传)的过程?
          • 4.15.1、主从复制(全量复制)流程图:
          • 4.15.2、数据部分复制
          • 4.15.3、主从复制(部分复制,断点续传)流程图:
        • 4.16、主从模式下假如主节点挂了之后?
        • 4.17、redis cluster模式下通过key查找value的过程了解吗?
      • 5、redis高可用
        • 5.1、redis cluster模式下如何扩容和缩容的?
        • 5.2、如何解决redis的内存碎片问题?产生的原因是什么?
          • 5.2.1、原因
          • 5.2.2、危害性
          • 5.4.3、解决方案
        • 5.3、redis的内存达到机器内存极限值之后有哪些策略可以避免?
        • 5.4、redis数据(过期键)删除的策略有哪些?
        • 5.5、redis的内存淘汰策略和过期键的删除策略怎么选?
        • 5.6、缓存击穿/缓存雪崩/缓存穿透/热点缓存key重建优化/缓存与数据库双写不一致
          • 5.6.1、缓存击穿(失效)
            • 5.6.1.1、原因
            • 5.6.1.2、解决方案
          • 5.6.2、缓存雪崩
            • 5.6.2.1、原因
            • 5.6.2.2、解决方案
          • 5.6.3、缓存穿透
            • 5.6.3.1、原因
            • 5.6.3.2、解决方案
          • 5.6.4、热点缓存key重建优化
            • 5.6.4.1、原因
            • 5.6.4.2、解决方案
          • 5.6.5、缓存与数据库双写不一致
            • 5.6.5.1、原因
            • 5.6.5.2、解决方案
        • 5.7、bigkey危害,原因,优化方案
          • 5.7.1、危害
          • 5.7.2、原因
          • 5.7.3、优化方案
      • 6、Redis的单线程和高性能
        • 6.1、Redis是单线程吗?
        • 6.2、Redis 单线程为什么还能这么快?
        • 6.3、Redis 单线程如何处理那么多的并发客户端连接?
      • 7、场景
        • 7.1、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

面试系列

深入了解JUC并发编程+集合(已写)
深入了解JVM(JDK8)(已写)
深入了解Redis(已写)
深入了解MySQL(已写)
深入了解Spring(未写)
深入了解MyBatis(未写)
深入了解SpringMVC(未写)
深入了解SpringBoot(未写)
深入了解zookeeper(未写)
深入了解Dubbo(未写)
深入了解RabbitMQ(未写)
深入了解RcoketMQ(未写)
深入了解kafka(未写)
深入了解ElasticSearch(未写)
深入了解ShardingSphere(未写)
深入了解MongoDB(未写)
深入了解Netty(未写)
深入了解Eureka(未写)
深入了解OpenFeign(未写)
深入了解Nacos(未写)
深入了解Ribbon(未写)
深入了解Sentinel(未写)
深入了解Seata(未写)
深入了解GateWay(未写)

一、Redis总结

1、Redis数据结构

1.1、Redis 基本特性

  1. 非关系型的键值对数据库,可以根据键以O(1) 的时间复杂度取出或插入关联值
  2. Redis 的数据是存在内存中的
  3. 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的
  4. 键值对中的值类型可以是string,hash,list,set,sorted set 等
  5. Redis 内置了复制,磁盘持久化,LUA脚本,事务,SSL, ACLs,客户端缓存,客户端代理等功能
  6. 通过Redis哨兵和Redis Cluster 模式提供高可用性

1.2、Redis应用场景

1、计数器
可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

2、分布式ID生成
利用自增特性,一次请求一个大一点的步长如 incr 2000 ,缓存在本地使用,用完再请求。

3、海量数据统计
位图(bitmap):存储是否参过某次活动,是否已读谋篇文章,用户是否为会员, 日活统计。

4、会话缓存
可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

5、分布式队列/阻塞队列
List 是一个双向链表,可以通过 lpush/rpush 和 rpop/lpop 写入和读取消息。可以通过使用brpop/blpop来实现阻塞队列

6、分布式锁实现
在分布式场景下,无法使用基于进程的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁。

7、热点数据存储
最新评论,最新文章列表,使用list 存储,ltrim取出热点数据,删除老数据。

8、社交类需求
Set 可以实现交集,从而实现共同好友等功能,Set通过求差集,可以进行好友推荐,文章推荐。

9、排行榜
sorted_set可以实现有序性操作,从而实现排行榜等功能。

10、延迟队列
使用sorted_set,使用 【当前时间戳 + 需要延迟的时长】做score, 消息内容作为元素,调用zadd来生产消息,消费者使用zrangbyscore获取当前时间之前的数据做轮询处理。消费完再删除任务 rem key member

1.3、Redis有哪几种数据结构,原理,应用场景?

先了解下:Redis的redisObject结构:

typedef struct redisObject {unsigned type:4;     // 类型unsigned encoding:4;    // 编码unsigned lru:LRU_BITS;  int refcount;void *ptr;               // 指向底层实现数据结构的指针
} robj;

其中encoding属性记录了队形所使用的编码,即这个对象底层使用哪种数据结构实现。我们在存入key-value键值对时并不会指定对象的encoding,而是Redis会根据不统的使用场景来为一个对象设置不同的编码,可以达到节约内存、加快访问速度等目的。

1.3.1、String

底层代码:

struct sdshdr {int len;int free;char buf[];
};
1.3.1.1、原理

3.2以前

字符串对象底层数据结构实现为简单动态字符串(SDS)和直接存储,但其编码方式可以是int、raw或者embstr,区别在于内存结构的不同

(1)int编码

字符串保存的是整数值,并且这个正式可以用long类型来表示,那么其就会直接保存在redisObject的ptr属性里,并将编码设置为int

(2)raw编码

字符串保存的大于32字节的字符串值,则使用简单动态字符串(SDS)结构,并将编码设置为raw,此时内存结构与SDS结构一致,内存分配次数为两次,创建redisObject对象和sdshdr结构

(3)embstr编码

字符串保存的小于等于32字节的字符串值,使用的也是简单的动态字符串(SDS结构),但是内存结构做了优化,用于保存顿消的字符串;内存分配也只需要一次就可完成,分配一块连续的空间即可

字符串对象总结:

    • 在Redis中,存储long、double类型的浮点数是先转换为字符串再进行存储的。
  • raw与embstr编码效果是相同的,不同在于内存分配与释放,raw两次,embstr一次。
  • embstr内存块连续,能更好的利用缓存在来的优势
  • int编码和embstr编码如果做追加字符串等操作,满足条件下会被转换为raw编码;embstr编码的对象是只读的,一旦修改会先转码到raw。

3.2以后:

底层
typedef char *sds;struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {........逻辑代码:static inline char sdsReqType(size_t string_size) {if (string_size < 32)  return SDS_TYPE_5;if (string_size < 0xff) //2^8 -1  return SDS_TYPE_8;if (string_size < 0xffff) // 2^16 -1  return SDS_TYPE_16;if (string_size < 0xffffffff)  // 2^32 -1 return SDS_TYPE_32;return SDS_TYPE_64;
}
上面的参数定义
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

1.3.1.2、常用API

cli客户端中输入:help @string
SET/GET
SETNX
GETRANGE/SETRANGE
INCR/INCRBY/DECR/DECRBY
GETBIT/SETBIT/BITOPS/BITCOUNT
MGET/MSET

1.3.1.3、应用场景
  - 单值缓存- 分布式锁- 计数器- Web集群session共享- 分布式系统全局序列号- 对象缓存
1.3.2、List
1.3.2.1、原理:

List是一个有序(按加入的时序排序)的数据结构,Redis采用quicklist(双端链表) 和 ziplist 作为List的底层实现

可以通过设置每个ziplist的最大容量,quicklist的数据压缩范围,提升数据存取效率

list-max-ziplist-size  -2        //  单个ziplist节点最大能存储  8kb  ,超过则进行分裂,将数据存储在新的ziplist节点中
list-compress-depth  1        //  0 代表所有节点,都不进行压缩,1, 代表从头节点往后走一个,尾节点往前走一个不用压缩,其他的全部压缩,2,3,4 ... 以此类推

源码:

一、ziplist

robj *createZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_LIST,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}
unsigned char *ziplistNew(void) {unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;unsigned char *zl = zmalloc(bytes);ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);ZIPLIST_LENGTH(zl) = 0;zl[bytes-1] = ZIP_END;return zl;
}
robj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;  } else {o->lru = LRU_CLOCK();   // 获取 24bit 当前时间秒数}return o;
}
zlbytes:32bit,表示ziplist占用的字节总数。
zltail:    32bit,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。通过zltail我们可以很方便地找到最后一项,从而可以在ziplist尾端快速地执行push或pop操作
zlen:     16bit, 表示ziplist中数据项(entry)的个数。
entry:表示真正存放数据的数据项,长度不定
zlend: ziplist最后1个字节,是一个结束标记,值固定等于255。
prerawlen: 前一个entry的数据长度。
len: entry中数据的长度
data: 真实数据存储

二、quicklist

robj *createQuicklistObject(void) {quicklist *l = quicklistCreate();robj *o = createObject(OBJ_LIST,l);o->encoding = OBJ_ENCODING_QUICKLIST;return o;
}quicklist *quicklistCreate(void) {struct quicklist *quicklist;quicklist = zmalloc(sizeof(*quicklist));quicklist->head = quicklist->tail = NULL;quicklist->len = 0;quicklist->count = 0;quicklist->compress = 0;quicklist->fill = -2;quicklist->bookmark_count = 0;return quicklist;
}typedef struct quicklist {quicklistNode *head;quicklistNode *tail;unsigned long count;       unsigned long len;           int fill : QL_FILL_BITS;                unsigned int compress : QL_COMP_BITS;  unsigned int bookmark_count: QL_BM_BITS;quicklistBookmark bookmarks[];
} quicklist;typedef struct quicklistNode {struct quicklistNode *prev;struct quicklistNode *next;unsigned char *zl;unsigned int sz;            unsigned int count : 16;    unsigned int encoding : 2;    unsigned int container : 2;  unsigned int recompress : 1; unsigned int attempted_compress : 1; unsigned int extra : 10;
} quicklistNode;

1.3.2.2、常用API

cli客户端输入:help @list
LPUSH key element [element …]
RPOP key
RPUSH key element [element …]
LPOP key
BLPOP key [key …] timeout
BRPOP key [key …] timeout
BRPOPLPUSH source destination timeout
RPOPLPUSH source destination
LINDEX key index
LLEN key
LINSERT key BEFORE|AFTER pivot element
LRANGE key start stop
LREM key count element
LSET key index element
LTRIM key start stop

1.3.2.3、应用场景
  • 常用数据结构
  • Stack(栈) = LPUSH + LPOP
  • Queue(队列)= LPUSH + RPOP
  • Blocking MQ(阻塞队列)= LPUSH + BRPOP
  • 微博和微信公号消息流
  • 微博消息和微信公号消息
1.3.3、hash( 哈希):购物车
1.3.3.1、原理:

Hash 数据结构底层实现为一个字典( dict ),也是RedisBb用来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用ziplist存储,数据大小和元素数量阈值可以通过如下参数设置

hash-max-ziplist-entries  512    //  ziplist 元素个数超过 512 ,将改为hashtable编码
hash-max-ziplist-value    64      //  单个元素大小超过 64 byte时,将改为hashtable编码
1.3.3.2、常用API

CLI客户端输入:help @hash

HSET key field value [field value …]
HGET key field
HMGET key field [field …]
HKEYS key
HGETALL key
HVALS key
HEXISTS key field
HDEL key field [field …]
HINCRBY key field increment
HINCRBYFLOAT key field increment
HLEN key
HSCAN key cursor [MATCH pattern] [COUNT count]
HSETNX key field value
HSTRLEN key field

1.3.3.3、应用场景:
  • 对象缓存
  • 电商购物车
  • 购物车操作(添加商品,增加数量,商品总数,删除商品,获取购物车所有商品)
1.3.4、set
1.3.4.1、原理

Set 为无序的,自动去重的集合数据类型,Set 数据结构底层实现为一个value 为 null 的 字典( dict ),当数据可以用整形表示时,Set集合将被编码为intset数据结构。两个条件任意满足时Set将用hashtable存储数据。

1, 元素个数大于 set-max-intset-entries ,

2 , 元素无法用整形表示

set-max-intset-entries 512       // intset 能存储的最大元素个数,超过则用hashtable编码

intset底层源码:

typedef struct intset {uint32_t  encoding;
uint32_t  length;
int8_t      contents[];
} intset; #define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))整数集合是一个有序的,存储整型数据的结构。整型集合在Redis
中可以保存int16_t,int32_t,int64_t类型的整型数据,并且可以保证
集合中不会出现重复数据。encoding: 编码类型
length: 元素个数
contents[]: 元素存储

1.3.4.2、常用API

CLI客户端输入help @set

SADD key member [member …]
SCARD key
SISMEMBER key member
SPOP key [count]
SDIFF key [key …]
SINTER key [key …]
SUNION key [key …]
SMEMBERS key
SRANDMEMBER key [count]
SREM key member [member …]
SMOVE source destination member
SUNIONSTORE destination key [key …]
SDIFFSTORE destination key [key …]
SINTERSTORE destination key [key …]
SSCAN key cursor [MATCH pattern] [COUNT count]

1.3.4.3、应用场景
  - 微信抽奖小程序- 微信微博点赞,收藏,标签- 集合操作实现微博微信关注模型- 集合操作实现电商商品筛选
1.3.5、zset
1.3.5.1、原理

ZSet 为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储。

zset-max-ziplist-entries  128    // 元素个数超过128 ,将用skiplist编码
zset-max-ziplist-value     64     //  单个元素大小超过 64 byte, 将用 skiplist编码

Zset 底层源码

// 创建zset 数据结构: 字典 + 跳表
robj *createZsetObject(void) {zset *zs = zmalloc(sizeof(*zs));robj *o;// dict用来查询数据到分数的对应关系, 如 zscore 就可以直接根据 元素拿到分值 zs->dict = dictCreate(&zsetDictType,NULL);// skiplist用来根据分数查询数据(可能是范围查找)zs->zsl = zslCreate();// 设置对象类型 o = createObject(OBJ_ZSET,zs);// 设置编码类型 o->encoding = OBJ_ENCODING_SKIPLIST;return o;
}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;
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

1.3.5.2、常用API

CLI客户端输入:help @sorted_set

ZADD key [NX|XX] [CH] [INCR] score member [score member …]
ZCARD key
ZCOUNT key min max
ZINCRBY key increment member
ZRANGE key start stop [WITHSCORES]
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANK key member
ZREM key member [member …]
ZREMRANGEBYRANK key start stop
ZREMRANGEBYSCORE key min max
ZREVRANGE key start stop [WITHSCORES]
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
ZREVRANK key member
ZSCAN key cursor [MATCH pattern] [COUNT count]
ZSCORE key member

1.3.5.3、应用场景:
  - 点击新闻- 展示当日排行前十- 七日搜索榜单计算- 展示七日排行前十
1.3.6、其他
1.3.6.1、GeoHash(附近位置、摇一摇)

1、原理:

GeoHash是一种地理位置编码方法。 由Gustavo Niemeyer 和 G.M. Morton于2008年发明,它将地理位置编码为一串简短的字母和数字。它是一种分层的空间数据结构,将空间细分为网格形状的桶,这是所谓的z顺序曲线的众多应用之一,通常是空间填充曲线。

2、GeoHash经纬度编码:

经度范围是东经180到西经180,纬度范围是南纬90到北纬90,我们设定西经为负,
南纬为负,所以地球上的经度范围就是[-180, 180],纬度范围就是[-90,90]。
如果以本初子午线、赤道为界,地球可以分成4个部分。

如果纬度范围[-90°, 0°)用二进制0代表,(0°, 90°]用二进制1代表,经度范围[-180°, 0°)用二进制0代表,
(0°, 180°]用二进制1代表,那么地球可以分成如下(左图 )4个部分

通过GeoHash算法,可以将经纬度的二维坐标变成一个可排序、可比较的的字符串编码。 在编码中的
每个字符代表一个区域,并且前面的字符是后面字符的父区域。其算法的过程如下:
根据GeoHash 来计算 纬度的 二进制编码
地球纬度区间是[-90,90], 如某纬度是39.92324,可以通过下面算法来进行维度编码:
1)区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.92324属于右区间[0,90],给标记为1
2)接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.92324属于左区间 [0,45),给标记为0
3)递归上述过程39.92324总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167
4)如果给定的纬度(39.92324)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进行会 产生一个序列1011 1000 1100 0111 1001,序列的长度跟给定的区间划分次数有关。

纬度产生的编码为1011 1000 1100 0111 1001,经度产生的编码为1101 0010 1100 0100 0100。偶数位放经度,奇数位放纬度,把2串编码组合生成新串:
11100 11101 00100 01111 00000 01101 01011 00001。

最后使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11101 00100 01111 00000 01101 01011 00001转成十进制 28,29,4,15,0,13,11,1,十进制对应的编码就是wx4g0ec1。同理,将编码转换成经纬度的解码算法与之相反

3、优缺点

优点:

GeoHash利用Z阶曲线进行编码,Z阶曲线可以将二维所有点都转换成一阶曲线。地理位置坐标点通过编码转化成一维值,利用 有序数据结构如B树、SkipList等,均可进行范围搜索。因此利用GeoHash算法查找邻近点比较快

缺点:

Z 阶曲线有一个比较严重的问题,虽然有局部保序性,但是它也有突变性。在每个 Z 字母的拐角,都有可能出现顺序的突变。

1.3.6.2、BloomFilter

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。

布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用很少。

1.3.6.3、其他

HyperLogLog(基数统计):统计用户访问量

Pub/Sub。如果你说还玩过 Redis Module,像 BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

1.2、zset用过吗,底层数据结构是什么?

跳表(具体代码流程看上面)

1.3、为什么使用跳表?跟B+树怎么比?查找的时间复杂度是多少?

为什么使用跳表:1、性能考虑:在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部

2、实现考虑:在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;

为啥不用B+:B+树的原理是 叶子节点存储数据,非叶子节点存储索引,B+树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点,每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一

时间复杂度:O(logn)

1.4、GEO是干嘛的?举个场景?

主要用于存储地理位置信息,看上面

1.5、HyperLogLog是干嘛的?

统计用户访问量

1.6、Redis可以做消息队列吗?有什么缺点?

可以,用list,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候, 要适当 sleep(或者blpop) 一会再重试。如果要生产一次消费多次,可以使用使用 pub/sub 主题订阅者模式, 可以实现1:N 的消息队列

缺点:在消费者下线的情况下,生产的消息会丢失,使用专业的消息队列(activitymq,rabbitmq,rocketmq,kafka等)

1.7、可以实现延时队列吗?

使用 sortedset,拿时间戳作为score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。

2、redis持久化

2.1、redis持久化了解吗?有哪些方式?

定义:持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

方式:rdb(快照),aof(追加),混合(aof+rdb)

2.2、RDB和AOF的定义&生成方式&区别&各自的优缺点?

2.2.1、RDB
2.2.1.1、定义

对内存中数据库状态进行快照

RDB 方式:将 Redis 在内存中的数据库状态保存到磁盘里面,RDB 文件是一个经过压缩的二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态 ( 默认下,持久化到dump.rdb 文件,并且在 redis 重启后,自动读取其中文件,据悉,通常情况下一千万的字符串类型键,1GB 的快照文件,同步到内存中的 时间是 20-30 秒)

2.2.1.2、生成方式
1)执行命令手动生成手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件,SAVE命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求,BGSAVE 命令会派生出一个子进程,然后由子进程负责创建RDB 文件,服务器进程(父进程)继续处理命令请求,创建 RDB 文件结束之前,客户端发送的 BGSAVE 和 SAVE 命令会被服务器拒绝
2)通过配置自动生成可以设置服务器配置的 save 选项,让服务器每隔一段时间自动执行一次BGSAVE 命令,可以通过 save 选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行 BGSAVE 命令
例如:save 900 1        //服务器在 900 秒之内,对数据库进行了至少 1 次修改save 300 10        //服务器在 300 秒之内,对数据库进行了至少 10 次修改save 60 10000 //服务器在 60 秒之内,对数据库进行了至少 10000 次修改
2.2.1.3、bgsave的写时复制(COW)机制

Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理写命令。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

配置自动生成rdb文件后台使用的是bgsave方式。

2.2.1.4、优点

1、只有一个文件 dump.rdb, 方便持久化。

2、容灾性好, 一个文件可以保存到安全的磁盘。

3、性能最大化, fork 子进程来完成写操作, 让主进程继续处理命令, 所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能)

4.相对于数据集大时, 比 AOF 的启动效率更高。(fork+cow)

2.2.1.5、缺点

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

2.2.2、AOF
2.2.2.1、定义

将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间
fsync到磁盘)

2.2.2.2、AOF重写

AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF
注意,AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响

2.2.2.3、数据恢复方式

AOF 数据恢复方式:

appendfsync always - 每提交一个修改命令都调用 fsync 刷新到 AOF 文件,非常非常
慢,但也非常安全
appendfsync everysec - 每秒钟都调用 fsync 刷新到 AOF 文件,很快,但可能会丢失
一秒以内的数据
appendfsync no - 依靠 OS 进行刷新,redis 不主动刷新 AOF,这样最快,但安全性就
差

默认并推荐每秒刷新,这样在速度和安全上都做到了兼顾

2.2.2.4、优点:

1、数据安全, aof 持久化可以配置 appendfsync 属性, 有 always, 每进行一次命令操作就记录到 aof 文件中一次。

2、通过 append 模式写文件, 即使中途服务器宕机, 可以通过 redis-check-aof 工具解决数据一致性问题。

3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前( 文件过大时会对命令进行合并重写), 可以删除其中的某些命令( 比如误操作的 flushall))

2.2.2.5、缺点:

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

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

2.2.3、AOF和RDB两者比较:
  1. AOF文件比RDB更新频率高,优先使用AOF还原数据。
  2. AOF比RDB更安全也更大
  3. RDB性能比AOF好
  4. 如果两个都配了优先加载AOF

2.3、说一下redis持久化的过程?

载入 AOF 文件
创建模拟客户端
从 AOF 文件中读取一条命令
使用模拟客户端执行命令
循环读取并执行命令,直到全部完成
如果同时启用了RDB 和 AOF 方式,AOF 优先,启动时只加载 AOF 文件恢复数据

2.4、redis持久化方式你们是怎么选型的?

  • 一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
    有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的bug。
  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任
    何持久化方式。

2.5、了解过redis混合持久化吗?是一个文件还是多个文件?

如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的AOF 全量文件重放,因此重启效率大幅得到提升。
混合持久化AOF文件结构如下:

所以是一个文件。

2.6、redis进行持久化的时候会阻塞主线程吗?

不会,rdb会bsave异步,aof会fork一个子进程

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

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

2.8、Redis数据备份策略

  1. 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
  2. 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
  3. 每次copy备份的时候,都把太旧的备份给删了
  4. 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

3、redis 实现分布式锁

3.1、redis分布式锁了解吗?

redis的setnx

redssion

3.2、setnx的原理

使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除

3.3、假如我的业务代码执行时间不是很稳定?使用redis做分布式锁会有什么问题呢?(锁失效)

锁续命,

1、开启一个守护进程

  • 当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。当线程A执行完任务,会显式关掉守护线程。
    另一种情况,如果节点1 忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了

2、用redssion实现

3.4、你怎么解决锁超时的问题?有什么方案吗?跟zk的分布式锁有什么区别?

3.4.1、解决方案

lua脚本

3.4.2、区别

redis

  1. Rdis只保证最终一致性,副本间的数据复制是异步进行(Set是写,Get是读,Reids集群一般是读写分离架构,存在主从同步延迟情况),主从切换之后可能有部分数据没有复制过去可能会**「丢失锁」**情况,故强一致性要求的业务不推荐使用Reids,推荐使用zk。
  2. Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公有集群影响因素偏大),但是极限qps可以达到最大且基本无异常

zookeeper

  1. 使用ZooKeeper集群,锁原理是使用ZooKeeper的临时节点,临时节点的生命周期在Client与集群的Session结束时结束。因此如果某个Client节点存在网络问题,与ZooKeeper集群断开连接,Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此ZooKeeper也无法保证完全一致。
  2. ZK具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。

3.5、zk分布式锁实现原理

使用 ZooKeeper 的顺序节点特性,假如我们在/lock/目录下创建3个节点,ZK集群会按照发起创建的顺序来创建节点,节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003,最后一位数是依次递增的,节点名由zk来完成。

ZK中还有一种名为临时节点的节点,临时节点由某个客户端创建,当客户端与ZK集群断开连接,则该节点自动被删除。EPHEMERAL_SEQUENTIAL为临时顺序节点。

根据ZK中节点是否存在,可以作为分布式锁的锁状态,以此来实现一个分布式锁,下面是分布式锁的基本逻辑:

  1. 客户端调用create()方法创建名为“/dlm-locks/lockname/lock-”的临时顺序节点。
  2. 客户端调用getChildren(“lockname”)方法来获取所有已经创建的子节点。
  3. 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,就是看自己创建的序列号是否排第一,如果是第一,那么就认为这个客户端获得了锁,在它前面没有别的客户端拿到锁。
  4. 如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。

释放锁的过程相对比较简单,就是删除自己创建的那个子节点即可,不过也仍需要考虑删除节点失败等异常情况。

4、redis集群部署

4.1、redis集群部署模式有哪些,介绍一下

1、redis cluster(高可用集群部署,主从)

2、redis sentinel(哨兵模式)

3、基于客户端分配

4、基于代理服务器分片

4.2、介绍一下sentinel模式如何感知sever节点上下线的?

主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。

客观下线(主节点的概念):哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。

4.3、哨兵leader选举流程

当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。
不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。

4.4、sentinel的定时任务

sentinel机制中有三种重要的定时任务。

  1. 每10秒每个sentinel对master和slave执行info

    作用:

    • 发现slave节点。
    • 确认主从关系。
  2. 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)

    作用:

    • 互相通信掌握节点的信息和自身信息,可以感知新加入的sentinel

    通过master节点的__sentinel__:hello频道进行交互,所有sentinel订阅这个频道并每2秒向该频道发布信息

  3. 每1秒每个sentinel对其他sentinel和master,slave进行ping

作用:

  • 心跳检测

4.5、故障转移过程

  1. 当多个sentinel发现并确认了master有问题
  2. 接着会选举出一个sentinel作为领导
  3. 再选举出一个slave作为master
  4. 通知其余的slave,新的master是谁
  5. 通知客户端一个主从的变化
  6. 最后,sentinel会等待旧的master复活,然后将新master成为slave

那么,如何选择“合适”的slave节点呢?

  1. 选择slave-priority(slave节点优先级,人为配置)最高的slave节点,如果存在则返回,不存在则继续。
  2. 其次会选择复制偏移量最大的slave节点(复制得最完整),如果存在则返回,不存在则继续
  3. 最后会选择run_id最小的slave节点(启动最早的节点)

4.6、Redis集群原理分析

Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每
个节点中。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这
样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。

4.7、Redis集群选举原理分析

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master
可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

  1. slave发现自己的master变为FAIL
  2. 将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
  3. 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
  4. 尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
  5. slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
  6. slave广播Pong消息通知其他集群节点。
    从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票

    • 延迟计算公式:
      DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
    • SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。

4.8、槽位定位算法

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模
来得到具体槽位。
HASH_SLOT = CRC16(key) mod 16384

4.9、跳转重定位

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。

4.9、Redis集群节点间的通信机制

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

  • 维护集群的元数据(集群节点信息,主从角色,节点数量,各节点共享的数据等)有两种方式:集中
    式和gossip

    • 集中式:
      优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。 很多中间件都会借助zookeeper集中式存储元数据。

    • gossip:

      gossip协议包含多种消息,包括ping,pong,meet,fail等等。

      ==meet:==某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信;

      ==ping:==每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等);

      ==pong: ==对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新;

      fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。

      gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。

      gossip通信的10000端口
      每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。 每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping消息之后返回pong消息。

4.10、网络抖动

真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。
为解决这种问题,Redis Cluster 提供了一种选项cluster­node­timeout,表示当某个节点持续timeout的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)。

4.11、集群脑裂数据丢失问题

redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。
规避方法可以在redis配置里加上参数(这种方法不可能百分百避免数据丢失,参考集群leader选举机制):

1 min‐replicas‐to‐write 1  //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数

注意:这个配置在一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要具体场景权衡选择。

4.12、集群是否完整才能对外提供服务

当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从
库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。

4.13、Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中
一个挂了,是达不到选举新master的条件的。
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举。
新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。

4.14、redis-server的ping/pong协议了解吗?怎么用的?

4.14.1、ping

ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。

ping 消息深入

ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。

每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 cluster_node_timeout 可以调节,如果调得比较大,那么会降低 ping 的频率。

每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 3 个其它节点的信息,最多包含 总节点数减 2 个其它节点的信息。

4.14.2、pong

pong:返回ping和meeet,包括自己的状态和其他信息,也用于信息广播和更新。fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点说,某个节点宕机啦。

4.15、讲一下redis主从复制(全量复制),数据部分复制,主从复制(部分复制,断点续传)的过程?

4.15.1、主从复制(全量复制)流程图:

4.15.2、数据部分复制

当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。

4.15.3、主从复制(部分复制,断点续传)流程图:

如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如
下架构,让部分从节点与从节点(与主节点同步)同步数据

4.16、主从模式下假如主节点挂了之后?

会选举,具体看==》4.7、Redis集群选举原理分析

4.17、redis cluster模式下通过key查找value的过程了解吗?

5、redis高可用

5.1、redis cluster模式下如何扩容和缩容的?

第一种:水平扩容

第二种:高可用集群模式

5.2、如何解决redis的内存碎片问题?产生的原因是什么?

5.2.1、原因

常用的增删改redis都会产生一定的碎片。

1.写入数据

内存是根据分配策略固定的大小来划分内存空间的 , 为了减少分配次数,Redis 会根据申请的内存最接近的固定值分配相应大小的空间。

2.修改数据

键值对进行修改时,可能会变大也会变小,相应的就会占用额外空间或者释放不用的空间。

3.删除数据

删除某个value后,删除了字节,释放了空间。

5.2.2、危害性

在 Redis 中,由于大量的碎片存在,会导致实际利用率变低。

5.4.3、解决方案

1.版本低于4.0

如果你的Redis版本是4.0以下的,Redis服务器重启后,Redis会将没用的内存归还给操作系统,碎片率会降下来。

2.版本高于4.0

可以在不重启的情况下,线上整理内存碎片 ,自动碎片清理,只要设置了如下的配置,内存就会自动清理了。

config set activedefrag yes

下面参数都是满足任一条件后就可以进行清理:

active-defrag-ignore-bytes 100mb:碎片达到100MB时,开启清理。

active-defrag-threshold-lower 10:当碎片超过 10% 时,开启清理。

active-defrag-threshold-upper 100 :内存碎片超过 100%,尽最大清理。。

在处理的过程中,为了避免对正常请求的影响,同时又能保证性能。Redis 同时还提供了监控 CPU 占用比例的参数,在满足以下条件时才会保证清理正常开展:

active-defrag-cycle-min 5:清理内存碎片占用 CPU 时间的比例不低于此值,保证清理能正常开展。

active-defrag-cycle-max 75:清理内存碎片占用 CPU 时间的比例不高于此值。一旦超过则停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致其它请求延迟。

手动清理 :memory purge

查看内存 :info memory

​ 这里有一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率 。

​ mem_fragmentation_ratio = used_memory_rss/ used_memory

​ 大于1:说明内存有碎片,一般在1到1.5之间是正常的。

​ 大于1.5:说明内存碎片率比较大,需要考虑是否要进行内存碎片清理,要引起重视。

​ 小于1:说明已经开始使用交换内存,也就是使用硬盘了,正常的内存不够用了,需要考虑是否要进行内存的扩容。

5.3、redis的内存达到机器内存极限值之后有哪些策略可以避免?

  • a) 针对设置了过期时间的key做处理:
  1. volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删
    除,越早过期的越先被删除。
  2. volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  3. volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
  4. volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
  • b) 针对所有的key做处理:
  1. allkeys-random:从所有键值对中随机选择并删除数据。
  2. allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
  3. allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
  • c) 不处理:

​ 8、noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)OOM command not allowed when used memory",此时Redis只响应读操作。

5.4、redis数据(过期键)删除的策略有哪些?

  1. 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
  2. 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
  3. 当前已用内存超过maxmemory限定时,触发主动清理策略
  4. LRU 算法(Least Recently Used,最近最少使用):淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
  5. LFU 算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问次数最少的数据,以次数作为参考

5.5、redis的内存淘汰策略和过期键的删除策略怎么选?

当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。这时使用LFU可能更好点。
根据自身业务类型,配置好maxmemory-policy(默认是noeviction),推荐使用volatile-lru。如果不设置最大内存,当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap),会让 Redis 的性能急剧下降。
当Redis运行在主从模式时,只有主结点才会执行过期删除策略,然后把删除操作”del key”同步到从结点删除数据。

5.6、缓存击穿/缓存雪崩/缓存穿透/热点缓存key重建优化/缓存与数据库双写不一致

5.6.1、缓存击穿(失效)
5.6.1.1、原因

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,

5.6.1.2、解决方案

1、在批量增加缓存时将这一批数据的缓存过期时间设置为一个时间段内的不同时间。

2、分布式锁

5.6.2、缓存雪崩
5.6.2.1、原因

缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会像奔逃的野牛一样, 打向后端存储层。
由于缓存层承载着大量请求, 有效地保护了存储层, 但是如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降), 于是大量请求都会打到存储层, 存储层的调用量会暴增, 造成存储层也会级联宕机的情况。

5.6.2.2、解决方案

1) 保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster。
2) 依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。
3) 提前演练。 在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定。

5.6.3、缓存穿透
5.6.3.1、原因

缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。
造成缓存穿透的基本原因有两个:

  • 第一, 自身业务代码或者数据出现问题。
  • 第二, 一些恶意攻击、 爬虫等造成大量空命中。
5.6.3.2、解决方案

1、缓存空对象

2、布隆过滤器(redission里面有个getBloomFilter()方法实现,布隆过滤器不能删除数据,如果要删除得重新初始化数据)

5.6.4、热点缓存key重建优化
5.6.4.1、原因

开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害:

  • 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
  • 重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。

在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。
要解决这个问题主要就是要避免大量线程同时重建缓存。

5.6.4.2、解决方案

互斥锁(也就是所谓的分布式锁)

5.6.5、缓存与数据库双写不一致
5.6.5.1、原因

1、双写不一致情况

2、读写并发不一致

5.6.5.2、解决方案

1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
2、就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致,可以通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。
4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。

总结:

以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!

5.7、bigkey危害,原因,优化方案

5.7.1、危害

1.导致redis阻塞

2.网络拥塞

3.过期删除(4.0后可以设置过期异步删除 lazyfree-lazy-expire yes,解决大key)

5.7.2、原因

一般来说,bigkey的产生都是由于程序设计不当,或者对于数据规模预料不清楚造成的,来看几个例子:
(1) 社交类:粉丝列表,如果某些明星或者大v不精心设计下,必是bigkey。
(2) 统计类:例如按天存储某项功能或者网站的用户集合,除非没几个人用,否则必是bigkey。
(3) 缓存类:将数据从数据库load出来序列化放到Redis里,这个方式非常常用,但有两个地方需要注意,第一,是不是有必要把所有字段都缓存;第二,有没有相关关联的数据,有的同学为了图方便把相关数据都存一个key下,产生bigkey。

5.7.3、优化方案
  1. 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要
    hmget,而不是hgetall),删除也是一样,尽量使用优雅的方式来处理。

  2. 【推荐】:选择适合的数据类型。(批量操作:hmset,mset。。。)

  3. 【推荐】:控制key的生命周期,redis不是垃圾桶。(建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期)

6、Redis的单线程和高性能

6.1、Redis是单线程吗?

Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

6.2、Redis 单线程为什么还能这么快?

  • 它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如
    keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。
  • Redis完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,数据存在内存中。
  • 数据结构简单,对数据操作也简单。
  • 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的CPU切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。
  • 使用多路复用IO模型,非阻塞IO。

6.3、Redis 单线程如何处理那么多的并发客户端连接?

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

7、场景

7.1、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。

对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

下面是本人的公众号:(有兴趣可以扫一下,文章会同步过去)

我是小白弟弟,一个在互联网行业的小白,立志成为一名架构师
https://blog.csdn.net/zhouhengzhe?t=1

面试专题——Redis相关推荐

  1. HTTP协议——面试专题

    HTTP协议--面试专题 HTTP协议--面试专题 HTTP协议--面试专题 HTTP协议--面试专题 前言 一.http状态码和method知识点考查 1.常见的Http Method有哪些,使用场 ...

  2. Java开发者跳槽必备,面试:Redis为什么这么快呢

    void *ptr; -.. } redisObject除了记录实际数据,还需要额外的内存空间记录数据长度.空间使用等元数据信息,其中包含了 8 字节的元数据和一个 8 字节指针,指针指向具体数据类型 ...

  3. 阿里、京东、鹅厂、并夕夕、美团BATJ面试之Redis+多线程+JVM+微服务...

    非常非常不错的一篇面经文章,文中很多的面试题目都值得我们在面试前刷一遍,强烈推荐阅读3遍以上.如果觉得不错,可以收藏分享一下.(面经没有具体答案,需要大家一起来探讨学习一下.) 资料免费分享:帮忙转发 ...

  4. 2021最新腾讯面经分享:知识大纲+技术文档+面试专题

    背景 985毕业至今刚好一年,我曾做过两三个月的测试感觉不是很合适,后面选择从事后端开发,还挺香.现在已经进入秋招的提前批了,想着去大厂试试水,就去了腾讯,整个一面下来我整个人都傻了,表示怀疑人生.. ...

  5. hashmap中用红黑树不用其他树_HashMap面试专题:常问六题深入解析

    引言 其实我很早以前就想写一篇关于HashMap的面试专题.对于JAVA求职者来说,HashMap可谓是集合类的重中之重,甚至你在复习的时候,其他集合类都不用看,专攻HashMap即可. 然而,鉴于网 ...

  6. BAT Android面试专题深入探究:四大组件+ViewPager+组件化架构+Bitmap

    本篇是结合我之前面试别人的经验,以及跟一些在BAT上班的朋友,讨论总结出的一份很深的大公司需要用到的一些高端Android技术.这里也专门整理了一个文档,重点和难点都有详细解析.这些题目有点技术含量, ...

  7. php面试专题---2、常量及数据类型考点

    php面试专题---2.常量及数据类型考点 一.总结 一句话总结: 变量为null和变量判断为false的情况需要仔细注意下 1.PHP中字符串可以使用哪三种定义方法以及各自的区别是什么? 单引号:不 ...

  8. php面试专题---MYSQL查询语句优化

    php面试专题---MYSQL查询语句优化 一.总结 一句话总结: mysql的性能优化包罗甚广: 索引优化,查询优化,查询缓存,服务器设置优化,操作系统和硬件优化,应用层面优化(web服务器,缓存) ...

  9. mysql优化php面试_php面试专题---18、MySQL查询优化考点

    php面试专题---18.MySQL查询优化考点 一.总结 一句话总结: 慢查询:查找分析查询速度慢的原因 数据访问:优化查询过程中的数据访问 长难句:优化长难的查询语句 特定类型:优化特定类型的查询 ...

最新文章

  1. 用 go 实现跨平台 Autoit/AutoHotkey 和按键精灵功能示例代码
  2. 说一说限制字数的输入框踩的坑
  3. POJ2942 Knights of the Round Table 点双连通分量 二分图判定
  4. mysql 一个文章多个分类_jdbc mysql 插入一篇文章并与多个标签,一个分类建立关联关系。...
  5. 还原oracle控制文件位置,oracle 11.2 控制文件还原
  6. 计算机期末考试方案,初中信息技术期末考试方案.doc
  7. 阵列天线方向图-均匀直线/平面阵列matlab仿真
  8. java判断闰年的方法_Java判断闰年的2种方法示例|chu
  9. win10天气是英文的
  10. 新装的服务器wincc上一些图形不显示,wincc画面无法全部显示
  11. c java多态_浅谈Java多态
  12. 一起来捉妖找不到服务器,一起来捉妖妖怪分布大全 所有妖灵不同地点位置详解...
  13. js 批量坐标转换经纬度_如何批量转换为百度经纬度
  14. Swift入门笔记(二)
  15. OSI七层模型的功能及协议
  16. 1500ml等于多少l_1500毫升是多少升
  17. dockers存储卷
  18. php获取今日、昨日、本周、本月 日期方法
  19. Python 之 列表推导式
  20. 云呐数据备份|什么是离线磁带设备

热门文章

  1. 马尔可夫链(Markov chain)的性质
  2. 搭建turnserver(转) 稍加整理
  3. Vmware虚拟化概念原理
  4. 中国二次锂离子电池电解液市场需求现状与销售策略分析报告2022-2028年
  5. 什么是PON(无源光纤网络)、PON的发展及演进
  6. WAP手机安全上网防病毒攻略
  7. JavaScript给网页添加水印
  8. 《盗梦空间》完全解析
  9. 人在四合院:我变成了何雨柱(二)
  10. 斯坦福神经调控疗法(Stanford Neuromodulation Therapy, SNT)