websocket server

1
2
3
4
5
6
FROM maven:3.8.5-openjdk-17 as build
WORKDIR /user/src/app
COPY . .
RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& mvn clean package -DskipTests
CMD mvn exec:java -f pom.xml

部署(保证pod可以被独立访问)

statefulset方式

使用statefulset+headless service的方式对pod进行独立访问

DNS: pod名称-编号.headless名称.namespace.svc.cluster.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
apiVersion: v1
kind: Service
metadata:
name: netty-socketio-demo-headless
namespace: socket-test
spec:
ports:
- port: 9092
name: web
targetPort: 9092
clusterIP: None
selector:
app: netty-socketio-demo
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: netty-socketio-demo
namespace: socket-test
spec:
# 指定StatefulSet控制器要使用这个Headless Service
serviceName: netty-socketio-demo-headless
replicas: 2
selector:
matchLabels:
app: netty-socketio-demo
template:
metadata:
labels:
app: netty-socketio-demo
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- netty-socketio-demo
containers:
- name: netty-socketio-demo
image: $REGISTRY_ADDRESS/${NODE_ENV}/${CI_PROJECT_NAME}:v${CI_PIPELINE_ID}
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 512Mi
requests:
memory: 512Mi
livenessProbe:
tcpSocket:
port: 9092
readinessProbe:
tcpSocket:
port: 9092
ports:
- containerPort: 9092
name: web
protocol: TCP
imagePullSecrets:
- name: nexus3secret
dnsPolicy: ClusterFirst
dnsConfig:
options:
- name: ndots
value: "2"
restartPolicy: Always

statefulset在istio中存在的问题

现象: client 通过 headless service 访问 server,当 server 的 pod 发生重建后,client 访问 server 失败,网关返回502

Istio 运维实战系列(3):让人头大的『无头服务』-下

https://github.com/imroc/istio-guide/issues/8

使用k8s service的externalTrafficPolicy: Cluster + NodePort方式进行部署

  • 介绍

externalTrafficPolicy是k8s中默认提供的一种实现session粘性的方法,当配置externalTrafficPolicy: Cluster,代表请求打到当前节点kube-proxy后不再进行二次转发,但前提是当前的Service必须是Nodeport/Loadbalence类型

  • 问题

默认session粘性持续的有效期是3个小时,最大可以配置一天,但是我们的服务需要保持一个长链接,所以此方案不行

使用deployment+多实例/多端口方式进行部署【✓】

如下测试案例为一个server,两个deployment,两个service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
apiVersion: v1
kind: Service
metadata:
name: netty-socketio-demo1
namespace: socket-test
spec:
selector:
app: netty-socketio-demo1
type: ClusterIP
ports:
- port: 9092
targetPort: 9092
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: netty-socketio-demo1
namespace: socket-test
labels:
app: netty-socketio-demo1
spec:
replicas: 1
selector:
matchLabels:
app: netty-socketio-demo1
template:
metadata:
labels:
app: netty-socketio-demo1
spec:
containers:
- image: $REGISTRY_ADDRESS/${NODE_ENV}/${CI_PROJECT_NAME}:v${CI_PIPELINE_ID}
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 128Mi
requests:
memory: 128Mi
livenessProbe:
tcpSocket:
port: 9092
readinessProbe:
tcpSocket:
port: 9092
name: netty-socketio-demo1
ports:
- containerPort: 9092
protocol: TCP
imagePullSecrets:
- name: nexus3secret
dnsPolicy: ClusterFirst
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: netty-socketio-demo2
namespace: socket-test
spec:
selector:
app: netty-socketio-demo2
type: ClusterIP
ports:
- port: 9093
targetPort: 9092
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: netty-socketio-demo2
namespace: socket-test
labels:
app: netty-socketio-demo2
spec:
replicas: 1
selector:
matchLabels:
app: netty-socketio-demo2
template:
metadata:
labels:
app: netty-socketio-demo2
spec:
containers:
- image: $REGISTRY_ADDRESS/${NODE_ENV}/${CI_PROJECT_NAME}:v${CI_PIPELINE_ID}
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 128Mi
requests:
memory: 128Mi
livenessProbe:
tcpSocket:
port: 9092
readinessProbe:
tcpSocket:
port: 9092
name: netty-socketio-demo2
ports:
- containerPort: 9092
protocol: TCP
imagePullSecrets:
- name: nexus3secret
dnsPolicy: ClusterFirst
restartPolicy: Always

网关(nginx)

nginx实现负载方式(保证session粘性)

ip_hash

  • 介绍

Nginx的ip_hash策略,可以在客户端IP不变的情况下,将其发出的请求路由到同一台目标服务器, 实现会话粘滞,避免处理session共享问题。

  • 问题

普通Hash算法存在⼀个问题,以ip_hash为例,集群中有tomcat1,tomcat2,tomcat3 三台Tomcat服务器,

假定客户端ip固定没有发⽣改变,现在tomcat3出现问题宕机了,服务器数量由3个变为了2个,之前所有的求模都需要重新计算。

在真实的生产环境下,若后台服务器有许多台,客户端数量也非常庞大的情况下,影响是非常大的;

服务器缩容/扩容都会发生这样的问题,大量的客户端请求被路由到其他的服务器,客户端在原来服务器的会话都会丢失。

那么如何解决这样的问题呢?

答案就是一致性Hash算法

一致性Hash

  • 优点

在一致性Hash算法实现过程中,每⼀台服务器负责⼀段,⼀致性哈希算法对于节点的增减都只需重定位环空间中的⼀⼩部分数据,具有较好的容错性和可扩展性。

  • 缺点

数据(请求)倾斜问题

⼀致性哈希算法在服务节点太少时,容易因为节点分布不均匀⽽造成数据倾斜问题。
例如系统中只有两台服务器,其环分布如下,节点2只能负责⾮常⼩的⼀段,⼤量的客户端请求就落在了节点1上

sticky【✓】

  • 介绍

Sticky是nginx的一个模块,它是基于cookie的一种nginx的负载均衡解决方案,通过分发和识别cookie,来使同一个客户端的请求落在同一台服务器上,默认标识名为route

  1. 客户端首次发起访问请求,nginx接收后,发现请求头没有cookie,则以轮询方式将请求分发给后端服务器。
  2. 后端服务器处理完请求,将响应数据返回给nginx。
  3. 此时nginx生成带route的cookie,返回给客户端。route的值与后端服务器对应,可能是明文,也可能是md5、sha1等Hash值
  4. 客户端接收请求,并保存带route的cookie。
  5. 当客户端下一次发送请求时,会带上route,nginx根据接收到的cookie中的route值,转发给对应的后端服务器。
  • 较ip_hash负载方式来说,stick可以保障负载方式更为均匀,而且不需要考虑同域的情况

nginx代理方式

仅为 ws 协议代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
upstream server {
sticky;
#server netty-socketio-demo-0.netty-socketio-demo-headless.socket-test:9092 weight=1 max_fails=3 fail_timeout=20;
#server netty-socketio-demo-1.netty-socketio-demo-headless.socket-test:9092 weight=1 max_fails=3 fail_timeout=20;
server netty-socketio-demo1.socket-test:9092 weight=1 max_fails=3 fail_timeout=20;
server netty-socketio-demo2.socket-test:9093 weight=1 max_fails=3 fail_timeout=20;
}
location /websocket/ {
proxy_pass http://myserver;

proxy_http_version 1.1;
proxy_read_timeout 360s;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; #配置连接为升级连接
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Host $server_name;
}

既支持http又支持 ws 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
#自定义变量 $connection_upgrade
map $http_upgrade $connection_upgrade {
default keep-alive; #默认为keep-alive 可以支持 一般http请求
'websocket' upgrade; #如果为websocket 则为 upgrade 可升级的。
}

server {
...
location /websocket/ {
proxy_pass http://myserver;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; #此处配置 上面定义的变量
proxy_set_header Connection $connection_upgrade;
}
}

测试

未带route-cookie访问会进行轮询,然后返回cookie到client,下次访问就会带着cookie找到上一次访问的server实例