> For the complete documentation index, see [llms.txt](https://1005281342.gitbook.io/code-porter/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://1005281342.gitbook.io/code-porter/go-shi-zhan/vue+go-da-zao-yi-ji-liu-liang-wang-guan.md).

# Go实现网关服务

## 前言

### 基本功能

1. 支持多种协议代理：tcp/http/websocket/grpc
2. 支持多种负载均衡策略：轮询/加权轮询/一致性Hash
3. 支持下游服务发现：主动探测/自动服务发现
4. 支持横向扩容：加机器就能解决高并发

### 借助网关处理高可用、高并发

1. 限流：请求QPS限制
2. 熔断：错误率达阈值服务熔断
3. 降级：确保核心业务可用
4. 权限认证：拦截没有权限的用户

## 计算机网络基础

### OSI七层网络协议与TCP/IP协议

![image-20201014193343592](/files/-MJbyj4Gg2OyOF1jeyx3)

### 经典协议与数据包

#### 数据包

![image-20201014193624753](/files/-MJbyj4IA8Kz4U5orZFs)

在物理层传输的是以太网帧，它由以太网首部（主要包含机器mac信息等）和IP数据包构成。

在网络层传输的是IP数据包，它由IP首部和TCP段构成。

在传输层传输的是TCP段，它由TCP首部和应用数据构成。如果应用数据过大则会进行拆包，那么需要在应用数据中数据的包定义起点、终点。

在应用层传输的是应用数据，它由应用头部和用户数据构成。

#### HTTP协议

HTTP协议是明文传输的。

如下是HTTP的请求报文和响应报文

![image-20201014194613130](/files/-MJbyj4JZyU2aEiB3w0U)

#### Websocket握手协议

基于HTTP协议实现，完成连接后以二进制流方式进行传输

![image-20201014194822267](/files/-MJbyj4KClq42SEWjdTu)

**Websocket数据包**

![image-20201014195012535](/files/-MJbyj4LocY0nKOF2-kD)

### 三次握手与四次挥手

#### 三次握手

![image-20201014195350112](/files/-MJbyj4MMpiQH2iRHt7V)

#### 四次挥手

![image-20201014195447121](/files/-MJbyj4NJby-xnyb51SM)

#### 抓包分析三次握手和四次挥手

命令：tcpdump -n -S -i eth0 host [www.baidu.com](http://www.baidu.com) and tcp port 80

![image-20201014200427763](/files/-MJbyj4O3XFkNOmq05d1)

#### 为什么关闭方要等待2MSL？

1. 保证连接可靠关闭
2. 保证所有数据都传输完成

#### 为什么会大量close\_wait?

1. 并发请求数太多导致的
2. 被动关闭方未及时释放端口资源导致

close\_wait一般出现在被动关闭方

### TCP流量控制

流量控制来控制通讯双方数据发送的频率

由于通讯双方网速不同。任一方数据发送过快都会导致对方消息处理不过来，一般会先把数据放到缓存区中。如果缓冲区满了，发送方还在疯狂发送数据，数据包将会被丢弃。因此我们需要控制发送频率，TCP则是通过流量控制来控制发送频率。

![image-20201014201934777](/files/-MJbyj4PuWIkh_kUhHiv)

### TCP拥塞控制

拥塞控制用来调节网络的负载

接收方网络资源繁忙，因未及时响应ACK导致发送方重传大量数据，将是的网络更加拥堵。拥塞控制可以动态调整窗口大小，不只是依赖缓冲区大小确定窗口。

#### 慢启动和拥塞避免

![image-20201014203306434](/files/-MJbyj4QxYgdyyV39-Kt)

在慢启动阶段从1开始以2倍扩大的方式指数增长至慢启动阈值，接着进入拥塞避免加法增大（每次增大1），如果遇到网络拥塞的情况则会直接将窗口置为1，慢启动阈值置为原来的一般，继续进入慢启动阶段。

#### 快速重传和快速恢复

![image-20201014203836336](/files/-MJbyj4RWBRroNsFQjQy)

最开始仍然采用慢启动和拥塞避免算法。不同的是在**收到3个重复ACK执行快重传**，快重传（乘法减小）是将拥塞窗口降为原来的一半，将拥塞避免的阈值也降为原来的一半，接着进入快速恢复阶段。快速恢复对数据进行重传，重传完毕后，进入拥塞避免（加法增大）。

### 为啥会出现粘包、拆包，如何处理？

![image-20201014205744465](/files/-MJbyj4S_hmdDmMapJ8w)

应用程序写入的数据大于套接字缓冲区大小，将会发生拆包。

进行MSS（最大报文长度）大小的TCP分段，当TCP报文长度 - TCP头部长度 > MSS时将发生拆包。

应用程序写入的数据小于套接字缓存区大小，网卡将应用多次写入的数据发送到网络上，将发送粘包。

接收方法不及时读取套接字缓冲区数据，将发生粘包。

#### 如何获取完整的应用数据报文？

1. 使用带消息头的协议，头部写入包长度，然后读取包内容。
2. 设置定长消息，每次读取定长内容，长度不够时空位补固定字符。
3. 设置消息边界，服务端从网络流中按消息边界分离出消息内容，一般使用
4. 在应用层我们还可以直接选择如`protobuf`、`json`等协议。

### 基于go实现TCP、UDP、HTTP服务器与客户端

#### TCP

服务端

```go
// 1. net.Listen 监听端口，选择协议和端口
// 2. 通过listener.Accept() 建立套接字连接，并挂起
// 3. go func 创建处理协程并处理消息

func main() {
	// 1. 监听端口
	var (
		listener net.Listener
		err      error
		conn     net.Conn
	)
	if listener, err = net.Listen("tcp", "0.0.0.0:8989"); err != nil {
		log.Fatalf("listen failed, err: %s \n", err.Error())
	}

	// 2. 建立连接并挂起
	for {
		if conn, err = listener.Accept(); err != nil {
			log.Fatalf("create conn failed, err: %s \n", err.Error())
		}

		// 3. 处理消息
		go handle(conn)
	}
}

func handle(conn net.Conn) {
	defer conn.Close()
	for {
		var buf [128]byte
		var n, err = conn.Read(buf[:])
		if err != nil {
			fmt.Printf("read from connect failed, err: %v\n", err)
			break
		}
		var str = string(buf[:n])
		fmt.Printf("receive from client, data: %v\n", str)
	}
}
```

客户端

```go
// 1. 建立连接器
// 2. 读取命令行输入
// 3. 挂起，一直读取，知道读到\n
// 4. 读取Q时停止
// 5. 发送信息到服务器
func main() {
	sendMessage()
	fmt.Println("send over")
}

func sendMessage() {
	// 1. 连接服务器
	var (
		conn net.Conn
		err  error
	)

	if conn, err = net.Dial("tcp", "localhost:8989"); err != nil {
		log.Fatalf("connect failed, err: %s", err.Error())
	}
	defer conn.Close()

	var (
		inputReader  *bufio.Reader
		input        string
		trimmedInput string
	)
	// 2. 读取命令行输入
	inputReader = bufio.NewReader(os.Stdin)

	for {
		// 3. 直到读取到\n
		if input, err = inputReader.ReadString('\n'); err != nil {
			fmt.Printf("read from console failed, err: %s \n", err.Error())
			break
		}

		// 4. 读取Q时停止
		if trimmedInput = strings.TrimSpace(input); trimmedInput == "Q" {
			break
		}

		// 5. 发送消息
		if _, err = conn.Write([]byte(trimmedInput)); err != nil {
			fmt.Printf("write failed , err : %s\n", err.Error())
			break
		}
	}
}
```

#### UDP

服务端

```go
package main

import (
	"fmt"
	"log"
	"net"
)

// 1. 监听端口
// 2. 循环读取消息
// 3. 打印消息（接收到的客户端消息）
// 4. 响应消息（回复确认消息）
func main() {
	var (
		conn *net.UDPConn
		err  error
	)
	// 监听端口
	if conn, err = net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 8989,
	}); err != nil {
		log.Fatalf("listen failed, err: %s\n", err.Error())
	}

	// 2. 循环读取消息内容
	for {
		var (
			data [1024]byte
			n    int
			addr *net.UDPAddr
		)
		if n, addr, err = conn.ReadFromUDP(data[:]); err != nil {
			log.Fatalf("read failed from addr: %v, err: %s\n", addr, err.Error())
		}

		go func() {
			// 3. 打印消息
			fmt.Printf("addr: %v data: %v  count: %v\n", addr, string(data[:n]), n)
			// 4. 响应消息
			_, err = conn.WriteToUDP([]byte("received success!"), addr)
			if err != nil {
				fmt.Printf("write failed, err: %v\n", err)
			}
		}()
	}
}
```

客户端

```go
package main

import (
	"log"
	"net"
	"strconv"
)

func main() {
	// 1. 连接服务器
	// 2. 发送数据
	// 3. 接收数据
	var (
		conn       *net.UDPConn
		err        error
		rspMsg     = make([]byte, 1024)
		n          int
		remoteAddr *net.UDPAddr
	)
	if conn, err = net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: 8989,
	}); err != nil {
		log.Fatalf("connect failed, err: %s\n", err.Error())
	}

	for i := 0; i < 16; i++ {
		// 发送数据
		if _, err = conn.Write([]byte("hello server, this is msg: " + strconv.Itoa(i))); err != nil {
			log.Printf("send data failed, err : %s\n", err.Error())
			return
		}

		// 接收数据（服务端响应消息）
		if n, remoteAddr, err = conn.ReadFromUDP(rspMsg); err != nil {
			log.Printf("receive data failed, err: %s\n", err.Error())
			return
		}

		log.Printf("receive from addr: %v  data: %v\n", remoteAddr, string(rspMsg[:n]))
	}
}
```

#### HTTP

服务端

```go
var (
	addr = ":8989"
)

// 1. 创建路由器
// 2. 设置路由规则
// 3. 创建服务器
// 4. 监听端口并提供服务
func main() {
	var (
		mux    *http.ServeMux
		server *http.Server
	)
	// 1. 创建路由器
	mux = http.NewServeMux()
	// 2. 设置路由规则
	mux.HandleFunc("/bye", sayBey)
	// 3. 创建服务器
	server = &http.Server{
		Addr:         addr,
		WriteTimeout: time.Second * 3,
		Handler:      mux,
	}
	// 4. 监听端口并提供服务
	log.Println("staring HTTPServer at ", addr)
	log.Fatal(server.ListenAndServe())
}

func sayBey(w http.ResponseWriter, r *http.Request) {
	time.Sleep(1 * time.Second)
	w.Write([]byte("bye bye, server addr: " + r.Host + " your addr: " + r.RemoteAddr))
}
```

客户端

```go
// 1. 创建连接池
// 2. 创建客户端
// 3. 请求数据
// 4. 读取内容
func main() {
	var (
		transport *http.Transport
		client    *http.Client
		rsp       *http.Response
		err       error
		bds       []byte
	)
	// 1. 创建连接池
	transport = &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   time.Second * 30, // 连接超时
			KeepAlive: time.Second * 30, // 探活时间
		}).DialContext,
		MaxIdleConns:          100,              // 最大空闲连接数
		IdleConnTimeout:       time.Second * 90, // 空闲连接超时时间
		TLSHandshakeTimeout:   time.Second * 10, // tls握手超时时间
		ExpectContinueTimeout: time.Second,      // 100-continue状态码超时时间
	}

	// 2. 创建客户端
	client = &http.Client{
		Timeout:   time.Second * 30, // 请求超时时间
		Transport: transport,
	}

	// 3. 请求数据
	if rsp, err = client.Get("http://127.0.0.1:8989/bye"); err != nil {
		panic(err)
	}
	defer rsp.Body.Close()

	// 4. 读取内容
	if bds, err = ioutil.ReadAll(rsp.Body); err != nil {
		panic(err)
	}

	log.Println(string(bds))
}
```

### net包阅读（有一定难度，建议看完如Gin等库实现后再看）

## 网络代理

### 网络代理和网络转发的区别

#### 网络代理

用户不直接连接服务器，网络代理去连接。获取数据后返回给用户。

用户通过代理请求信息，请求通过网络代理完成转发到达目的服务器，目标服务器响应后再通过网络代理回传给用户。

![image-20201015191826326](/files/-MJgA47-Jc5ZRzsYXxEE)

#### 网络转发

是路由器对报文的转发操作，中间可能对数据包修改。

![image-20201015191901825](/files/-MJgA4704ZIuUKJY01np)

### 网络代理类型

#### 正向代理

是一种客户端的代理技术，帮助客户端访问无法访问的服务资源，可以隐藏用户真实IP。比如：浏览器web代理、VPN等。

#### 正向代理示例：实现一个简单的浏览器代理

```go
import (
	"io"
	"log"
	"net"
	"net/http"
	"strings"
)

type Proxy struct{}

func main() {
	log.Println("Serve on :8080")
	http.Handle("/", &Proxy{})
	http.ListenAndServe("0.0.0.0:8080", nil)
}

func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	log.Printf("received request method:%s, host:%s, remote:%s\n", req.Method, req.Host, req.RemoteAddr)
	transport := http.DefaultTransport
	// step 1，浅拷贝对象，然后就再新增属性数据
	outReq := new(http.Request)
	*outReq = *req
	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		outReq.Header.Set("X-Forwarded-For", clientIP)
	}

	// step 2, 请求下游
	res, err := transport.RoundTrip(outReq)
	if err != nil {
		rw.WriteHeader(http.StatusBadGateway)
		return
	}

	// step 3, 把下游请求内容返回给上游
	for key, value := range res.Header {
		for _, v := range value {
			rw.Header().Add(key, v)
		}
	}
	rw.WriteHeader(res.StatusCode)
	io.Copy(rw, res.Body)
	res.Body.Close()
}
```

#### 反向代理

是一种服务端的代理技术，帮助服务器做负载均衡、缓存、提供安全校验等，可以隐藏服务器的真实IP。比如：LVS技术、nginx等

![image-20201015194635411](/files/-MJgA4723JWEVktYjzg2)

#### 反向代理示例：简单的服务器代理

代理

```go
package main

import (
	"bufio"
	"log"
	"net/http"
	"net/url"
)

var (
	proxyAddr = "http://127.0.0.1:2003"
	port      = "2002"
)

func main() {
	http.HandleFunc("/", handler)
	log.Println("Start serving on port " + port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal(err)
	}
}

func handler(w http.ResponseWriter, r *http.Request) {
	//step 1 解析代理地址，并更改请求体的协议和主机
	proxy, err := url.Parse(proxyAddr)
	r.URL.Scheme = proxy.Scheme
	r.URL.Host = proxy.Host

	//step 2 请求下游
	transport := http.DefaultTransport
	resp, err := transport.RoundTrip(r)
	if err != nil {
		log.Print(err)
		return
	}

	//step 3 把下游请求内容返回给上游
	for k, vv := range resp.Header {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}
	defer resp.Body.Close()
	bufio.NewReader(resp.Body).WriteTo(w)
}
```

服务器

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	rs1 := &RealServer{Addr: "127.0.0.1:2003"}
	rs1.Run()
	rs2 := &RealServer{Addr: "127.0.0.1:2004"}
	rs2.Run()

	//监听关闭信号
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
}

type RealServer struct {
	Addr string
}

func (r *RealServer) Run() {
	log.Println("Starting HTTPServer at " + r.Addr)
	mux := http.NewServeMux()
	mux.HandleFunc("/", r.HelloHandler)
	mux.HandleFunc("/base/error", r.ErrorHandler)
	mux.HandleFunc("/test_http_string/test_http_string/aaa", r.TimeoutHandler)
	server := &http.Server{
		Addr:         r.Addr,
		WriteTimeout: time.Second * 3,
		Handler:      mux,
	}
	go func() {
		log.Fatal(server.ListenAndServe())
	}()
}

func (r *RealServer) HelloHandler(w http.ResponseWriter, req *http.Request) {
	//127.0.0.1:8008/abc?sdsdsa=11
	//r.Addr=127.0.0.1:8008
	//req.URL.Path=/abc
	//fmt.Println(req.Host)
	uPath := fmt.Sprintf("http://%s%s\n", r.Addr, req.URL.Path)
	realIP := fmt.Sprintf("RemoteAddr=%s,X-Forwarded-For=%v,X-Real-Ip=%v\n", req.RemoteAddr, req.Header.Get("X-Forwarded-For"), req.Header.Get("X-Real-Ip"))
	header := fmt.Sprintf("headers =%v\n", req.Header)
	io.WriteString(w, uPath)
	io.WriteString(w, realIP)
	io.WriteString(w, header)

}

func (r *RealServer) ErrorHandler(w http.ResponseWriter, req *http.Request) {
	uPath := "error handler"
	w.WriteHeader(500)
	io.WriteString(w, uPath)
}

func (r *RealServer) TimeoutHandler(w http.ResponseWriter, req *http.Request) {
	time.Sleep(6 * time.Second)
	uPath := "timeout handler"
	w.WriteHeader(200)
	io.WriteString(w, uPath)
}
```

演示

```shell
# 未开代理
[~] curl 'http://127.0.0.1:2004'                                      20:02:46
http://127.0.0.1:2004/
RemoteAddr=127.0.0.1:53942,X-Forwarded-For=,X-Real-Ip=
headers =map[Accept:[*/*] User-Agent:[curl/7.64.1]]
[~] curl 'http://127.0.0.1:2003'                                      20:02:53
http://127.0.0.1:2003/
RemoteAddr=127.0.0.1:53943,X-Forwarded-For=,X-Real-Ip=
headers =map[Accept:[*/*] User-Agent:[curl/7.64.1]]

# 2002只代理2003
[~] curl 'http://127.0.0.1:2002'                                      20:02:56
http://127.0.0.1:2003/
RemoteAddr=127.0.0.1:53963,X-Forwarded-For=,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.64.1]]
[~] curl 'http://127.0.0.1:2004'                                      20:03:22
http://127.0.0.1:2004/
RemoteAddr=127.0.0.1:53967,X-Forwarded-For=,X-Real-Ip=
headers =map[Accept:[*/*] User-Agent:[curl/7.64.1]]
```

### HTTP代理

功能：

1. 错误回调及错误日志处理
2. 更改代理返回内容
3. 负载均衡
4. URL重写
5. 限流、熔断、降级
6. 数据统计
7. 权限认证

#### ReverseProxy功能点

1. 支持更改内容
2. 错误信息回调
3. 支持自定义负载均衡
4. URL重写功能
5. 连接池
6. 支持Websocket服务（后续介绍）
7. 支持HTTPS代理（后续介绍）

在net/http/httputil下定义了ReverseProxy

#### 基于ReverseProxy实现简易的HTTP代理

```go
package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

var addr = "127.0.0.1:2002"

func main() {
	var (
		rs1  string
		url1 *url.URL
		err1 error
	)
	// IN:
	//	127.0.0.1:2002/xxx
	// OUT:
	//	127.0.0.1:2003/base/xxx
	rs1 = "http://127.0.0.1:2003/base"
	url1, err1 = url.Parse(rs1)
	if err1 != nil {
		log.Println(err1)
	}
	proxy := httputil.NewSingleHostReverseProxy(url1)
	log.Println("Starting HTTPServer at " + addr)
	log.Fatal(http.ListenAndServe(addr, proxy))
}
```

#### ReverseProxy更改内容支持

#### 特殊的Header头

"X-Forwarded-For": 标记客户端地址每个方向服务器代理的IP

"X-Real-IP": 我的实际请求的标记

"Connection": 标记连接是关闭、长连接等状态

"TE": 标记传输类型是什么

"Trailer": 允许发送方在消息后面添加的一些源信息

#### 拓展ReverseProxy功能

**负载均衡**

1. 随机负载：随机挑选目标服务器IP
2. 轮询负载：遍历服务器列表
3. 加权负载：给目标设置访问权重，按权重轮询（参考Nginx的负载均衡）

   <img src="https://github.com/1005281342/code_porter/blob/master/8_Go实战/vue+Go打造亿级流量网关.assets/image-20220116000319482.png" alt="加权轮询" data-size="original">
4. 一致性Hash负载：请求固定URL访问指定IP

### HTTPS代理

### Websocket代理

### TCP代理

## 中间件（可以参考Gin）

### 洋葱结构的原理

go中间件数据传递

### 中间件实现

中间件一般都封装在路由上，路由是URL请求分发的的管理器。

**基于数据构建中间件**，使用灵活，控制方便。基于链表实现不仅复杂，且难以调用。

### 责任链模式（基于链表构建）

### 方法切片模式（基于数组构建）

#### 方法组装

1. 构建中间件路由
2. 构建URL的中间件方法数组
3. 使用use方法整合路由与方法数组

![image-20201016215511479](/files/3VCg5WvkhI4UYd545Hyd)

#### 方法调用

1. 构建方法请求逻辑
2. 封装`http.Handler`接口与`http.Server`整合

![image-20201016215820640](/files/QA3imsDSb9O97s8nJ8A6)

### 中间件的意义

1. 提高复用、隔离业务。将公共逻辑剥离

   <img src="/files/QUaQz30XKnKVGcsND9q5" alt="image-20201017113106390" data-size="original">
2. 调用清晰、组合随意

   <img src="/files/UkU5ktrjbcFM6RQ0eHUY" alt="image-20201017123125967" data-size="original">

#### 参考

[Golang Web入门（3）：如何优雅的设计中间件](https://zhuanlan.zhihu.com/p/134661627)

## 限流和熔断降级

### 限流

**高并发系统的三大利器**：缓存、降级、限流

缓存：提升业务系统访问速度和增大处理容量，为相应业务增加缓存。

降级：当服务器压力剧增时，根据业务策略降级，以此释放服务资源保证业务正常。

限流：通过对并发限速，以达到拒绝服务、排队或等待、降级等处理。

#### 限流原理

1. 漏桶限流：每次请求时计算桶流量，超过阈值则降级请求。

   <img src="/files/bN2Neuvo0igxPyFR3nYe" alt="image-20201017160329470" data-size="original">
2. 令牌桶限流：每次请求时从令牌桶里取出令牌，取不到则降级请求。

   <img src="/files/D6TrJ7tlp8IicuhAX9Fu" alt="image-20201017160408401" data-size="original">

#### 网关集成限流

### 熔断降级

熔断器是当依赖的服务已经出现故障时，为了保证自身服务的正常运行不再访问依赖的服务，防止雪崩效应。

降级是当服务器压力剧增时，根据业务策略降级，以此释放服务资源保证业务正常。

#### 熔断器的三个状态

1. 服务关闭：服务正常，维护失败率统计，达到失败率阈值时，转到开启状态。
2. 开启状态：调用fallback函数，一段时间后，进入半开启状态。
3. 半开启状态：尝试恢复服务，失败率高于阈值，进入开启状态，低于阈值，进入关闭状态。

#### 熔断降级原理

![image-20201017164146851](/files/HwD19lFN7Ew0NCg2R6hL)

#### 网关集成熔断降级

选择go类库：`hystrix-go`

## Websocket

### WebSocket协议

![image-20201017192553837](/files/EzVP3Li8utIlGVUGSldI)

请求URL如`ws://127.0.0.1:2002/echo`，以`ws`开头表示协议是`websocket`

#### Websocket数据传输协议

![image-20201017193117906](/files/N3vdi2mPXnoWSh1ixEHo)

#### Connection Header头意义

标记请求发起方与第一代理的状态

决定当前事物完成后，是否关闭网络

* Connection: keep-alive 不关闭网络
* Connection: close 关闭网络
* Connection: Upgrade 协议升级

### Websocket代理服务器

## HTTP、HTTPS、HTTP2

### HTTPS与HTTP的区别

HTTPS是HTTP协议的安全版本，HTTP协议的数据传输是明文的，是不安全的，HTTPS使用了SSL/TLS协议进行了加密处理

HTTP默认端口是80，HTTPS默认端口是443。

HTTPS请求流程：

![image-20201017212151871](/files/fdNh2C07Xf3okwJ99I8i)

### HTTP2和HTTP

1. HTTP2采用二进制格式而非文本格式。
2. HTTP2使用一个连接实现多路复用。
3. HTTP2压缩了报头，降低了开销。
4. HTTP2让服务器可以将响应主动“推送”到客户端缓存。

### HTTP2和HTTPS的关系

1. HTTP2代表多路复用的传输协议
2. HTTPS代表HTTP服务器使用了加密传输
3. 一个启用HTTPS的服务器不一定使用HTTP2
4. 但是使用HTTP2的服务器必须启动HTTPS（浏览器强制）

### HTTP2的补充

#### HTTP2设计目标

1. 大多数情况下的感知延迟要有实质上改进
2. 解决HTTP1.1中“队首阻塞”问题
3. 并行操作无需与服务器建立多个连接
4. 保持HTTP1.1的语义，只是标准拓展非替代

#### HTTP2基本概念

1. 流：流是连接中的一个虚拟信道，可以承载双向的消息
2. 消息：是指逻辑上的HTTP消息，比如请求、响应等
3. 帧：HTTP2通信的最小单位，没个帧包含帧首部，至少会标识出当前帧所属的流，承载着特定类型的数据。

#### HTTP2多路复用

#### HTTP2首部压缩

#### HTTP2协议磋商

1. 询问服务器是否支持HTTP2，不支持则将为HTTP1.1
   * 发起带有HTTP2 Upgrade首部的HTTP1.1请求
   * 服务端拒绝升级，通过HTTP1.1返回响应
   * 服务器接受HTTP2升级，切换到新分帧

## TCP代理

### 四层负载均衡和七层负载均衡

路由转发客户端和服务器之间有一次三次握手

反向代理客户端和服务端之间有两次三次握手

#### 四层负载均衡（路由转发）

![image-20201018181534845](/files/O5M4knN5jncsOCSZ7eOV)

NAT作用于内核运行的

#### 七层负载均衡（反向代理）

![image-20201018181646281](/files/Kw45qfDIPfRoMxueeZwd)

代理是用户程序运行的。代理的数据会进入程序buffer中

## gRPC透明代理

## 数据库表整理与创建

### 设计原则：数据库三大范式

1. 列不可再分：如服务名、服务描述等属性不可再进一步拆分了。
2. 属性完全依赖于主键：服务名依赖于服务ID
3. 属性直接依赖于主键

### E-R图
