前言
基本功能
支持多种协议代理:tcp/http/websocket/grpc
支持多种负载均衡策略:轮询/加权轮询/一致性Hash
借助网关处理高可用、高并发
计算机网络基础
OSI七层网络协议与TCP/IP协议
经典协议与数据包
数据包
在物理层传输的是以太网帧,它由以太网首部(主要包含机器mac信息等)和IP数据包构成。
在网络层传输的是IP数据包,它由IP首部和TCP段构成。
在传输层传输的是TCP段,它由TCP首部和应用数据构成。如果应用数据过大则会进行拆包,那么需要在应用数据中数据的包定义起点、终点。
在应用层传输的是应用数据,它由应用头部和用户数据构成。
HTTP协议
HTTP协议是明文传输的。
如下是HTTP的请求报文和响应报文
Websocket握手协议
基于HTTP协议实现,完成连接后以二进制流方式进行传输
Websocket数据包
三次握手与四次挥手
三次握手
四次挥手
抓包分析三次握手和四次挥手
命令:tcpdump -n -S -i eth0 host www.baidu.com and tcp port 80
为什么关闭方要等待2MSL?
为什么会大量close_wait?
close_wait一般出现在被动关闭方
TCP流量控制
流量控制来控制通讯双方数据发送的频率
由于通讯双方网速不同。任一方数据发送过快都会导致对方消息处理不过来,一般会先把数据放到缓存区中。如果缓冲区满了,发送方还在疯狂发送数据,数据包将会被丢弃。因此我们需要控制发送频率,TCP则是通过流量控制来控制发送频率。
TCP拥塞控制
拥塞控制用来调节网络的负载
接收方网络资源繁忙,因未及时响应ACK导致发送方重传大量数据,将是的网络更加拥堵。拥塞控制可以动态调整窗口大小,不只是依赖缓冲区大小确定窗口。
慢启动和拥塞避免
在慢启动阶段从1开始以2倍扩大的方式指数增长至慢启动阈值,接着进入拥塞避免加法增大(每次增大1),如果遇到网络拥塞的情况则会直接将窗口置为1,慢启动阈值置为原来的一般,继续进入慢启动阶段。
快速重传和快速恢复
最开始仍然采用慢启动和拥塞避免算法。不同的是在收到3个重复ACK执行快重传,快重传(乘法减小)是将拥塞窗口降为原来的一半,将拥塞避免的阈值也降为原来的一半,接着进入快速恢复阶段。快速恢复对数据进行重传,重传完毕后,进入拥塞避免(加法增大)。
为啥会出现粘包、拆包,如何处理?
应用程序写入的数据大于套接字缓冲区大小,将会发生拆包。
进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度 - TCP头部长度 > MSS时将发生拆包。
应用程序写入的数据小于套接字缓存区大小,网卡将应用多次写入的数据发送到网络上,将发送粘包。
接收方法不及时读取套接字缓冲区数据,将发生粘包。
如何获取完整的应用数据报文?
使用带消息头的协议,头部写入包长度,然后读取包内容。
设置定长消息,每次读取定长内容,长度不够时空位补固定字符。
设置消息边界,服务端从网络流中按消息边界分离出消息内容,一般使用
在应用层我们还可以直接选择如protobuf
、json
等协议。
基于go实现TCP、UDP、HTTP服务器与客户端
TCP
服务端
// 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)
}
}
客户端
// 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
服务端
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)
}
}()
}
}
客户端
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
服务端
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))
}
客户端
// 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等库实现后再看)
网络代理
网络代理和网络转发的区别
网络代理
用户不直接连接服务器,网络代理去连接。获取数据后返回给用户。
用户通过代理请求信息,请求通过网络代理完成转发到达目的服务器,目标服务器响应后再通过网络代理回传给用户。
网络转发
是路由器对报文的转发操作,中间可能对数据包修改。
网络代理类型
正向代理
是一种客户端的代理技术,帮助客户端访问无法访问的服务资源,可以隐藏用户真实IP。比如:浏览器web代理、VPN等。
正向代理示例:实现一个简单的浏览器代理
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等
反向代理示例:简单的服务器代理
代理
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)
}
服务器
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)
}
演示
# 未开代理
[~] 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代理
功能:
ReverseProxy功能点
在net/http/httputil下定义了ReverseProxy
基于ReverseProxy实现简易的HTTP代理
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更改内容支持
"X-Forwarded-For": 标记客户端地址每个方向服务器代理的IP
"X-Real-IP": 我的实际请求的标记
"Connection": 标记连接是关闭、长连接等状态
"TE": 标记传输类型是什么
"Trailer": 允许发送方在消息后面添加的一些源信息
拓展ReverseProxy功能
负载均衡
加权负载:给目标设置访问权重,按权重轮询(参考Nginx的负载均衡)
HTTPS代理
Websocket代理
TCP代理
中间件(可以参考Gin)
洋葱结构的原理
go中间件数据传递
中间件实现
中间件一般都封装在路由上,路由是URL请求分发的的管理器。
基于数据构建中间件,使用灵活,控制方便。基于链表实现不仅复杂,且难以调用。
责任链模式(基于链表构建)
方法切片模式(基于数组构建)
方法组装
方法调用
封装http.Handler
接口与http.Server
整合
中间件的意义
参考
限流和熔断降级
限流
高并发系统的三大利器:缓存、降级、限流
缓存:提升业务系统访问速度和增大处理容量,为相应业务增加缓存。
降级:当服务器压力剧增时,根据业务策略降级,以此释放服务资源保证业务正常。
限流:通过对并发限速,以达到拒绝服务、排队或等待、降级等处理。
限流原理
漏桶限流:每次请求时计算桶流量,超过阈值则降级请求。
令牌桶限流:每次请求时从令牌桶里取出令牌,取不到则降级请求。
网关集成限流
熔断降级
熔断器是当依赖的服务已经出现故障时,为了保证自身服务的正常运行不再访问依赖的服务,防止雪崩效应。
降级是当服务器压力剧增时,根据业务策略降级,以此释放服务资源保证业务正常。
熔断器的三个状态
服务关闭:服务正常,维护失败率统计,达到失败率阈值时,转到开启状态。
开启状态:调用fallback函数,一段时间后,进入半开启状态。
半开启状态:尝试恢复服务,失败率高于阈值,进入开启状态,低于阈值,进入关闭状态。
熔断降级原理
网关集成熔断降级
选择go类库:hystrix-go
Websocket
WebSocket协议
请求URL如ws://127.0.0.1:2002/echo
,以ws
开头表示协议是websocket
Websocket数据传输协议
标记请求发起方与第一代理的状态
决定当前事物完成后,是否关闭网络
Connection: keep-alive 不关闭网络
Websocket代理服务器
HTTP、HTTPS、HTTP2
HTTPS与HTTP的区别
HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理
HTTP默认端口是80,HTTPS默认端口是443。
HTTPS请求流程:
HTTP2和HTTP
HTTP2让服务器可以将响应主动“推送”到客户端缓存。
HTTP2和HTTPS的关系
但是使用HTTP2的服务器必须启动HTTPS(浏览器强制)
HTTP2的补充
HTTP2设计目标
HTTP2基本概念
帧:HTTP2通信的最小单位,没个帧包含帧首部,至少会标识出当前帧所属的流,承载着特定类型的数据。
HTTP2多路复用
HTTP2首部压缩
HTTP2协议磋商
询问服务器是否支持HTTP2,不支持则将为HTTP1.1
发起带有HTTP2 Upgrade首部的HTTP1.1请求
TCP代理
四层负载均衡和七层负载均衡
路由转发客户端和服务器之间有一次三次握手
反向代理客户端和服务端之间有两次三次握手
四层负载均衡(路由转发)
NAT作用于内核运行的
七层负载均衡(反向代理)
代理是用户程序运行的。代理的数据会进入程序buffer中
gRPC透明代理
数据库表整理与创建
设计原则:数据库三大范式
列不可再分:如服务名、服务描述等属性不可再进一步拆分了。
E-R图