一.HTTP和HTTPS
1.HTTP协议是什么?
HTTP协议是超文本传输协议的缩写,英文是Hyper Text Transfer Protocol。它是从WEB服务器传输超文本标记语言(HTML)到本地浏览器的传送协议。
超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。
2.HTTP原理
HTTP是一个基于TCP/IP通信协议来传递数据的协议,传输的数据类型为HTML 文件,、图片文件, 查询结果等。
HTTP协议一般用于B/S架构()。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。
- DNS域名解析为服务器IP
- 发起TCP请求,3次握手建立连接
- HTTP请求
- HTTP响应
请求报文:
-
请求行:包括请求方法、URL、协议/版本
-
请求头(Request Header)
-
请求正文
响应报文
- 状态行
- 响应头
- 响应正文
3.请求方式
- GET:请求指定的页面信息,并返回实体主体。
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
- HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
- PUT:从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE:请求服务器删除指定的页面。
post和get的区别:
- 都包含请求头请求行,post多了请求body。
- get多用来查询,请求参数放在url中,不会对服务器上的内容产生作用。post用来提交,如把账号密码放入body中。
- GET是直接添加到URL后面的,直接就可以在URL中看到内容,而POST是放在报文内部的,用户无法直接看到。
- GET提交的数据长度是有限制的,因为URL长度有限制,具体的长度限制视浏览器而定。而POST没有。
4.响应状态码
访问一个网页时,浏览器会向web服务器发出请求。此网页所在的服务器会返回一个包含HTTP状态码的信息头用以响应浏览器的请求。
状态码分类:
- 1XX- 信息型,服务器收到请求,需要请求者继续操作。
- 2XX- 成功型,请求成功收到,理解并处理。
- 3XX - 重定向,需要进一步的操作以完成请求。
- 4XX - 客户端错误,请求包含语法错误或无法完成请求。
- 5XX - 服务器错误,服务器在处理请求的过程中发生了错误。
常见状态码:
- 200 OK - 客户端请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 302 - 临时跳转
- 400 Bad Request - 客户端请求有语法错误,不能被服务器所理解
- 401 Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
- 404 - 请求资源不存在,可能是输入了错误的URL
- 500 - 服务器内部发生了不可预期的错误
- 503 Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
5.为什么要用https?
实际使用中,绝大说的网站现在都采用的是https协议,这也是未来互联网发展的趋势。下面是通过wireshark抓取的一个博客网站的登录请求过程。
可以看到访问的账号密码都是明文传输, 这样客户端发出的请求很容易被不法分子截取利用,因此,HTTP协议不适合传输一些敏感信息,比如:各种账号、密码等信息,使用http协议传输隐私信息非常不安全。
一般http中存在如下问题:
- 请求信息明文传输,容易被窃听截取。
- 数据的完整性未校验,容易被篡改
- 没有验证对方身份,存在冒充危险
为了解决上述HTTP存在的问题,就用到了HTTPS。
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。
那么SSL又是什么?
SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。
6.HTTPS传输流程
- 首先客户端通过URL访问服务器建立SSL连接。
- 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
- 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
- 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
- 服务器利用自己的私钥解密出会话密钥。
- 服务器利用会话密钥加密与客户端之间的通信。
7.HTTPS与HTTP比较
- HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理。
- http和https使用连接方式不同,默认端口也不一样,http是80,https是443。
HTTPS缺点
- HTTPS协议多次握手,导致页面的加载时间延长近50%;
- HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
- 申请SSL证书需要钱,功能越强大的证书费用越高。
- SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。
二.TCP/IP的UDP和TCP
TCP/IP传输层的两个主要协议都是因特网的重要标准,传输控制协议TCP、用户数据报协议UDP
TCP/IP是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇,
只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP 与 UDP 的应用场景
从特点上我们已经知道,TCP 是可靠的但传输速度慢 ,UDP 是不可靠的但传输速度快。因此在选用具体协议通信时,应该根据通信数据的要求而决定。
若通信数据完整性需让位与通信实时性,则应该选用 TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通信等)。
TCP&UDP的比较
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
TCP是面向连接的协议。运输连接是用来传送TCP报文的。TCP传输连接的建立和释放是每一次面向连接的通信中必不可少的过程。因此,传输连接就由三个阶段,即:连接建立、数据传送和连接释放。
这里的SYN=SYNchronization,SYN=1,ACK=0,表示连接请求报文段;同意建立连接则SYN=1,ACK=1,连接后所有的ACK=1。
三次握手(three-way handshake)方案解决了由于网络层会丢失、存储和重复分组带来的问题。试想不进行三次握手可能出现的问题?
如果仅仅是2次握手的话,可能出现的问题如下:
Host A发送的数据包由于网络的原因,出现了滞留,即延时到达了HostB。此时,B以为HostA发来了请求,于是就向HostA发送确认报文,以建立连接。而HostA收到报文后,由于其没有向HostB发起建立连接的请 求,因此不会理睬HostB的确认,也不会向HostB发送数据。而此时的B认为已经建立起连接了,就等待HostA发送的数据,导致HostB的资源白白浪费!
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频 流;IP语音(VoIP)。
视屏、会话和语音电话,丢失一部分数据影响不大,不在乎数据的正确性。简洁、快速、高效,但不能提供必要的差错控制报文,同时在拥塞控制严重时缺乏必要的控制和调节机制。
- 与TCP相比,UDP不提供流量控制、数据应答和状态维护,最大的优势就是快,无连接,不保证数据的正确性,也不保证数据顺序,是一种无序的存在。
问题1:什么是面向连接、面向无连接?
面向连接举例:两个人之间通过电话进行通信。
面向无连接举例:邮政服务,用户把信函放在邮件中期待邮政处理流程来传递邮政包裹。显然,不可达代表不可靠。
从程序实现的角度解析面向连接、面向无连接如下:
TCP面向连接,UDP面向无连接(在默认的阻塞模式下):
在TCP协议中,当客户端退出程序或断开连接时,TCP协议的recv函数会立即返回不再阻塞,因为服务端自己知道客户端已经退出或断开连接,证明它是面向连接的;
而在UDP协议中,recvfrom这个接收函数将会始终保持阻塞,因为服务端自己不知道客户端已经退出或断开连接,证明它是面向无连接的)。
从TCP通信需要服务器端侦听listen、接收客户端连接请求accept,等待客户端connect建立连接后才能进行数据包的收发(recv/send)工作。
而UDP则服务器和客户端的概念不明显,服务器端即接收端需要绑定端口,等待客户端的数据的到来。后续便可以进行数据的收发(recvfrom/sendto)工作。
问题2:UDP 如何发送大量的数据?如何处理分包?
用 updclient 一次不能发送太大的数据量,不然就会报错:一个在数据报套接字上发送的消息大于内部消息缓冲器或其他一些网络限制,或该用户用于接收数据报的缓冲器比数据报小。但不知道一次到底能发多少字节?如果把一个大的字节数组拆分成若干个字节数组发送,那么接收时如何判断和处理呢?
答:方法很简单:
1、在客户端将你要发送的内容(文件什么的都可以)分块,每块内容进行编号,然后发送;
2、服务端在接收到你的分块数据以后,根据你的客户端数据内容的编号重新组装;
3、一般我们在发送数据的时候,尽量采用比较小的数据块的方式(我的都没有超过1024的),数据块太大的话容易出现发送和接收的数据时间长,匹配出问题。
问题3:TCP的四次握手(TCP的3次握手建立连接,4次握手释放连接)
建立起一个TCP连接需要经过“三次握手”:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连 接之前,TCP 连接都将被一直保持下去。
断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写 了,就是服务器和客户端交互,最终确定断开)
总结
TCP是底层通讯协议,定义的是数据传输和连接方式的规范
HTTP是应用层协议,定义的是传输数据的内容的规范
HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP也就一定支持TCP
HTTP支持的是www服务
而TCP/IP是协议
它是Internet国际互联网络的基础。TCP/IP是网络中使用的基本的通信协议。
TCP/IP实际上是一组协议,它包括上百个各种功能的协议,如:远程登录、文件传输和电子邮件等,而TCP协议和IP协议是保证数据完整传输的两个基本的重要协议。
通常说TCP/IP是Internet协议族,而不单单是TCP和IP。
三.Socket
通常情况下Socket连接就是TCP连接、HTTP底层就是一个Socket、使用TCP协议进行连接时,该Socket连接就是一个TCP连接。HTTP是网络上层协议。底层还是Socket短连接是发送数据时进行联接。发送完关闭 HTTPTCP都是协议,底层都走的Socket
HTTP连接:以HTTP协议为通信协议的TCP连接,HTTP连接是基于TCP连接的,当浏览器需要从服务器获取网页数据的时候,会发出一次HTTP请求。HTTP会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,HTTP会立即将TCP连接断开
Socket连接:基于TCP连接或UDP连接,基于TCP需要先三次握手,可靠传输,基于UDP不需要三次握手不可靠(即时通讯IM),不可靠传输
HTTP协议:简单的对象访问协议,对应于应用层。Http协议是基于TCP链接的。
TCP协议:对应于传输层
IP协议:对应与网络层
TCP/IP是传输层协议,主要解决数据如何在网络中传输;而Http是应用层协议,主要解决如何包装数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。像创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
Socket是TCP/IP网络的API,是为了方便大家直接使用更底层协议而存在的一个抽象层。它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议
HTTP是短连接,Socket连接是长连接
1.HTTP的长连接一般就只能坚持一分钟而已,而且是浏览器决定的,你的页面很难控制这个行为。
Socket连接就可以维持很久,几天、数月都有可能,只要网络不断、程序不结束,而且是可以编程灵活控制的。
2.HTTP连接是建立在Socket连接之上。在实际的网络栈中,Socket连接的确是HTTP连接的一部分。但是从HTTP协议看,它的连接一般是指它本身的那部分。
TCP/IP协议栈主要分为四层:应用层、传输层、网络层、数据链路层,
1.Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,
一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。Socket 是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口(API)。
2.SOCKET连接与TCP连接
创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,
例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导 致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据,然后关闭这个连接。
3.Socket通信方式:
? 同步:客户端在发送请求之后必须等到服务器回应之后才可以发送下一条请求。串行运行
? 异步:客户端请求之后,不必等到服务器回应之后就可以发送下一条请求。并行运行
套接字模式:
? 阻塞:执行此套接字调用时,所有调用函数只有在得到返回结果之后才会返回。在调用结果返回之前,当前进程会被挂起。即此套接字一直被阻塞在网络调用上。
? 非阻塞:执行此套接字调用时,调用函数即使得不到得到返回结果也会返回。
套接字工作步骤:
? 服务器监听:监听时服务器端套接字并不定位具体客户端套接字,而是处于等待链接的状态,实时监控网络状态
? 客户端链接:客户端发出链接请求,要连接的目标是服务器端的套接字。为此客户端套接字必须描述服务器端套接字的服务器地址与端口号。
? 链接确认:是指服务器端套接字监听到客户端套接字的链接请求时,它响应客户端链接请求,建立一个新的线程,把服务器端套接字的描述发送给客户端,一旦客户端确认此描述,则链接建立好。而服务器端的套接字继续处于监听状态,继续接受其他客户端套接字请求。
send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器 在TCP/IP等通信中,,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络 接收缓冲区的大小默认8192 设置为100000
阻塞的socket 适用于 一对一 (单个或少量的socket通讯)
非阻塞的socket适用于 大量的socket通讯
断线重连 使用while循环 信号器 阻塞线程
1、TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;
而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
2、也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。
知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,
因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,
即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率
1)TCP提供面向连接的传输,通信前要先建立连接(三次握手机制);UDP提供无连接的传输,通信前不需要建立连接。
2)TCP提供可靠的传输(有序,无差错,不丢失,不重复);UDP提供不可靠的传输。
3)TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组;UDP是面向数据报的传输,没有分组开销。
4.异步通信中长连接问题
如果在指定的时间内(一般为2个小时)没有数据传送,会给对端发送一个Keep-Alive数据,对端如果收到这个数据,回送一个TCP的ACK,确认这个字节已经收到,这样就知道此连接没有被断开。如果一段时间没有收到对方的响应,会进行重试,重试几次后,向对端发一个reset,然后将连接断掉。
这是个很常见的问题,产生原因一把由于网络不通畅造成数据延迟接收或发送,可以设置一个心跳包机制,即在程序空闲的时候每隔一定时间就向对端发送一个很小的数据包,然后对端接受到后也发回同样的包,以确保双方都知道连接还在,否则就设置超时,踢掉对端。
send函数用来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答,send的作用是将要发送的数据拷贝到缓冲区,协议负责传输。
recv函数用来从TCP连接的另一端接收数据,当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,然后从缓冲区中读取接收到的内容给应用层。
accept函数用了接收一个连接,内核维护了半连接队列和一个已完成连接队列,当队列为空的时候,accept函数阻塞,不为空的时候accept函数从上边取下来一个已完成连接,返回一个文件描述符。
服务端TCP:创建一个Socket类指定IP和协议(TCP)=>绑定IP和端口号=>开始监听(0为监听无限个客户端 设置的10)=>设置20秒后监听,20秒发送一次=>开启一个线程=>开始调用异步连接
客户端TCP:获取到Socket协议=>绑定IP与端口=>与服务端进行连接 =>开启异步接收模式
5.WebSocket
WebSocket协议是基于TCP的一种新的网络协议,和http协议一样属于应用层协议
可以把WebSocket想象成HTTP(应用层),HTTP和Socket什么关系,WebSocket和Socket就是什么关系。
HTTP 链接分为短链接和长链接,短链接是每次请求都要重新建立TCP链接,TCP又要三次握手才能建立,然后发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持TCP连接不断开。客户端与服务器通信,必须要由客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。
简单的说,WebSocket协议之前,双工通信是通过多个HTTP 链接轮询来实现的,这导致了效率低下。WebSocket解决了这个问题,他实现了多路复用,他是全双工通信。
HTTP 协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。
WebSocket 协议 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种
WebSocket和HTTP一样是一种应用层协议,都是基于TCP传输的, WebSocket是HTML5中的协议,支持持久连接(长),HTTP不支持持久连接(短)
四. RabbitMq 和队列
1.RabbitMq
接收数据的方式是什么呢?自然是端口监听啦。
那消息队列是什么就很好解释了?
它就是端口监听,接到数据后,将数据排列起来。
Publish:生产者(发布消息)
Subscribe:消费者(接收消息)
Channel:信道
Queue:队列名称
Exchange:交换机名称
交换机类型有4种:
fanout(扇形交换机):扇形交换机是最基本的交换机类型,它能做的事非常简单——广播消息,扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要"思考",所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。
direct(直连交换机):
直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去。同样的一个binding_key也是支持应用到多个队列中的。
这样当一个交换机绑定多个队列时,就会被送到对应的队列去处理。
调用发送者的send方法后,发现结果是队列1和2收到了消息,符合预期
适用场景:有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以派更多的资源去处理高优先级的队列。
topic:这是一种模糊匹配的消息传递
headers:根据携带的头部信息来进行匹配
队列申明:
设置是否是排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,
并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可
以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连
接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者
客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
发送:实例化连接工厂=>建立连接=>创建信道=>声明队列=>构建字节数据包=>发送数据包
接受:实例化连接工厂=>建立连接=>创建信道=>声明队列=>构造消费者实例=>绑定消息接收后的事件委托=>启动消费者
信息确认(如何确保消息正确地发送至RabbitMQ)
信息一旦发送到Subscribe中,则该信息就会从队列中移除。一旦中间信息处理异常/失败,Subscribe端程序退出等,都将会导致信息未处理完成,而此时队列中已将信息移除了,那么就会导致一系列的问题。我们可以在Subscribe端设置手动确认信息,从而解决上述问题的发生。
只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;
// 发送信息确认信号(手动信息确认) channel.BasicAck(ea.DeliveryTag, false);
信息持久化
消息持久化的前提是:将交换器/队列的durable属性设置为true,表示交换器/队列是持久交换器/队列,在服务器崩溃或重启之后不需要重新创建交换器/队列(交换器/队列会自动创建)。
当RabbitMQ退出或死机时会清空队列和信息。通过将队列和信息标记为持久的,来告知RabbitMQ将信息持久化
durable: true, //标记队列持久
消息基于什么传输
生产者产生了消息,然后发布到 RabbitMQ 服务器,发布之前肯定要先连接上服务器,也就是要在应用程序和rabbitmq 服务器之间建立一条 TCP 连接,一旦连接建立,应用程序就可以创建一条 AMQP 信道。
信道是建立在“真实的”TCP 连接内的虚拟连接,AMQP 命令都是通过信道发送出去的,每条信道都会被指派一个唯一的ID(AMQP库会帮你记住ID的),不论是发布消息、订阅队列或者接收消息,这些动作都是通过信道来完成的。
TCP的创建和销毁开销特别大,创建需要3次握手,销毁需要4次挥手。如果不用信道,那么应用程序就会以TCP连接到RabbitMQ,高峰时每秒成千上万条连接会造成资源的巨大浪费,而且操作系统每秒处理TCP连接数也是有限制的,必定造成性能瓶颈。
信道的原理是一条线程一条信道,多条线程多条信道同用一条TCP连接。一条TCP连接可以容纳无线信道即使每秒成千上万的请求也不会成为性能的瓶颈。
通信过程
假设P1和C1注册了相同的Broker,Exchange和Queue。P1发送的消息最终会被C1消费。基本的通信流程大概如下所示:
P1生产消息,发送给服务器端的Exchange
Exchange收到消息,根据ROUTINKEY,将消息转发给匹配的Queue1
Queue1收到消息,将消息发送给订阅者C1
C1收到消息,发送ACK给队列确认收到消息
Queue1收到ACK,删除队列中缓存的此条消息
注意要点:
Consumer收到消息时需要显式的向rabbit broker发送basic.ack消息或者consumer订阅消息时设置auto_ack参数为true。在通信过程中,队列对ACK的处理有以下几种情况:
如果consumer接收了消息,发送ack,rabbitmq会删除队列中这个消息,发送另一条消息给consumer。
如果cosumer接受了消息, 但在发送ack之前断开连接,rabbitmq会认为这条消息没有被deliver,在consumer在次连接的时候,这条消息会被redeliver。
如果consumer接受了消息,但是程序中有bug,忘记了ack,rabbitmq不会重复发送消息。
rabbitmq2.0.0和之后的版本支持consumer reject某条(类)消息,可以通过设置requeue参数中的reject为true达到目地,那么rabbitmq将会把消息发送给下一个注册的consumer。
1 为什么要使用消息队列?
回答:这个问题,咱只答三个最主要的应用场景(不可否认还有其他的,但是只答三个主要的),即以下六个字:
(1)解耦
传统模式:
传统模式的缺点: 系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
中间件模式:
中间件模式的的优点:
-
将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。
(2)异步
传统模式:
传统模式的缺点:
-
一些非必要的业务逻辑以同步的方式运行,太耗费时间。
中间件模式:
中间件模式的的优点:
-
将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
(3)削峰
传统模式
传统模式的缺点:
-
并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
中间件模式:
中间件模式的的优点:
-
系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。
2 使用了消息队列会有什么缺点?
分析:一个使用了MQ的项目,如果连这个问题都没有考虑过,就把MQ引进去了,那就给自己的项目带来了风险。我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。要记住,不要给公司挖坑!
回答:回答也很容易,从以下两个个角度来答
系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
2.队列
什么是队列?我想学习过数据结构应该很清楚,如果没有仔细了解,只要记住队列是一个先进先出的列表即可,列表中可以是线程,可以是预备执行的函数的入口,可以是地址,可以是数据,在C#中,Queue<T> 类可以实现队列,这一个类可以简单的让我们完成数据的插入和获取,可以在便利性这一块十分出众的。
Queue队列就是先进先出。它并没有实现 IList,ICollection。所以它不能按索引访问元素,不能使用Add和Remove。下面是 Queue的一些方法和属性
Enqueue():在队列的末端添加元素
Dequeue():在队列的头部读取和删除一个元素,注意,这里读取元素的同时也删除了这个元素。如果队列中不再有任何元素。就抛出异常
我们将建立一个存储String数据的队列,为了实用,队列能够用多线程的方式来插入,后台将有一个长时间运行的线程来不断将数据从队列取出并执行我们编写的内容。因此需要规划多线程之间资源冲突如何避免的措施
使用Queue类的Enqueue函数来添加,这里定义一个Enqueue函数来添加数据到队列,可以看到有一个object类的锁,它的作用就是防止冲突
当读取数据时候,上锁,防止此时写入数据,当线程读取到-1时候,线程退出程序结束。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace QueueApp { class Program { struct bbchecklist { public string stand_title; public string fail_title; public string success_title; public int num; } struct FactorySet { static int FPLHP25; static int FPLHP40; } struct TTCDM //时间法测试数据项目 { static int PLHP40; static int PLHP25; public double IHP; public int CCDT40; public int CCDT25; public int DIW; public int v40s; public int v25s; public int v40e; public int v25e; } struct PTCDM //能量法测试数据项目 { public int ACDT70; public int ACDT60; public int ACDT50; public int ACDT40; public int ACDT30; public int ACDT25; public int ACDT20; } static Queue<bbchecklist> _tasks = new Queue<bbchecklist>(); //队列 readonly static object _locker = new object(); //使用一个锁来保护_task的访问 static EventWaitHandle _wh = new AutoResetEvent(false); //通知Work线程的信号 static Thread _worker; static void Main(string[] args) { _worker = new Thread(Work); _worker.Start(); SetQueueData(); Dispose(); } /// <summary> /// 任务执行线程 /// </summary> static void Work() { while(true) { bbchecklist title=new bbchecklist(); lock ( _locker) { if(_tasks.Count > 0) { title = _tasks.Dequeue(); DispalyData(title); if (title.num == -1) { System.Console.WriteLine("---Queue Over---"); return; } } } if (title.fail_title != null) //任务不为空 { Thread.Sleep(1200); } else { _wh.WaitOne(); } } } /// <summary> /// 插入队列 /// </summary> /// <param name="num"></param> /// <param name="stdStr">一般提示语句</param> /// <param name="failStr">失败提示语句</param> /// <param name="succeedStr">成功提示语句</param> static bool EnqueuelTask(int num ,string stdStr,string failStr,string succeedStr) { lock(_locker) { bbchecklist newitem =new bbchecklist(); newitem.stand_title = stdStr; newitem.fail_title = failStr; newitem.num = num; newitem.success_title = succeedStr; _tasks.Enqueue(newitem); } return true; } /// <summary> /// 设置队列预置的数据 /// </summary> /// <param name="num"></param> /// <returns>设置成功true,设置失败false</returns> static bool SetQueueData() { EnqueuelTask(0, "测功机预热中..", "预热失败,按F8继续", "测功机预热完成按F8继续"); EnqueuelTask(1, "正在进行时间法滑行测试..", "测试失败!按F8继续进行能量法测试", "测试已通过!按F8继续"); EnqueuelTask(2, "正在进行能量法滑行测试..", "能量法测试失败!", "能量法测试成功!"); return true; } /// <summary> /// 停止线程的运行并释放所有的资源 /// </summary> static void Dispose() { EnqueuelTask(-1, "正在结束测试..", "结束测试未完成", "测试已结束");//加入最后的 _worker.Join();//阻止调用线程 _wh.Close();//释放信号 } /// <summary> /// /// </summary> static bool DispalyData(bbchecklist bb) { System.Console.WriteLine("{0}:{1} {2} {3}", bb.num, bb.stand_title, bb.success_title, bb.fail_title); return true; } } }
先进后出 栈Stack
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Stack<string> _name = new Stack<string>() ; //元素入栈,就是向栈中添加元素 _name.Push ("小三"); _name.Push ("小四"); _name.Push ("小五"); _name.Push ("小六"); //获取stack 中的元素个数 int a= _name.Count ( ); //使用 Pop 出栈,并获取元素(元素在出栈后,栈中将不会在存有这个元素,相当于剪切出来,而不是复制) //出栈是个方法,不能指定出栈的元素只能返回最上方的元素,也就是最后入栈的元素 //栈 符合后进先出的逻辑 //使用出栈,返回最最后入栈的元素并删除栈中的元素 string s =_name.Pop ( ); //使用Peek返回最后入栈的元素不删除栈中的元素 string s1 =_name.Peek ( ); //清空栈中的元素 _name.Clear ( ); } } }
五.Redis和Memcached
redis比如key-value string list hash zset 等
Redis缓存数据库(多个) Redis缓存文件夹(多个目录)
redis 简介
简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。
存储数据有限制 只能存string 但是呢 速度快
.Redis只能使用单线程,性能受限于CPU性能
memorycache无法进行持久化
memorycache只支持简单的key/value数据结构
应用场景
redis:数据量较小的更性能操作和运算上。
memcache:用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding)。
为什么要用 redis?/为什么要用缓存?
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
redis 的线程模型
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 socket
IO 多路复用程序
文件事件分派器
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
redis 和 memcached 的区别
对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了!
1、redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
2、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
3、集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
4、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
HTTP和HTTPS TCP/IP的UDP和TCP Socket和WebSocket RabbitMq和队列 Redis和Memcached