LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

Nginx 反向代理(二):实现 http 协议反向代理的负载均衡

admin
2025年6月29日 18:36 本文热度 216

4 Nginx 反向代理

4.4 实现 http 协议反向代理的负载均衡

4.4.1 相关指令和参数

在实现 Nginx 反向代理的基础上,可以基于 ngx_http_upstream_module 模块实现后端服务器的分组,权重分配,状态监测,调度算法等高级功能

https://nginx.org/en/docs/http/ngx_http_upstream_module.html
upstream name { server address [parameters]; } # 定义一个后端服务器组,可以包含一台或多台服务器                        # 定义好后在 proxy_pass 指令中引用,作用域 httpserver address [parameters]; # 在 upstream 中定义一个具体的后端服务器,作用域 upstream        # address 指定后端服务器 可以是IP地址,主机名,或UNIX Socket,可以加端口号        # parameters 是可选掺数,具体有以下几个属性        # weight=number 指定该 server 的权重,默认值都是1        # max_conns=number 该 Server 的最大活动连接数,达到后将不再给该 Server 发送请求,默认值 0,表示不限制        # max_fails=number 后端服务器的下线条件,当客户端访问时,对本次调度选中的后端服务器连续进行检测多少次,如果都失败就标记为不可用,默认为1次,当客户端访问时,才会利用TCP触发对探测后端服务器健康性检查,而非周期性的探测        # fail_timeout=time 后端服务器的上线条件,对已经检测到处于不可用的后端服务器,每隔此时间间隔再次进行检测是否恢复可用,如果发现可用,则将后端服务器参与调度,默认为10秒        # backup 标记该 Server 为备用,当所有后端服务器不可用时,才使用此服务器        # down 标记该 Server 临时不可用,可用于平滑下线后端服务器,新请求不再调度到此服务器,原有连接不受影响hash key [consistent]; # 使用自行指定的 Key 做 hash 运算后进行调度,Key 可以是变量,比如请求头中的字段,URI等,如果对应的 server 条目配置发生了变化,会导致相同的 key 被重新 hash                  # consistent 表示使用一致性 hash,此参数确保该 upstream 中的 server 条目发生变化时,尽可能少的重新 hash,适用于做缓存服务的场景,提高缓存命中率                  # 作用域 upstreamip_hash; # 源地址hash调度方法,基于的客户端的remote_addr(源地址IPv4的前24位或整个IPv6地址)做hash计算,以实现会话保持,作用域 upstreamleast_conn; # 最少连接调度算法,优先将客户端请求调度到当前连接最少的后端服务器,相当于LVS中的 LC 算法          # 配合权重,能实现类似于 LVS 中的 WLC 算法          # 作用域 upstreamkeepalive connections; # 为每个 worker 进程保留最多多少个空闲保活连接数,超过此值,最近最少使用的连接将被关闭          # 默认不设置,作用域 upstreamkeepalive_time time; # 空闲连接保持的时长,超过该时间,一直没使用的空闲连接将被销毁          # 默认值 1h,作用域 upstream

4.4.2 基本配置

基本使用

角色 
IP
Client 
10.0.0.208
Proxy Server 
10.0.0.206
Real Server - 1 
10.0.0.210
Real Server - 2 
10.0.0.159


# Proxy Server 配置
upstream group1 {  server 10.0.0.210;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
# Real Server-1 配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.com/index.html10.0.0.210 index
# Real Server-2 配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.com/index.html10.0.0.159 index
#客户端测试-轮循调度到后端服务器[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index
一台 Proxy Server 调度多个 Upstream

角色 
所属upstream 
IP
Client 

10.0.0.208
Proxy Server 

10.0.0.206
Real Server - 1 
group1 
10.0.0.210
Real Server - 2 
group1 
10.0.0.159
Real Server - 3 
group2 
10.0.0.110
Real Server - 4 
group2 
10.0.0.120


# Proxy Server 配置
upstream group1 {  server 10.0.0.210;  server 10.0.0.159;}
upstream group2 {  server 10.0.0.110:8080;  server 10.0.0.120:8080;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
server {  listen 80;  server_name www.m99-josedu.net;  location / {    proxy_pass http://group2;    proxy_set_header host $http_host;  }}
# Real Server-1 配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.com/index.html10.0.0.210 index
# Real Server-2 配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.com/index.html10.0.0.159 index
# Real Server-3 配置server {  listen 8080;  root /var/www/html/www.m99-josedu.net;  server_name www.m99-josedu.net;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.net/index.html10.0.0.110 index
# Real Server-4 配置server {  listen 8080;  root /var/www/html/www.m99-josedu.net;  server_name www.m99-josedu.net;}[root@ubuntu ~]# cat /var/www/html/www.m99-josedu.net/index.html10.0.0.120 index
# 客户端配置[root@ubuntu ~]# cat /etc/hosts10.0.0.206 www.m99-josedu.com www.m99-josedu.net
#客户端 group1 测试[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index
#客户端 group2 测试[root@ubuntu ~]# curl http://www.m99-josedu.net10.0.0.110 index[root@ubuntu ~]# curl http://www.m99-josedu.net10.0.0.120 index[root@ubuntu ~]# curl http://www.m99-josedu.net10.0.0.110 index[root@ubuntu ~]# curl http://www.m99-josedu.net10.0.0.120 index
设置权重
#每个 server 配置的默认权重是 1,这种写法,两个 server 被调度的比例为 3:1upstream group1 {  server 10.0.0.210 weight=3;  server 10.0.0.159;
#客户端测试[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index
限制最大活动连接数
#10.0.0.210 同时只能维持两个活动连接upstream group1 {  server 10.0.0.210 max_conns=2;  server 10.0.0.159;}
#后端服务器配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;  limit_rate 10k;
#客户端测试,开6个窗口下载文件[root@ubuntu ~]# wget http://www.m99-josedu.com/test.img
#查看 10.0.0.210 上的连接,2个活动连接[root@ubuntu ~]# ss -tnpe | grep 80
#查看 10.0.0.159 上的连接,4个活动连接[root@ubuntu ~]# ss -tnep | grep 80
#客户端继续测试,新的请求都不会调度给 10.0.0.210[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl http://www.m99-josedu.com10.0.0.159 index

后端服务器健康性检查

Nginx 的 upstream 指令对于后端服务器的健康性检查是被动检查,当有客户端请求被调度到该服务器上时,会在TCP协议层的三次握手时检查该服务器是否可用,如果不可用就调度到别的服务器,当不可用的次数达到指定次数时(默认是1次,由 Server 配置中的 max_fails 选项决定),在规定时间内(默认是10S,由 Server 配置中的 fail_timeout 选项决定),不会再向该服务器调度请求,直到超过规定时间后再次向该服务器调度请求,如果再次调度该服务器还是不可用,则继续等待一个时间段,如果再次调度该服务器可用,则恢复该服务器到调度列表中

upstream group1 {  server 10.0.0.210;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }
#停止 10.0.0.210 上的Nginx 服务[root@ubuntu ~]# systemctl stop nginx.service
#客户端测试,检测到 10.0.0.210不可用,将请求都调度到 10.0.0.159[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index
#启用 10.0.0.210 上的Nginx 服务,客户端需要在距离上次检测该服务器不可用10S后才能调度到该服务器[root@ubuntu ~]# systemctl stop nginx.service
#如果后端服务器上的资源不存在,则不会影响调度,会返回对应的状态码和状态页[root@ubuntu ~]# mv /var/www/html/www.m99-josedu.com/index.html{,bak}
#客户端测试[root@ubuntu ~]# curl www.m99-josedu.com/index.html10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com/index.html<html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</h1></center><hr><center>nginx/1.18.0 (Ubuntu)</center></body></html>
设置备用服务器
#设置 backup,当 10.0.0.210 和 10.0.0.159 都不可用时,请求会被调度到 10.0.0.213upstream group1 {  server 10.0.0.210;  server 10.0.0.159;  server 10.0.0.213 backup;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }
#当前159 和 210 可用,客户端测试[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.210 index
#停止159和210的 Nginx 服务后再次测试[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.213 -- 网站正在维护中,请稍后访问
设置后端服务器平滑下线#Proxy Server 配置
upstream group1 {  server 10.0.0.210;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }
#后端服务器配置server {  listen 80;  root /var/www/html/www.m99-josedu.com;  server_name www.m99-josedu.com;  limit_rate 10k;
#客户端测试 - 开启两个窗口下载文件[root@ubuntu ~]# wget http://www.m99-josedu.com/test.img#在10.0.0.210 上查看,有一个连接[root@ubuntu ~]# ss -tnep | grep 80ESTAB 0 0 10.0.0.210:80 10.0.0.206:35844 users:(("nginx",pid=905,fd=5)) uid:33 ino:37122 sk:1001cgroup:/system.slice/nginx.service <->
#在10.0.0.159 上查看,也有一个连接[root@ubuntu ~]# ss -tnep | grep 80ESTAB 0 0 10.0.0.210:80 10.0.0.206:35844 users:(("nginx",pid=905,fd=5)) uid:33 ino:37122 sk:1001cgroup:/system.slice/nginx.service <->
#修改Proxy Server 配置,将 10.0.0.210 下线upstream group1 {  server 10.0.0.210 down;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }
#重载生效[root@ubuntu ~]# nginx -s reload
#你会发现原来保持的下载连接没有中断,但新的请求,不会再被调度到 10.0.0.210[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index

4.4.3 负载均衡调度算法

源IP地址hash

ip_hash 算法只使用 IPV4 的前 24 位做 hash 运算,如果客户端IP前24位一致,则会被调度到同一台后端服务器

#Proxy Server 配置upstream group1 {  ip_hash;  server 10.0.0.210;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
#10.0.0.208 - 客户端测试,被调度到 10.0.0.159[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index
#10.0.0.213 - 客户端测试,被调度到 10.0.0.159[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index
#10.0.0.1 - 客户端测试,被调度到 10.0.0.159C:\Users\44301>curl www.m99-josedu.com10.0.0.159 indexC:\Users\44301>curl www.m99-josedu.com10.0.0.159 indexC:\Users\44301>curl www.m99-josedu.com10.0.0.159 index
使用自行指定的 key 做 hash 调度
#Proxy Server 配置#三台 server 权重一样,调度算法 hash($remoute_addr)%3,值为 0,1,2根据不同的值调度到不同 serverupstream group1 {  hash $remote_addr;  server 10.0.0.210;  server 10.0.0.159;  server 10.0.0.213;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
#10.0.0.208 - 客户端测试,被调度到 10.0.0.159[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index[root@ubuntu ~]# curl www.m99-josedu.com10.0.0.159 index
#10.0.0.207 - 客户端测试,被调度到 10.0.0.213[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index
#三台 server 权重不一样,调度算法 hash($remoute_addr)%(1+2+3),值为 0,1,2,3,4,5#0 调度到 210#1,2调度到 159#3,4,5调度到 213upstream group1 {  hash $remote_addr;  server 10.0.0.210 weight=1;  server 10.0.0.159 weight=2;  server 10.0.0.213 weight=3;}
#Proxy Server 配置#根据 request_uri 进行调度,不同客户端访问同一个资源会被调度到同一台后端服务器上upstream group1 {  hash $request_uri;  server 10.0.0.210;  server 10.0.0.159;  server 10.0.0.213;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
#10.0.0.208 - 客户端测试[root@ubuntu ~]# curl www.m99-josedu.com/index.html10.0.0.213 index[root@ubuntu ~]# curl www.m99-josedu.com/index.html10.0.0.213 index[root@ubuntu ~]# curl www.m99-josedu.com/index.html10.0.0.213 index[root@ubuntu ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa[root@ubuntu ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa[root@ubuntu ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa
#10.0.0.207 - 客户端测试[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.213 index[root@rocky ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa[root@rocky ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa[root@rocky ~]# curl www.m99-josedu.com/a.html10.0.0.210 aaa
最少连接调度算法
#Proxy Server 配置,最少连接调度算法upstream group1 {  least_conn;  server 10.0.0.210;  server 10.0.0.159;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }
#客户端开启一个下载连接,限速,让该连接一直保持[root@ubuntu ~]# wget www.m99-josedu.com/test.img
#下载请求被调度到 10.0.0.210上了[root@ubuntu ~]# ss -tnep | grep 80ESTAB 0 0 10.0.0.210:80 10.0.0.206:38880 users:(("nginx",pid=905,fd=5)) uid:33 ino:57728 sk:1003cgroup:/system.slice/nginx.service <->
#新开客户端测试,请求不会被调度到 10.0.0.210 上[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index[root@rocky ~]# curl www.m99-josedu.com10.0.0.159 index
4.4.4 一致性 hash
#Proxy Server 配置#三台 server 权重一样,调度算法 hash($remoute_addr)%3,值为 0,1,2 根据不同的值调度到不同 serverupstream group1 {  hash $remote_addr;  server 10.0.0.210;  server 10.0.0.159;  server 10.0.0.213;}
server {  listen 80;  server_name www.m99-josedu.com;  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}

在上述配置中,三台后端服务器的权重都为 1,则总权重为 3,再使用客户端IP的 hash 值对总权重求余

假设当前调度情况如下

hash($remoute_addr) 
hash($remoute_addr)%3 
server
36
00
10.0.0.210
14
11
10.0.0.159
25
22
10.0.0.213

此时如果后端新增一台服务器,则总权重会变为 4,那么同样的 hash 值,最后的调度结果如下

hash($remoute_addr) 
hash($remoute_addr)%4 
server
4
0
10.0.0.210
15
11
10.0.0.159
2
2
10.0.0.213
3
3
10.0.0.223

我们会发现,新增后端服务器后,总权重发生变化,则所有前端的请求都会被重新计算,调度到和原来不同的后端服务器上了,这样会导致在原来后端服务器上创建的数据,在新的服务器上没有了

减少后端服务器或修改后端服务器权重,都会导致重新调度,会导致原有缓存数据失效(例如登录状态,购物车等)

一致性哈希

一致性哈希(Consistent Hashing)是一种用于分布式系统中数据分片和负载均衡的算法,其中的"hash环"是该算法的核心概念之一

在一致性哈希中,所有可能的数据节点或服务器被映射到一个虚拟的环上。这个环的范围通常是一个固定的哈希空间,比如0到2^32-1,每个数据节点或服务器被映射到环上的一个点,通过对其进行哈希计算得到。这个哈希值的范围也是在0到2^32-1之间

在这个环上,数据会被分散到最接近它的节点。当有新的数据要存储时,首先通过哈希计算得到该数据的哈希值,然后在环上找到离这个哈希值最近的节点,将数据存储在这个节点上。同样,当要查询数据时,也是通过哈希计算得到数据的哈希值,然后找到最近的节点进行查询

由于哈希环是一个环形结构,节点的添加和删除对整体的影响相对较小。当添加或删除节点时,只有相邻的节点受到影响,而其他节点保持不变。这使得一致性哈希算法在分布式系统中能够提供较好的负载均衡性能,同时减小了数据迁移的开销

总的来说,一致性哈希中的哈希环是通过哈希计算将数据节点映射到环上,以实现数据分片和负载均衡的分布式算法

upstream group1 {  hash $remote_addr consistent; #添加一致性 hash 参数  server 10.0.0.210;  server 10.0.0.159;  server 10.0.0.213;}

hash($remoute_addr) 
hash($remoute_addr)%(2^32-1) 
Node 
Server
顺时针第一个
顺时针第一个
顺时针第一个
顺时针第一个
顺时针第一个


哈希环

环偏移

在一致性哈希中,哈希环可能会面临的一个问题是环偏移(Ring Wrapping)。环偏移指的是哈希环上的某个区域过于拥挤,而其他区域相对空闲,这可能导致负载不均衡。为了解决这个问题,一些改进的一致性哈希算法引入了虚拟节点(Virtual Nodes)的概念

虚拟节点是对物理节点的一种扩展,通过为每个物理节点创建多个虚拟节点,将它们均匀地分布在哈希环上。这样一来,每个物理节点在环上的位置会有多个副本,而不是只有一个位置。这样一来,即使哈希环上的某个区域过于拥挤,也可以通过调整虚拟节点的数量来使得负载更均衡

4.5 综合案例

实现 http 自动重定向至 https,并将客户端 https 请求通过负载均衡的方式反向代理到后端的多台 http 服务器上

# upstream 配置upstream group1 {  server 10.0.0.210;  server 10.0.0.159;}
# http 重定向到 httpsserver {  listen 80;  server_name www.m99-josedu.com;  return 302 https://$server_name$request_uri;}
# https 配置server {  listen 443 ssl http2;  server_name www.m99-josedu.com;  ssl_certificate /usr/share/easy-rsa/pki/www.m99-josedu.com.pem;  ssl_certificate_key /usr/share/easy-rsa/pki/private/www.m99-josedu.com.key;  ssl_session_cache shared:sslcache:20m;  ssl_session_timeout 10m;
  location / {    proxy_pass http://group1;    proxy_set_header host $http_host;  }}
#客户端测试[root@ubuntu ~]# curl -Lk www.m99-josedu.com10.0.0.159 index
[root@ubuntu ~]# curl -LkI www.m99-josedu.comHTTP/1.1 302 Moved TemporarilyServer: nginxDate: Sat, 17 Feb 2025 13:55:01 GMTContent-Type: text/htmlContent-Length: 138Connection: keep-aliveLocation: https://www.m99-josedu.com/
HTTP/2 200server: nginxdate: Sat, 17 Feb 2025 13:55:01 GMTcontent-type: text/html; charset=utf8content-length: 17last-modified: Wed, 14 Feb 2025 02:45:15 GMTetag: "65cc293b-11"accept-ranges: bytes

— END —


阅读原文:原文链接


该文章在 2025/7/1 23:14:08 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved