深度神经网络
激活函数
ReLU
ReLU(Rectified linear unit),线性整流函数。
ReLU(a) = max(0, a)
ReLU(Rectified linear unit),线性整流函数。
ReLU(a) = max(0, a)
我们的 consul dashboard(基于 consul ui 源码,新增了权限控制,独立部署)突然出现 504 异常。
打开浏览器调试器的 network,查看异常的请求。
/v1/*
接口,向部署的node应用发送请求,node 应用再将请求通过插件( express-http-proxy
)代理转发至 consul server。
很奇怪,其他所有机房都没有问题。而且,多刷几次,偶尔可以正常响应。
这一台机器的 Java 服务报了奇怪的 \n not found: limit=0 content=�~@�
异常。
1 | [ERROR 2021-03-15 20:21:39.114] com...init(ConsulBase.java:58) [[consul] init consul base failed, e=[com.orbitz.consul.ConsulException: Error connecting to Consul |
看样子,是无法连接consul,接口异常?此外,由于无法连接consol,报了一堆无法找到服务端的RPC异常,这也正常,注册中心连不上,肯定也就拿不到服务列表。
我们的 C++ 服务( 使用库ppconsul
),直接报了无法注册的异常。
1 | E0315 19:28:54.129444 11661 ...cpp:456] [consul] register local consul agent failed! |
首先,尝试重启 consul server。不重启还好,一重启,这台机器之前注册的服务全没了,怎么重启服务都注册不上。
随后,查看consul日志,伴随着不太明了的异常。
于是,想通过consul程序,看下members,确认加入了集群,但是…
他,出错了,很奇怪的错。
1 | Error retrieving members: Get "http://127.0.0.1:8500/v1/agent/members?segment=_all": EOF |
到这里,可以想到的是,是consul接口出问题。开始的时候前端请求异常,应该也是因为代理请求consul接口,出错导致的。凡事都有个但是,排查端口发现,有正常的连接建立啊。
而且呢,还很多。
等等… 难道… 是因为… 太多了?赶紧搜下。
好吧,找到了这里。
单个 IP 限制的最大连接数,是 200。行吧。改大试试吧。
1 | { |
至此,好了。
错误信息
1 | 2023/11/10 14:48:23 [ERR] memberlist: Failed to send gossip to 172.31.153.158:9301: write udp 127.0.0.1:8301->192.168.6.8:9301: sendto: invalid argument |
解决
修改绑定地址 -bind=192.168.6.5
。
1 | consul agent -config-dir=/etc/consul.d/ -data-dir=/var/lib/consul/ -bind=192.168.6.5 |
1 | 列举目录 |
层 | 功能 | 常见协议 |
---|---|---|
应用层(Application Layer) | 应用层提供网络服务和用户应用程序的接口。它定义了各种应用协议,如HTTP、FTP、SMTP等,以满足特定应用需求 | HTTP、HTTPS、FTP、SMTP、POP、IMAP、DNS、DHCP、Telnet |
表示层(Presentation Layer) | 表示层负责处理数据的表示和格式转换,以确保在不同系统间的互操作性。它处理数据的压缩、加密、解密和格式转换等任务 | |
会话层(Session Layer) | 会话层管理不同应用程序之间的通信会话,建立、维护和终止会话连接。它提供会话控制和同步功能,支持多个会话和数据交换 | |
传输层(Transport Layer) | 传输层提供可靠的端到端数据传输服务,确保数据的完整性和顺序。它定义了传输协议(如TCP和UDP),并处理数据分段、流量控制和错误恢复 | TCP、UDP |
网络层(Network Layer) | 网络层负责在不同网络之间进行数据包的路由和转发。它为数据包选择最佳路径,并处理跨网络的寻址和拥塞控制 | IP、ICMP |
数据链路层(Data Link Layer) | 数据链路层管理通过物理层建立的点对点链接或局域网,确保可靠的数据传输。它处理帧的组装、错误检测和校正,并提供对物理层的透明访问 | Ethernet、ARP |
物理层(Physical Layer) | 物理层处理物理连接以及数据传输的物理介质,如电缆、光纤等。它负责为传输比特流提供传输媒介和基本的电气和物理特性 |
层 | 功能 |
---|---|
应用层(Application Layer) | 应用层是用户与网络进行交互的接口。它包含众多的协议和应用程序,用于实现各种网络服务和应用需求。常见的应用层协议有HTTP(超文本传输协议)、FTP(文件传输协议)、SMTP(简单邮件传输协议)等 |
传输层(Transport Layer) | 传输层提供端到端的数据传输服务。它定义了两个主要的传输协议:TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)。TCP提供可靠的、面向连接的数据传输,而UDP提供无连接的、不可靠的数据传输。该层还处理数据分段、流量控制和错误恢复等任务 |
网络层(Internet Layer) | 网络层实现了IP协议,负责在不同网络之间进行数据包的路由和转发。它使用IP地址标识主机和网络,并通过路由选择算法确定最佳路径。主要协议包括IP(Internet Protocol)、ICMP(Internet Control Message Protocol)和ARP(Address Resolution Protocol) |
网络接口层(Network Interface Layer) | 也称为数据链路层或物理层。该层处理与物理网络介质的直接交互,例如以太网、Wi-Fi等。它负责将数据包封装为帧,并进行物理传输 |
互联网(Internet)是一个概念,是广域网的一种(WAN,Wide Area Network),在全世界被广泛使用。互联网通过 TCP/IP 协议,把全球范围内的局域网连接在一起,可以实现点对点的网络通讯。
以太网(Ethernet)是一种常见的局域网(LAN,Local Area Network)技术,工作在物理层和数据链路层。在物理层,它定义了电气特性、信号传输速率和距离限制等规范。在数据链路层,以太网定义了帧格式、帧起始和结束标记、帧同步和帧检验序列等。
从网络模型来看,互联网是在网络层及之上构建的互联网络,而以太网则是工作在数据链路层及之下的一种技术。互联网是被人普遍认为的网络,而以太网则是被广泛使用的构建局域网的技术。
万维网(World Wide Web)是一种基于英特网的信息系统,通过超文本链接的方式来组织、检索和共享文档和资源。在万维网上,不同的文档和资源通过统一的标识符(URL,统一资源定位符)进行唯一标识。万维网的核心技术是HTTP(Hypertext Transfer Protocol),它定义了在网络上传输超文本资源的规范。
MAC地址(Media Access Control Address)是网络设备(如网卡、无线适配器)在出厂时固定分配的唯一标识符。它是一个由12个十六进制数(0-9,A-F)组成的字符串,通常以冒号或短横线分隔。MAC地址由两部分组成:前6个十六进制数表示厂商代码(OUI,Organizationally Unique Identifier),用于标识设备制造商;后面的6个十六进制数是设备的序列号,由制造商分配。
MAC地址是在数据链路层上使用的,用于局域网内设备之间的直接通信。当设备在局域网上发送数据时,目标设备的MAC地址用于将数据帧传送到正确的设备。
需要注意的是,MAC地址只在局域网内部有效,不会跨越路由器传输。在互联网上进行通信时,数据包会根据目标IP地址进行路由,而不是MAC地址。
用于判断网络联通状态。
1 | ipv4 地址 |
注意:
可以远程连接开启 telnet 服务器的目标设备,也可以判断目标设备的某个端口的连通性。
1 | telnet 10.0.10.1 8080 |
FLAGS | 含义 |
---|---|
SYN | 建立连接 |
FIN | 关闭连接 |
ACK | 响应 |
PSH | 有数据传输 |
RST | 重置连接 |
首部字段 | 说明 |
---|---|
seq | 序列号 |
ack | 对方需要发送的下一个报文序号 |
再述三次握手。
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。当一方完成它的数据发送后发送一个FIN来终止这个方向的连接,另一端收到FIN后,仍可以发送数据。首先进行关闭的一方执行主动关闭,另一方执行被动关闭。
再述四次握手,终止连接。
终止连接,需要双方都发送终止请求,并回复对方。
状态 | 说明 | 备注 |
---|---|---|
CLOSED |
初始状态 | |
LISTEN |
监听状态, 可以接受连接 | |
SYN_RCVD |
接受到SYN 报文 |
正常情况下, 这个状态是服务端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态. 在这种状态时, 当收到客户端的ACK报文后, 会进入ESTABLISHED状态 |
SYN_SEND |
客户端已发送SYN报文 | 与SYN_RCVD 呼应, 当客户端SOCKET执行CONNECT连接时, 首先发送SYN报文, 随即进入SYN_SEND状态, 并等待服务端发送三次握手中的第二个报文 |
ESTABLISHED |
连接已建立 | |
FIN_WAIT_1 |
等待对方FIN报文 | FIN_WAIT_1和FIN_WAIT_2都表示等待FIN报文. FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1状态, 而当对方回应ACK报文后,则进入到FIN_WAIT_2状态. |
FIN_WAIT_2 |
等待对方FIN报文 | FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接 |
TIME_WAIT |
收到对方FIN报文, 并发送了ACK报文 | 等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态 |
CLOSING |
表示发送FIN报文后, 没有收到对方的ACK报文, 却收到了对方FIN报文 | 如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报 文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接 |
CLOSE_WAIT |
等待关闭 | 收到对方FIN报文, 并回应了ACK报文, 进入到CLOSE_WAIT状态. 在CLOSE_WAIT状态下,需要完成的事情是等待关闭连接. |
LAST_ACK |
被动关闭一方在发送FIN报文后, 等待对方的ACK报文 | 当收到ACK报文后, 进入CLOSED状态 |
这是因为,服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持TIME_WAIT状态 。
允许老的重复分节在网络中消逝(实际也就是避免同一端口对应多个套接字). TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。
不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的。
参考
通常I/O操作都是 阻塞I/O,以一次读操作为例,数据先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间,对应数据准备、数据拷贝两个阶段。在数据准备阶段,线程/进程会被挂起,当数据拷贝至应用程序内存空间后,线程/进程被唤醒。
如果使用阻塞I/O,同时处理1000个请求,则需要1000个线程,虽然大多数线程可能被挂起,但不可避免的是频繁的线程上下文切换,非常占用系统资源,每个线程的时间槽也会非常短,导致系统整体有效负载降低,此外每个线程还会占用 512KB / 1MB 的内存。
此时,引入了 非阻塞I/O 的概念,通过调用 fcntl(POSIX)/ ioctl(Unix)设为非阻塞模式。在此模式下,如果有数据收到,就会返回数据,否则立即返回一个错误。这样虽然不会阻塞线程,但需要线程不断轮询来读取或写入。
为了解决单线程轮询单个文件描述符效率低下的问题,有了 I/O多路复用 的概念,多路是指多个文件描述符(socket连接),复用指的是复用一个线程。
所以,I/O多路复用是指,使用一个线程来检查多个文件描述符的就绪状态,如调用 select / poll 函数,传入多个文件描述符,如果有一个就绪,则返回,否则阻塞至超时。
这样在处理1000个连接时,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。
I/O多路复用(多路是指多个文件描述符(fd,file descriptor,socket 是fd的一种),复用指的是复用一个线程),主要技术有 select、poll、epoll。
- | select | poll | epoll |
---|---|---|---|
时间处理复杂度 | O(n) | O(n) | O(1) |
连接数 | 需要通过 FD_SETSIZE 设置最大连接数 | 无限制 | |
事件触发机制 | 轮询 | 轮询 | 回调 |
工作模式 | LT | LT | LT / ET |
fd 拷贝 | 每次调用,每次拷贝 | 每次调用,每次拷贝 | 通过 mmap 内存映射,避免内存拷贝 |
select 会将全量fd_set
从用户空间拷贝到内核空间,并注册回调函数, 在内核态空间来判断每个请求是否准备好数据 。select在没有查询到有文件描述符就绪的情况下,将一直阻塞(select是一个阻塞函数)。如果有一个或者多个描述符就绪,那么select将就绪的文件描述符置位,然后select返回。返回后,由程序遍历查看哪个请求有数据。
缺陷
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd
结构代替select的fd_set
(网上讲:类似于位图)结构,其他的本质上都差不多。所以Poll机制突破了Select机制中的文件描述符数量最大为1024的限制。
缺陷
epoll 对 select 的三个缺陷进行了修复。
解决内存拷贝:epoll 方式下,fd 是通过mmap共享内存的方式,避免在用户空间和内核空间的拷贝。
解决多次遍历:epoll不像select或poll一样每次都把当前线程轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把当前线程挂一遍(这一遍必不可少),并为每个fd指定一个回调函数。当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。那么当我们调用epoll_wait时,epoll_wait只需要检查链表中是否有存在就绪的fd即可,效率非常可观。
解决fd 数量限制:pollfd 结构代替 fd_set 解决数量限制, 使用双向链表保存就绪状态的fd。
工作模式
epoll 分为两种工作模式,LT(level trigger,水平触发) 和 ET(edge trigger,边缘触发),两种模式的区别是在于对就绪fd的处理。
进程 / 线程在发起IO操作之后,内核会立刻返回,不会阻塞用户线程/进程。当内核把数据准备完毕后,向用户进程发送一个signal通知。
简单的说,netty 提供了一个NIO(异步非阻塞IO)网络框架,基于此可以轻松地开发高性能的网络应用。
reactor 模式是事件驱动的一种实现,将服务端接受请求和事件处理分离,从而提升系统处理并发的能力,java的NIO的reactor模式是基于系统内核的多路复用技术实现的。
reactor模式有三个组件,Handler、Demultiplexer、Reactor,核心是 IO复用 + 非阻塞编程 + bind/function。
reactor 是一种网络编程模式,其底层多使用系统实现的I/O多路复用能力。
在 Netty 主要靠 NioEventLoopGroup 线程池来实现具体的线程模型的 。我们实现服务端的时候,一般会初始化两个线程组,Boss Group 和 Worker Group,前者用于接收连接,后者负责具体的处理,交由对应的 Handler 处理。
Netty 可以实现多种线程模型,单线程、多线程、主从多线程模型。
CPU核心数 * 2。
Selector 能够检测多个注册的通道上是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
Channel 是为 Netty 网络操作的抽象类,EventLoop 负责处理注册到其上的Channel,处理I/O操作,两者配合完成I/O操作
EventLoopGroup 是一组 EventLoop 的抽象,提供了 next 接口,从一组 EventLoop 中按规则选择一个 EventLoop 来处理任务。
BootStrap 通过 connect 方法连接远程主机,作为TCP协议的客户端,或通过bind方法绑定本地端口,作为UDP协议的一端。只需配置一组 EventLoopGroup。
ServerBootStrap 通过 bind 方法绑定本地端口,等待客户端连接。需配置两组 EventLoopGroup ,一个用于接收,一个用于处理,即 Boss EventLoopGroup 和 Worker EventLoopGroup。
[toc]
redis的部署模式分为集群模式和哨兵模式,哨兵模式下的角色有主 + 从 + sentinel。无论哪种模式,主从模式都是存在的,起到读写分离、数据备份的作用。
哨兵是一个独立与数据节点的进程,具备集群监控、消息通知、故障自动转移、配置中心的功能,用于解决高可用的问题。
这里了解两个概念,主观下线和客观下线。单个哨兵判断一个主节点下线,为主观下线;该哨兵向其他哨兵确认主节点是否下线,当一半以上哨兵确定主节点下线,则判定该主节点为客观下线。主节点被判定为客观下线后,哨兵会启动Raft选主程序,最终被投为领导者的哨兵节点完成主从自动切换的过程。
官方提供的分布式方案,包含槽指派、重新分片、故障转移。
string 类型是二进制安全的,一个键最大能存储512MB数据。存储结构分位整数、EmbeddingString、SDS,整数存储字符串长度小于21且能转化为整数的字符串;EmbeddingString 存储字符串长度小于 39 的字符串;SDS 存储不满足上述条件的字符串。
只对长度小于或等于 21 字节,并且可以被解释为整数的字符串进行编码,使用整数存储
尝试将 RAW 编码的字符串编码为 EMBSTR 编码,使用EMBSTR 编码
这个对象没办法进行编码,尝试从 SDS 中移除所有空余空间,使用SDS编码
SDS
1 | /* |
SDS扩容
当字符串长度小于SDS_MAX_PREALLOC (1024*1024),那么就以2倍的速度扩容,当字符串长度大于SDS_MAX_PREALLOC,那么就以+SDS_MAX_PREALLOC的速度扩容。
SDS缩容
释放内存的过程中修改len和free字段,并不释放实际占用内存。
底层使用哈希表实现,结构为数组+链表。redis 字典包含两个哈希表,用于在 redis 扩缩容时的渐进式rehash。
redis 4.0 之后,新增了后台线程处理重操作,比如清理脏数据、无用连接的释放、大 key 的删除,但是工作线程仍然是单线程。基于内存的redis,性能瓶颈不在处理器,主要受限于内存和网络。redis 6.0 新增 I/O 线程,用于处理处理 I/O 的读写,使多个socket的读写可以并行,为工作线程减压。
在集群模式下,一个 redis 实例对应一个 RedisDB,一个RedisDB对应一个Dict,一个Dict对应两个Dictht(dict hash table),正常情况下只用到 ht[0];ht[1] 在 rehash 时使用。
rehash 是redis在字典扩缩容时进行的操作,rehash 过程中,字典同时持有两个哈希表,删除、查找、更新等操作都会在两个哈希表上进行,新增操作只在新的哈希表进行,原有的数据的搬迁通过后续的客户端指令(hset、hdel)及定时任务完成。
触发条件
扩容条件:数据量大于哈希表数组长度时触发,如果数据量大于数组长度5倍,则进行强制扩容;缩容条件:数据量小于数组长度的10%。
问题
redis中使用IO多路复用(多路是指多个socket连接,复用指的是复用一个线程)的epoll方式。
可从过期策略和淘汰策略来分析。过期策略分位被动删除和主动删除两类,被动删除主要是读取过期key,惰性删除;主动删除,包括定期清理和内存超过阈值触发主动清理。
淘汰策略有多种。
淘汰策略 | 说明 |
---|---|
volatile-lru | 仅对设置了过期时间的key采取lru淘汰 |
volatile-lfu | 对设置了过期时间的key执行最近最少使用淘汰,redis 4.0开始支持 |
volatile-ttl | 仅对设置了过期时间的key生效,淘汰最早写入的key |
volatile-random | 随机回收设置过期时间的key |
allkeys-lru | 对所有key都采用lru淘汰 |
allkeys-random | 随机回收所有key |
allkeys-lfu | 对所有key执行最近最少使用淘汰,redis 4.0开始支持 |
no-enviction | 默认策略,向客户端返回错误响应 |
注:
所谓锁,在计算机中,本质是内存中的一块空间,其值被赋予了加锁/未加锁的含义,其底层实现,是基于计算机系统中的原子操作。
mutex lock 核心包含两点。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
协程 (coroutine)是可暂停和恢复执行的过程,是一种编程思想。
子程序,或者称为函数,在所有语言中都是层级调用。子程序调用是通过栈实现的,一个线程同一时间就是执行一个子程序。协程本质上也是子程序,协程作为子程序最大的特征是可中断可恢复。
green threads VS native threads
green threads 是一种由运行环境或虚拟机(VM)调度,而不是由本地底层操作系统调度的线程。绿色线程并不依赖底层的系统功能,模拟实现了多线程的运行,这种线程的管理调配发生在用户空间而不是内核空间,所以它们可以在没有原生线程支持的环境中工作。
在Java 1.1中,绿色线程(至少在 Solaris 上)是JVM 中使用的唯一一种线程模型。 由于绿色线程和原生线程比起来在使用时有一些限制,随后的 Java 版本中放弃了绿色线程,转而使用native threads。[2]
在 Java1.2 之后. Linux中的JVM是基于pthread
实现的,即 现在的Java中线程的本质,其实就是操作系统中的线程。
对比
1 | addr2line -f -e path/to/binary <address> |
列出对象文件中的符号
1 | nm libssl.a |
1 | 安装 |
1 | ldd main |
strings 程序的主要功能是找出文件(包括文本文件、二进制文件等)内容中的可打印字符串。
1 | strings /usr/lib/libstdc++.so.6 | grep GLIBCXX |