目录
一.用户空间和内核空间
为什么划分出了用户态和内核态:
操作系统是应用程序和硬件打交道的桥梁,应用程序通过操作系统提供的用户接口程序间接的和硬件打交道。硬件就包括了像CPU,内存,IO设备等,硬件之上的内容称为软件,但是不可能让用户或者应用程序随意操作我们的硬件。在CPU的所有指令中,有些指令是非常危险的,如果错用会导致系统崩溃,从程序运行的安全性方便考虑,又将程序的运行状态划分成用户态和内核态,将两者隔离开。操作系统属于内核态,而操作系统提供的用户接口程序和应用程序都属于用户态。
用户态和内核态的区别:
内核态:在内核中运行的所有程序是可以访问所有的硬件资源的,可以在硬件上执行各种指令。权限较高。
用户态:只能执行一部分指令,对于影响系统稳定运行的程序是不允许执行的,当然IO指令也是不允许执行的,权限较低。
用户态和内核态的切换:
当一个进程在执行的过程中,执行的业务较多的情况下,会执行一些普通的命令也可能会执行一些特权命令,这时也就涉及到了用户态到内核态的切换。以下面一个IO读写来举例子:IO读写涉及到磁盘。
读时:读的时候,如果还没有数据就得等待磁盘的数据过来,等数据来了读到内核空间的缓冲区中,再拷贝回我们的用户空间。
写出到磁盘:就要经过内核,但内核中没有我们的数据,所以就需要从用户空间拷贝到内核空间,然后写入到磁盘
从上面的图中可以看出,想要提高IO效率,主要是两个方面:一个是等待一个是拷贝
1.在读数据时,当没有数据过来的时候,会出现等待。
2.在读写数据的时候,需要从用户空间的缓冲区拷贝到内核空间的缓冲区,或者反过来,都是比较影响效率的。
所以后续的5种IO模式都是为了解决上述的两点问题来展开(减少等待,减少拷贝)的。接着往下看。
二.阻塞IO(BIO)
三.非阻塞IO
不断的询问,有没有数据。 有没有,有没有。
四.***IO多路复用***
1.介绍
2.Select
select模式存在的缺点
1.需要将整个fd_set从用户空间拷贝到内核空间,select结束还要再次拷贝回用户空间
2.无法得知具体是哪个fd就绪了,需要遍历整个集合
3.监听的fd数量不能超过1024
3.poll
4.***epoll***
1.介绍与对比
2.事件通知机制
3.Web服务流程
serversocket监听端口,如6379等待客户端的连接。
5.异步IO
在高并发的场景下,老板就不管员工的死活,疯狂派任务。IO读写是很慢的,容易崩溃。
6.总结
五.Redis网络模型
1.redis是单线程还是多线程
先问讨论的是什么部分,是核心业务处理部分还是整个redis。
2.网络模型
1.单线程下的网络模型(6.0之前)
先主要讲解一下client来了读事件怎么解决:
当客户端来了读事件,Redis会将它封装成一个client实例(包含了客户端的所有信息,包括请求),redis给客户端有里面输入和输出的缓冲区(后面要用),输入缓冲区中缓存了客户端请求参数,但我们读到缓冲区的是一个个的字节,所以还需要将它解析,比如说set name jack,会将它解析成一个个的SDS字符串,然后把它放一个argv数组中,然后通过拿到argv[0],也就是第一个位置的命令,找到可执行的函数,因为在redis内部封装了一个个command函数,维护了一个字典,k就是命令,v就是函数。找到这个函数后,就可以执行命令,也会有对应的响应,比如说ping就是pong,set就是ok。但是并没有将它直接写出,而是放到了client中的输出缓冲区中,然后将一个个的client实例放入了一个队列中,这个队列维护的就是一个个要写出的客户端socket。
这时beforeSleep就派上了用场,在epool_wait之前,beforeSleep会给每一个客户端绑定一个写处理器,就可以将一个个clientsocket写出了。
本质上就是IO的多路复用加时间派发。不断监听,监听serverSocket,监听clientSocket,然后派发下去,总共事件就3种,server读,client读,client写,分别派发给不同的处理器去处理。
2.多线程(6.0后哪些地方变成了多线程)
两个地方涉及到了多线程,一个是从客户端读取数据,解析数据时,另一个就是往客户端写数据。涉及到网络读写IO(涉及网络带宽,网络状态的影响)都很慢,就开启了多线程。
3.通信协议
1.RESP协议
以首字节来区分不同类型
六.内存回收
1.过期策略
1.Redis怎么知道一个key过期了没有
*expire中存的是设置了过期时间的节点
回答上面的问题:
根据key来查就可以了。
2.是不是TTL到期了就立即删除呢
1.惰性删除
存在的问题:如果一个key已经过期了,但我一直没有去访问它(没有进行CRUD的操作),那这个key不就永远不会被删除了嘛
2.周期删除
2.内存淘汰策略
不是过期了才删了,内存不够用了,主动去删。
1.LRU(最久未使用的)
手写LRU算法实现
思路:需要一张哈希表用来存放元素,一个双向链表来看哪些元素长时间未被使用(只要出现CRUD就将元素放到链表的头部)那么链表最后的元素就是最久未使用的元素了。
static class LRUCache {
//节点类
static class Node {
int key;
int value;
Node prev;
Node next;
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
//双向链表
static class DoublyLinkedList {
Node head;
Node tail;
public DoublyLinkedList() {
head = tail = new Node();
head.next = tail;
tail.prev = head;
}
//添加节点到头部
public void addFirst(Node node) {
Node oldNode = head.next;
node.prev = head;
node.next = oldNode;
head.next = node;
oldNode.prev = node;
}
//删除节点
public void remove(Node node) {
Node prev = node.prev;
Node next = node.next;
prev.next = next;
next.prev = prev;
}
//删除链表尾节点
public Node removeLast() {
Node last = tail.prev;
remove(last);
return last;
}
}
private final Map<Integer, Node> map = new HashMap<>();
private final DoublyLinkedList list = new DoublyLinkedList();
private int capacity;
public LRUCache(int capacity){
this.capacity = capacity;
}
//获取节点
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
Node node = map.get(key);
list.addFirst(node);
return node.value;
}
//新增节点,存在就更新,不存在就新增
public void put(int key , int value){
if(map.containsKey(key)) { // 更新
Node node = map.get(key);
node.value = value;
list.remove(node);
list.addFirst(node);
} else { // 新增
Node node = new Node(key, value);
map.put(key, node);
list.addFirst(node);
if (map.size() > capacity) {
Node removed = list.removeLast();
map.remove(removed.key);
}
}
}
}
2.LFU(最不经常使用)
手写LFU算法实现
思路:需要两张哈希表,一张哈希表用来存放元素,另一张哈希表以执行的次数为key,存放数据,value以双向链表的方式来存(只要出现CRUD就将元素放到链表的头部)。在超过容量时,需要删除元素,找到该表中最小的存放元素的key中最后一个元素删除即可。
package com.itheima.leetcode;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* <h3>设计 LFU 缓存</h3>
*/
public class LFUCacheLeetcode460 {
static class LFUCache {
static class Node {
Node prev;
Node next;
int key;
int value;
int freq = 1; // 频度
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
static class DoublyLinkedList {
Node head;
Node tail;
int size;
public DoublyLinkedList() {
head = tail = new Node();
head.next = tail;
tail.prev = head;
}
// 头部添加 head<->tail
public void addFirst(Node newFirst) { // O(1)
Node oldFirst = head.next;
newFirst.prev = head;
newFirst.next = oldFirst;
head.next = newFirst;
oldFirst.prev = newFirst;
size++;
}
// 已知节点删除
public void remove(Node node) { // O(1)
Node prev = node.prev;
Node next = node.next;
prev.next = next;
next.prev = prev;
size--;
}
// 尾部删除
public Node removeLast() { // O(1)
Node last = tail.prev;
remove(last);
return last;
}
public boolean isEmpty() {
return size == 0;
}
}
private HashMap<Integer, Node> kvMap = new HashMap<>();
private HashMap<Integer, DoublyLinkedList> freqMap = new HashMap<>();
private int capacity;
private int minFreq = 1; // 最小频度
public LFUCache(int capacity) {
this.capacity = capacity;
}
/*
key 不存在
返回 -1
key 存在
返回 value 值
增加节点的使用频度,将其转移到频度+1的链表当中
*/
public int get(int key) {
if (!kvMap.containsKey(key)) {
return -1;
}
Node node = kvMap.get(key);
DoublyLinkedList list = freqMap.get(node.freq);
list.remove(node);
if (list.isEmpty() && node.freq == minFreq) {
minFreq++;
}
node.freq++;
freqMap.computeIfAbsent(node.freq, k -> new DoublyLinkedList())
.addFirst(node);
return node.value;
}
/*
更新
将节点的 value 更新,增加节点的使用频度,将其转移到频度+1的链表当中
新增
检查是否超过容量,若超过,淘汰 minFreq 链表的最后节点
创建新节点,放入 kvMap,并加入频度为 1 的双向链表
*/
public void put(int key, int value) {
if(kvMap.containsKey(key)) { // 更新
Node node = kvMap.get(key);
DoublyLinkedList list = freqMap.get(node.freq);
list.remove(node);
if (list.isEmpty() && node.freq == minFreq) {
minFreq++;
}
node.freq++;
freqMap.computeIfAbsent(node.freq, k -> new DoublyLinkedList())
.addFirst(node);
node.value = value;
} else { // 新增
if (kvMap.size() == capacity) {
Node node = freqMap.get(minFreq).removeLast();
kvMap.remove(node.key);
}
Node node = new Node(key, value);
kvMap.put(key, node);
freqMap.computeIfAbsent(1, k->new DoublyLinkedList())
.addFirst(node);
minFreq = 1;
}
}
}
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 1 freq=2
cache.put(3, 3);
System.out.println(cache.get(2)); // -1
System.out.println(cache.get(3)); // 3 freq=2
cache.put(4, 4);
System.out.println(cache.get(1)); // -1
System.out.println(cache.get(3)); // 3 freq=3
System.out.println(cache.get(4)); // 4 freq=2
}
}