【Linux】进程信号

news2024/12/24 2:19:59

目录

一、什么是信号

二、信号产生的条件

1、键盘产生

2、进程异常

3、命令产生

4、软件条件

三、信号保存的方式

四、信号处理的方式

1、信号处理接口

2、信号处理时机

3、进程为什么要切换成为用户态才进行信号的捕获方法?

4、sigaction

五、可重入函数

六、volatile

七、SIGCHLD

总结


一、什么是信号

生活中有很多的信号

闹钟,红绿灯等等,这些信号还没有发出,我们就知道要干什么,对于信号的处理动作我们是早于信号产生就知道了,我们知道的原因是因为我们早就记住了“信号”

进程对于信号的处理也是这样,进程收到某种信号,它不一定会立即处理,在它合适的时机才会处理信号,所以在处理信号之前,我们需要保存信号,信号的本质是数据,向进程发送信号本质是向进程的PCB中写入信号数据。

二、信号产生的条件

在说明信号产生的条件之前,先看看Linux都有哪些信号

 前31个信号称之为普通信号,后31个称之为实时信号

接下来引入一个函数signal

 它是用来修改信号的默认行为的,第一个参数sig是信号编号,可以传入上面的宏,也可以传入数字,第二个参数是一个函数指针,返回值是void,参数是int

1、键盘产生

我们前面知道ctrl + c是用来终止进程,ctrl + z是暂停进程

我们可以验证ctrl + c是向进程发送什么信号 : ctrl + c是向进程发送2号信号SIGINT

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    signal(2, handler);

    while(true)
    {
        std::cout << "Hello World" << std::endl;
        sleep(1);
    }

    return 0;
}

注意:signal是注册函数,并不是调用函数,只有当信号到来的时候,这个函数才会被调用

信号产生的方式之一便是通过键盘产生

接下来我们将9号信号修改为自定义动作

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    signal(9, handler);

    while(true)
    {
        std::cout << "pid: > " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

然后我们就发现,9号信号没有被捕捉(自定义)

原因是9号信号是管理员信号,OS不允许存在一个刀枪不入的进程

总结:一般而言,进程收到信号有三种处理方案

1、默认动作

2、忽略动作

3、自定义动作(signal捕获信号)

2、进程异常

进程异常也会发送信号,我们常见的段错误,除零错误的本质都是OS发送信号将对应进程杀掉

int main()
{
    int a = 10 / 0;
    std::cout << a << std::endl;

    return 0;
}

int main()
{
    int* p = nullptr;
    *p = 100;

    return 0;
}

 

那么进程异常OS是怎么检测到的呢?

硬件异常被硬件以某种方式被硬件检测到并通知内核 , 然后内核向当前进程发送适当的信号。例如当前进程执行了除以0 的指令 ,CPU 的运算单元会产生异常 , 内核将这个异常解释 为 SIGFPE 信号发送给进程。再比如当前进程访问了非法内存地址,,MMU 会产生异常 , 内核将这个异常解释 SIGSEGV 信号发送给进程
程序中存在异常问题,导致我们收到信号退出
当程序崩溃的时候我们最想要知道的是崩溃的原因,及在哪一行崩溃了
崩溃的原因:该进程的父进程一定会通过进程等待的方式来获取子进程的退出状态和退出信号

在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置

当一个进程异常退出的时候进程退出信号被设置,表明当前进程退出的原因

如果有必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转储到磁盘当中,方便后期调试

某些平台会将core dump关闭,打开方式:输入命令ulimit -a

 我们观察到core file size 大小是0

我们使用命令

ulimit -c 10240

 将core file size大小调整为10240


int main()
{
  
    int a = 10 / 0;
    std::cout << a << std::endl;
}

 

21291是进程pid

我们打开gdb来获取错误的行号

注意:并不是所有的信号都会产生core dump

3、命令产生

kill用来向任意进程发送信号

我们写两个程序来验证

//proc.cpp  将来接受信号的进程
#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    for (size_t i = 1; i < 32; i++)
    {
        signal(i, handler);
    }

    while (true)
    {
        std::cout << "pid: " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

将来发送信号的进程

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    pid_t id = 0;
    int signo = 0;
    std::cout << "请输入要发送信号进程的pid";
    std::cin >> id;
    std::cout << "请输入要发送的信号" << std::endl;
    std::cin >> signo;

    kill(id, signo);

}

另一个系统调用接口是raise

raise是向自己发送信号

#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    for (size_t i = 1; i < 32; i++)
    {
        signal(i, handler);
    }

    std::cout << "pid: " << getpid() << std::endl;
    sleep(5);

    raise(11);

    return 0;
}

 

最后一个系统调用接口是abort

功 能: 异常终止一个进程。中止当前进程,返回一个错误代码。错误代码的缺省值是3。

该函数产生SIGABRT信号并发送给自己,默认情况下导致程序终止不成功的终止错误代码返回到主机环境。

自动或静态存储持续时间的对象,而无需调用任何atexit函数,析构函数不执行程序终止。函数永远不会返回到其调用者。

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    for (size_t i = 1; i < 32; i++)
    {
        signal(i, handler);
    }

    std::cout << "pid: " << getpid() << std::endl;
    sleep(5);

    abort();

    return 0;
}

4、软件条件

通过某种软件(OS),来触发信号的发送,系统层面设置定时器,或者某种操作而导致的条件不就绪等这样的场景下,触发信号发送

例如进程间的通信,当读端不光不读,而且还关闭fd的时候,写端一直在写,最终写端进程会受到sigpipe(13)信号,将该进程杀掉

另一种实现方法是alarm

调用 alarm 函数可以设定一个闹钟 , 也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号 , 该信号的默认处理动作是终止当前进程。
这个函数的返回值是 0 或者是以前设定的闹钟时间还余下的秒数。打个比方 , 某人要小睡一觉 , 设定闹钟为 30 分钟之后响,20 分钟后被人吵醒了 , 还想多睡一会儿 , 于是重新设定闹钟为 15 分钟之后响 ,“ 以前设定的闹钟时间还余下的时间 就是10 分钟。如果 seconds 值为 0, 表示取消以前设定的闹钟 , 函数的返回值仍然是以前设定的闹钟时间还余下的秒数

三、信号保存的方式

OS给进程发送信号 -> OS发送信号数据给task_struct -> 本质是OS向指定进程的task_struct中的信号位图写入比特位1,即完成信号的发送

信号的编号是有规律的[1, 31]

进程中,采用位图标识该进程是否收到信号

所谓的比特位的位置(第几个比特位),代表的就是哪一个信号比特位的内容(0,1),代表的就是是否收到信号

实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

递达-忽略 VS 阻塞 的区别

忽略是递达的一种方式,阻塞是没有被递达,它是独立状态

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里

四、信号处理的方式

1、信号处理接口

sigprocmask可以读取或者更改进程的信号屏蔽字,它是用来修改block位图的

它的第一个参数是怎样修改

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask = mask | set
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞信号,相当于mask = mask &~ set
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask = set

 第二个参数和第三个参数的类型是sigset_t,sigset_t是一个位图结构,但是不同的OS的实现细节是不一样的,不能让用户直接修改该变量,需要使用特定的函数

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
函数sigfifillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfifillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
这四个函数都是成功返回0, 出错返回 -1 sigismember 是一个布尔函数 , 用于判断一个信号集的有效信号中是否包含某种 信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1
在回到sigprocmask函数的参数,第二个参数是输入型参数,它是用来发送屏蔽信号的
第三个参数是输入型参数,它返回旧的block位图

这个函数不是对pending位图进行修改,因为前面我们已经说明了,如何向进程发送信号,向进程发送信号的本质就是修改该进程的pending位图 

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void show_pending(const sigset_t* pending)
{
    std::cout << "cur process pending" << std::endl;
    for(size_t i = 1; i < 32; i++)
    {
        if(sigismember(pending, i))
        {
            std::cout << "1";
        }
        else
        {
            std::cout << "0";
        }
    }
}

int main()
{
    sigset_t set;
    sigset_t oset;
    sigemptyset(&set);
    sigemptyset(&oset);

    sigaddset(&set, 2);
    sigprocmask(SIG_BLOCK, &set, &oset);

    sigset_t pending;
    while(true)
    {
        sigemptyset(&pending);
        sigpending(&pending);
        show_pending(&pending);
        sleep(1);
    }

    return 0;
}

 

 这段代码的含义是,将二号信号block,然后向该进程发送二号信号,同时打印该进程的pending位图

进程预先屏蔽二号信号,不断获取进程的pending位图并始终打印0000  0000  0000  0000

然后手动发送二号信号,不断获取pending位图01000  0000  0000  0000

2、信号处理时机

信号什么时候被处理?因为信号是被保存在进程PCB中的pending位图里面,要处理前面说到是在合适的时机处理,那么什么是合适的时机?

当进程从内核态返回到用户态的时候,对信号进行检测,处理动作

内核态:执行操作系统的代码和数据时所处的状态,操作系统的所有代码的执行全部在内核态

用户态:用户执行自己的代码和数据所处的状态,我们写的代码全部在用户态执行的

它们两个的本质区别在于:权限不同

在CPU中有一个CR3寄存器,它保存了当前进程的状态,它被置为0时是内核态,它被置为3时是用户态 

前面我们知道:在32位系统下,[0G, 3G]是用户空间,[3G, 4G]是内核空间

在用户空间中,是有一个用户级页表,同时在内核中也有一个内核级页表,这也就说明:进程具有地址空间是能够看到用户和内核的所有内容,但是不一定能够被访问

内核级页表是被所有的进程所共享的,整个操作系统只有一份。

进程间无论如何切换,我们能够保证一定能找到同一个操作系统,因为我们每一个进程都有3G~4G内核空间,使用同一张内核页表

所谓的系统调用,就是进程的身份由用户态转化为内核态,然后根据内核页表找到系统函数,执行。

信号处理流程:

进程收到信号,放到PCB中的pending位图中,当进程从用户态切换到内核态之后,开始查找pending位图,寻找被pending位图中比特位为1的信号,然后查看该信号是否被block,没有被block,然后执行handler函数指针数组的方法,如果是自定义(捕获)就切换到用户态,执行handler函数,然后切换回内核态,接着遍历PCB中的三张表,如果还有信号重复上述过程,最后,返回到用户态的执行代码的下一条命令

 

 内核态 < - > 用户态,不单单是发生在信号部分,进程的时间片到了,操作系统切换进程时,会从用户态到内核态,将该进程从操作系统上拔下来。当再次执行该进程时,操作系统会唤醒该进程,从内核态切换回用户态

3、进程为什么要切换成为用户态才进行信号的捕获方法?

 

操作系统在理论上能够直接执行用户代码,但是操作系统并不相信任何人,它不能直接执行用户代码,降低权限,切换回用户态在执行,因为用户可能会写一些恶意代码,使操作系统崩溃

4、sigaction

sigaction函数与signal类似,不过它的功能更加强大

它的第二个第三个参数与sigprocmask类似,第二个是输入型参数,第三个是输出型参数

不过它是sigaction结构体

 

这里添加一个补充知识:当进程处理一个信号时,操作系统会将处理的信号block,当处理完该信号,才会解除block。

结构体第一个成员就是signal的函数指针,sa_mask是用来设定需要额外屏蔽的信号

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "get a signal " << signo << std::endl;
}

int main()
{
    struct sigaction act;
    memset(&act, 0, sizeof(struct sigaction));

    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    
    sigaction(2, &act, nullptr);
    while (true)
    {
        std::cout << "pid: " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

 

五、可重入函数

先看一个场景:

我们实现了一个函数用来对链表进行头插

void push_front(ListNode* p)
{
    head->_next = p;
    head = p;
}

当我们执行完head->_next = p时,进程接收到信号,跳转到handler函数

void handler()
{
    push_front(&node2);
}

执行完handler之后跳转回push_front函数的head = p; 这就会导致了内存泄漏

 

如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

六、volatile

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

我们在这里通过信号的角度理解

int flag = 0;

void handler(int signo)
{
    flag = 1;
    std::cout << "change flag 0 to 1" << std::endl;
}

int main()
{
    signal(2, handler);

    while (!flag);

    std::cout << "进程正常退出" << std::endl;

    return 0;
}

我们这里的逻辑是将2号信号修改为将flag置为1,然后让进程正常退出

不过我们这里还要做一个特殊处理

signal:test.cpp
	g++ -o $@ $^ -std=c++11 -O4

.PHONY:clean
clean:
	rm -f signal

将g++的编译器优化等级调整到O4

这里发送2号信号没有将该进程终止,不过进程捕获到了该信号

这是为什么呢?

因为g++在main函数中没有找到修改flag的代码,所以将flag的值直接放到了寄存器中,而我们的handler函数是修改了内存中的flag,而实际上进程根本不会去内存中读,所以出现了这个问题

我们在flag前面加上关键字volatile,让进程每次都去内存中读数据

volatile int flag = 0;

 

 

七、SIGCHLD

进程一章讲过用 wait waitpid 函数清理僵尸进程 , 父进程可以阻塞等待子进程结束 , 也可以非阻 塞地查询是否有子进程结束等待清理( 也就是轮询的方式 ) 。采用第一种方式 , 父进程阻塞了就不 能处理自己的工作了 ; 采用第二种方式 , 父进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。
其实 , 子进程在终止时会给父进程发 SIGCHLD 信号 , 该信号的默认处理动作是忽略 , 父进程可以自 定义 SIGCHLD 信号的处理函数, 这样父进程只需专心处理自己的工作 , 不必关心子进程了 , 子进程 终止时会通知父进程 , 父进程在信号处理函数中调用wait 清理子进程即可。
void handler(int signo)
{
    pid_t id = 0;
    while((id == waitpid(-1, nullptr, WNOHANG)) > 0)
    {
        std::cout << "wait child success" << std::endl;
   }
    std::cout << "child process quit success" << std::endl;
}

int main()
{
    signal(17, handler);
 
    if(fork() == 0)
    {
        std::cout << "I am child " << std::endl;
        sleep(3);
        exit(1);
    }

    while(true)
    {
        std::cout << "I am parent" << std::endl;
        sleep(1);
    }
    return 0;
}

 

另一种方法是将17号信号SIGCHLD信号屏蔽,当进程退出之后,自动释放僵尸进程

int main()
{
    //signal(17, handler);
    signal(17, SIG_IGN);

    if(fork() == 0)
    {
        std::cout << "I am child " << std::endl;
        sleep(3);
        exit(1);
    }

    while(true)
    {
        std::cout << "I am parent" << std::endl;
        sleep(1);
    }
    return 0;
}

子进程运行了几秒之后就不在打印证明子进程已经退出

 


总结


以上就是今天要讲的内容,本文仅仅简单介绍了进程间的信号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/108718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java+MySQL基于ssm的会议交接平台

随着社会竞争压力的不断加强,企事业单位内部的会议都在不断的增加,有效的会议可以提高企事业内部的沟通,更好的做出符合战略目标的决策,但是传统的会议交接有一定的问题存在,首先就是必须面对面进行传达,其次就是对任务的安排和执行没有很好的记录,为了改变这些情况,于是我们提…

信贷产品年终总结之贷后逾期分析

自本月月初疫情全面放开后&#xff0c;身边的朋友基本都阳了一遍&#xff0c;希望正在浏览本篇文章的读者您是还没阳过的幸运儿。另外&#xff0c;今天也是冬至了&#xff0c;祝各位读者身边健康&#xff0c;远离羊群&#xff01; 最近我们分享了信贷业务年终总结系列的前2篇文…

Python中转义字符是个啥

文章目录前言一、转义字符是什么&#xff1f;二、常见的转义字符有哪些&#xff1f;总结前言 昨天有粉丝问了我这个代码问题&#xff0c;如下图&#xff1a; 他很好奇代码都没有错误&#xff0c;怎么运行就报错&#xff0c;不知道有咩有小伙伴能看出问题在哪呢&#xff1f; 其…

Cookie 和 Session 的工作流程

文章目录1.Cookie1.什么是Cookie2.Cookie可以干嘛3.Cookie实现登陆逻辑的流程2.session1.session是什么2.session有什么用3.session的工作流程3.Cookie 和 session的区别(重点)1.Cookie 1.什么是Cookie Cookie中存储的是字符串,是浏览器在本地持久化保存数据的一种方案 通过点…

2023春招:Javaweb面试锦囊

cookie 和 session 的区别&#xff1f;&#xff08;必会&#xff09; 存储位置不同 cookie 存放在客户端电脑&#xff0c;是一个磁盘文件。Ie 浏览器是可以从文件夹中找到。session 是存放在服务器内存中的一个对象。 chrome 浏览器进行安全处理&#xff0c;只能通过浏览器找…

圣诞 HTML 代码汇总

文章目录Part.I 音效圣诞树Part.II 圣诞树小球Part.III 简笔圣诞树圣诞节快到了&#xff0c;在网上找了一些 html 代码&#xff0c;觉得挺有意思的&#xff0c;顺带分享一下~ Part.I 音效圣诞树 来源&#xff1a;https://blog.csdn.net/m0_73309780/article/details/128176149…

面临项目失控?四个维度应对项目进度优化【洞见2】

常见的对进度的管理的流程是制定进度目标&#xff0c;WBS工作任务拆解&#xff0c;任务的时间估算&#xff0c;然后执行监督。 有时候这样的过程就会出现的进度延迟&#xff0c;而针对进度的延迟&#xff0c;往往企业多选择是通过加班赶工来完成。 项目进度优化方案 但是如…

【第一章 Linux特点,结构,网路连接模式,Linux目录结构】

第一章 Linux特点&#xff0c;结构&#xff0c;网路连接模式&#xff0c;Linux目录结构 1.操作系统&#xff1a; 用于管理和控制计算机所有软、硬件资源的一组程序。 2. Linux特点总结&#xff1a; ① 开放性&#xff1b; ② 多用户&#xff1b; ③ 多任务&#xff1b; ④ 良好…

喇叭天线设计

电磁喇叭天线是最简单而常用的微波天线。它的主要优点是结构简单&#xff0c;馈电简便&#xff0c;便于控制主面波束宽度和增益&#xff0c;频率特性好且损耗较小。它由波导逐渐张开来形成&#xff0c;其作用是加强方向性&#xff0c;这与声学喇叭的原理相似。若主模TE10的矩形…

python爬虫爬取网页上的图片

目录 一&#xff1a;爬虫基础 二&#xff1a;安装html解析的python工具 三&#xff1a;爬取网页图片 一&#xff1a;爬虫基础 爬虫基本过程&#xff1a; 1.请求标头 headers 2.创建一个会话 requests.Session 3.确定请求的路径 4.根据路径获取网页资源(HTML文件) 5.解析html…

UG/NX二次开发Siemens官方NXOPEN实例解析—2.6 CreateNote

列文章目录 UG/NX二次开发Siemens官方NXOPEN实例解析—2.1 AssemblyViewer UG/NX二次开发Siemens官方NXOPEN实例解析—2.2 Selection UG/NX二次开发Siemens官方NXOPEN实例解析—2.3 Selection_UIStyler UG/NX二次开发Siemens官方NXOPEN实例解析—2.4 File2Points UG/NX二次…

【java随笔】面向对象思维

1.怎么建立面向对象的思维方式 &#xff08;1&#xff09;先整体&#xff0c;再局部 &#xff08;2&#xff09;先抽象&#xff0c;再具体 &#xff08;3&#xff09;能做什么&#xff0c;再怎么做 2.如何学习面向对象 &#xff08;1&#xff09;语法 &#xff08;2&#xff09…

执行docker restart [CONTAINER ID]命令时会把之前的参数都带上吗

我在搭建rocketmq时候&#xff0c;运行了好几个容器命令也比较长如下&#xff1a; # 启动 namesrv docker run -d -p 9876:9876 -v "D:/Program Files/rocketmq/namesrv/logs:/root/logs" -v "D:/Program Files/rocketmq/namesrv/store:/root/store" …

JAVA手机网站销售

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a;

Hazelcast 在springboot下的使用集成

一、Hazelcast简介 Hazelcast是一款由Hazelcast公司开发的基于jvm环境的为各种应用提供分布式集群服务的分布式缓存解决方案。可以嵌入到java、c、.net等开发的产品中使用。其主要功能有&#xff1a; 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等接口的分布…

RocketMQ消费者没有成功消费消息的问题排查

背景 今天下游同事反馈&#xff0c;有一些以取消的订单库存还原异常了&#xff0c;导致部分商品库存没有还原。查日志发现没有收到还原消息&#xff0c;但是查看发送方是可以确认消息是已经发了的&#xff0c;那么是什么原因导致消费者没有收到&#xff0c;或者收到后没有处理消…

算法刷题打卡第53天:排序数组---希尔排序

排序数组 难度&#xff1a;中等 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,5]希尔排…

六、Buffer缓冲区和Stream流

Buffer —— 缓冲区 JavaScript 提供了大量对字符串的便捷操作&#xff0c;但没有读取或操作二进制数据流机制&#xff0c;而在Node.js中可以直接用Buffer类提供的构造函数创建Buffer实例&#xff0c;一个Buffer实例代表一个缓冲区&#xff0c;Buffer的缓冲区专门用于存放二进制…

全球「数字游民」现状,有人已经72岁了

近年来&#xff0c;“数字游民”在国内越来越流行&#xff0c;特别是受疫情的影响&#xff0c;很多人对这种工作生活方式越来越喜欢&#xff0c;也越来越习惯&#xff0c;在此之前&#xff0c;当你告诉别人你是一名“数字游民”时&#xff0c;大家通常会认为你是没有工作的无业…

RocketMQ-双主双从集群+DashBorad搭建(云服务器)

文章目录一、理论垫基础1. NameServer 集群2. Producer集群3. Consumer集群4. Broker集群4.1 节点间数据同步 以及 消息的持久化4.2 Broker的集群模式4.2.1、单Master模式4.2.2、多Master模式4.2.3、多Master多Slave模式-异步复制4.2.4、多Master多Slave模式-同步双写最佳实践4…