Introduction
Application Scenarios
- 文本处理
- 输出格式化的文本报表
- 执行算数运算
- 执行字符串操作等等
IFTTT 是 If This Then That 的缩写,它是一个新生的网络服务平台。通俗的来讲,IFTTT 的作用就是如果触发了一件事,则执行设定好的另一件事。所谓的「事」,指的是各种应用、服务之间可以进行有趣的连锁反应。IFTTT 的宗旨是 Put the internet to work for you (让互联网为你服务)。用户可以在 IFTTT 里设定任何一个你需要的条件,当条件达到时,便会触发下一个指定好的动作。它就像是一座神奇的桥梁,能连接我们日常所用的各种网络服务。
举个例子:在你每天订阅的RSS(Feedly)里,如果你觉得一篇文章很好,你会给它加上星标(Favorite)以便下次查询或者浏览,但如果你想把每一次加过星标的文章自动存入你的 Evernote (印象笔记)里呢?使用 IFTTT ,通过简单的设定,就能很好的完成这个流程,即:RSS feed → Favorite → IFTTT → Evernote。一次设定后就会一劳永逸,当然,这只是 IFTTT 中一点微不足道的功能。
1 | // 一般对象 |
1 | @field:FieldSerializer.Optional("ignored") |
1 | // A类 |
虽然A类和B类中的list的元素类型不同,但是都用到了MutableList(其他List类型同样),因此在序列化或反序列的时候,用不同的SerializerUtil对象来注册和使用。
HTTP代理服务器会自动提取请求数据包的HTTP Request数据,并且把HTTP Response的数据转发给发送请求的客户端;HTTP代理服务器使用的端口通常是8080。
HTTP请求协议(不使用代理协议)报文
1 | GET / HTTP/1.1 |
HTTP请求协议(使用代理协议)报文
1 | GET http://www.xxxx.com/ HTTP/1.1 |
差异点:
OPTIONS方法:这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*‘来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
HEAD方法:与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部份。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
GET方法:向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。参见安全方法
POST方法:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
PUT方法:向指定资源位置上传其最新内容。
DELETE方法:请求服务器删除Request-URI所标识的资源。
TRACE方法:回显服务器收到的请求,主要用于测试或诊断。
CONNECT方法:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
connect方法的通常就是把服务器作为跳板机,让服务器直接代理客户端访问,然后把数据原原本本的返回给客户端,connect方法的原理就是TCP直连。
1 | CONNECT http://www.xxxx.com:80/ HTTP/1.1 |
HTTPS代理有多种做法,通常使用CONNECT method,通过proxy建立一条隧道(隧道代理),这样proxy无法解密数据;此外,还有一种类似于中间人攻击的代理手法。
正常情况下浏览器与服务器在TLS链接下内容是加密的,第三方即使可以嗅探到所有的数据,也不能解密。
中间人可以与你建立链接,然后中间人再与服务器建立链接,转发你们之间的内容。这时候中间人就获得了明文的信息。
在访问https链接的时候,查看一下服务器提供的证书是不是正确的。除非入侵并取得服务器的证书私钥,否则中间人是不能完全伪装成服务器的样子的。
用来防范由「伪造或不正当手段获得网站证书」造成的中间人攻击
https://lilywei739.github.io/2017/01/25/principle_for_http_https.html
HTTP Public Key Pinning 介绍: https://imququ.com/post/http-public-key-pinning.html
HeapByteBuffer分配在Java Heap内。
当向NIO Channel写入HeapByteBuffer数据时,需要先把HeapByteBuffer数据拷贝到DirectMemory后,才能把数据发送出去。
1 | // capacity为字节的数量 |
DirectByteBuffer分配在Java Heap外。
当向NIO Channel写入DirectByteBuffer数据时,不需要拷贝,直接把数据发送出去。这样的话,无疑DirectByteBuffer的IO性能肯定强于使用HeapByteBuffer,因为它省去了临时buffer的拷贝开销,这也是为什么各个NIO框架大多使用DirectByteBuffer的原因。
1 | // 可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的, |
JVM进程的Java堆外申请的内存,是用户空间的,DirectByteBuffer的创建就是使用了malloc申请的内存。
Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer’s content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system’s native I/O operations.
如果是使用DirectBuffer就会少一次内存拷贝。如果是非DirectBuffer,JDK会先创建一个DirectBuffer,再去执行真正的写操作。
这是因为当我们把一个地址通过JNI传递给底层的C库的时候,有一个基本的要求:就是这个地址上的内容不能失效。然而,在GC管理下的对象是会在Java堆中移动的。也就是说,有可能我把一个地址传给底层的write,但是这段内存却因为GC整理内存而失效了。所以我必须要把待发送的数据放到一个GC管不着的地方。这就是调用native方法之前,数据一定要在堆外内存的原因。
此外,使用DirectBuffer好处还有,GC压力更小。虽然GC仍然管理着DirectBuffer的回收,但它是使用PhantomReference(虚引用)来达到的,在平常的Young GC或者mark and compact的时候却不会在内存里搬动。如果IO的数量比较大,比如在网络发送很大的文件,那么GC的压力下降就会很明显。
需要特别注意的是,DirectBuffer直接分配在JVM之外的物理内存,而不是JVM中的逻辑内存,需要往Socket或其他接口写的时候,不需要将数据从JVM复制到物理内存,直接输出即可。缺点是当你需要对这些数据进行额外处理的时候,如编码,过滤等,数据还是会复制到 JVM,所以请确保你不需要对数据进行这些额外操作,只是从一个文件复制数据到另一个文件,一个Socket到另一个的时候才使用。
https://blog.csdn.net/xieyuooo/article/details/7547435
https://www.zhihu.com/question/60892134
socket是计算机网络中用于在节点内发送或接收数据的内部端点。
具体来说,它是网络软件 (协议栈) 中这个端点的一种表示,包含通信协议、目标地址、状态等,是系统资源的一种形式。
socket所处的位置大致就是下面的黑色部分,应用层与传输层之间:
其中的传输层就是TCP/IP所在的地方,socket在这里起到就是连接应用层与传输层的作用。
socket的诞生是为了应用程序能够更方便的将数据经由传输层来传输,所以它本质上就是对TCP/IP的运用进行了一层封装,然后应用程序直接调用socket API即可进行通信。
那么它是如何工作的呢?它分为2个部分,服务端需要建立socket来监听指定的地址,然后等待客户端来连接。而客户端则需要建立socket并与服务端的socket地址进行连接。
上图展示的就是建立TCP/IP连接的过程,经典的叫法为“三次握手”的过程。顾名思义,这个过程中来回产生了三次网络通信。
接下来的数据传输过程就简单很多,发送数据就是客户端往服务端通信,服务端处理完之后的数据返回则相反。
值得注意的是,传输的过程涉及到数据Copy,不过这些Copy是必不可少的。其中的发送缓冲区和接收缓冲区就是套接字缓存 (socket buffer)。
连接使用完之后需要关闭,不过 TCP/IP 连接关闭过程比创建更复杂一些,次数多了一次,这就是经典的“四次握手”过程。
socket是进程间数据传输的媒介,为了保证连接的可靠,你需要特别注意建立连接和关闭连接的过程。为了确保准确、完整的数据传输,客户端和服务端来回进行了多次网络通信才得以完成连接的创建和关闭,这同时也是你在运用一个连接时所花费的额外成本。
长连接意味着进行一次数据传输后,不关闭连接,长期保持连通状态。如果两个应用程序之间有新的数据需要传输,则直接复用这个连接,无需再建立一个新的连接。就像下图这样。
它的优势是在多次通信中可以省去连接建立和关闭连接的开销,并且从总体上来看,进行多次数据传输的总耗时更少。缺点是需要花费额外的精力来保持这个连接一直是可用的,因为网络抖动、服务器故障等都会导致这个连接不可用,甚至是由于防火墙的原因。
所以,一般我们会通过下面这几种方式来做“保活”工作,确保连接在被使用的时候是可用状态:
利用TCP自身的保活(Keepalive)机制来实现,保活机制会定时发送探测报文来识别对方是否可达。一般的默认定时间隔是2小时,你可以根据自己的需要在操作系统层面去调整这个间隔,不管是Linux还是Windows系统。
上层应用主动的定时发送一个小数据包作为“心跳”,探测是否能成功送达到另外一端。 保活功能大多数情况下用于服务端探测客户端的场景,一旦识别客户端不可达,则断开连接,缓解服务端压力。
如果在做了高可用的分布式系统场景中运用长连接会更麻烦一些。因为高可用必然包含自动故障转移、故障隔离等机制。这恰恰导致了一旦发生故障,客户端需要及时发现哪些连接已处于不可用状态,并进行相应的重连,包括重新做负载均衡等工作。
短连接意味着每一次的数据传输都需要建立一个新的连接,用完再马上关闭它。下次再用的时候重新建立一个新的连接,如此反复。
它的优势是由于每次使用的连接都是新建的,所以基本上只要能够建立连接,数据就大概率能送达到对方。并且哪怕这次传输出现异常也不用担心影响后续新的数据传输,因为届时又是一个新的连接。缺点是每个连接都需要经过三次握手和四次握手的过程,耗时大大增加。
此外,短连接还有一个致命的缺点。前面提到的维基百科对socket的定义,其中说到socket包含通信协议、目标地址、状态等。实际当你在基于socket 进行开发的时候,这些包含的具体资源主要就是这5个:
有个专业的叫法称之为“五元组”。在一台计算机上只要这五元组的值不重复,那么连接就可以被建立。然而一台计算机最多只能开启65535个端口,如果现在两个进程之间需要通信,作为服务端的IP和端口必然是固定的,因此单个客户端理论上最多只能与服务端同时建立65535个socket连接。
如果除去操作系统和其它进程所占用的端口,实际还会更少。所以,一旦使用不当,在很短的时间内建立了大量连接,端口很容易被占用完。这不但会导致自身无法正常工作,还会影响到同一台计算机上的其它进程。
一些监控或者实时报价类系统,比如股票软件,它需要在几秒之内刷新最新的价格。像这种场景中同时包含了需要运用长连接的三个主要因素:
高频
因为频次越高的话,使用短连接带来的建立连接和关闭连接的总开销越大。
服务端主动推送
而服务端主动推送也需要长连接的原因是,由于服务端往往是“中心化”的,一般都是1个服务端为多个客户端提供服务。所以,如果使用短连接的方式,那么在客户端未主动连接到服务端的情况下,服务端并不知道需要往哪些客户端去推送数据,这是原因之一。所以此时,长连接成为了一个很好的选择。另外一个原因是,哪怕客户端通过定时的短连接轮询方式进行主动连接,除了增加了额外的建立连接和关闭连接的开销外,还可能遇到通信完成后结果数据并未发生变化,做了无用功。
有状态
成熟股票软件的服务端,为了支撑更多的用户以及做高可用,必然部署了多台。但是这个业务场景,用户无法容忍由于多个服务端之间数据同步的误差导致他在客户端看到的价格刷新产生“回退”现象。所以,只能尽量保持一直连接在同一台服务器上,才能避免这个情况。这种场景被称之为“有状态”,也可以理解为是“串行”的,因为多次请求的前后需要保持“连续性”。
短连接则更适用于诸如阅读类软件的场景中,例如,很多时候用户点开一篇文章后需要花一些时间进行阅读,这个时间有长有短,并且直到用户下一次操作之前都没有数据传输发生。这个场景中包含了运用短连接的两个主要因素:
低频
因为低频,所以更能容忍建立连接和关闭连接的开销。
无状态
用户的下一次点击往往跳转到了其它文章,并且新打开的与当前文章并不需要具有“连续性”,所以这种场景我们称之为“无状态”的。另外,理论上同一时刻打开几篇文章也不会存在什么不妥。
通过这两个案例我们可以总结出一个决定何时运用长连接和短连接的最佳实践。
长连接适用于:两个进程之间需要高频通信并且具备服务端主动推送或者有状态(需串行)两者之一的场景,否则并不是必选项。
短连接适用于:两个进程之间通信频率较低,或者属于无状态(可并行)的场景,否则并不是必选项。
其它情况就根据所需的侧重点来,比如侧重性能就长连接,侧重编码的便捷性就选择短连接。
不过有时候我们可能需要一个中庸的方案来作为默认选择,因为很多场景中的请求并不是平稳的,甚至波动会较大,而且可能同时存在有状态和无状态的场景,此时如果单方面的选择长连接或者短连接都会产生较多的资源浪费。
那么我们可以通过增加一些复杂度来实现一个能够综合长连接和短连接各自优点的方案:建立多个长连接,每次数据传输的时候独占使用,用完之后放回,再给后续使用。这种方案被称之为“连接池”。
例如, 很多的数据库访问框架都内置了连接池机制,因为作为底层框架的它不知道会被使用到何种场景的系统中,所以提供了这个选项。
一般都会在应用程序启动时预先建立好指定数据量的连接,以更好应对冷启动后请求数快速上升带来的资源竞争问题,这个数量一般称之为最小连接数。另外,如果新的请求进来时,所有已建立的连接都在使用中,但是连接数的上限未达到指定数量,可以再建立新的长连接来使用,用完依旧放回到空闲池,相当于把连接池扩大了,这个上限数量一般称之为最大连接数。
TCP作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率,而用三次恰恰可以满足以上两方面的需求。
TCP连接的一方A,由操作系统动态随机选取一个32位长的序列号(Initial Sequence Number),假设A的初始序列号为1000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,1001,1002,1003…,并把自己的初始序列号ISN告诉B,让B有一个思想准备,什么样编号的数据是合法的,什么编号是非法的,比如编号900就是非法的,同时B还可以对A每一个编号的字节数据进行确认。如果A收到B确认编号为2001,则意味着字节编号为1001-2000,共1000个字节已经安全到达。
同理B也是类似的操作,假设B的初始序列号ISN为2000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,2001,2002,2003…,并把自己的初始序列号ISN告诉A,以便A可以确认B发送的每一个字节。如果B收到A确认编号为4001,则意味着字节编号为2001-4000,共2000个字节已经安全到达。
A <--> B 的TCP握手过程:-->
其中2和3可以合并成一个步骤
为什么是三次握手,而不是四次,甚至五次?
在Google Groups的TopLanguage中看到一帖讨论TCP“三次握手”觉得很有意思。贴主提出“TCP建立连接为什么是三次握手?”的问题,在众多回复中,有一条回复写道:“这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足”在不可靠信道上可靠地传输信息”这一需求所导致的. 请注意这里的本质需求,信道不可靠, 数据传输要可靠. 三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了. 因此,如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到, 或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了.”。这可视为对“三次握手”目的的另一种解答思路。
第一次握手 - 建立连接
客户端发送连接请求报文段,将SYN位置为1,Sequence Number为X。然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手 - 服务器收到SYN
服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为X+1(Sequence Number+1),同时,自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为Y。服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态。
第三次握手 - 客户端收到服务器的SYN
客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为Y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
https://www.zhihu.com/question/24853633
https://github.com/jawil/blog/issues/14
部署应用时,单独读取主机的“/etc/localtime”文件,即创建pod时同步时区,无需修改镜像,但是每个应用都要单独设置。
1 | apiVersion: extensions/v1beta1 |
Linux:timezone info /usr/share/zoneinfo
如果使用GMT+0,则需要使用/usr/share/zoneinfo/GMT+0
Modify:ln -s /usr/share/zoneinfo/US/Pacific localtime