Monday, August 10, 2020

一文读懂 HTTP/2 及 HTTP/3 特性

 原文

HTTP 协议 and HTTP/1.x 的缺陷

  1. 连接无法复用

连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对大量小文件请求影响较大(没有达到最大窗口请求就被终止)。


  • HTTP/1.0 传输数据时,每次都需要重新建立连接,增加延迟。

  • HTTP/1.1 虽然加入 keep-alive 可以复用一部分连接,但域名分片等情况下仍然需要建立多个 connection,耗费资源,给服务器带来性能压力。


  1. Head-Of-Line Blocking(HOLB)

导致带宽无法被充分利用,以及后续健康请求被阻塞。HOLB是指一系列包(package)因为第一个包被阻塞;当页面中需要请求很多资源的时候,HOLB(队头阻塞)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。


  • HTTP 1.0:下个请求必须在前一个请求返回后才能发出,request-response对按序发生。显然,如果某个请求长时间没有返回,那么接下来的请求就全部阻塞了。

  • HTTP 1.1:尝试使用 pipeling 来解决,即浏览器可以一次性发出多个请求(同个域名,同一条 TCP 链接)。但 pipeling 要求返回是按序的,那么前一个请求如果很耗时(比如处理大图片),那么后面的请求即使服务器已经处理完,仍会等待前面的请求处理完才开始按序返回。所以,pipeling 只部分解决了 HOLB。


  1. 协议开销大:无状态特性 – 带来巨大的 HTTP 头部

HTTP1.x 在使用时,header 里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求 header 基本不怎么变化,尤其在移动端增加用户流量。


  1. 安全因素

HTTP1.x 在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性


  1. HTTP/1.1 无法为重要的资源指定优先级

每个 HTTP 请求都是一视同仁。


SPDY 协议 and HTTP/2 


HTTP/2 基于 SPDY3,专注于性能

最大的一个目标是在用户和网站间只用一个连接(connection)



HTTP/2 新特性

  1. 二进制传输


HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 

HTTP/1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。

HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。

E.g. Headers frame, Data frame


  1. 多路复用 Multiplexing


在 HTTP/1.1 中,如果客户端想发送多个并行的请求,那么必须使用多个 TCP 连接。


HTTP/2 中,所有的请求和响应都在同一个 TCP 连接上发送:客户端和服务器把 HTTP 消息分解成多个帧,然后乱序发送,最后在另一端再根据流 ID 重新组合起来。


  • Stream:流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2…N);

  • 消息:是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成。

  • Frame:HTTP 2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流,承载着特定类型的数据,如 HTTP 首部、负荷,等等


在 HTTP/2 中,每个请求都可以带一个 31bit 的优先值,0 表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。


  1. 流量控制


每一跳,而不是端对端


  1. Header 压缩


在 HTTP/1 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。


为了减少这块的资源消耗并提升性能, HTTP/2 对这些 Header 采取了压缩策略:

  • HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;

  • 首部表在 HTTP/2 的连接存续期内始终存在,由客户端和服务器共同渐进地更新;

  • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值


  1. Server Push


Server Push 即服务端能通过 push 的方式将客户端需要的内容预先推送过去,

也叫“cache push”。

某些资源客户端是一定会请求的

服务端可以主动推送,客户端也有权利选择是否接收。

如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送 RST_STREAM 帧来拒收。

主动推送也遵守同源策略,换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方确认才行。


HTTP/2 的缺点

HTTP/2 仍然实建立在 TCP 协议上的。

TCP 和 TCP+TLS 建立连接的延时

TCP 的队头阻塞并没有彻底解决,而且当出现了丢包时,HTTP/2 的表现反倒不如 HTTP/1 了(TCP“丢包重传”机制,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了)。


HTTP 3.0


  1. 0-RTT

通过使用类似 TCP 快速打开的技术,缓存当前会话的上下文,在下次恢复会话的时候,只需要将之前的缓存传递给服务端验证通过就可以进行传输了。0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。


  • 传输层 0RTT 就能建立连接。

  • 加密层 0RTT 就能建立加密连接。


  1. Multiplexing 依旧

同 HTTP2.0 一样,同一条 QUIC 连接上可以创建多个 stream,来发送多个 HTTP 请求,但是,QUIC 是基于 UDP 的,一个连接上的多个 stream 之间没有依赖。比如下图中 stream2 丢了一个 UDP 包,不会影响后面跟着 Stream3 和 Stream4,不存在 TCP 队头阻塞。虽然 stream2 的那个包需要重新传,但是 stream3、stream4 的包无需等待,就可以发给用户。


  1. 加密

所有报文 Header 都是经过认证的,报文 Body 都是经过加密的


  1. 向前纠错机制

QUIC 协议有一个非常独特的特性,称为向前纠错 (Forward Error Correction,FEC),每个数据包除了它本身的内容之外,还包括了部分其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。向前纠错牺牲了每个数据包可以发送数据的上限,但是减少了因为丢包导致的数据重传,因为数据重传将会消耗更多的时间(包括确认数据包丢失、请求重传、等待新数据包等步骤的时间消耗)

假如说这次我要发送三个包,那么协议会算出这三个包的异或值并单独发出一个校验包,也就是总共发出了四个包。当出现其中的非校验包丢包的情况时,可以通过另外三个包计算出丢失的数据包的内容。当然这种技术只能使用在丢失一个包的情况下,如果出现丢失多个包就不能使用纠错机制了,只能使用重传的方式了。


Monday, July 20, 2020

TCP IP 原理

数据库连接 Transaction 保证可以复用


HTTP协议不能共用,相当于IO

无状态,没办法区分不同的requests的replies

如果共用一个连接,没有办法保证reply给到request,必须要对连接上锁,保证一对一


连接 --> 能不能复用 --> 连接数 --> 需不需要close再open

连接pool


ThreadLocal


OSI 7层模型


应用层,表示层,会话层,传输控制层,网络层,链路层,物理层


应用层(,表示层,会话层):应用程序员负责

传输控制层(TCP, UDP),网络层,链路层:内核负责


nc // 创建连接

E.g. 

nc localhost 6379 //connection with local redis

nc www.google.com // then you can send GET HTTP 2.0..


netstat -natp // 看连接


nc是内核负责,HTTP或者redis是应用层


什么是TCP?

面向连接的,可靠的传输协议

三次握手


链接是双向的


三次握手都在传输控制层,内核控制,所以是可靠的

连接走通后,双方需要开辟资源

资源链接后,发送数据


什么是socket?

ip:port + port:ip 四元组,唯一确认/区分每一个 socket 对应关系

ip:主机地址,port:映射进程


port 总共有65535个

对于一个http server端,无论多少socket连接,对于server只消耗一个端口号80

对于一个port用完的client,如果server不一样,还能继续开辟socket (client可以用同样的port)


如果加网卡,还能突破 65535 ports


nc -l 192.168.150.11 9090

netstat -natp //可以看到四元组socket 跟进程之间的映射关系


四次分手

为了释放资源,释放port


When connection sets up, there is:

Client ------SYN-----> Server

Client <---ACK/SYN---- Server ----①

Client ------ACK-----> Server


It takes four segments to terminate a connection since a FIN and an ACK are required in each direction.

and when termination comes, there is:

Client ------FIN-----> Server

Client <-----ACK------ Server ----②

Client <-----FIN------ Server ----③

Client ------ACK-----> Server


tcpdump //抓包程序,自行下载

tcpdump -nn -i eth0 port 80

前三个:三次握手

然后request head, server ack; 

server 分了两次发包 length = 1460, 1321

最后四个是分手


DDoS攻击,就是不发最后的那个ack,占用server资源


网络层


网关 Gateway,掩码 Netmask


UIP & 掩码 = 网络地址

子网掩码(subnet mask)是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。 子网掩码不能单独存在,它必须结合IP地址一起使用。 子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分


网关(Gateway)又称网间连接器、协议转换器。 网关在网络层以上实现网络互连,是复杂的网络互连设备,仅用于两个高层协议不同的网络互连。 网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。“下一跳”


路由表

route -n


找出下一跳

这样就不用把所有的route都存在每一台地址里


链路层


mac 地址,硬件系统

全球唯一


节点,端点


MAC地址换,但是IP地址不换


arp -a


高并发

负载均衡:七层 or 四层


Nginx:七层,基于reverse proxy的load balance

LVS:四层,注意!不能破环连接的原子性




Friday, July 17, 2020

How to resolve circular dependencies in Spring?

Spring 循环依赖

  1. 如何解决

  2. 为什么要使用三级缓存

  3. 如果只保留二级缓存行不行


BeanFacotryPostProcessor

BeanPostProcessor


BeanDefinition -> BeanFacotryPostProcessor 

-> BeanFactory 

-> Init Bean -> BeanPostProcessor (before) -> Init Method -> BeanPostProcessor -- after -> context.getBean() 


区别 FactoryBean:产生特殊对象



Spring 在整个创建过程中,如果想在不同阶段做不同的事情怎么处理?

观察者模式


AbstractApplicationContext  refresh()


refreshBeanFactory

Int multicaster

finishBeanFactoryInitialization(beanFactory)



实例化:开辟内存

初始化:初始值



DefaultListableBeanFactory

getBean(beanName)



DefaultSingltonBeanRegistry 包含三级缓存


singletonObjects; //一级缓存

singletonFactories; //三级缓存

earlySingletonObjects; //二级缓存


每次获取bean对象的时候先从以及缓存中获取值


doCreateBean

createBeanInstance 通过reflection实例化


// Eagerly cache singletons:

addSingletonFactory(beanName, () -> getEarlyBeanReference(..)); // 创建中(实例化,但未初始化)


populateProperty //填充属性

addPropertyValues


resolveValueIfNecessary



在刚刚开始我们创建对象的时候有一个循环

在正常情况下,先创造A,再创建B

但是有循环依赖的时候,在创建A的时候就把B创建了


执行 Anonymous inner class,找到创建中的A

给B中的a赋值

 

创建B对象目的:是再给A对象填充属性的时候发现需要B对象,所以顺带创建了B对象


一级:完整对象,(实例化+初始化都完成)

二级:创建中的对象,实例化完成,但未初始化

三级:实例化完成,但未初始化,获取 Anonymous inner class


3级用来保存获取bean的方法,方法内可以递归调用bean的创建

BeanPostProcessor


AbstractAutoProxyCreator

createProxy


如果在A上配置AOP,是否需要生成一个proxy对象?


exposedObject = ...getEarlyBeanReference(...)









我们需要三级缓存:妮所需要的类有可能是简单对象(实例化,初始化),也可能是需要进行代理的代理对象,当我向三级缓存中放置 Anonymous inner class 的时候,可以在获取的时候决定到底是简单对象,还是代理对象


二级放的是object,三级放的是 object factory



不管在什么时候需要对象,都是在需要的时候通过 Anonymous inner class 来生成,而不是提前放治好了一个对象


Monday, July 13, 2020

Micro service + How to handle HF

微服务架构:


Eureka 注册与发现

Actuator 监控

TrestTemplate 服务器远程调用

Ribbon 负载均衡

OpenFeigh 声明式服务调用

Hystrix 熔断,降级,资源隔离

HystrixDashboard

Zuul 网关

Config 配置中心

Sleuth zipkin 链路追踪



流量介入层


域名服务

动态DNS:把一个域名分布到多个IP,多个机房,去本地的load balancer

分流,延迟降低,容灾

HttpDns:

传统DNS是UDP协议(不可靠,数据更新不及时,tree structure 域名机构太庞大,不安全被劫持),而HttpDns是HTTP,不适合浏览器,适合 mobile app,HTTP 协议接口,参数 = 域名


硬防:防止DDoS之类的,买硬件就好


LVS:part of Linux

LVS:Linux 虚拟机、流量调度,负载均衡

单向的 End user -----> LVS -----> tomcat -----> end user,适合IO密集型

nginx:高性能代理服务器,系统内部流量分发,反向代理

有来回 End user -----> nginx-----> tomcat -----> nginx-----> end user


计算密集型服务:加机器

IO型:扩带宽(比如视屏文件)


有多个LVS

Keepalived:IP漂移


流量网关层

Nginx, Tomcat, Apache Httpd, IIS

LVS不认识后面是谁,需要路由层(网关层组件),硬防是保安,lvs是接待,Nginx是大堂经理

  1. 路由

  2. 流量清洗,防止 SQL injection, XSS, CSRF(网络爬虫),软防

流量网关是双向的,网络IO

需要集群



服务网关,需要集群

Zuul, Spring Gateway

  1. 服务路由

  2. 权限校验


权限

Shiro -> CA server

Spring Security

OAuth2 -> OpenID 我提供的服务,通过你的授权,给第三方的服务用


JWT:会话无状态

初次登录:用户初次登录,输入用户名密码

密码验证:服务器从数据库取出用户名和密码进行验证

生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT

返还JWT:服务器的HTTP RESPONSE中将JWT返还

带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorization字段都要有值,为JWT


JWT token 自带权限信息

权限信息:role + rule (更细腻)


服务网关+服务controller 用 Eureka 来服务注册


微服务

服务controller


Wednesday, July 8, 2020

Dig a little bit deeper into Redis...

特征:

In-memory type

Key-Value pair (Value 可以定义本地方法)

The worker thread is single-threaded; there might be more IO threads

Persistent

同步

集群 → 中间件(本地的,分布式)

 

 

  1. 查询状态

多路复用器

Linux 里面用的是 epoll

  1. Receive and read

  2. 计算

 

 

比较同类型(技术选型)

Memcached: value can only be string.

However, you can serialize more complicated structure 

  1. 全量IO

  2. Need to deserialize,浪费 client 计算力

数据向计算移动

 

Redis: value可以是hashset, list, etc

V: function

E.g. Index(n)

计算向数据移动,(弊端:移动数据成本大于移动计算)

 

 

单线性:需要串行

Read client1 -> calc client 1 -> write client 1 -> read client 2 -> calc client 2 -> write client 2

单线程,串行不一定慢!

 

E.g. 秒杀情景:高并发 vs 并行度

Loads of requests -> Load balancer -> Multi Servers -------> 数据库?

看上去servers是并行的,但实际上因为数据库lock,几乎是串行的

 

Loads of requests -> Load balancer -> Multi Servers ---func: decr----> Redis

Redis内部是串行

 

单线程弊端:只能用一个cpu,不能发挥硬件

如何解决?

增加io threads (read and write),worker thread还是 single-threaded

Netty

 

问题:IO顺序?

IO 执行在kernel上是无序的

但是!在一个request里面是有序的:

无状态协议 e.g. http

通信的层次:3

业务应用层次:可以加 uniqle identifier 来识别消息,以此保证顺序

 

Load balancing: 亲密度/粘性

 

Redis的5大Value类型解析

注意使用场景

 

String

  • String, append, strilen -> json,图片,文件 -> 内存级的文件系统(静态小文件),分布一致性/服务无状态

  • 数值 incr, decr

  • Bitmap

 

二进制安全:redis自己不会做编码/解码,需要提供字节数组, 同理 zookeeper, hbase, kafka

需要统一编码 (utf-8)

 

Bitmap 设计拓展:

setbit [key] [offset] [value]

 

用户统计:

任意时间窗口,给出登录天数?

 

存储时是明细全量时点数据,要是用数据库,超级难 aggregation

但是使用redis bitmap,每个bit可以1 or 0,如果告知offset and length,就可以直接得出登录天数

 

活跃用户数

用日期作为key

使用or在bit上的操作,可以得出在时间窗口里面每天都登陆的活跃用户

 

List

 

底层:Double linked list

可以从left push/ pop, 可以right push/pop

可以模拟 queue 和 stack

 

[lindex]: 数组

 

微服务需要stateless

自身不带private fields,只有methods

Private fields 可以放在redis里面被share

 

Hash

 

E.g.

 

set zsquared::name zz

set zsquared::age 18

 

hset zsquared name zz

hset zsquared age 18

hgetall zsquared → name, zz, age, 18

hkeys zsquared → name, agh

hvals zsquared → zz, 18

 

Set

 

去重,无序,用于交集并集差集

在集群里不推荐使用,或者单独设置set redis:如果在redis集群里面被分散,集合操作会伴随大量IO,干扰性能

应用场景:朋友圈交集,共同好友,推荐好友

 

srandmember: 随机取出无序,参数>0的时候会保证去重,<0时不保证

sunion, sinter, sdiff (left join, right join, inner join)

 

Sorted Set/ ZSet

 

zadd k1 2.2 apple 3.3. orange 1.1 banana → 这里的score是float,有精度差

zrange k1 0 -1 withscores → 从小到大

zrevrange k1 0 -1 withscores → 从大到小

Redis这些方程,能让计算向数据流动

 

如何保持顺序: skiplist

最底下就是普通的linked list

造层是随机的,每层间隔也随机,上面的层也是linked list,相当于2分法方便快速insert(insert 时已经排序了)

 

 

Linux系统的支持:fork、copy on write

 

Redis的持久化:RDB、AOF、RDB&AOF混合使用

 

Persistance: 

  1. Snapshot rdb 恢复快,缺失多

  2. 日志 aof 完整 (慢,冗余)
    1)每次操作,完整IO,但是性能下降一半!

2)每秒,buffer and flush to disk,丢失小于一个buffer

3)靠kernel buffer and flush,丢失一个buffer,经验数据是2G

 

混合使用:

E.g. 8点take a snapshot,然后写日志 aof 追加增量;9点再take a snapshot,抹去之前日志(减量)

 

Redis 到底做数据库还是cache?

只是cache需要持久化么?需要,要不然重启期间request会击垮数据库

 

生产系统之中的问题:

Single point of failure: Master-slave mirror cluster,同步

Pressure test: Sharded cluster,不需要同步

 

通过Redis学AKF划分原则、CAP定理

 

Pressure test: Sharded cluster,不需要同步

强一致性,弱一致性

PAXOS 算法(一半)

 

AKF划分(可用于DB, Redis, web service)

X dimension: master-slave cluster 冗余

Y dimension: divided by business purpose 业务划分

Z dimension: sharding → 去哪台fetch data?

 

How to shard?

 

客户端可以给出映射算法,缺点:所有客户端要统一算法

来一个proxy统一算法,但是single point failure

Redis 自带 算法,映射,虚槽位

 

Redis vs zookeeper分布式锁的探索


Most Recent Posts