0%

面试知识点

网络基础

大端小端网络字节序

    ”大端”,”小端”指的是表示多字节的值哪一端存储在该值的起始地址处;多字节小端存储起始地址处称为小端字节序,多字节的大端存储在起始地址处称为大端字节序。
    通俗的说就是。

    常用的X86架构就是采用的小端存储,最高有效位在最高位地址。

    大端字节序:最高有效位存于最低的内存地址,最低的有效位存于最高的内存地址。
    小端字节序:最高有效位存于最高的内存地址,最低的有效位存于最低的内存地址。

image-20201220131133527

网络字节序,网络上传输的数据都是字节流。**UDP/TCP/IP协议规定:**把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节。而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
union 联合体成员存放的顺序都是从低地址开始存放,并且是所有的成员共享同一个内存空间,
可以利用这个特性进行一个大小端的判断。
*/

/* 定义联合体结构 */
typedef union _testUnion{
int a;
char b;
}testUnion;

/* 判断大小端 */
int isBigEndStorage()
{
testUnion a;
a.a = 0x01;
return a.b;
}

int main(int argc, char *argv[])
{
if(1 == isBigEndStorage())
printf("小端存储");
else
printf("大端存储");
return 0;
}

tcp三次握手

    为什么建立连接需要三次握手?TCP的两次握手是最基本的,第一次握手,客户端发了个连接请求消息到服务端,服务端收到信息后知道自己与客户端是可以连接成功的,但此时客户端并不知道服务端是否已经接收到了它的请求,所以服务端接收到消息后的应答,客户端得到服务端的反馈后,才确定自己与服务端是可以连接上的,这就是第二次握手。
    客户端只有确定了自己能与服务端连接上才能开始发数据。所以两次握手肯定是最基本的。
    到了这,有人肯定又有疑问,两次握手不就可以了吗,为什么需要第三次握手。其实,第三次握手主要的原因是防止已经失效的连接请求报文由于某种原因突然又回到服务端,从而就会产生错误。比如,一个请求的数据包,因为某些原因滞留在网络中很久,一直到了连接释放的时候才到达服务端,这时候,服务端以为是客户端的第一次连接回应了客户端从而建立了连接,实际上是不必要的连接客户端并没有任何数据要发送,当存在大量的这种情况下,会造成很大的资源浪费。所以需要第三次握手,只有客户端再次回应一下,就可以避免这种情况。

image-20201220131233527

TCP与UDP的概念

    TCP:一种面向连接的,可靠的,基于字节流的通信协议。
    UDP:提供无连接的通信,不可靠的,基于数据报的通信协议。
两者的主要区别是
    (1)TCP是面向连接的传输控制协议,而UDP是提供无连接的数据报服务。
    (2)TCP具有高可靠性,能确保数据的完整性和正确性。
    (3)TCP需要的系统资源比较多,而UDP需要的就相对少一些。
    (4)UDP实时性比较高,低延迟,但是TCP的工作效率相对高一些。

UDP调用connect函数作用

    UDP调用connect函数的作用,并不会引起和服务器目标端的网络交互,也就是说并不会触发所谓的”握手🤝“报文的过程。其主要的作用是让应用程序能够接收到”异步错误“的信息。假设在服务器不开启的情况下,客户端程序是不会报错的,程序会一直阻塞在recvfrom函数上,等待返回或者超时。如果将UDP套接字进行connect绑定,将UDP套接字建立“上下文”,那么这时候操作系统内核接收到的信息就可以和相应的套接字进行相关联。

TCP如何设定超时时间

image-20201220131433527

如果TCP握手🤝的SYN超时按照上图来backoff。在Linux中,net.ipv4.tcp_syn_retries参数表示建立TCP连接时SYN报文重试的次数,最多会尝试7次。2的7次幂减去1刚好是127。我们将net.ipv4.tcp_syn_retries参数改成1,就可以将connect超时时间改成3秒。

    tcp设置发送和接收超时,可以通过SO_SNDTIMEO和SO_RCVTIMEO的选项来设置超时。

SYN flood洪水攻击

    正常的情况下,TCP需要经过三次握手才能建立连接。于是就出现了对握手🤝,过程进行的攻击。通过发送大量的SYN数据包,服务器响应(SYN+ACK)包。但是这个时候,攻击者不会回应ACK数据包,这个时候服务器不知道发送的(SYN+ACK)包是否发送成功,默认情况下会重试5次(tcp_syn_retries)。这样的话,对于服务器的内存、带宽都会有消耗。如果处于公网地址,攻击者伪造大量的报文进行攻击,那么如果服务器没有任何防护设施的话,可能会直接导致服务器连接资源耗尽,导致拒绝对外服务。

    从服务器防御的角度来看,可以采取以下措施。

   (1)对内核参数进行调优。

   (2)利用防火墙禁止掉部分IP。

    Linux内核参数调优主要从下面几个方面进行:

   (1)限制SYN并发的数量,超时时间

   (2)增大tcp_max_syn_backblog

    当半连接的请求数量超过了tcp_max_syn_backlog时,内核就会启用SYN cookie机制,不再把半连接请求放到队列里,而是用SYN cookie来检验。

   (3)减小tcp_synack_retries

   (4)启用tcp_syncookies

     SYN cookie是非常巧妙地利用了TCP规范来绕过了TCP连接建立过程的验证过程,从而让服务器的负载可以大大降低在三次握手中,当服务器回应(SYN + ACK)包后,客户端要回应一个n + 1的ACK到服务器。其中n是服务器自己指定的。当启用tcp_syncookies时,backlog满了后,linux内核生成一个特定的n值,而不并把客户的连接放到半连接的队列backlog里(即没有存储任何关于这个连接的信息,不浪费内存)。当客户端提交第三次握手的ACK包时,linux内核取出n值,进行校验,如果通过,则认为这个是一个合法的连接。

tcp 粘包半包问题怎么处理?

粘包与分包的处理方法:

(1)一个是采用分隔符方式。在封装要传输的数据包的时候,采用的固定的符号作为结束符🔚。这样接收到的数据中,如果接收到数据后,出如果出现结尾标识,即进行人为的将粘包分开,如果一个包中没有我们定义的结尾标识符,则是人为出现了分包,则此时我们需要等待下一个数据包进行组包。如HTTP协议以\r\n结尾。

(2)在数据包中添加长度的方式。在数据包的头部或者某个固定的位置封装一个数据包的长度信息。当收到数据包之后,先解析长度然后按照长度截取数据包。

(3)

域套接字比流式套接字快的原因

域套接字用于同一台计算机上进程间的通信,

1、它仅仅只是复制数据.。

2、不执行协议的处理,不需要增加或删除网络报头。

3、不进行检验和的计算,也不产生序列号,不需要进行校验和的计算

4、无需发送确认报文

tcp的socket怎么收取数据的,recv的返回值。

TCP收取数据的时候,可以使用函数recv或者read对数据进行读取。

一般的返回值有:

ret > 0 成功从内核缓冲区读取到的数据大小

ret = 0 TCP连接已经关闭

ret=-1 错误,需要读取错误码进行判断

主要的错误码(errno)有:

EINTR:操作被信号中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while(1)
{
cnt = (int)recv(m_socket, pBuf,RECVSIZE, 0);
if( cnt >0 )
{
//正常处理数据
}
else
{
if((cnt<0) &&(errno == EAGAIN||errno == EWOULDBLOCK||errno == EINTR))
//这几种错误码,认为连接是正常的,继续接收
{
continue;//继续接收数据
}
break;//跳出接收循环
}
}

TCP慢启动、拥塞控制、快重传、快恢复

操作系统

进程和线程

    进程:进程是具有一定独立功能的程序关于某一个数据集合的一心运行活动,他是系统进程资源分配和调度的一个基本单位。
    线程:线程是进程的一个实体,他是CPU分配和调度的基本单位。
线程的优点
    (1)易于调度。
    (2)线程可以提供并发性,利用线程可以有效的实现并发。
    (3)线程开销小。
    (4)有利于发挥对处理器的优点。通过创建多线程,每个线程可以不同的处理器上运行,从而实现应用程序的并行,使得每个处理器都能得到充分发挥。
进程与线程的区别
     (1) 一个线程属于一个进程,而一个进程可以有多个线程。
    (2)一个进程的所有线程共享一个进程的所有资源,这些资源包括有打开的文件,创建的socket,不同的进程是相互独立的
    (3)线程是轻量级进程,进程有进程控制块,线程也有线程控制块。但是线程控制块比进程控制块小的很多,线程切换代价小,进程空间切换代价大,线程空间切换代价小。

僵尸进程

定义
    ps 命令观察进程的执行状态的时候,看到状态栏为defunct状态的进程,这些就是所谓的僵尸进程。
僵尸进程的危害
    进程表中占用一个位置(slot),由于进程表的容量是有限的,所以defunct进程不仅会占用系统资源,影响系统的性能,如果数目太多的话会导致系统的崩溃。
僵尸进程产生的原因
    (1)操作系统对所有的进程维护一张进程表,每一个进程在进程表中都有一个entry(进入点),核心程序在执行该进程时候所使用的一切信息都是存储在entry(进入点)中,ps命令查看的就是这个进程表中的相关数据。
    (2)当一个父进程fork()系统调用建立一个新的进程之后,核心进程就会在进程表中给子进程分配一个entry(进入点),然后将这些信息存放在对应的进程表内,这些信息中有一项就是其父进程的识别码。
    (3)当子进程结束之后,其实该没有被真正的销毁,而此时进程表中的数据会被该进程的退出码(exit code),执行所需要的的CPU时间等等数据结构所取代,这些数据会一直保存到父进程读取为止。
    (4)此时,该进程几乎已经放弃所有的把内存空间,没有任何可执行代码,同时也不能被调度,仅仅只是在进程表中保留一个位置,除此之外该进程不占用任何存储空间。该父进程一直没有注册SIGCHILD信号处理函数调用wait或者waitpid等待子进程结束,或者也没有注册忽略该信号,那么这些进程将会变成僵尸进程(zombie),如果父进程一直处于循环状态,系统中就会有很多的僵尸进程。
解决的方法
    (1)重启服务器电脑,这个是最简单,最易用的方法,但是如果你服务器电脑上运行有其他的程序,那么这个方法,代价很大。
    (2)找到僵尸进程的父进程,将父进程杀掉。ps -ef |grep defunct_process_id。
如何防止僵尸进程
    (1)父进程fork之前注册之前忽略SIG_CLILD信号,忽略子进程退出相关的信息。调用函数:signal(SIGCHILD, SIG_IGN)。
    (2)父进程调用wait,waitpid函数收集子进程退出的状态码。
    (3)在父进程创建子进程的时候,连续调用两次fork(),而且使紧跟的子进程直接退出,使其孙子进程成为孤儿进程,从而init进程将代替父进程来接手,负责清除这个孤儿进程,系统自动回收。
##死锁是怎么样产生的##
死锁的定义
    系统中有若干个进程在并发运行,他们不断的申请和释放资源,在这一个过程中,由于争夺资源而处于无限期的等待资源的状态,此时导致程序无法继续进行,若无外力的作用,他都将无法推进下去,这是系统处于死锁状态,或者系统产生了死锁。
产生死锁的原因
    (1)系统的资源不足,例如多个打印机,但是由于纸张不够,进程推进不下去,产生了死锁。
    (2)进程推进顺序不对。
    (3)资源的分配不当。
产生死锁的条件
    (1)互斥性,每个资源每次只能被一个进程使用。
    (2)请求与保持等待,当一个进程因为请求资源而被阻塞等待时,对已经获得的资源保持不释放。
    (3)不可剥夺,进程已经获得资源,未使用完之前,不可被其他剥夺。
    (4)环路等待,若干进程之间 形成首尾相接的的等待资源的关系。

CPU的执行方式

image-20201220131633527

CPU的工作主要分为五个阶段:
1、取指令(IF,instruction fetch)。将一条程序执行指令从主存中取出来放到指令寄存器的一个过程。
2、指令译码阶段(ID,instruction decode)。取出指令之后,指令译码器按照预定的指令格式对取回的指令进行拆分和解释,识别区分不同的指令类别以及获取各种操作数的方法。
3、执行指令的阶段(EX,execute)。具体实现指令的功能,CPU的不同部分被连接起来,以执行所需的操作。
4、访问存取数的阶段(MEM,memory)。根据指令的需要访问主存,读取操作数,CPU得到操作数在主存的地址,并从主存中读取该操作数用于运算。
5、结果回写阶段(WB,write back)。作为最后一个阶段,结果写回阶段把指令的运行结果数据“写回”到某种存储形式。

资料参考
[1] https://www.jianshu.com/p/05c6c1d73144
[2] https://www.jianshu.com/p/bfff5d0e718e

共享内存

资料参考
[1] https://blog.csdn.net/Al_xin/article/details/38602093

堆栈

image-20201220131733527

32位系统0-4G地址空间,用户空间内存,从低到高分别是五种不同的内存段。
1、只读段,主要包括有代码和一些常量。
2、数据段,主要包括全局变量。
3、堆,主要包括分配的内存,从低地址开始向上增长。
4、文件映射段,主要包括动态库、共享内存等,从高地址向下增长。
5、栈,包括局部变量,函数的调用的上下文等,栈的大小一般是固定的,一般是8M。
在这五个内存段中,堆和文件映射段是动态内存分配的,malloc或者mmap等。
堆是线程私有还是共有?栈呢?
1、在多线程环境下,每个线程拥有一个栈和一个程序计数器。
2、栈和程序计数器用来保存线程的执行历史和线程的执行状态,是线程私有的资源。
3、其他的资源(比如堆、地址空间、全局变量)是由同一个进程内的多个线程共享。

资料参考
[1] https://time.geekbang.org/column/article/74272

协程

协程,指的就是微线程。是应用层的一种概念。

    协程最大的优势就是具有极高的执行效率。可以把协程看做是一种子程序,因为子程序的切换不是进程也不是线程的切换,因此没有进程或者线程的切换的开销。

列出常见的信号,并解释下信号是怎么处理的。

    linux 系统下可以使用命令kill -l查看常见的信号。

    信号实际上就是一种很短的信息,信号可以被发送到一个进程或者一组进程。

    不可靠信号:指的是非实时的信号📶,其中1~31信号就是不可靠信号。

    可靠信号:指的是实时的信号,其中32~64信号是可靠信号📶。

    可靠信号与不可靠信号的区别在于:不可靠信号不支持排队,可能会造成信号丢失,但是可靠信号不会。如果一个不可靠的信号📶被连续发送多次,那么只有其中的一个会被发送到接收进城。

    内核给进程发送信号📶,实在进程所在的进程表项的信号阈设置相应的信号的位。

    进程检查信号的时机是:进程即将从内核态返回用户态的时候。如果进程睡眠了,要看睡眠能不能被中断,如果能被中断则是进行唤醒。

i++或者++i是否是原子操作,为什么。

    这个不是原子操作。因为i++这个可以在拆分为三个过程。

    1、内存到寄存器

    2、寄存器自增

    3、写回内存。

    这其中的任何一个阶段都有可能会被中断。

    ++i这个其实在多核处理器上,CPU在读取内存时候,也会可能发生同时读取到一个值,这样其实也会导致两次自增,实际上只是增加了一次。

    所以,i++和++i都是不是原子操作。

linux下的同步机制,以及死锁,如何避免死锁。

    1、原子操作。原子操作不会被任何事物所打断,通常用于资源的计数,引用的计数。例如有TCP/IP协议栈的IP碎片计数。

    2、信号量。就像房间内有好几把钥匙🗝,拿到钥匙就去访问。设置为1的时候变为了mutex。绝大多数部分可以作为互斥锁来进行使用。

    3、读写信号量。可以允许多个读,一个写。一旦有人在写,就大家都不可以读取,如果没有在写,可以允许多个人进行读取。

    4、锁🔐。自旋锁和互斥锁的区别在于是否会进行休眠。如果自旋锁被其他执行单元持有,那么调用者就会一直在那自旋,循环等待资源的释放。在持有时间很短的情况下一般使用自旋锁会比互斥锁稍微高效一些。

死锁产生的必要条件:

    1、资源一定是互斥的。

    2、资源是不可抢占的。

    3、占有且需要申请的。

    4、需要循环等待的。

exit和_exit(_EXIT)的区别

    _exit和_EXIT函数调用时候立即进入内核,exit函数则是会先进行一些清理处理,然后再返回内核。

    exit函数总是会先执行一个标准的I/O库的清理关闭操作,对于所有打开流调用fclose函数,会将输出缓冲区中的所有数据都被冲洗。

    _exit函数调用则是直接关闭文件📃,文件缓冲区中的内容也就直接消失了,这个时候是不会再输出到显示设备了。

linux内存管理机制

    linux操作系统采用虚拟内存管理技术,将不同进程的虚拟地址空间和不同的内存物理地址映射起来,使得每个进程都有各自互不干扰的进程地址空间。

    在32位的系统上该空间的大小为4G的现行虚拟空间,用户所看到的或者接触的都是虚拟地址空间,并不会看到实际的物理地址。操作系统引入了虚拟内存,那么进程持有的虚拟地址就会通过CPU的内存管理单元(MMU)的映射关系,转化成物理地址,然后再通过物理地址进程访问。只有那些实际使用的UNINEICUN才分配物理内存,并且分配之后的物理内存是通过内存映射来管理的。

    内存映射,其实就是讲虚拟内存地址映射到物理内存地址,为了完成内存映射,内核为每一个进程都维护了一张页表,记录虚拟地址与物理地址之间的映射关系。页表实际上存储在CPU的内存管理单元MMU中,这样情况下,处理器可以直接通过硬件找出需要访问的内存。当进程访问虚拟地址在页表中查询不到的时候,系统就会产生一个缺页异常,进入到内核空间进行分配物理内存,更新进程页表,最后返回到用户空间,回复进程的运行。

image-20201220131833527

MMU并不是以字节为单位来管理内存的,而是规定了一乐内存映射的最小单位,页。通常是4KB大小,这样每一个内存映射,都需要关联4KB或者4KB的整数倍的内存空间。

多级页表

    多级页表就是把内存分成区块来进行管理,将原来的映射关系改成区块索引和区块偏移。由于虚拟地址空间通常只用很少的一部分,那么多级页表就只保存这些使用的区块,这样就可以大大的减少页表的项数。

Linux是用过四级页表来管理内存的,如下图。虚拟地址被分为5个部分,前四个表项用于选择页,而最后一个表项这是索引表示页内偏移。

image-20201220131933527

再看大页,大页指的是比普通页更大的内存块,常见的大小有2MB和1GB。大页通常用在使用大量内存的进程上,比如Oracle、DPDK等。

内存的分配与回收♻️

    在C标准库中,malloc是其提供的内存分配函数,对应的系统调用上主要有两种实现的方式,即brk()和mmap()。

    (1)对于小块的内存,C标准库使用brk()来进行分配,也就是通过移动堆顶的位置来进行分配内存,这些内存释放之后不会立刻归还给系统,而是会被缓存起来,这样可以重复使用。

    (2)对于大块的内存(大于128K),则是使用内内存映射mmap()来进行分配,也就是在文件映射段找到一块空闲的内内存分配出去。

这两种方式的优缺点:

    brk()方式的缓存,可以减少却也一行的发生,可以提高内存的访问效率,不过,由于这些内存没有归还给系统,在内存繁忙的时候,频繁的内存分配和释放会造成内存碎片。

    mmap()方式的分配内存,会在释放时候直接归还给系统,所以每次mmap都会发生缺页异常。在内存工作繁忙的时候,频繁的内存分配会导致大量的缺页异常的发生,使得内存的管理负担增大,这也是malloc只对大块内存使用mmap的原因。

    了解了这两种,调用方式之后,我们还需要清楚的了解一点,就是当发生这两种调用方式之后,其实没有镇长的分配内存,这些实际使用的内存,只有在首次访问的时候才进行分配,也就是通过缺页异常进入到内核中,再由内核进行分配内存。

Linux的任务调度机制

     Linux作为一个多任务的操作系统,必须支持程序的并发执行。

     多任务一般分为两类,非抢占式多任务与抢占式多任务。

     非抢占式多任务:除非任务自己结束🔚,否则将会一直执行。

     抢占式多任务:这种情况下,由调度程序来决定什么时候停止一个进程的运行,这个强制的挂起动作则是抢占。抢占式多任务的基础就是使用时间片轮转机制来为每一个进程分配可以运行的时间单位。

策略:

     I/O消耗性和CPU消耗形。为了保证交互式桌面系统的性能,linux一般更倾向于优先调度I/O消耗形的进程。

     进程优先级。Linux采用两种不同的优先级范围。

    (1)使用nice值:越大的nice值意味着更低的优先级。(-19~20之间)

    (2)实时优先级:可以配置,越高表示进程的优先级越高。

标准库函数和系统调用的区别

    标准库函数,是语言本身的一部分,系统函数则是内核提供给应用层程序的一个接口,属于系统的一部分。函数库调用是语言或者应用程序的一部分,而系统调用则是操作系统的一部分。

    系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。

    系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。

如何查看进程打开的文件

1
2
3
4
5
# 1、查看谁正在使用某个文件
lsof /filepath/file

# 2、通过某个进程号显示该进行打开的文件
lsof -p 1

gdb调试

1
https://blog.csdn.net/zhangye3017/article/details/80382496

C语言基础

memcpy比较两个结构体

不能。结构体struct 对象由于内存对齐会有内存间隙,就算其中的成员变量相等,内存直接比较还是不能相等。

1、结构体赋值之前可以初始化memset一下,然后可以用memcpy来进行对比,(如果有指针的话 不行)

2、可以使用运算符重载进行成员一一比较,看看变量是否相等。

每个特定平台上的编译器都有自己的默认“对齐系数”。可以通过预编译命令#pragma pack(n)

在经过对齐原则分析后,检查计算出的存储单元是否为所有元素中所占内存最大的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。

strcpy与memcpy的区别

1、一个是字符串拷贝函数一个是内存拷贝函数

2、strcpy无需制定长度,遇到’\0’结束,无法复制0值

3、memcpy是内存拷贝函数,将制定长度的字节直接整个拷贝不进行内容检查。

4、strcpy是不安全字符串操作函数,如果参数dest所指定的内存空间不够大,那么久会出现缓冲区溢出问题,严重的话会导致程序崩溃。

Redis

Redis内存数据库的内存指的是共享内存么

    Redis 是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说 Redis 是实现网站高并发不可或缺的一部分,并不是指在一定只是在共享内存存取数据。

redis持久化方式

Redis的持久化方式主要有两种RDB和AOF两种。
    RDB持久化是在一定的时间间隔内生成内存数据集的时间点的一个快照。
    AOF持久化则是记录服务器执行的所有写操作命令,并且在服务器启动的时候,通过执行这些命令来对数据进行还原。
RDB的优点
    1、采用RDB方式持久化,整个redis数据库将会只包含一个文件,对于这个备份文件来说很完美的。一旦系统出现故障,我们可以很快的进行恢复。
    2、对于灾难恢复,RDB是一个不错的选择,可以压缩成 其他格式进行存储。
    3、能使性能最大化。在进行持久化之时候,只需要fork一个子进程进行持久化工作,这样服务进程就能极大的避免进行IO操作。
    4、相对于AOF而言,如果数据库的数据集很大的话,RDB的启动效率会高一些。
RDB的缺点
主要有两点:
    1、在持久化任务进行之前,如果系统出现宕机,那么此前还没有进行持久化写入磁盘的数据将会丢失,对于数据的高可用性、完整性要求比较高的场景这种持久化的方式将不会是一个很好的选择。
    2、因为RDB是通过fork子进程来进行协助完成持久化,如果数据集特别大的话,可能这个时候服务器会停止服务几百毫秒甚至一秒。
AOF的优点
    1、相比于RDB有更好的数据安全性和数据持久性。在redis中主要提供了三种同步策略,每秒同步,每修改同步,还有不同同步。
    2、由于这个机制对日志文件的写入操作是以append追加的方式进行。所以即使系统宕机了也不会破坏日志文件中已经存在的内容。
    3、AOF日志文件中包含了对数据库的所有修改操作,我们可以通过该日志文件完成数据的重建。
AOF的缺点
    1、对于相同的数据集而言,AOF文件通常大于RDB文件,在进行数据恢复的时候速通常比RDB慢。
    2、根据同步的策略,AOF在运行效率上往往会慢于RDB。

持久化方式的选择

    1、对于数据一致性要求比较高的场景,建议使用使用AOF。
    2、对于数据丢失的一部分没有那么看重的话,建议选择RDB。
    3、如果对于数据的一致性、完整性特别重视,建议两种都开启。
    4、AOF方式在不开启fsync的时候,性能和RDB相当。建议开启AOF方式的每秒钟同步一次。

redis和mysql有什么区别,用于什么场景

    1、redis 基于内存,读写速度快,可以持久化但是内存空间有限,数据量超过内存空间时,需要进行扩充内存。
    2、MySQL则是磁盘存储,读写相对于redis扫稍慢些,但是不受空间容量限制,性价比高。

    在大多数的场景下都是MySQL+Redis配合使用,MySQL作为主存储库,Redis用作缓存可以加快访问速度。在需要高性能的地方使用Redis在不需要的高性能的地方使用MySQL。存储数据在MySQL和Redis之间做同步。

    使用Redis缓存到MySQL。可以把表中经常访问的数据记录在Redis中,在需要查询数据的时候,先去Redis查询,如果查询不到再去MySQL中进行查找,这样可以实现读写分离。现在的软件大量的软件使用Redis作为MySQL在本地的缓存数据库,再适当的时候和MySQL同步。

Redis缓存更新的模式以及出现的问题和应对思路

首先了解三个概念。
    1、缓存雪崩
    2、缓存穿透
    3、缓存击穿
缓存雪崩
    目前电商的首页以及热点的数据都会去做缓存,一般的缓存都是定时任务去更新或者说是查不到数据之后去数据库更新,在这里定时更新就会可能出现问题。缓存雪崩就是同一时间内大量的key值失效的瞬间或者同一瞬间大面积的请求数据库。这一瞬间,Redis缓存和没有一样,这个时候这个数量级别的请求直接打在数据库上几乎就是灾难性的。这就是缓存雪崩。

应对思路:在批量往Redis存储数据的时候,需要把每个key的失效时间都加个随机值就好了,这样可以保证同一时间大面积失效。

1
setRedis(key, values, time+Math.random()*10000)	

缓存穿透

    缓存穿透,指的是缓存和数据库中都没有请求的数据,而且用户却不断发起请求,导致数据库压力大,严重时候会直接击垮数据库。

应对思路:接口层做参数校验,比如用户授权检验,参数做校验,对于不符合的数据直接return返回。对于Java来说,还有个高级的用法采用布隆过滤器(Bloom Filter)这个也可以很好的防止缓存穿透的发生,他的原理就是利用高效的数据结构和算法快速判断这个key在数据库中是否存在。

缓存击穿

    缓存击穿和和缓存雪崩有点类似,但是两者有点区别。雪崩是因为大面积缓存失效,打崩数据库。缓存击穿则是指的是一个key非常热点,在不停的扛着大并发,大并发集中对一个点进行访问,当这个key在失效的瞬间,持续的大并发就会击穿缓存,直接请求数据库,就像在一个完好无损的桶上开了一个洞。

应对思路:设置热点数据永不过期,或者加上互斥锁🔐就可以解决这个问题。

Redis的hash是什么实现的

    Redis中的字典采用哈希表作为底层实现,一个哈希表多个节点,每个节点保存一个键值对。Redis数据库就是使用字典作为底层实现的,通过key和value的键值对形式,代表了数据库中的全部数据。而且对数据库的增删改查都是建立在对字典的操作上。

常见问题

1、100万数据个是topK。

2、32位无符号整形个数的QQ号记录QQ号的状态 上线和下线,计算使用多少内存。

3、printf(“%d “, 1, 2, 3); printf(“%s”);

4、HTTP 和 HTTPS的区别。1、对称加密和非对称加密。2、效率问题的瓶颈。

5、阻塞问题

1、对端接收处理不过来 。流量控制。

2、网络阻塞。拥塞控制。

3、项目问题

Reference

[1] https://blog.csdn.net/xp731574722/article/details/82868560

[2] https://blog.csdn.net/kozazyh/article/details/5495532

[3] https://blog.csdn.net/zhangye3017/article/details/80382496

小主,路过打个赏再走呗~