Redis进阶
一、redis底层IO多路复用
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都
是阻塞的
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个
描述符就绪,能够通知程序进行相应的操作。
epoll实现原理
epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的
1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3)调用epoll_wait收集发生的事件的连接
如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象
中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复
制这100万个连接的句柄数据,内核也不需要去遍历全部的连接
epoll底层实现
二、redis内存模型
127.0.0.1:6379> info memory
# Memory
#Redis分配的内存总量,包括虚拟内存(字节)
used_memory:853464
#占操作系统的内存,不包括虚拟内存(字节)
used_memory_rss:12247040
#内存碎片比例 如果小于0说明使用了虚拟内存
mem_fragmentation_ratio:15.07
#Redis使用的内存分配器
mem_allocator:jemalloc-5.1.0
三、redis数据类型底层实现
3.1string
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GcgL08a6-1625897075407)(G:\Data\java-growup\java-growup\数据库\Redis\assets\1624414781142.png)]
struct sdshdr{
//记录buf数组中已使用字节的数量 //等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[]; }
简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示。
优点
- O(1)获取长度: C字符串需要遍历而sds中有len可以直接获得;
- 防止缓冲区溢出bufferoverflow: 当sds需要对字符串进行修改时,首先借助于len和alloc检查空间是否满足修改所需的要求,如果空间不够的话,SDS会自动扩展空间,避免了像C字符串操作中的覆盖情况;
- 有效降低内存分配次数:C字符串在涉及增加或者清除操作时会改变底层数组的大小造成重新分配、sds使用了空间预分配和惰性空间释放机制,说白了就是每次在扩展时是成倍的多分配的,在缩容是也是先留着并不正式归还给OS,这两个机制也是比较好理解的;
- 二进制安全:C语言字符串只能保存ascii码,对于图片、音频等信息无法保存,sds是二进制安全的,写入什么读取就是什么,不做任何过滤和限制;
3.2list
typedef struct listNode {
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点的值
void *value;
}listNode
typedef struct list {
//表头节点
listNode.head;
//表尾节点
listNode.tail;
//链表所包含的节点数量
unsigned long len;
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void *(*free)(void *ptr);
//节点值对比函数
int (*match)(void *ptr,void *key);
} list;
Redis链表优势:
①、双向:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
与传统链表(单链表)相比,Redis链表结构的优势有:
普通链表(单链表):节点类保留下一节点的引用。链表类只保留头节点的引用,只能从头节点插入删除
②、无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL结束。
③、带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
④、多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。
3.3hash
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q2cYbzYl-1625897075409)(G:\Data\java-growup\java-growup\数据库\Redis\assets\1624414835273.png)]
Redis 的字典使用哈希表作为底层实现。
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于
size-1 unsigned long sizemask; //该哈希表已有节点的数量
unsigned long used;
}dictht
/*哈希表是由数组 table 组成,table 中每个元素都是指向 dict.h/dictEntry 结构, dictEntry 结构定义如下: */
typedef struct dictEntry{
//键
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下一个哈希表节点,形成链表
struct dictEntry *next;
}dictEntry
四、redis乐观锁实现秒杀
public class Second{
public static void main(String[] args){
String rediskey="second";
ExecutorService executorService = Executors.newFixedThreadPool(20);
try {
Jedis jedis = new Jedis("127.0.0.1", 6378);
// 初始值
jedis.set(redisKey, "0"); jedis.close();
} catch (Exception e) { e.printStackTrace();
}
}
}
五、redis应用场景
内存数据库 不存在DB中 登录信息、浏览记录、购物车、关注模型
缓存服务器 缓存DB信息减少DB压力,商品数据信息 代码表
session存储 多个应用服务器
任务队列 list 秒杀 请求限流
分布式锁 set nx
应用排行 zset
数据过期 冷热数据 expire
六、redis和lua脚本整合
七、Redis哨兵
Redis 主从复制的缺点:没有办法对 master 进行动态选举(master宕机后,需要重新选举master),需要使
用 Sentinel机制完成动态选举。
哨兵进程的作用:
监控:哨兵(sentinel)会不断金叉Mater和slave是否运作正常
提醒:当监控的某个redis节点出现问题,哨兵(sentinel),可以想管理员或其他应用程序发送通知
自动故障迁移:当一个 Master 不能正常工作时,哨兵****( sentinel ) 会开始一 次自动故障迁移操作
自动故障迁移
它会将失效 Master 的其中一个 Slave 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复制新的 Master ;
当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用现在的 Master 替换失效 Master 。
Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即, Master 主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置, sentinel.conf 的监控目标会随之调换。
八、redis集群
1、主节点投票,如果超过半数的主都认为某主down了,则该主就down了 (主选择单数)
redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即, Master 主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置, sentinel.conf 的监控目标会随之调换。
八、redis集群
1、主节点投票,如果超过半数的主都认为某主down了,则该主就down了 (主选择单数)
2、主节点投票,选出挂了的主的从升级为主