监听连接时间过长如何解决

本文涉及的产品
.cn 域名,1个 12个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 当我们用PL/SQL Developer或用SQLlplus (sqlplus user/password@orcl)连接数据库的时候非常缓慢,连接有时需要30秒左右才能正常登录到数据库,连接之后数据库一切正常。为了能更好的解决上面的问题,我们需要有如下的一些Oracle的网络基础知识,我们一起来讨论!

讨论话题: 
1.默认的动态监听与非默认的动态监听的本质区别是什么?配置非默认的动态监听需要注意什么?
2.你真的理解了静态监听中的GLOBAL_DBNAME是什么意思了吗?知道的说说。
3.什么场合必须要用静态监听,说的越多越多,最好能用一个实验演示一个案例。
4.静态监听与动态监听的区别?
5.最后我们真正的核心话题是:如何解决上面的问题,给些思路?

1.默认的动态监听与非默认的动态监听的本质区别是什么?配置非默认的动态监听需要注意什么?
(1)什么叫默认动态监听呢?满虽以下条件: 
  a.协议必须为TCP
  b.端口必须为1521
  c.对于监听的名称无要求
 
(2)我们再来看如何配置默认的动态监听,实际不须任何配置,Oracle软件安装完成后就天生了默认的动态监听,有些人说我的$ORACLE_HOME/network/admiin/listener.ora被删除了,怎么还能客户端与服务器端还能连接,那只能说明现在用的就是天生的默认的动态监听(即无须listener.ora配置)。
(3)非默认动态监听,即端口非1521,这从里可以了解到默认的动态监听与非默认的动态监听的本质区别就是端口是不是1521.
(4)如何配置非默认的动态监听呢?
  a.配置listener.ora
 
vi listener.ora
[oracle@ocm admin]$ more listener.ora

listener.ora Network Configuration File: /u01/app/oracle/product/11.2.0/network/admin/listener.ora

Generated by Oracle configuration tools.

ADR_BASE_LSNR2 = /u01/app/oracle
LSNR2 =
(DESCRIPTION_LIST =

(DESCRIPTION =
  (ADDRESS = (PROTOCOL = TCP)(HOST = ocm)(PORT = 1522))
)

)
   
b.alter system set local_listener='(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.132)(PORT = 1522))';

c.alter system register;

--或着在tnsnames.ora中添加如下内容
--LISTENERS_ORADB =
--(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.132)(PORT = 1522))
--alter system set local_listener='LISTENERS_ORADB'  

  这里我要提醒的是:同时开启两个动态监听也只能用一个,另一个动态监听是不能被注册服务的,注册服务主要是由后台进程pmon由做,它以每60秒j时间间隔去观察参数service_names的值,如果有新的服务就把它注册到监听local_listener的参数对应的监听,如果此值为空,说明用的是默认的动态监听。

2.你真的理解了静态监听中的GLOBAL_DBNAME是什么意思了吗?知道的说说。
  如果说看完这幅图还不清楚GLOBAL_DBNAME是什么意思,那我也无语了,有些人说GLOBAL_DBNAME是我们在dbca时让我们输入的GLOBAL DABASE NAME,那完全是不对的,这里的GLOBAL_DBNAME只是一个服务名而已,说到这里我想大家都明白了,还不明白可以给我回贴,我用实验演示给给你看。

4.jpg
3.什么场合必须要用静态监听,说的越多越多,最好能用一个实验演示一个案例。
 
有很多场合是必须要用到静态监听的,这里我举一个例子,比如通过远程客户端(只提供sys用户,而不提供os用户)开启数据库服务器:
 

实验步验如下:
 
(1)先在服务器端配置一个静态监听
vi listener.ora
LSNR3=
(DESCRIPTION=

(ADDRESS_LIST=
  (ADDRESS=(PROTOCOL=tcp)(HOST= ocm)(PORT=1523))
  (ADDRESS=(PROTOCOL=ipc)(KEY=extproc))))

SID_LIST_LSNR3=
(SID_LIST=

(SID_DESC=
  (GLOBAL_DBNAME= jt)
  (ORACLE_HOME=/u01/app/oracle/product/11.2.0)
  (SID_NAME=ocm)))

(2)启动静态监听
 [oracle@ocm admin]$ lsnrctl start lsnr3
LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 28-APR-2013 19:44:36
Copyright (c) 1991, 2009, Oracle. All rights reserved.
Starting /u01/app/oracle/product/11.2.0/bin/tnslsnr: please wait...
TNSLSNR for Linux: Version 11.2.0.1.0 - Production
System parameter file is /u01/app/oracle/product/11.2.0/network/admin/listener.ora
Log messages written to /u01/app/oracle/diag/tnslsnr/ocm/lsnr3/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ocm)(PORT=1523)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=extproc)))
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ocm)(PORT=1523)))

STATUS of the LISTENER

Alias lsnr3
Version TNSLSNR for Linux: Version 11.2.0.1.0 - Production
Start Date 28-APR-2013 19:44:38
Uptime 0 days 0 hr. 0 min. 2 sec
Trace Level off
Security ON: Local OS Authentication
SNMP OFF
Listener Parameter File /u01/app/oracle/product/11.2.0/network/admin/listener.ora
Listener Log File /u01/app/oracle/diag/tnslsnr/ocm/lsnr3/alert/log.xml
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ocm)(PORT=1523)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=extproc)))
Services Summary...
Service "jt" has 1 instance(s).
Instance "ocm", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

(3)我在客户端配置tnsnames.ora
gyj130 =
(DESCRIPTION =

(ADDRESS_LIST =
  (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.217.130)(PORT = 1523))
)
(CONNECT_DATA =
  (SERVICE_NAME = jt)
)

)

注意这里的:端口号1523,服务名jt,这些要以监听状态提供的信息为准。

(4)在服务器端用命令关闭数据库服务器
sys@OCM> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

(5)最后用客户端连接数据库,并启动数据库
从客户端连接数据库:
image.png

从客户端启动数据库:

image.png
4.静态监听与动态监听的区别?
 (1)动态注册的为readly,静态注册的状态为unknow
(2)动态是由PMON进程将服务注册到监听中:service_names/instance_name,静态注册是将服务写入listener.ora文件中 global_dbname/sid_name
 (3)动态修改不需要重启,静态每次修改都要重启监听
 (4)动态重启不能马上注册服务,静态重启马上注册服务
 (5)动态有相关的视图v$service_event/v$service_stats可以查等待事件及物理读逻辑读,而静态监听不能
 
 既然有动态监听为什么还要静态监听呢?原因如下:
   ①监听器不是最早启动,oracle实例先启动
   ②监听器重启
   ③oracle实例没有open
5.最后我们真正的核心话题是:如何解决上面的问题,给些思路?
 某日,接到客户电话,反映plsql连接数据库时特别缓慢,大约要30s左右才能连到数据库,但连到数据库后可以正常使用数据库,而且查询数据一点也不慢,这就奇怪了。对于这样的疑难杂症我们如何下手呢?
  数据库缓慢,常常是一个让dba困扰的运维问题。如果没能掌握处理此类问题的思路,面对此类问题时往往会“雾里看花,水中望月”。我们可以确定的是,业务处理的缓慢导致最终用户连连抱怨;dba难以确定的是,数据库究竟哪里缓慢,因为什么缓慢。因此,诊断数据库哪里缓慢,因为什么缓慢的思路,就显得尤为重要。
  碰到这样的问题先尝试在listener.log和sqlnet.log中寻找问题的原因(两个日志均存放在$ORACLE_HOME/network/log下),日志中无任何报错信息。根据经验,监听器日志文件过大(超过2G)也会引起监听器运行异常,于是校验了监听器文件大小,只有数百兆。为了减轻监听器运行的负担,关闭了监听器日志功能,问题依旧。
 
  对于这样的疑难杂症有时我们只能出绝招,用跟踪工具一步步跟踪,呵呵跟踪测试也是一名艺术,对于上面的9步网络连接借助于strace工具进行跟踪,strace命令,是linux提供的用于跟踪程序执行细节的命令:

 解决任何问题都要有一定的相关理论知识,基础越扎实越有思路,我们先来看看网络连接的整个过程:
image.png

(1)客户端SQL Plus请求连接,监听接受客户端的TCP连接,并获取客户端发过来的TNS数据包。
(2)监听进程打开用于与子进程通信的管道,同时fork一个子进程,称为“监听子进程1”的子进程,然后监听进程一直等待,直到这个“监听子进程1”结束。
(3)监听子进程1 Fork出子进程2。
(4)完成上面一步,子进程1马上退出并结束子进程1。
(5)子进程2收集本进程所在的主机名、IP地址及进程号等信息,并把子进程2重名成server process(这里我们也把server process叫前台进程或叫服务器进程),申请占用一小块PGA内存。
(6)前台进程把主机名、IP地址及进程号发送给监听进程。
(7)监听进程收到前台进程的信息,并返回客户端的信息(比如用户密码环境变量等)给前台进程。
(8)前台进程查询USER$、PROFILE$等数据字典,校验用户名密码是否合法,如果用户密码错误就报错用户名密码无效,否则就与客户端进行交互。
(9)客户端收到前台进程的信息与之交互,整个连接创建完成。

[oracle@ocm dbs]$ ps -ef |grep tnslsnr
oracle 4913 1 0 13:56 ? 00:00:00 /u01/app/oracle/product/11.2.0/bin/tnslsnr LISTENER -inherit
 
strace -rf -o /gyj/lsnr.log -p 4913
整个监听过程的处理流程如下几步:
1.监听接受客户端的TCP连接,并获取客户端发过来的TNS数据包
4926 0.000053 getsockname(8, {sa_family=AF_INET6, sin6_port=htons(1521), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [9169787475114065948]) = 0
4926 0.000226 getpeername(8, 0x7fff2c68e5f8, [9169787475114065948]) = -1 ENOTCONN (Transport endpoint is not connected)
4926 0.000055 accept(8, {sa_family=AF_INET6, sin6_port=htons(42055), inet_pton(AF_INET6, "::ffff:192.168.0.103", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [120259084316]) = 12
4926 0.000063 getsockname(12, {sa_family=AF_INET6, sin6_port=htons(1521), inet_pton(AF_INET6, "::ffff:192.168.0.103", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [120259084316]) = 0
4926 0.000051 fcntl(12, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
4926 0.000034 getsockopt(12, SOL_SOCKET, SO_SNDBUF, [3200064202492396996], [4]) = 0
4926 0.000033 getsockopt(12, SOL_SOCKET, SO_RCVBUF, [3200064202492433792], [4]) = 0
4926 0.000036 setsockopt(12, SOL_TCP, TCP_NODELAY, [1], 4) = 0
4926 0.000087 fcntl(12, F_SETFD, FD_CLOEXEC) = 0
2.监听进程打开用于与子进程通信的管道,同时fork一个子进程,也就是前面我们称为“监听子进程1”的子进程,这里进程号为10209。然后监听进程一直等待,直到这个子进程10209结束
4926 0.000053 pipe([13, 14]) = 0
4926 0.000037 pipe([15, 16]) = 0
4926 0.000042 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b3d814b1320) = 10209
4926 0.000765 wait4(10209,
3.在监听进程等待子进程10209结束的同时,子进程10209完成的工作相对比较简单,仅仅是fork一个子程,也就是前面称为“子进程2”的子进程,新的子进程号为10210。子进程10209完成fork子进程10210之后,就立即退出:
10209 0.000116 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b3d814b1320) = 10210
10209 0.001169 exit_group(0) = ?
4.回到监听主进程,监听进程在子进程10209退出后,在管道上读取数据,这就是一个会阻塞的操作,只有在管理上读到期数据后,才会返回:
4926 0.000567 <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 10209
4926 0.000046 --- SIGCHLD (Child exited) @ 0 (0) ---
4926 0.000040 close(13) = 0
4926 0.000055 close(16) = 0
4926 0.000063 fcntl(15, F_SETFD, FD_CLOEXEC) = 0
4926 0.000056 fcntl(14, F_SETFD, FD_CLOEXEC) = 0
4926 0.000127 fcntl(12, F_SETFD, FD_CLOEXEC) = 0
4926 0.000270 poll([{fd=8, events=POLLIN|POLLRDNORM}, {fd=11, events=POLLIN|POLLRDNORM}, {fd=15, events=POLLIN|POLLRDNORM}, {fd=14, events=0}], 4, -1
10210 0.000197 close(14) = 0
10210 0.000073 close(15) = 0
5.监听进程被阻塞的同时,“子进程2”,也就是进程号为10210的进程,通过exec调用,转而成为Oracle Sever Process:
10210 0.000319 setsid() = 10210
10210 0.000088 geteuid() = 500
10210 0.000112 setsid() = -1 EPERM (Operation not permitted)
10210 0.000169 execve("/u01/app/oracle/product/11g/bin/oracle", ["oracleocp", "(LOCAL=NO)"], [/ 29 vars /]) = 0
6.Server Process执行初始化动作,然后向管道中写入数据:
10210 0.000041 fstat(3, {st_mode=S_IFREG|0644, st_size=12755, ...}) = 0
10210 0.000043 mmap(NULL, 1053208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2b9880bc3000
10210 0.000031 mprotect(0x2b9880bc5000, 1044480, PROT_NONE) = 0
10210 0.000030 mmap(0x2b9880cc4000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x2b9880cc4000
10210 0.000036 close(3) = 0
10210 0.000054 open("/u01/app/oracle/product/11g/lib/libocr11.so", O_RDONLY) = 3
10210 0.000040 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\302\0\0\0\0\0\0"..., 832) = 832
10210 0.000039 fstat(3, {st_mode=S_IFREG|0644, st_size=1590995, ...}) = 0
10210 0.000043 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b9880cc5000
10210 0.000046 mmap(NULL, 1743432, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2b9880cc6000
10210 0.000031 mprotect(0x2b9880d6d000, 1048576, PROT_NONE) = 0
10210 0.000031 mmap(0x2b9880e6d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa7000) = 0x2b9880e6d000
10210 0.000044 close(3) = 0
10210 0.000032 open("/u01/app/oracle/product/11g/lib/libocrb11.so", O_RDONLY) = 3
10210 0.000039 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340{\0\0\0\0\0\0"..., 832) = 832

7.一直到期现在为止,我们还没有看到任何异常的地方。但接下来我们往下看,哇,情况已很明显了:
10210 0.000042 uname({sys-“Linux”,node=”localhost.localdomain”, ….})=0
10210 0.000112 open(“/etc/resolv.conf”,O_RDONLY)=9
10210 0.000047 read(9,”search localdomain\nnameserver 10” …, 4096)=43
这段调用的含义是子进程10210尝试取得node名字localhost.localdomain,接着打开/etc/resolv.conf文件,这个是域名解析的配置文件,接下来d(9,”search localdomain\nnameserver 10” …, 4096)=43,这个地方后面省略的10开头的应该是域名服务器IP地址。表明通过这个服务器解析域名。
接下来是:
10210 0.0000057 connect(9,{sa_family=AF_INET,sin_port=host(53)},sin_addr=inet_addr(“10.54.170.70”) }),28)=0
10210 0.0000056 poll([{fd=9,events=POLLIN}],1,5000
这段调用含义是子进程10210尝试向10.54.170.70这个IP地址,UDP协议端口53,也就是DNS协议端口请求解析域名localhost.localdomain.
Poll是子进程10210在检查返回的数据,5000ms,也就是5s.注意这里的结果是unfinished,表明是在解析域名localhost.localdomain的时候出了问题,等待了5000ms,也就是5s.
接着是:
10210 4.055269 <…poll resumed>;) =0 (Timeout)
10210 0.000119 poll([{fd=9,events=POLLIN}],1,5000<unfinished…>
这说明子进程10210在执行poll的时候超时,然后继续poll.

看完上面的跟踪日志已基本可以定位问题了:OK先来模拟上面连接缓慢的现象,只有重现现象才才知道问题原来是这么简单啊。
这只修改/etc/resolv.conf,估计写错DNS服务器的IP地址,其它什么都不变。
vi /etc/resolv.conf
; generated by /sbin/dhclient-script
search localdomain
nameserver 192.168.217.130

注这里192.168.217.130这个IP不是对应真正的DNS服务器,而是随便写了一个IP.

好马上用sqlplus来做连接:
[oracle@ocm ~]$ sqlplus gyj/gyj@ocm
连接非常缓慢,大约等待10S左右,请耐心等待,OK终于连接正去了。。。。后面操作正常的!!!!!!!!!!!!!!
[oracle@ocm ~]$ date;sqlplus gyj/gyj@ocm <<EOF;date

exit
EOF
Mon Apr 29 21:54:45 CST 2013
SQL*Plus: Release 11.2.0.1.0 Production on Mon Apr 29 21:54:45 2013
Copyright (c) 1982, 2009, Oracle. All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
gyj@OCM> Disconnected from Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Mon Apr 29 21:54:56 CST 2013

对比下面的时间:相差11s
Mon Apr 29 21:54:45 CST 2013
Mon Apr 29 21:54:56 CST 2013

解决:在resolv.conf中配置正确的DNS IP.如果数据库服务器不接外网,干掉就去掉nameserver 192.168.217.131这行。
把resolve那个里面的条目写成8.8.8.8连接时间就会变成30秒,比原来的时间稍微长一点(这个8.8.8.8 本机必须ping不同,想尽办法将外网断掉)


好,如果出现的结果是另一个错误,怎么办???????????????????

报错如下:
[oracle@ocm ~]$ sqlplus gyj/gyj@ocm
SQL*Plus: Release 11.2.0.1.0 Production on Mon Apr 29 20:14:30 2013
Copyright (c) 1982, 2009, Oracle. All rights reserved.
ERROR:
ORA-12545: Connect failed because target host or object does not exist
Enter user-name:
ERROR:
ORA-01017: invalid username/password; logon denied

Enter user-name:
ERROR:
ORA-01017: invalid username/password; logon denied
SP2-0157: unable to CONNECT to ORACLE after 3 attempts, exiting SQL*Plus

对下面的配置做一系列的检查:
1.查/etc/nsswitch.conf 配置
[root@ocm ~]# more /etc/nsswitch.conf
hosts: files dns

2.查/etc/hosts
root@ocm ~]# more /etc/hosts

Do not remove the following line, or various programs

that require network functionality will fail.

127.0.0.1 localhost.localdomain localhost
::1 localhost6.localdomain6 localhost6
192.168.217.130 ocm.example.com ocm

3.查/etc/resolv.conf
[root@ocm ~]# more /etc/resolv.conf
; generated by /sbin/dhclient-script
search localdomain
nameserver 192.168.217.131

4.查DSN
[root@ocm named]# more /var/named/chroot/var/named/example.file
$TTL 86400
@ IN SOA server1.example.com. root (

                                    42              ; serial (d. adams)
                                    3H              ; refresh
                                    15M             ; retry
                                    1W              ; expiry
                                    1D )            ; minimum
            IN NS   server1.example.com.

server1 IN A 192.168.217.130
ocm IN A 192.168.217.130
ocp IN A 172.34.45.57

/etc/nsswitch.conf 这个文件 定义了查找域名解析的顺序 但不是每个应用都会按照这个生面的顺序去走的
/etc/hosts 默认系统的第一解析文件
/etc/resolv.conf 默认系统定义dnsserver的ip地址
最后一个example.file 区域解析文件,负责整个example.com的解析

相关文章
|
SQL 运维 监控
高并发接口超时时间过长,导致服务雪崩
高频访问接口超时时间过长,导致服务雪崩
545 0
高并发接口超时时间过长,导致服务雪崩
|
1月前
|
网络协议 Java Linux
如何解决“连接超时”的问题
当遇到“连接超时”问题时,可尝试以下方法:检查网络连接、重启路由器、清除浏览器缓存、关闭防火墙或杀毒软件、更改DNS服务器等。若问题依旧,建议联系网络服务提供商或技术人员寻求帮助。
305 5
|
5月前
|
存储 前端开发 安全
前端轮询问题之在setTimeout版轮询中,如何避免旧请求的响应继续触发定时
前端轮询问题之在setTimeout版轮询中,如何避免旧请求的响应继续触发定时
105 1
|
5月前
|
前端开发
前端轮询问题之在使用setInterval进行轮询时重复发送请求如何解决
前端轮询问题之在使用setInterval进行轮询时重复发送请求如何解决
172 0
|
7月前
|
存储 索引
webserver--基于小根堆实现定时器,关闭超时的非活跃连接
webserver--基于小根堆实现定时器,关闭超时的非活跃连接
|
7月前
|
存储 缓存 前端开发
如何实现设备组缓存的正确清除?——基于心跳请求和心跳响应的解决方案
如何实现设备组缓存的正确清除?——基于心跳请求和心跳响应的解决方案
82 0
|
存储 SQL 数据库
超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
674 0
|
存储 安全 网络协议
WCF服务调用超时错误:套接字连接已中止。这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的。本地套接字超时是“00:05:30”(已解决)
WCF服务调用超时错误:套接字连接已中止。这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的。本地套接字超时是“00:05:30”(已解决)
714 0
心跳 —— 超时机制分析
在C/S模式中,有时我们会长时间保持一个连接,以避免频繁地建立连接,但同时,一般会有一个超时时间,在这个时间内没发起任何请求的连接会被断开,以减少负载,节约资源。并且该机制一般都是在服务端实现,因为client强制关闭或意外断开连接,server端在此刻是感知不到的,如果放到client端实现,在上述情况下,该超时机制就失效了。本来这问题很普通,不太值得一提,但最近在项目中看到了该机制的一种糟糕的实现,故在此深入分析一下。
926 0
心跳 —— 超时机制分析