Lease机制又叫租约机制,具有以下几个特点:
在分布式系统中一个不可避免的问题就是:数据一致性问题
对于一个状态的修改,如何使所有节点都能够读到相同的修改结果,做法一般有:
部署单数据处理节点:
这种处理方式一般是强制规定某个单节点作为数据处理节点,负责数据状态的收集,其他所有节点当需要获取数据状态时都与该节点进行交互。 这种方式一个显而易见的好处就是数据具有高度一致性,也易于编码实现,但是一个严重的问题就是当该节点宕机或者网络情况不佳时,将会导致其他节点无法与之成功交互,进而影响服务的运行。
集群化数据处理节点:
单点部署不具有高可用性,自然能够想到通过部署多个节点共同提供服务,提高系统的可用性,具体的实施方案有几种做法: - 数据均衡分不到多个几点(如以 hash 的方式),这样当某个节点故障时,只会有部分数据不可访问,一定程度减少了影响面; - 集群中的每一个节点都有完整的数据,这样当某个节点故障时,其他节点依然可以提供完整的服务数据,不会影响服务的运行;
当进程处于S
状态,说明当前进程正处于可中断的阻塞状态,正在阻塞的等待某个函数调用的返回,可以使用strace
命令,查看进程当前究竟阻塞在哪个函数调用。
以blocking_server
为例:
rxsi@VM-20-9-debian:~$ ps -aux | grep blocking_server
rxsi 5777 0.0 0.0 6564 1728 pts/0 S+ 16:56 0:00 ./blocking_server
rxsi 5989 0.0 0.0 7256 892 pts/2 S+ 16:56 0:00 grep blocking_server
strace
命令查看:
rxsi@VM-20-9-debian:~$ strace -T -tt -e trace=all -p 5777
strace: Process 5777 attached
15:41:02.639978 accept(3,
可见当前正阻塞在accept
方法,且当前的套接字fd == 3
使用lsof
可以查看当前进程打开的套接字
rxsi@VM-20-9-debian:~$ lsof -p 5777
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
blocking_ 5777 rxsi cwd DIR 254,1 4096 1192109 /home/rxsi/learncpp/socket_test/block_socket
blocking_ 5777 rxsi rtd DIR 254,1 4096 2 /
blocking_ 5777 rxsi txt REG 254,1 17624 1192141 /home/rxsi/learncpp/socket_test/block_socket/blocking_server
blocking_ 5777 rxsi mem REG 254,1 14592 269841 /usr/lib/x86_64-linux-gnu/libdl-2.28.so
blocking_ 5777 rxsi mem REG 254,1 1820400 269837 /usr/lib/x86_64-linux-gnu/libc-2.28.so
blocking_ 5777 rxsi mem REG 254,1 100712 262180 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
blocking_ 5777 rxsi mem REG 254,1 1579448 269843 /usr/lib/x86_64-linux-gnu/libm-2.28.so
blocking_ 5777 rxsi mem REG 254,1 1570256 265583 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
blocking_ 5777 rxsi mem REG 254,1 42880 269177 /usr/lib/x86_64-linux-gnu/libonion_security.so.1.0.19
blocking_ 5777 rxsi mem REG 254,1 165632 269829 /usr/lib/x86_64-linux-gnu/ld-2.28.so
blocking_ 5777 rxsi 0u CHR 136,0 0t0 3 /dev/pts/0
blocking_ 5777 rxsi 1u CHR 136,0 0t0 3 /dev/pts/0
blocking_ 5777 rxsi 2u CHR 136,0 0t0 3 /dev/pts/0
blocking_ 5777 rxsi 3u IPv4 168274455 0t0 TCP *:3000 (LISTEN)
可以看到 3 号套接字是TCP
协议,且当前处于LISTEN
状态
可以使用gdb program -p pid
命令连接到正在运行的进程,然后看其当前被挂起的地方
rxsi@VM-20-9-debian:~$ gdb program -p 5777
然后使用bt
命令查看当前的堆栈信息
(gdb) bt
#0 0x00007f0481a9fe34 in accept () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x000056226bdab317 in main ()
可以看到当前进程被阻塞在accept
函数
游戏进程一般是 C++ 引擎调用 Python 解释器的方式运行,因此实际的进程是 C++的。
但是如果我们有个 Python 进程,那么就可以使用gdb
+python-dbg
的方式进行调试。
首先安装python-dbg
:
sudo apt-get install gdb python3.6-dbg
然后下载libpython
包到文件系统中,最后采用gdb
链接正在运行的进程,然后按以下步骤调试:
(gdb) python
>import sys
>sys.path.insert(0, '/home/xxx')
>import libpython
>end
(gdb) py-bt
Traceback (most recent call first):
File "run_forever_block.py", line 10, in simple_server
data, addr = s.recvfrom(2048)
File "run_forever_block.py", line 17, in <module>
simple_server()
libpython
中常见的命令有:
py-list 查看当前python应用程序上下文
py-bt 查看当前python应用程序调用堆栈
py-bt-full 查看当前python应用程序调用堆栈,并且显示每个frame的详细情况
py-print 查看python变量
py-locals 查看当前的scope的变量
py-up 查看上一个frame
py-down 查看下一个frame
rxsi@VM-20-9-debian:~$ top
top - 16:19:48 up 138 days, 5:22, 0 users, load average: 0.64, 0.17, 0.06
Tasks: 112 total, 2 running, 110 sleeping, 0 stopped, 0 zombie
%Cpu(s): 25.0 us, 0.2 sy, 0.0 ni, 74.5 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 3818.4 total, 554.2 free, 619.1 used, 2645.1 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 2886.8 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13578 rxsi 20 0 6564 1728 1568 R 99.7 0.0 0:51.99 test_circle_block
如果进程进入了死循环状态,那么就会处于R
状态,并且占用了近乎所有CPU
资源(当然如果程序本身是 CPU 密集型程序也是可能的。。。)
此时使用strace
是无法查看进程死循环原因的,只能使用gdb
命令
rxsi@VM-20-9-debian:~$ gdb program -p 13578
然后依然调用bt
显示堆栈信息
(gdb) bt
#0 0x000055cecc2a1129 in fun1() ()
#1 0x000055cecc2a1140 in main ()
这时候就需要分析为何fun1()
函数是否存在死循环问题
如果进程发生死锁,那么进程会处于S
状态,因此依然可以使用strace
或者gdb
命令进行分析
运行thread_test
可执行文件,这个文件会运行两个进程并形成死锁
rxsi@VM-20-9-debian:~/learncpp$ ./thread_test &
[1] 14829
rxsi@VM-20-9-debian:~/learncpp$
0x7ffe2db36700: lock in mutext
0x7ffe2db366f8: lock in mutext2
0x7ffe2db36700: thread running
0x7ffe2db366f8: thread running
0x7ffe2db36700: try to lock mymutext2
0x7ffe2db366f8: try to lock mymutext
rxsi@VM-20-9-debian:~/learncpp$ ps -aux | grep thread_test
rxsi 14829 0.0 0.0 88624 1792 pts/0 Sl 19:48 0:00 ./thread_test
rxsi 15519 0.0 0.0 7256 912 pts/0 S+ 20:27 0:00 grep thread_test
使用gdb
命令链接到进程,并打印当前的堆栈信息
rxsi@VM-20-9-debian:~/learncpp$ gdb program -p 14829
//..........
(gdb) info thread
Id Target Id Frame
* 1 Thread 0x7fd65a842740 (LWP 14829) "thread_test" 0x00007fd65ad36495 in __pthread_timedjoin_ex () from /lib/x86_64-linux-gnu/libpthread.so.0
2 Thread 0x7fd65a841700 (LWP 14830) "thread_test" 0x00007fd65ad3e29c in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0
3 Thread 0x7fd65a040700 (LWP 14831) "thread_test" 0x00007fd65ad3e29c in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0
(gdb) thread 2
[Switching to thread 2 (Thread 0x7fd65a841700 (LWP 14830))]
#0 0x00007fd65ad3e29c in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0
(gdb) bt
#0 0x00007fd65ad3e29c in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0
#1 0x00007fd65ad37714 in pthread_mutex_lock () from /lib/x86_64-linux-gnu/libpthread.so.0
#2 0x000055d30aac92c0 in thread_fun(void*) ()
#3 0x00007fd65ad34fa3 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
#4 0x00007fd65a944eff in clone () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)
从上面的输出可以知道,此时子进程正阻塞在__lll_lock_wait
函数,等待着锁的释放
在《线程与进程》中有介绍了 linux 系统信号类型,其中有一些信号的产生会使进程终止并生成coredump
文件,因此对于严重阻塞生产环境的进程,可以使之先生成coredump
文件,然后再杀掉进程,再重启进程,而进程异常的原因就可以从coredump
文件中分析得知。
默认情况下 linux 系统是不允许生成coredump
文件的,限制了文件的大小为 0,从下面的指令可知:
rxsi@VM-20-9-debian:~$ ulimit -c
0
因此需要先调整coredump
文件的大小限制,这里可以先设置为unlimited
rxsi@VM-20-9-debian:~$ ulimit -c unlimited
rxsi@VM-20-9-debian:~$ ulimit -c
unlimited
不过这个命令只能存在于当前的会话,因此我们可以临时建立个会话调整并生成coredump
文件之后就退出会话,这样就可以使配置回复原样了
如果要永久修改配置,则需要通过下面的方式
rxsi@VM-20-9-debian:~$ sudo vim /etc/security/limits.conf
将文件中这个注释去掉,并修改coredump
文件的大小
生成的coredump
文件默认存在于当前执行命令的位置
rxsi@VM-20-9-debian:~$ cat /proc/sys/kernel/core_pattern
core
也可以通过下面命令自定义路径
echo "core-%e-%p-%s-%t" > /proc/sys/kernel/core_pattern
%p 出Core进程的PID
%u 出Core进程的UID
%s 造成Core的signal号
%t 出Core的时间,从1970-01-0100:00:00开始的秒数
%e 出Core进程对应的可执行文件名
使用kill
命令向进程发出会使其终止并生成coredump
文件的信号,11
是SIGSEGV
信号
rxsi@VM-20-9-debian:~$ kill -11 19694
先使用gdb
连接入进程,再使之生成coredump
文件,然后再kill
掉进程
rxsi@VM-20-9-debian:~$ gdb program -p 20278
(gdb) gcore
Saved corefile core.20278
// 退出gdb之后
rxsi@VM-20-9-debian:~$ kill -9 20278
要分析coredump
文件,首先需要对源文件加上-g
参数后编译,然后再使用下面的命令:
rxsi@VM-20-9-debian:~$ gdb ./learncpp/test_circle_blocking core.20278
GNU gdb (GDB) 8.3.1
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./learncpp/test_circle_blocking...
warning: core file may not match specified executable file.
[New LWP 20278]
Core was generated by `./test_circle_blocking'.
#0 fun1 () at test_circle_blocking.cpp:4
4 while(true){}
可以看出coredump
时正在调用的函数,然后就可以配合gdb
的其他命令进行分析了。
当进程在运行过程中被系统kill
时,可以通过dmesg
指令查看原因
运行helloworld
可执行程序,一开始进程宕机的原因是1/0
,后面再修改为内存溢出,指令输出如下:
rxsi@VM-20-9-debian:~$ sudo dmesg -T
# .....
[Thu Sep 29 20:44:29 2022] traps: helloworld[23862] trap divide error ip:56226e4d51b5 sp:7ffc588645a0 error:0 in helloworld[56226e4d5000+1000]
[Fri Sep 30 10:17:19 2022] helloworld[3414]: segfault at 0 ip 00007fa569c0a5dc sp 00007fff5e094cd8 error 6 in libc-2.28.so[7fa569ad0000+147000]
[Fri Sep 30 10:17:19 2022] Code: 9d 48 81 fa 80 00 00 00 77 19 c5 fe 7f 07 c5 fe 7f 47 20 c5 fe 7f 44 17 e0 c5 fe 7f 44 17 c0 c5 f8 77 c3 48 8d 8f 80 00 00 00 <c5> fe 7f 07 48 83 e1 80 c5 fe 7f 44 17 e0 c5 fe 7f 47 20 c5 fe 7f
当 TCP 主动发起挥手并成功关闭之后,则会进入TIME_WAIT
状态
之所以设计一个长时间(2MSL,linux 中默认是 60s)的等待状态,主要有两个目的:
确保连接的可靠正常关闭
在第四次挥手阶段发出的ACK
报文是可能会丢失的,因此需要有一个缓冲期用以等待对端重发FIN
报文,否则如果本端直接进入了CLOSED
状态,那么对于对端重发的FIN
报文会直接回复RST
报文,那么对于对端来说即使所有数据都正确发送了,仍然会是以一种“异常”的方式结束
确保重用相同 IP、端口的连接不会接收到上个连接的旧数据包
如果在重用之后网络环境中还存在上条连接的旧数据包,那么可能会错认为这些数据包是当前连接的,而造成错误的处理。但是实际上这种情况的几率极小,因为至少要满足两点,一是使用了相同的IP、端口信息(客户端的端口一般是随机的),二是该数据包的序列号正好在新连接中是有效的(服务端的序列号、客户端的序列号都是重新随机的)。所以TIME_WAIT
状态预防的就是这种极端情况下可能出现的异常问题,状态的持续时间是2MSL
,在 linux 系统中则是为60s
。
如果开启了SO_REUSEADDR
和SO_REUSEPORT
那么新建立的连接就可以立即结束上一个就连接的TIME_WAIT
状态进行IP、端口的重用,这会使得接收到上条连接的旧数据包的概率增大,如果一条新连接接收到了旧数据包,那么极大概率会因为序列号匹配失败而回复正确的ACK
报文,而对端接收到了正确的ACK
报文不会造成任何异常,因此连接可以正常进行。
注意:当客户端重新接收到 FIN 报文时,2MSL 会重新计时
每个进程都是对应了一个task_struct
结构,里面保存有该进程申请并占用的 socket 描述符,而 socket 实际上是一种系统资源,因此是具有与进程不同的生命周期的。TCP 的连接信息是由内核维护,当进程崩溃之后,内核会回收该进程申请的资源。当进程宕机后,所有已建立的连接会发送 FIN 报文,进行四次挥手,因此本端和对端的 TCP 连接都可以被正常关闭。
在单机事务中,我们可以满足 ACID 特性。
在实际工程项目中,为了提升系统的承载上限,降低数据库系统的并发访问量,一般我们会在应用和数据库系统中间增加一层缓存服务。当应用需要读取数据时先访问缓存服务,如果缓存服务存有目标数据,则可直接返回数据,否则则到数据库访问。
根据不同的更新策略,可以有以下几种方案: