后端开发面经系列 -- 腾讯微信一面凉经

腾讯微信一面凉经,上来就是两道算法题。。。

公众号:阿Q技术站

来源:https://www.nowcoder.com/feed/main/detail/6720cdde74d04f69802ac776088132e0

1、手撕两道算法题

1.1、旋转字符串
思路
  1. 字符串长度检查:首先检查两个字符串的长度,如果长度不一致,则 s 不可能通过旋转变成 goal,直接返回 false
  2. 模拟旋转操作:循环 s.length() 次,每次将字符串 s 的最左边字符移动到最右边,然后检查旋转后的字符串是否等于 goal
  3. 返回结果:如果在某次旋转中,字符串 s 等于 goal,返回 true;如果循环结束后仍未找到匹配,则返回 false
参考代码
C++
#include <iostream>
#include <string>

// 判断字符串 s 经过若干次旋转后能否变成字符串 goal
bool canBeRotatedToGoal(const std::string& s, const std::string& goal) {
    // 如果 s 和 goal 长度不一致,直接返回 false
    if (s.length() != goal.length()) {
        return false;
    }

    // 模拟旋转操作
    std::string rotated_s = s;
    for (size_t i = 0; i < s.length(); ++i) {
        // 将最左边的字符移动到最右边
        rotated_s = rotated_s.substr(1) + rotated_s[0];
        // 检查旋转后的字符串是否等于 goal
        if (rotated_s == goal) {
            return true;
        }
    }

    // 如果没有找到匹配,返回 false
    return false;
}

int main() {
    std::string s, goal;
    // 输入字符串 s 和 goal
    std::cout << "请输入字符串 s: ";
    std::cin >> s;
    std::cout << "请输入字符串 goal: ";
    std::cin >> goal;

    // 调用函数判断是否可以通过旋转 s 变成 goal
    if (canBeRotatedToGoal(s, goal)) {
        std::cout << "true" << std::endl;
    } else {
        std::cout << "false" << std::endl;
    }

    return 0;
}
1.2、删除链表中重复元素
思路
  1. 特殊情况处理:首先检查链表是否为空或只有一个节点。如果是,直接返回链表头节点,因为没有重复元素。
  2. 遍历链表:使用一个指针遍历链表,从头节点开始。
  3. 检查重复元素:如果当前节点的值与下一个节点的值相同,则跳过下一个节点,即将当前节点的 next 指针指向下一个节点的 next 节点,删除下一个节点。
  4. 继续遍历:如果当前节点的值与下一个节点的值不同,则将指针移动到下一个节点。
  5. 返回结果:遍历结束后,返回处理后的链表头节点。
参考代码
C++
#include <iostream>

// 定义链表节点结构
struct ListNode {
    int val;           // 节点的值
    ListNode* next;    // 指向下一个节点的指针
    ListNode(int x) : val(x), next(nullptr) {}
};

// 删除排序链表中的重复元素
ListNode* deleteDuplicates(ListNode* head) {
    // 特殊情况处理:链表为空或只有一个节点
    if (head == nullptr || head->next == nullptr) {
        return head;
    }

    // 指针初始化,指向链表头节点
    ListNode* current = head;
    
    // 遍历链表
    while (current != nullptr && current->next != nullptr) {
        // 如果当前节点的值与下一个节点的值相同
        if (current->val == current->next->val) {
            // 跳过下一个节点
            ListNode* temp = current->next;
            current->next = current->next->next;
            delete temp;  // 释放内存
        } else {
            // 移动指针到下一个节点
            current = current->next;
        }
    }
    
    // 返回处理后的链表头节点
    return head;
}

// 打印链表
void printList(ListNode* head) {
    ListNode* current = head;
    while (current != nullptr) {
        std::cout << current->val << " ";
        current = current->next;
    }
    std::cout << std::endl;
}

// 创建链表用于测试
ListNode* createList(const std::initializer_list<int>& values) {
    ListNode* head = nullptr;
    ListNode* tail = nullptr;
    for (int value : values) {
        ListNode* newNode = new ListNode(value);
        if (head == nullptr) {
            head = newNode;
            tail = newNode;
        } else {
            tail->next = newNode;
            tail = newNode;
        }
    }
    return head;
}

int main() {
    // 输入链表的元素
    ListNode* head = createList({1, 1, 2, 3, 3});
    
    std::cout << "原始链表: ";
    printList(head);

    // 调用函数删除重复元素
    head = deleteDuplicates(head);
    
    std::cout << "删除重复元素后的链表: ";
    printList(head);

    return 0;
}

2、Linux常用命令有哪些?

3、awk命令有了解过嘛

4、平时怎么查看日志?

1. 使用命令行查看日志

在Linux和Unix系统中,日志文件通常保存在/var/log目录下。常用的日志文件包括:

  • /var/log/syslog:系统日志
  • /var/log/auth.log:认证日志
  • /var/log/kern.log:内核日志
  • /var/log/apache2:Apache日志
  • /var/log/nginx:Nginx日志

常用的命令行工具包括:

a. cat

cat命令可以显示文件的内容:

cat /var/log/syslog
b. less

less命令可以分页查看文件内容,适合查看较长的日志文件:

less /var/log/syslog

使用less时可以使用以下快捷键:

  • Space:向下翻页
  • b:向上翻页
  • q:退出
c. tail

tail命令用于查看文件的最后几行:

tail /var/log/syslog

tail的常用选项包括:

  • -n:指定显示的行数
  • -f:实时跟踪日志文件的更新
tail -n 100 /var/log/syslog
tail -f /var/log/syslog
d. grep

grep命令用于搜索日志文件中的特定关键字:

grep "error" /var/log/syslog

可以结合tail -fgrep实时监控日志中特定关键字的出现:

tail -f /var/log/syslog | grep "error"
2. 使用日志管理工具
a. Logrotate

logrotate是一个日志轮转工具,用于管理日志文件的大小和归档。配置文件通常在/etc/logrotate.conf/etc/logrotate.d/目录下。

b. Journalctl

journalctl用于查看systemd管理的日志:

journalctl

常用选项包括:

  • -u:查看特定服务的日志
  • -f:实时查看日志
  • --since--until:指定时间范围
journalctl -u nginx
journalctl -f
journalctl --since "2023-05-01" --until "2023-05-02"

5、有没有用过命令查看程序运行的栈信息

1. 使用GDB查看栈信息

GDB(GNU调试器)是一个强大的调试工具,可以在程序运行时或程序崩溃后查看栈信息。

a. 调试正在运行的程序
  1. 启动程序:

    ./your_program
    
  2. 查找程序的PID(进程ID):

    pidof your_program
    
  3. 附加GDB到运行中的进程:

    gdb -p <pid>
    
  4. 在GDB中查看栈信息:

    (gdb) bt
    

    bt(backtrace)命令用于显示当前线程的栈帧信息。

b. 调试崩溃的程序
  1. 确保程序编译时包含调试信息:

    g++ -g -o your_program your_program.cpp
    
  2. 运行程序并生成core dump(内存转储文件):

    ulimit -c unlimited
    ./your_program
    
  3. 当程序崩溃时,会生成一个core dump文件(如corecore.<pid>)。

  4. 使用GDB加载core dump文件和程序执行文件:

    gdb your_program core
    
  5. 在GDB中查看栈信息:

    (gdb) bt
    
2. 使用pstack查看栈信息

pstack是一个简单的工具,可以快速查看正在运行的程序的栈信息。

  1. 安装pstack

    sudo apt-get install pstack  # 在Debian/Ubuntu系统上
    sudo yum install pstack     # 在RHEL/CentOS系统上
    
  2. 查找程序的PID:

    pidof your_program
    
  3. 使用pstack查看栈信息:

    pstack <pid>
    
3. 使用gstack查看栈信息

gstack是GDB的一个简单前端,用于快速获取正在运行的进程的栈信息。

  1. 安装gstack

    sudo apt-get install gdb  # 在Debian/Ubuntu系统上,gstack通常与gdb一起安装
    sudo yum install gdb      # 在RHEL/CentOS系统上
    
  2. 查找程序的PID:

    pidof your_program
    
  3. 使用gstack查看栈信息:

    gstack <pid>
    
4. 使用strace查看系统调用栈

strace可以跟踪程序的系统调用,虽然不直接提供函数调用栈,但在某些调试场景下非常有用。

  1. 安装strace

    sudo apt-get install strace  # 在Debian/Ubuntu系统上
    sudo yum install strace      # 在RHEL/CentOS系统上
    
  2. 使用strace启动程序:

    strace -f -o strace_output.txt ./your_program
    

    这会将程序运行期间的所有系统调用记录到strace_output.txt文件中。

  3. 查看strace_output.txt文件中的系统调用信息。

6、netstat命令怎么使用?用来干啥?

netstat命令是一个网络工具,用于显示网络连接、路由表、接口统计信息、伪装连接(masquerade connections),以及多播成员(multicast memberships)。它是诊断网络问题、监控网络流量和查看网络统计信息的强大工具。

基本用法
netstat [选项]
常见选项
  • -a:显示所有连接和监听端口。
  • -t:仅显示TCP连接。
  • -u:仅显示UDP连接。
  • -n:以数字形式显示地址和端口号(不进行DNS解析)。
  • -l:仅显示监听的套接字。
  • -p:显示每个连接的进程ID(PID)和进程名称。
  • -r:显示路由表。
  • -i:显示网络接口信息。
  • -s:显示网络统计信息。
  • -c:每隔一段时间重复显示统计信息。
具体示例
1. 显示所有连接

显示所有的网络连接(包括监听和非监听的):

netstat -a
2. 显示TCP连接

仅显示TCP连接:

netstat -t
3. 显示UDP连接

仅显示UDP连接:

netstat -u
4. 以数字形式显示地址和端口号

不进行DNS解析,以数字形式显示地址和端口号:

netstat -n
5. 显示监听套接字

仅显示监听的网络端口:

netstat -l
6. 显示进程信息

显示每个连接的进程ID(PID)和进程名称:

sudo netstat -p
7. 显示路由表

显示当前系统的路由表:

netstat -r
8. 显示网络接口信息

显示网络接口的信息(类似于ifconfigip addr):

netstat -i
9. 显示网络统计信息

显示各种协议的统计信息:

netstat -s
10. 持续刷新显示

每隔一段时间重复显示统计信息(默认每秒刷新一次,可以指定刷新间隔):

netstat -c

7、Linux网络抓包用什么命令来实现?

1. 使用tcpdump进行抓包

tcpdump是一个命令行工具,用于捕获网络数据包并显示网络接口上的流量。它非常灵活,可以根据不同的条件过滤和捕获数据包。

安装tcpdump

在大多数Linux发行版中,可以通过包管理器安装tcpdump

sudo apt-get install tcpdump    # 在Debian/Ubuntu系统上
sudo yum install tcpdump        # 在RHEL/CentOS系统上
基本用法
tcpdump [选项] [表达式]
  • -i:指定网络接口。
  • -w:将捕获的数据包保存到文件。
  • -r:从文件读取捕获的数据包。
  • -n:不进行主机名解析。
  • -v:详细输出。
  • -vv:更详细的输出。
  • -c:指定捕获的数据包数量。
  • -s:指定捕获数据包的长度。
常见示例
  1. 抓取所有流量
sudo tcpdump
  1. 指定网络接口抓包
sudo tcpdump -i eth0
  1. 抓取特定数量的数据包
sudo tcpdump -c 10
  1. 抓取并保存到文件
sudo tcpdump -i eth0 -w capture.pcap
  1. 从文件读取并解析
sudo tcpdump -r capture.pcap
  1. 抓取特定端口的流量
sudo tcpdump port 80
  1. 抓取特定主机的流量
sudo tcpdump host 192.168.1.1
  1. 抓取特定协议的流量
sudo tcpdump tcp
sudo tcpdump udp
sudo tcpdump icmp
  1. 抓取数据包并详细显示
sudo tcpdump -v
2. 使用Wireshark进行抓包

Wireshark是一个图形化的网络分析工具,功能非常强大,可以实时捕获和分析网络数据包。Wireshark也提供命令行工具tshark,功能与tcpdump类似。

安装Wireshark

在大多数Linux发行版中,可以通过包管理器安装Wireshark:

sudo apt-get install wireshark    # 在Debian/Ubuntu系统上
sudo yum install wireshark        # 在RHEL/CentOS系统上
使用Wireshark
  1. 启动Wireshark:

在命令行输入wireshark,启动图形化界面。

wireshark
  1. 选择网络接口进行捕获:

在Wireshark界面中,选择要捕获流量的网络接口,然后点击“Start”。

  1. 使用过滤器:

Wireshark提供了强大的过滤功能,可以在捕获过程中或捕获后进行过滤。常用的过滤表达式包括:

  • ip.addr == 192.168.1.1:过滤特定IP地址的流量。
  • tcp.port == 80:过滤特定端口的TCP流量。
  • http:过滤HTTP流量。
  • icmp:过滤ICMP流量。
  1. 保存捕获数据:

捕获的数据可以保存为pcap文件,以便以后分析。点击“File” -> “Save As”。

使用tshark

tshark是Wireshark的命令行版本,功能与tcpdump类似。

  1. 抓取所有流量:
sudo tshark
  1. 抓取并保存到文件:
sudo tshark -i eth0 -w capture.pcap
  1. 读取并解析文件:
sudo tshark -r capture.pcap

8、top命令用过嘛?如何查看僵死进程?

top命令是一个实时显示系统中任务信息的命令行工具,用于监控系统性能,包括CPU、内存使用情况,以及各个进程的状态。

基本用法

在终端输入top命令即可启动该工具:

top
top命令界面介绍

启动top命令后,你会看到一个动态更新的界面,显示系统的实时信息。界面主要分为以下几部分:

  1. 系统概要信息
    • 显示当前时间、系统运行时间、用户数、负载均值等。
    • CPU使用情况:包括用户态、系统态、空闲、等待I/O等。
    • 内存和交换空间使用情况。
  2. 进程信息
    • PID:进程ID。
    • USER:进程所有者。
    • PR:进程优先级。
    • NI:进程的nice值。
    • VIRT:进程使用的虚拟内存总量。
    • RES:进程使用的物理内存量。
    • SHR:进程使用的共享内存量。
    • S:进程状态(S表示睡眠,R表示运行,Z表示僵死)。
    • %CPU:进程占用的CPU百分比。
    • %MEM:进程占用的内存百分比。
    • TIME+:进程使用的CPU时间总量。
    • COMMAND:运行的命令。
查看僵死进程

僵死进程(Zombie Process)是指已经终止但其父进程尚未调用wait()系统调用获取其状态信息的进程。这种进程仍然占用进程表中的一个条目。

top命令中,僵死进程的状态标识为Z。查看僵死进程的方法如下:

  1. 启动top命令:

    top
    
  2. 查看进程状态列(S列)中标识为Z的进程:

    top界面中,找到S列,查看其中状态为Z的进程。这些即为僵死进程。

  3. 如果有大量进程,使用筛选功能:

    top界面中按下o键,然后输入以下筛选条件:

    S=Z
    

    按下回车键,top将只显示僵死进程。

处理僵死进程

处理僵死进程通常有以下几种方法:

  1. 查找并重启父进程

    如果父进程可以重启,重启父进程可能会清理僵死进程。

  2. 终止父进程

    如果父进程无法重启或不再需要,可以通过终止父进程来清理僵死进程。找到父进程的PID,然后使用kill命令终止父进程:

    sudo kill -9 <parent_pid>
    

    这将强制终止父进程,并且其所有子进程(包括僵死进程)将被init进程接管,init进程会调用wait()清理僵死进程。

top命令的其他常用操作

top命令界面中,还可以使用其他键来执行一些操作:

  • h:显示帮助。
  • q:退出top
  • u:按用户筛选进程。
  • k:终止进程。输入要终止的进程PID,然后输入信号编号(如9表示SIGKILL)。
  • r:调整进程优先级(nice值)。
  • s:更改刷新间隔时间(默认为3秒)。
  • f:添加或删除列。

9、进程,线程和协程有什么区别?

1. 进程(Process)

定义

  • 进程是操作系统分配资源和调度的基本单位。它是一个运行中的程序实例,包括程序代码、数据、进程控制块(PCB)、堆栈等。

特点

  • 独立性:进程之间独立运行,每个进程都有自己独立的地址空间。
  • 资源占用:每个进程占用一定的系统资源,如内存、文件句柄等。
  • 上下文切换开销大:进程切换需要保存和恢复大量的上下文信息,开销较大。
  • 并发性:进程可以并发执行,不同进程之间可以通过进程间通信(IPC)进行数据交换。
2. 线程(Thread)

定义

  • 线程是进程中的一个执行单元,是调度和执行的基本单位。一个进程可以包含多个线程,线程共享进程的资源(如内存、文件句柄等)。

特点

  • 轻量级:线程比进程更轻量级,同一个进程内的多个线程共享进程的资源。
  • 共享资源:线程共享进程的内存和资源,但每个线程有自己的堆栈和寄存器上下文。
  • 上下文切换开销小:线程切换的开销比进程切换小,因为线程共享相同的地址空间。
  • 并发性:线程可以并发执行,适用于需要高并发和高性能的应用。
3. 协程(Coroutine)

定义

  • 协程是一种用户态的轻量级线程,也称为微线程。协程由程序员在用户空间中调度,不依赖操作系统内核的线程调度机制。

特点

  • 轻量级:协程比线程更轻量级,创建和切换的开销非常小。
  • 非抢占式:协程的切换由程序员控制,通常是协作式的,即一个协程主动让出控制权给另一个协程。
  • 单线程执行:协程通常运行在单个线程中,适用于I/O密集型任务,可以避免多线程的同步问题。
  • 无需锁机制:由于协程在单线程中运行,不会出现多线程竞争问题,因此无需使用锁机制来保护共享资源。
区别表
特性进程(Process)线程(Thread)协程(Coroutine)
调度单位操作系统操作系统用户程序
创建开销
上下文切换开销极小
资源独立性独立资源,拥有独立的地址空间共享进程的资源共享线程的资源
资源共享不共享共享进程的内存和文件句柄共享线程的内存和文件句柄
并发性并发执行,可以多进程并行并发执行,可以多线程并行单线程内并发执行
通信方式进程间通信(IPC)线程间的共享内存和同步机制协程间直接调用和传递
适用场景独立运行的程序、进程间需要隔离的场景高并发、高性能的计算和I/O密集型应用I/O密集型任务、需要大量并发的轻量级任务

10、进程和线程的区别在哪里?

11、进程之间的通信方式有哪些?

进程间通信(IPC,Inter-Process Communication)是指在不同进程之间传递数据的机制。由于进程拥有独立的地址空间,相互隔离,因此需要特殊的方式来进行数据交换。

1. 管道(Pipe)

定义

  • 管道是一种半双工的通信方式,数据只能单向流动,通常用于具有亲缘关系的进程间通信。

特点

  • 匿名管道:只能在父子进程间使用。
  • 单向传输:数据只能从管道的一端流向另一端。

示例

int fd[2];
pipe(fd);
if (fork() == 0) {  // 子进程
    close(fd[0]);
    write(fd[1], "hello", 5);
    close(fd[1]);
} else {  // 父进程
    close(fd[1]);
    char buffer[5];
    read(fd[0], buffer, 5);
    close(fd[0]);
}
2. 命名管道(Named Pipe 或 FIFO)

定义

  • 命名管道是一种特殊的文件类型,允许无亲缘关系的进程间通信,支持半双工或全双工通信。

特点

  • 文件系统中的文件:存在于文件系统中,可以被无关的进程使用。
  • 全双工:可以支持双向通信。

示例

mkfifo /tmp/myfifo
3. 消息队列(Message Queue)

定义

  • 消息队列是存放在内核中的消息链表,进程可以通过消息队列发送和接收消息。

特点

  • 异步通信:进程可以非阻塞地发送和接收消息。
  • 消息持久化:内核维护消息队列,可以在进程重启后继续通信。

示例

int msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
msgsnd(msqid, &msg, sizeof(msg), 0);
msgrcv(msqid, &msg, sizeof(msg), 0, 0);
4. 共享内存(Shared Memory)

定义

  • 共享内存允许多个进程共享一段内存,可以实现最快的IPC,因为数据直接在内存中传输。

特点

  • 高效:不需要数据拷贝,直接在内存中读取和写入。
  • 同步问题:需要使用同步机制(如信号量)来避免竞争条件。

示例

int shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0666);
char *shmaddr = shmat(shmid, NULL, 0);
strcpy(shmaddr, "hello");
shmdt(shmaddr);
5. 信号(Signal)

定义

  • 信号是一种软件中断,用于通知进程某个事件发生。

特点

  • 异步通知:进程可以在任何时候接收信号。
  • 简单轻量:适用于进程间简单的通知和控制。

示例

kill(pid, SIGINT);
6. 套接字(Socket)

定义

  • 套接字是一种网络通信方式,可以用于同一台机器或不同机器上的进程间通信。

特点

  • 网络通信:支持跨网络的进程间通信。
  • 灵活:支持多种协议(TCP、UDP等)。

示例

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
send(sockfd, "hello", 5, 0);
recv(sockfd, buffer, 5, 0);
7. 信号量(Semaphore)

定义

  • 信号量是一种用于进程间同步的机制,可以控制多个进程对共享资源的访问。

特点

  • 同步控制:用于解决共享资源的并发访问问题。
  • 计数功能:支持计数,可以控制多个资源的并发访问。

示例

sem_t sem;
sem_init(&sem, 1, 1);
sem_wait(&sem);
sem_post(&sem);
sem_destroy(&sem);

区别表

特性管道(Pipe)命名管道(FIFO)消息队列(Message Queue)共享内存(Shared Memory)信号(Signal)套接字(Socket)信号量(Semaphore)
通信方向半双工半双工或全双工单向或双向双向单向双向单向(同步控制)
是否支持无亲缘关系
是否支持同步控制
通信速度
使用难度

12、前端发起请求之后到达后端,中间过程是什么?

1. DNS 解析
  1. 用户在浏览器中输入域名(例如 www.example.com)并按下回车。
  2. 浏览器向本地 DNS 服务器发送 DNS 查询请求,查找域名对应的 IP 地址。
  3. 如果本地 DNS 服务器没有缓存该域名的 IP 地址,它会向根域名服务器发起请求,根域名服务器返回顶级域名服务器的地址。
  4. 本地 DNS 服务器继续向顶级域名服务器发送请求,顶级域名服务器返回次级域名服务器的地址。
  5. 本地 DNS 服务器向次级域名服务器发送请求,次级域名服务器返回域名对应的 IP 地址。
  6. 本地 DNS 服务器将 IP 地址返回给浏览器。
2. 建立 TCP 连接
  1. 浏览器根据域名解析出的 IP 地址,向服务器发起 TCP 连接请求(三次握手)。
  2. 服务器接受连接请求,建立 TCP 连接(三次握手完成)。
3. 发送 HTTP 请求
  • 浏览器向服务器发送 HTTP 请求,请求包括请求方法(GET、POST等)、请求头(包括用户代理、Cookie等)、请求体(POST 请求的数据)等信息。
4. 服务器处理请求
  1. 服务器接收到请求后,根据请求的内容进行处理,可能包括访问数据库、调用后端逻辑处理程序等。
  2. 服务器处理完请求后,返回 HTTP 响应。
5. 返回 HTTP 响应
  1. 服务器将处理结果封装成 HTTP 响应,包括状态码、响应头(包括内容类型、内容长度等)、响应体(返回的数据)等。
  2. 服务器向浏览器发送 HTTP 响应。
6. 接收 HTTP 响应
  1. 浏览器接收到 HTTP 响应后,根据状态码和响应内容进行处理。
  2. 如果状态码为 200(OK),浏览器将响应体中的数据显示在页面上;如果是其他状态码,浏览器可能显示错误页面或者进行其他处理。
7. 关闭 TCP 连接
  • 数据传输完成后,浏览器和服务器之间的 TCP 连接可以被关闭(四次挥手)。

13、UDP访问DNS的过程是怎么样的?

  1. 客户端发起DNS查询请求
    • 用户在浏览器中输入一个域名(例如www.xxx.com)。
    • 浏览器将域名发送给操作系统的DNS解析器。
  2. DNS解析器检查缓存
    • DNS解析器首先检查本地缓存中是否有对应的IP地址。如果缓存中有,则直接返回该IP地址。
    • 如果缓存中没有对应的记录,则DNS解析器向配置的DNS服务器发送查询请求。
  3. 构造DNS请求报文
    • DNS解析器构造一个DNS请求报文。该报文通常包含查询域名、查询类型(如A记录)以及一些标志。
  4. 发送DNS请求
    • DNS解析器使用UDP协议将请求报文发送到DNS服务器的53号端口。UDP是一种无连接协议,适用于简单且快速的请求响应场景。
  5. DNS服务器接收请求
    • DNS服务器接收到请求后,解析请求报文并查找对应的域名记录。
    • 如果该DNS服务器本身没有缓存该域名的记录,它会向上级DNS服务器继续查询,直到找到该域名的最终解析记录。
  6. 构造DNS响应报文
    • DNS服务器找到解析记录后,构造一个DNS响应报文。该报文包含查询的域名、记录类型、TTL(生存时间)以及对应的IP地址。
  7. 发送DNS响应
    • DNS服务器使用UDP协议将响应报文发送回客户端(DNS解析器)。
  8. 客户端接收响应
    • DNS解析器接收到DNS响应报文后,从中提取IP地址并缓存一段时间(根据TTL值)。
    • 然后将IP地址返回给请求的应用程序(例如浏览器)。
  9. 浏览器与目标服务器通信
    • 浏览器使用获取的IP地址与目标服务器建立TCP连接(如HTTP/HTTPS),进行后续的网页请求和数据传输。

14、为什么要三次握手?

TCP(传输控制协议)中的三次握手是为了确保双方建立可靠的连接。三次握手过程确保了客户端和服务器之间的通信通道是有效的,并且双方都准备好开始传输数据。以下是三次握手的详细步骤和原因:

步骤一:SYN

客户端发送SYN:

  • 客户端向服务器发送一个SYN(同步)报文段,请求建立连接。
  • SYN报文段包含一个初始序列号(Sequence Number,简称Seq),例如Seq = X。
步骤二:SYN-ACK

服务器发送SYN-ACK:

  • 服务器接收到客户端的SYN报文段后,向客户端发送一个SYN-ACK(同步-确认)报文段。
  • 这个报文段包含服务器的初始序列号(例如,Seq = Y),以及对客户端SYN的确认序列号(Acknowledgment Number,简称Ack),Ack = X + 1。
步骤三:ACK

客户端发送ACK:

  • 客户端接收到服务器的SYN-ACK报文段后,向服务器发送一个ACK(确认)报文段。
  • 这个报文段包含客户端自己的确认序列号,Ack = Y + 1。
  • 至此,三次握手完成,连接建立。
三次握手的目的和原因
  1. 确认双方的接收能力
    • 第一次握手:客户端发送SYN,表示客户端希望建立连接,并向服务器告知自己的初始序列号。
    • 第二次握手:服务器收到SYN后,返回SYN-ACK,表示服务器接收到客户端的请求,并愿意建立连接。同时,服务器也发送自己的初始序列号。
    • 第三次握手:客户端收到SYN-ACK后,发送ACK,确认收到了服务器的初始序列号,并表示连接可以建立。
  2. 同步双方的初始序列号
    • 在三次握手过程中,双方交换初始序列号,用于后续数据传输中的序列控制和确认。
  3. 防止重复的连接初始化
    • 通过三次握手,可以避免因为网络延迟导致的重复连接初始化。假设没有三次握手,旧的连接请求可能在网络中滞留,并被错误地认为是新的连接请求,导致连接的混乱和资源的浪费。
  4. 确保双方都准备好通信
    • 三次握手确保了双方都收到了对方的连接请求,并且都同意建立连接。这样可以避免资源的浪费和潜在的通信问题。

15、三次握手与四次挥手的区别是什么?为什么要多一次?

连接建立(三次握手):
  • 三次握手的目的是确保双方都知道对方的存在,并且双方都可以发送和接收数据。三次握手中,客户端和服务器各发送一次确认报文就可以完成连接的建立。
连接终止(四次挥手):
  • 四次挥手的目的是确保双方都完成了所有数据的发送和接收,并且双方都可以安全地关闭连接。
  • 多出的一次握手是因为TCP连接是全双工的,即双方都可以同时发送和接收数据。在连接终止时,每一方都需要单独发送一个FIN报文段来表示自己已经完成了数据的发送,并需要对方的确认(ACK)。
四次挥手多一次的原因:
  • 全双工通信的特点:在TCP连接中,客户端和服务器都可以同时发送和接收数据。为了安全终止连接,每一方都需要单独发送FIN报文并等待对方的确认。
  • 顺序关闭:一方(如客户端)发送FIN报文后,另一方(如服务器)可能还有数据要发送,因此需要等待数据发送完毕后再发送自己的FIN报文。这就需要一个额外的步骤来完成整个连接的安全关闭。

16、四次挥手的过程中如果处在timewait状态的请求较多,会有什么结果?要怎么解决这个问题?

TIME_WAIT状态的原因

在四次挥手过程中,当一方(通常是主动关闭连接的一方)发送了最后一个ACK报文段并进入TIME_WAIT状态后,它会保持这个状态一段时间(通常是两个最大段寿命时间,2*MSL,MSL一般为2分钟,因此TIME_WAIT状态持续4分钟)。这样做的目的是确保所有的TCP段都已经从网络中消失,避免旧连接的数据干扰新连接。

TIME_WAIT状态带来的问题
  1. 资源浪费

    • 每个处于TIME_WAIT状态的连接都占用系统的资源(如TCP端口、内存等)。
    • 大量的TIME_WAIT连接可能导致系统资源耗尽,无法处理新的连接请求。
  2. 端口耗尽

    服务器上的可用端口数量是有限的。如果有大量连接处于TIME_WAIT状态,可用端口可能会被耗尽,导致新连接请求被拒绝。

  3. 新连接延迟

    当新的连接请求到达时,如果使用的端口仍处于TIME_WAIT状态,系统可能会延迟处理这些请求,等待TIME_WAIT状态结束。

解决方法
  1. 缩短TIME_WAIT持续时间

    • 调整系统参数以缩短TIME_WAIT状态的持续时间。例如,在Linux系统中,可以通过修改

      tcp_fin_timeout
      

      参数来缩短TIME_WAIT状态的持续时间:

      sysctl -w net.ipv4.tcp_fin_timeout=30
      
    • 这将TIME_WAIT状态的持续时间从默认的60秒缩短到30秒。

  2. 启用端口重用

    • 允许系统重用处于TIME_WAIT状态的端口,以便新连接可以使用这些端口。可以通过修改

      tcp_tw_reuse
      

      参数来实现:

      sysctl -w net.ipv4.tcp_tw_reuse=1
      
    • 这将允许在TIME_WAIT状态的端口被新连接重用。

  3. 启用快速重用

    • 允许快速回收处于TIME_WAIT状态的连接,以便新连接可以快速使用这些端口。可以通过修改

      tcp_tw_recycle
      

      参数来实现(注意,启用

      tcp_tw_recycle
      

      可能会引起一些问题,尤其是在NAT环境中,所以使用时需要谨慎):

      sysctl -w net.ipv4.tcp_tw_recycle=1
      
  4. 增加可用端口范围

    • 扩大可用端口范围,以增加系统可以使用的端口数量,从而减少端口耗尽的可能性。可以通过修改

      ip_local_port_range
      

      参数来实现:

      sysctl -w net.ipv4.ip_local_port_range="1024 65535"
      

17、Redis的缓存击穿有了解过吗?

Redis缓存击穿是指当一个热点数据在缓存中失效(或未命中)后,大量的请求同时到达数据库,导致数据库瞬间负载剧增的情况。这种现象会对数据库产生很大的压力,甚至可能导致数据库崩溃。

18、如何解决缓存击穿问题?

缓存击穿的原因

缓存击穿通常发生在以下情况下:

  1. 缓存失效:
    • 热点数据缓存的过期时间到期,而这个数据有大量请求访问。
  2. 高并发请求:
    • 有大量并发请求同时访问同一条缓存失效的数据。
解决缓存击穿的方法

为了防止缓存击穿,可以采用以下几种策略:

  1. 设置热点数据永不过期
    • 对于特别热点的数据,可以设置它们的缓存永不过期,或在数据过期前主动刷新缓存。
    • 优点:避免缓存失效带来的数据库压力。
    • 缺点:需要人工干预,不能动态管理所有数据。
  2. 互斥锁
    • 当缓存失效且有大量请求同时到达时,可以通过互斥锁(如分布式锁)来控制只有一个请求能查询数据库并刷新缓存,其他请求等待。
    • 实现方法:在查询缓存时,如果缓存未命中,使用分布式锁(如Redis的SETNX命令)锁住这个缓存键。当第一个请求获取到锁后,查询数据库并更新缓存,其他请求在锁释放后重新查询缓存。
  3. 提前更新缓存
    • 在缓存即将过期前,提前主动更新缓存。例如,在缓存过期前一段时间内(如1分钟),后台任务定时刷新缓存。
    • 实现方法:通过后台任务或定时器在缓存过期前刷新缓存,确保缓存数据始终有效。
  4. 请求分流
    • 对于高并发请求,可以对请求进行分流,减少同时到达数据库的请求数量。
    • 实现方法:通过负载均衡、降级策略等手段,将部分请求引导到备用缓存节点或降低请求频率。

19、Redis和MySQL的数据同步如何保证?

1. 缓存更新策略
1.1. 读写分离模式
  • 读操作:
    1. 客户端请求数据时,首先查询Redis缓存。
    2. 如果缓存命中,则返回缓存中的数据。
    3. 如果缓存未命中,则查询MySQL数据库,并将查询结果写入Redis缓存,然后返回数据。
  • 写操作:
    1. 更新数据时,首先更新MySQL数据库。
    2. 然后删除或更新Redis缓存中的相应数据。

伪代码示例:

def get_data(key):
    value = redis.get(key)
    if value is None:
        value = db.query("SELECT * FROM table WHERE key = %s", key)
        redis.set(key, value, ex=3600)  # 设置缓存,并设定过期时间
    return value

def update_data(key, value):
    db.update("UPDATE table SET value = %s WHERE key = %s", value, key)
    redis.delete(key)  # 删除缓存中的数据
1.2. 写操作后更新缓存
  • 在写操作后,不仅更新MySQL数据库,还要同步更新Redis缓存。

伪代码示例:

def update_data(key, value):
    db.update("UPDATE table SET value = %s WHERE key = %s", value, key)
    redis.set(key, value, ex=3600)  # 更新缓存中的数据
2. 延迟双删策略

延迟双删策略可以有效地避免由于写操作引起的缓存不一致问题。

  • 步骤:
    1. 更新数据库中的数据。
    2. 删除Redis缓存中的数据。
    3. 等待一段时间(例如500毫秒)。
    4. 再次删除Redis缓存中的数据。

伪代码示例:

def update_data(key, value):
    db.update("UPDATE table SET value = %s WHERE key = %s", value, key)
    redis.delete(key)  # 第一次删除缓存
    time.sleep(0.5)  # 等待500毫秒
    redis.delete(key)  # 第二次删除缓存
3. 异步同步机制
  • 使用消息队列(如Kafka、RabbitMQ)来异步同步数据变化。
  • 步骤
    1. 当数据在MySQL中发生变化时,发送消息到消息队列。
    2. 后台服务从消息队列中消费消息,并根据消息内容更新Redis缓存。

伪代码示例:

def update_data(key, value):
    db.update("UPDATE table SET value = %s WHERE key = %s", value, key)
    send_message_to_queue(key)  # 发送更新消息到消息队列

# 后台服务
def handle_queue_message():
    while True:
        message = get_message_from_queue()
        key = message['key']
        value = db.query("SELECT * FROM table WHERE key = %s", key)
        redis.set(key, value, ex=3600)  # 更新缓存
4. 缓存预热
  • 在系统启动或缓存失效时,预先加载热点数据到缓存中,避免缓存击穿。
  • 步骤
    1. 在系统启动时,读取热点数据并加载到Redis缓存中。
    2. 定期更新Redis缓存中的热点数据。

伪代码示例:

def preload_cache():
    hot_keys = get_hot_keys()  # 获取热点数据的key列表
    for key in hot_keys:
        value = db.query("SELECT * FROM table WHERE key = %s", key)
        redis.set(key, value, ex=3600)  # 预加载到缓存中
5. 双写一致性
  • 在写操作时,同时更新MySQL数据库和Redis缓存,确保两者数据一致。

伪代码示例:

def update_data(key, value):
    db.update("UPDATE table SET value = %s WHERE key = %s", value, key)
    redis.set(key, value, ex=3600)  # 同时更新缓存

20、Redis和MySQL的区别是?有什么关联?

Redis 和 MySQL 的区别
数据库类型
  • Redis:
    • 类型: 内存数据库(内存中键值存储)。
    • 用途: 高速缓存、消息队列、实时数据分析。
    • 数据模型: 基于键值对,支持多种数据结构(字符串、哈希、列表、集合、有序集合、位图、HyperLogLog、Geospatial)。
  • MySQL:
    • 类型: 关系型数据库(RDBMS)。
    • 用途: 事务处理、持久化存储、结构化数据管理。
    • 数据模型: 基于表格,支持复杂的SQL查询和事务。
性能
  • Redis:
    • 速度: 极快,数据存储在内存中,读写延迟通常在微秒级别。
    • 并发: 支持高并发访问,适合高频读写场景。
  • MySQL:
    • 速度: 相对较慢,数据存储在磁盘上,读写延迟通常在毫秒级别。
    • 并发: 并发性能较Redis稍低,但通过配置和优化可支持较高的并发。
持久化
  • Redis:
    • 持久化: 提供RDB(快照)和AOF(Append-Only File)两种持久化机制,但默认主要在内存中操作。
    • 数据丢失风险: 在持久化策略不当或故障时,可能会有数据丢失的风险。
  • MySQL:
    • 持久化: 数据默认存储在磁盘上,通过日志和事务机制保证数据的持久性和一致性。
    • 数据安全: 通过ACID(原子性、一致性、隔离性、持久性)事务模型,保证数据高度可靠和一致。
数据结构和操作
  • Redis:
    • 数据结构: 丰富的数据结构支持(如列表、集合、哈希等),适合多样化的应用场景。
    • 操作: 提供丰富的命令集合,针对不同数据结构进行高效操作。
  • MySQL:
    • 数据结构: 基于表的结构,适合结构化数据的存储和管理。
    • 操作: 支持复杂的SQL查询、索引、视图、存储过程等,适合复杂的业务逻辑处理。
Redis 和 MySQL 的关联

尽管Redis和MySQL是不同类型的数据库,它们可以结合使用,以发挥各自的优势:

  1. 缓存层和持久层:
    • Redis作为缓存:Redis常用作MySQL的缓存层,存储频繁访问的数据,减少MySQL的查询压力,提高系统的响应速度。
    • MySQL作为持久存储:MySQL用于持久化存储和复杂的事务处理,保证数据的完整性和一致性。
  2. 数据同步:
    • 缓存更新策略:当MySQL中的数据发生变化时,及时更新或删除Redis中的缓存数据,以保证数据一致性。
    • 延迟双删策略:在更新MySQL数据后,删除Redis缓存,并在短暂延迟后再次删除缓存,防止短时间内的数据不一致。
  3. 消息队列同步:
    • 异步更新:通过消息队列(如Kafka、RabbitMQ),将数据更新消息传递给后台服务,后台服务根据消息更新Redis缓存,实现异步同步。
  4. 缓存预热:
    • 启动时预热缓存:在系统启动或缓存失效时,预先将热点数据加载到Redis缓存中,避免缓存击穿带来的性能问题。

21、Redis的热查询有没有了解?怎么解决的?

Redis中的热查询(Hot Query)指的是某些特定的数据或键在短时间内被频繁访问的情况。这种情况可能会导致某些键的访问量过高,从而对Redis实例造成压力,甚至可能影响系统的整体性能。

1. 缓存预热

原理: 在系统启动或缓存失效时,预先加载热点数据到Redis缓存中,确保这些数据在第一次请求时已经在缓存中,避免缓存击穿。

实现方法

  • 启动时:在系统启动时,通过后台任务加载热点数据到缓存中。
  • 定时刷新:定期刷新热点数据,确保缓存中的数据是最新的。
2. 缓存雪崩

原理: 缓存雪崩是指在某一时刻大量缓存失效,导致大量请求直接落到数据库上,从而引起数据库压力过大甚至崩溃。

解决方法

  • 缓存数据过期时间随机化:为不同的缓存设置不同的过期时间,避免同一时间大量缓存失效。
  • 双缓存策略:在主缓存失效时,使用备用缓存进行查询,减少数据库的压力。
3. 分布式缓存

原理: 将缓存数据分布到多个Redis实例中,通过水平扩展缓解单实例的压力。

实现方法

  • 一致性哈希:使用一致性哈希算法,将键分布到多个Redis实例中,确保均匀分布。
  • 分布式缓存工具:使用Redis Cluster或其他分布式缓存工具,如Codis、Twemproxy等,实现自动分片和负载均衡。
4. 限流和降级

原理: 对访问量过高的热点数据进行限流和降级处理,保护后端数据库和缓存系统。

方法

  • 限流:对每秒访问频率进行限制,超过限制的请求进行排队或直接拒绝。
  • 降级:在缓存或数据库压力过大时,返回预先设定的默认值或友好提示。
5. 异步更新缓存

原理: 使用异步方式更新缓存,避免高并发请求同时访问数据库。

方法

  • 消息队列:使用消息队列(如Kafka、RabbitMQ)将更新请求异步处理。
  • 后台任务:使用后台任务系统(如Celery)异步更新缓存。
6. 热点数据分片

原理: 对热点数据进行分片,将单一热点键拆分为多个子键,分散访问压力。

方法

  • 分片策略:根据业务需求对数据进行分片,例如按时间、地域等维度进行拆分。
  • 组合键:使用组合键存储分片数据,减少单个键的访问压力。

22、MySQL的乐观锁和悲观锁是什么?

特性乐观锁悲观锁
定义假设并发冲突不会频繁发生,不直接加锁假设并发冲突会频繁发生,直接加锁
原理使用版本号或时间戳进行冲突检测使用数据库锁机制(如行锁、表锁)
使用场景读多写少,冲突较少写多读少,冲突较多
优点提高并发性能,避免锁竞争保证数据一致性,避免并发冲突
缺点冲突时需要重试,处理复杂加锁导致性能下降,特别是在高并发场景下
实现方法通过版本号或时间戳字段进行更新检查通过数据库的锁机制,如SELECT ... FOR UPDATE
典型示例版本号字段更新,条件检查是否一致在事务中使用SELECT ... FOR UPDATE加锁
适用场景大部分是读操作,少量写操作大部分是写操作,需要保证数据一致性
乐观锁(Optimistic Lock)

定义: 乐观锁假设并发冲突不会频繁发生,因此在数据操作时不直接加锁,而是在提交更新时检查是否有冲突。常见的实现方式是使用版本号(version)或时间戳(timestamp)。

原理: 每次读取数据时,获取该数据的版本号或时间戳。在更新数据时,检查数据库中的版本号或时间戳是否与读取时的一致。如果一致,则更新数据并修改版本号或时间戳;如果不一致,则说明数据已经被其他事务修改,操作失败,需要重新尝试。

使用场景: 适用于读多写少的场景,冲突较少时性能更优。

示例

-- 创建表时添加版本号字段
CREATE TABLE items (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    quantity INT,
    version INT
);

-- 更新数据时使用版本号进行乐观锁控制
UPDATE items
SET quantity = quantity - 1, version = version + 1
WHERE id = 1 AND version = 10;

-- 检查影响的行数,如果影响的行数为0,说明版本号不匹配,更新失败
SELECT ROW_COUNT();
悲观锁(Pessimistic Lock)

定义: 悲观锁假设并发冲突会频繁发生,因此在数据操作时直接加锁,阻止其他事务同时访问数据。常见的实现方式是使用数据库的锁机制,如行锁或表锁。

原理: 在读取或修改数据之前,先获取锁,其他事务必须等待锁释放后才能访问该数据。悲观锁确保了只有一个事务能够对数据进行操作,但会导致并发性能下降。

使用场景: 适用于写多读少的场景,冲突较多时更能保证数据一致性。

示例

-- 开启事务
START TRANSACTION;

-- 对行数据进行锁定(SELECT ... FOR UPDATE 会对读取的行加排他锁)
SELECT quantity
FROM items
WHERE id = 1
FOR UPDATE;

-- 执行更新操作
UPDATE items
SET quantity = quantity - 1
WHERE id = 1;

-- 提交事务
COMMIT;

23、MySQL如何定位慢查询?

1. 使用慢查询日志(Slow Query Log)

配置慢查询日志:在MySQL配置文件中启用慢查询日志,并设置阈值,例如将执行时间超过1秒的查询记录到日志中。

示例配置

slow_query_log = 1
slow_query_log_file = /path/to/slow_query.log
long_query_time = 1

查看慢查询日志:通过查看慢查询日志文件,可以定位执行时间较长的查询语句,并分析优化。

2. 使用Performance Schema

启用Performance Schema:在MySQL配置文件中启用Performance Schema,以收集有关查询性能的更多信息。

示例配置

performance_schema = ON

查询慢查询语句:使用Performance Schema的查询语句来查找执行时间较长的查询。

SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE '%your_query%';
3. 使用EXPLAIN分析查询计划

使用EXPLAIN:在执行查询语句前添加EXPLAIN关键字,可以查看查询的执行计划,了解MySQL如何执行查询。

示例

EXPLAIN SELECT * FROM your_table WHERE your_condition;

24、MySQL定位了慢查询之后,要怎么优化慢查询?

1. 使用合适的索引

分析查询语句:通过EXPLAIN关键字分析查询语句的执行计划,查看是否使用了索引,以及使用了哪些索引。

添加索引:根据查询条件和表的访问模式,添加适当的索引。避免过多索引,以免影响写入性能。

示例

CREATE INDEX idx_name ON your_table (column_name);
2. 优化查询语句

避免使用通配符:尽量避免在WHERE子句中使用通配符%,可以考虑使用前缀索引。

避免全表扫描:尽量避免使用没有索引或无法使用索引的条件进行查询,以免导致全表扫描。

减少查询返回的列:只选择需要的列,避免使用SELECT *

3. 使用缓存

查询缓存:考虑使用MySQL的查询缓存功能,缓存经常查询的结果。

应用缓存:考虑在应用层使用缓存,减少对数据库的频繁查询。

4. 优化表结构和数据类型

合理设计表结构:避免使用过多的冗余字段,合理拆分大表。

选择合适的数据类型:选择合适的数据类型,避免使用过大或不必要的数据类型,以节省存储空间和提高查询效率。

5. 定期优化数据库

定期分析查询日志:定期分析慢查询日志,发现潜在的性能问题。

定期优化表:定期对表进行优化,包括ANALYZE TABLEOPTIMIZE TABLE等操作。

25、MySQL的底层数据结构有没有了解?怎么实现的?

1. 表(Table)

数据存储方式:MySQL中的表数据通常存储在磁盘上,每个表对应一个或多个文件。表的结构定义存储在.frm文件中,而实际数据存储在.ibd.MYD文件中。

数据组织方式

  • 行存储(Row Storage):默认情况下,MySQL使用行存储方式,即每一行数据作为一个整体存储。这种方式适用于多数 OLTP 场景。
  • 列存储(Column Storage):某些存储引擎(如InnoDB的压缩表)支持列存储,将同一列的数据存储在一起,提高了查询性能,适用于 OLAP 场景。
2. 索引(Index)

索引类型:MySQL支持多种类型的索引,包括B-Tree索引、哈希索引、全文索引等。

B-Tree索引

  • 数据结构:B-Tree索引是MySQL中最常用的索引类型。B-Tree(Balance Tree)是一种平衡树,通过在每个节点中存储多个键和子节点的指针来实现快速查找。
  • 实现方式:在InnoDB存储引擎中,每个索引对应一棵B-Tree,主键索引和辅助索引都是B-Tree索引。

哈希索引

  • 数据结构:哈希索引将索引列的值通过哈希函数计算得到一个哈希值,然后将该哈希值映射到一个哈希表中的某个位置,通过这个位置来快速定位数据。
  • 实现方式:MySQL并不直接支持哈希索引,但可以通过在Memory存储引擎上创建临时表来实现哈希索引。
3. 日志(Log)

事务日志(Redo Log)

  • 数据结构:事务日志记录了事务对数据库的修改操作。InnoDB存储引擎使用两个重做日志文件(ib_logfile0ib_logfile1)来记录重做日志。
  • 实现方式:InnoDB存储引擎将事务的修改操作记录到重做日志中,然后再将这些修改操作应用到实际的数据页上,以保证事务的持久性。

二进制日志(Binary Log)

  • 数据结构:二进制日志记录了所有对数据库的修改操作,包括数据更新、插入、删除等。二进制日志是用于数据库备份、复制和恢复的重要组成部分。
  • 实现方式:MySQL将二进制日志记录到二进制日志文件中,可以通过设置参数来配置二进制日志的大小和保存时长。

错误日志(Error Log)

  • 数据结构:错误日志记录了MySQL运行过程中的错误信息,包括启动、运行和关闭过程中的错误信息。
  • 实现方式:错误日志通常记录到文件中,可以通过设置参数来配置错误日志的保存位置和级别。
  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值