web服务相关
1、域名和DNS
向阿里或腾讯申请域名,然后为域名设置一个权威DNS服务器,比如在腾讯云注册的域名,那么可以选择在腾讯DNS服务器上存储该域名的解析信息,那么腾讯云DNS就成为了该域名的权威DNS服务器。当有人通过 114.114.114.114 查询你的域名时,114DNS服务会先向根服务器查询顶级域名服务器地址,查询到该域名所属的顶级域名服务后,再向顶级域名服务器查询你的域名的 NS 记录(域名所属的权威服务器),查询到域名所属的权威服务器后,最后向你的权威 DNS 服务器查询具体解析记录,然后114会将该记录信息缓存一段时间,下次其他用户查询时直接返回缓存结果。
2、nginx反向代理
目前Nginx和HAProxy都支持七层和四层转发,Nginx对于七层的处理功能更丰富,比如正则表达式强大、支持复杂URL路由。而HAProxy则在会话保持(Cookie)方面更精细,而且性能比Nginx更高(延迟低、并发高),同时支持MySQL/Redis长连接(Nginx长连接支持弱,如MySQL连接池容易超时)。
1) nginx负载均衡
nginx除了提供URL路由功能外,还提供负载均衡功能,常见的负载均衡策略有轮询(请求被顺序分配,nginx默认策略)、加权轮询(根据权重分配请求,权重越高被分配到的请求越多,可以根据后端服务性能来设置权重)、最少连接(优先分配请求到当前活跃连接最少的后端,适合长连接通信)、IP哈希(根据客户IP地址计算哈希值,将同一IP的请求固定分配给同一台后端服务)、通用哈希(允许自定义哈希键,将相同键的请求定向到同一服务器,如将同一URL的请求固定到某台服务上)、随机(随机选择一台后端服务)、最短响应时间(按后端服务的响应时间来分配请求,属于第三方策略)。
2) nginx高可用
企业架构经常采用的模式是:公网入口→nginx反向代理服务(https)→内网服务(http),nginx的高可用通过keepalived来提供,内网集群服务的高可用由nginx的“健康监测”来实现:Nginx 会按照配置的频率向后端服务发送探测请求,比如一个 HTTP HEAD 请求(大部分Web服务器能够自动处理HEAD请求),如果某台后端服务器连续几次探测都失败,即无响应,标注该服务为非健康状态,新的请求不会被转发到该服务上,当健康检查探测到服务恢复健康后,会将其重新加入到可用的后端服务列表中。
新增nginx后端服务的话,需要将其加入到nginx后端服务列表,传统的方式是手动修改nginx配置,然后使用nginx -s reload命令来重载配置。推荐的方式是使用nginx插件nginx-upsync-module + 注册中心ETCD/Consul/ZooKeeper 来动态管理后端实例列表,省去手动修改 Nginx配置:新的后端服务启动后,自动向服务注册中心注册自己的地址和相关信息,nginx从注册中心动态获取(如每隔500ms发起一个请求)可用的服务实例列表(通过配置告诉 Nginx从 ETCD/Consul 的哪个路径拉取列表,拉取到的列表会存到本地,防止注册中心挂了)。
一个完整的高可用架构通常是多层级的组合:

3) nginx连接复用
nginx与后端应用服务之间推荐使用长连接来进行连接的复用:客户A发送请求,nginx与后端服务建立连接,数据传输结束后这个后端连接不会进行关闭,nginx将其放至连接池中,客户B发送请求的时候,nginx发现连接池中有空闲连接,然后就可以直接使用该连接与后端服务进行通信,通信结束后再将其放回连接池中。nginx连接池实际上是可以称为空闲连接池:请求到来的时候发现当连接池中无可用连接(连接都不是空闲的,都在使用)的时候就会创建新连接,当连接使用完毕后如果连接池中连接大于等于设置的keepalive值的话,就直接关闭连接,小于的话就将连接放入连接池,该连接称为空闲连接。
nginx开启连接池复用连接的配置如下所示,其中有五个关键设置:①、定义上游应用服务器组 ②、设置keepalive连接池大小(即最大空闲连接数)③、启用 HTTP/1.1 长连接 ④、清空客户端发来的Connection头,避免将Connection: close、Connection: keep-alive透传给后端服务来扰乱nginx与后端服务的长连接 ⑤、配置超时等其他常见配置。
http{ # ================= 客户端侧 keepalive ================= # 客户端与 Nginx 之间的长连接:即客户端与nginx之间也可以进行连接的复用 keepalive_timeout 65s; # 空闲超时:超过这个时间客户端没有发送请求的话nginx主动关闭与客户端的长连接(默认75秒),Tomcat的话为connectionTimeout(默认20秒) keepalive_requests 1000; # 单连接最大请求数:该连接上接受的请求超过这个数的话连接会被nginx关闭 keepalive_time 1h; # 连接总存活时间(Nginx 1.21 + ) # ================= 后端侧 keepalive ================= upstream backend_pool { # 上游服务器组 server 10.0.0.1:8080; server 10.0.0.2:8080; server 10.0.0.3:8080; # 【核心】每个 worker 的最大空闲连接数:总空闲连接数 = worker_processes × keepalive 值,如worker_processes为4,keepalive为32的话,最大空闲连接数为4 × 32 = 128 # 建议值:CPU 核心数 × 4 ~CPU 核心数 × 16 keepalive 32; # 【可选】连接空闲超时(Nginx 1.21 + ):超过这个时间nginx主动关闭与后端的长连接 keepalive_time 60s; # 【可选】单连接最大请求数(Nginx 1.21 + ):该连接上接受的请求超过这个数的话连接会被nginx关闭 keepalive_requests 1000; } server { listen 80; server_name api.example.com; location / api / { proxy_pass http ://backend_pool; # 【必须】启用 HTTP / 1.1 proxy_http_version 1.1; # 【必须】清空 Connection 头 proxy_set_header Connection ""; # 【推荐】标准代理头 proxy_set_header Host $host; proxy_set_header X - Real - IP $remote_addr; proxy_set_header X - Forwarded - For $proxy_add_x_forwarded_for; # 【重要】超时配置(避免连接占用过久) proxy_connect_timeout 5s; proxy_send_timeout 10s; proxy_read_timeout 30s; # 【推荐】缓冲配置 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; } } }
nginx应该关注的几个超时:①、假设黑客以1字节/每秒的速度发送请求,那么其发送多个请求的话连接就会被占满,防护手段是设置请求头/体的发送超时,如client_header_timeout 15s;表示15秒内必须发完请求头,client_body_timeout 30s;表示30秒内必须发完请求体。②、SQL查询死锁,导致响应时间无限延长,多个查询请求的话就会导致连接占满,解决方法是设置proxy_read_timeout超时,当超过指定的时间后端服务无数据返回的话,后端连接被关闭。proxy_read_timeout超时应该设置的比后端服务的connectionTimeout(连接空闲超时,超过这个时间后端服务没收到数据的话服务就关闭连接)要大,即后端服务主动关闭连接要优先与nginx主动关闭后端连接。③、nginx与后端服务之间建议不是永久连接,而是设置keepalive_timeout空闲连接超时,当一段时间浏览器无请求的话关闭连接。后端服务配置的connectionTimeout(连接空闲超时,超过这个时间后端服务没收到数据的话服务就关闭连接)应该长于 Nginx配置的keepalive_timeout空闲超时时间,否则会出现 Nginx 还保持着连接,但后端服务已经把连接关闭了,Nginx 转发请求给后端时出错。④、如果担心客户端处理响应慢的话,可以设置send_timeout,当发送缓冲区已满的话,连接关闭。
3、URL路由
URL路由就是指将用户请求的URL映射到指定的处理程序,在 Spring 中,路由可以通过将注解(如@RequestMapping、@GetMapping、@PostMapping等)定义在控制器(Controller)的方法上实现,如下所示。
@RestController public class UserController { // 匹配 GET /users 请求,对应"查询所有用户"逻辑(静态路由:URL 路径固定) @GetMapping("/users") public List<User> getAllUsers() { // 业务逻辑... } // 匹配 POST /users 请求,对应"创建用户"逻辑 @PostMapping("/users") public User createUser(@RequestBody User user) { // 业务逻辑... } // 匹配 GET /users/{id} 请求,对应"查询单个用户"逻辑(动态路由:id为动态的) @GetMapping("/users/{id}") public User getUserById(@PathVariable Long id) { // 业务逻辑... } }
4、使用Nginx对URL进行路由转发
1、使用location匹配指定的URL路径,进行不同的路由:
http{ upstream backend1 { server 192.168.1.10:80; } upstream backend2 { server 192.168.1.20:80; } server { location /api1/ { # 匹配以 /api1/ 开头的路径 proxy_pass http ://backend1; # 转发到backend1 } location /api2/ { # 匹配以 /api2/ 开头的路径 proxy_pass http ://backend2; # 转发到backend2 } } }
2、通过参数值,动态决策路由:
location /{ #匹配所有请求:/表示最宽泛的路径匹配
if ($arg_type = "app") { # 检查参数type = app
proxy_pass http ://app_server; #转发到 app_server
}
if ($arg_type = "game") { # 检查参数type = game
proxy_pass http ://game_server; #转发到 game_server
}
proxy_pass http ://default_backend; # 默认路由
}
3、检查文件是否存在:
location /{ try_files $uri /index.html; #将所有非静态资源请求重定向到入口文件:try_files用于按顺序检查文件是否存在,并返回第一个存在的文件,所以当访问的URL文件不存在的时候,返回index.html }
location /{
try_files $uri $uri/ @not_found; #首先尝试访问 $uri,比如/static/file,如文件不存在则尝试访问/static/file/(将URI作为目录),若前两者都不存在,就跳转到名为 @not_found 的命名location
}
location @not_found{ #命名location(用 @ 前缀标识,不能被客户端直接访问,只能被 Nginx 内部调用)
return 404 /custom_404.html; #返回404状态码以及/custom_404.html这个页面
}
4、使用正则表达式(主要应用在location、rewrite指令等场景)
将指定类型的URL请求转发到指定服务器:
location ~ ^/user/(\d+)$ { #匹配/user/开头,数字结尾的URL,比如/user/123:~表示区分大小写的正则匹配模式。^匹配字符串开头(只匹配以/user/开头的),(\d+)匹配一个或多个数字,$匹配字符串结尾(防止匹配/user/123abc),
proxy_pass http://user-service:8080; #将匹配到的请求转发到指定的服务
proxy_set_header Host $host; #在转发请求时,将 HTTP 头中的 Host 字段设置为客户端原始请求的 Host 值
}
将指定的请求重定向&将指定的URL请求修改为新的URL请求
server{ rewrite ^/old-path$ /new-path permanent; #当有请求访问/old-path时,将其重定向到/new-path:^/old-path$是一个正则表达式,表示匹配路径为/old-path的请求(^表示开头,$表示结尾,确保不会匹配类似/old-path/xxx 的路径)。permanent 表示这是 301 永久重定向,有利于搜索引擎权重的转移,避免因路径变更导致的流量损失 rewrite ^/user/(\d+)$ /profile?id=$1; #当请求以/user/开头,以数字结尾的时候,比如/user/123,URL会被重写成/profile?id=123: ^/user/(\d+)$是一个正则表达式,其中^/user/匹配以/user/开头的路径,(\d+)匹配一个或多个数字,$表示路径结束,确保不会匹配更长的路径(如/user/123会被匹配,/user/123abc不会被匹配), $1表示前面捕获到的数字。这里没有指定重定向类型(如 permanent 或 redirect),所以是内部重写(客户端不会感知到 URL 变化,地址栏显示的仍是原始 URL) }
将静态文件与动态内容分开处理,并且由Nginx 直接处理静态资源请求:
location ~* \.(jpg|jpeg|png|gif|css|js)$ { #~*表示不区分大小写(.jpg和.JPG都会被匹配),\.(jpg|jpeg|png|gif|css|js)匹配以这些后缀结尾的文件,$ 确保匹配的是文件后缀(而非文件名中间包含这些字符的文件)
root /var/www/static; #静态文件在服务器上的实际存储路径,例如,当请求/images/logo.png 时,Nginx 会去服务器的/var/www/static/images/logo.png路径寻找文件,然后直接返回给用户
expires 30d; #设置 HTTP 响应头中的缓存过期时间为 30 天,浏览器会根据这个设置缓存这些静态资源,在 30 天内再次访问时不会重新请求服务器
}
请求过滤,拦截不符合规则(恶意URL、爬虫)的请求。如下所示,为了防止路径遍历攻击,当URI中包含../的时候(比如../../etc/passwd 试图访问系统用户文件),返回403禁止访问 :
if ($request_uri ~* \.\../ ) { #$request_uri 是 Nginx 的内置变量,表示客户端请求的完整 URI,\.\../ 是匹配模式,用于检测 URI 中是否包含 ../ 字符串 return 403; }
实现灰度发布:根据http请求头中的特定标识,如X-Env: canary,将符合条件的流量路由到新版本(金丝雀)服务,其他流量仍走旧版本服务。
5、ALB
也可以不使用nginx,直接购买阿里云的ALB(或腾讯云的CLB)来实现负载均衡、URL路由转发等功能。ALB通过使用Tengine(基于Nginx深度定制)来实现七层负载均衡和URL路由,使用Keepalived 实现七层负载均衡服务的高可用,使用Tengine的健康检测来提供后端服务的高可用。使用ALB还有以下优点:
①、创建ALB/CLB的时候可以选择多个可用区(多可用区部署),这样就会创建多个负载均衡服务节点,形成负载均衡服务的集群,云厂商自动保障该集群的高可用性:请求会被自动的分发到健康的节点上,某个节点故障的话自动被标记为不可用。不同云服务厂商的高可用模式略有不同,阿里云ALB是“多可用区多活”模式,多个可用区的节点同时对外提供服务,共同分担流量,提供更高的可用性和故障恢复能力,腾讯云CLB采用“双可用区主备”模式,请求主要走主可用区,当主可用区整体不可用时,才会切换到备可用区。
②、类似nginx,ALB也提供对后端服务的高可用,仅需配置健康检测。比如用ALB挂载两台后端云服务节点,然后开启开启健康检查,单节点故障时自动切换服务。如果仅有一台后端服务节点的话,也可以配置自动重启脚本,节点故障的话自动重启服务。ALB健康检测原理:定期向后端服务发送健康探测请求即心跳(比如发送HEAD请求),如果某个实例连续多次检查失败(请求无响应),ALB 会自动将其从后端服务器列表中移除,后续流量不再分发到该故障实例,待实例恢复后,再自动加回。强烈建议后端服务配置一个专用的健康检查端点,例如 /health或 /status:通常ALB的默认“检测路径”为根路径/(可以通过配置修改),但是后端服务经常会提供根路径的映射处理方法,比如Controller方法@GetMapping(“/”),这样HEAD请求就会被这个方法处理,但HEAD请求的回应是会丢弃响应体的,相当于是浪费了请求的处理,所以一般是专门编写一个极简的Controller来专门处理健康监测请求,如下所示:

③、类似nginx,提供了多种负载均衡策略,除了轮询(默认)、加权轮询、IP哈希外,还提供了加权最小连接数(将请求分配给连接数/权重比值最小的服务——连接数越小、权重越高分配的请求越多)、一致性哈希策略(根据请求指定的哈希因子来分配请求——对请求中的特定字段如源 IP 地址、URL 参数等进行哈希计算,相同的源IP地址、URL参数会分配到同一服务上)。
④、类似nginx,提供连接池复用连接功能,通过简单的配置就可以开启后端长连接(默认不开放),类似nginx,ALB也可以配置后端长连接的空闲超时等。开启了后端长连接以后,如果是腾讯云的CLB的话,客户IP需从 X-Forwarded-For头获取,如果是阿里云的ALB的话,客户IP正常透传(可以正常获得),如果是自建nginx的话,客户IP通过proxy_set_header来获得。
⑤、提供丰富的URL路由功能,支持基于路径、HTTP标头、HTTP请求方法、查询字符串、Cookie、Source IP等多种条件来识别特定业务流量,将其转发至不同的后端服务器,同时支持重定向、重写等高级操作。
⑥、服务弹性伸缩,比如业务促销等情形自动扩展其处理能力以应对高并发,比如设置当 CPU 使用率超过 80% 时,系统自动购买新的 ECS后端云服务并自动挂载到 SLB 后端。新的后端服务器上服务的自动部署一般有三种方式:一种是通过云控制台预先制作一个“黄金镜像”,里边安装好JDK、Tomcat、部署好你的代码、配置好所有环境,在弹性伸缩组里,指定使用这个“自定义镜像”来创建新机器。这种方式的缺点就是如果代码更新了,需要重新制作镜像(不过现在很多 CI/CD 工具可以自动构建镜像)。第二种方式是使用“启动脚本”(阿里云叫“用户数据”,腾讯云叫“自定义数据”),在控制台选择使用标准镜像(比如纯净的 CentOS 或 Ubuntu),然后编写启动脚本,脚本内容为安装JDK、安装Tomcat、去指定地址下载你的应用包(比如从对象存储OSS/COS上下载JAR/WAR应用包)等命令,新增后端服务器的时候会自动执行这个脚本。这种方式的缺点是服务弹性扩展的时候较慢,因为是现场下载安装。第三种方式是结合云厂商的运维编排(OOS),如阿里云的“运维编排服务”、腾讯云的“自动化助手”,弹性伸缩只负责新机器的加入,然后触发运维编排任务——在新增机器上安装Tomcat等应用 -> 从Git拉取代码 -> 构建 -> 部署。这种方式的优点是实现了硬件交付(新机器)和应用交付(装软件应用)的解耦。
⑦、零运维,提供可视化控制台,且能与云服务厂商生态无缝集成,比如与ALB与ECS云服务器绑定,实现后端服务的自动扩缩容以构建一个完全自动化的弹性架构。而且云厂商通常规定,ALB 与后端云服务器之间的内网流量是免费的。
⑧、SSL 证书管理,支持直接在控制台上传和管理 SSL 证书,并开启 HTTPS 卸载(即 ALB 处理 HTTPS,后端只需处理 HTTP),比在 Nginx 里配置https证书更方便。
⑨、提供基础的 DDoS 等安全防护能力。
⑩、目前项目都是推荐前后端分离,静态文件(HTML / CSS / JS)交由专门的 CDN 或对象存储(OSS / COS),不再由nginx提供静态文件的访问。
也可以购买两台不同地区的ALB/CLB服务,以实现跨地域的负载均衡服务集群:如下图所示,购买一个北京ALB实例,一个上海ALB实例,云厂商的DNS云解析服务除了可以使用轮询的负载均衡策略外,还可以使用就近访问策略——来自华北地区的用户解析到北京ALB的IP,来自华东地区的用户解析到上海ALB的IP。跨地域的负载均衡服务集群有一个问题:北京的ALB挂了后,除非手动修改DNS记录,否则华北的用户还会解析到北京的IP,而且新的DNS记录生效时间依赖DNS TTL,可能会等到一两分钟才生效,使用云服务厂商的“全局流量管理GTM”可以解决这个问题,它通过健康检测(通过Ping、TCP、HTTP/HTTPS协议探测服务健康状态)和故障切换来提供跨地域ALB/CLB集群的高可用。

如果服务是面向全球的话,可以使用云厂商的“全球加速”服务,如下所示,中国的用户想要访问位于美国的后端业务,可以使用“全球加速”来转发流量,因为“全球加速”访问业务服务的话走的是云厂商的网络专线,速度很快。

如果没有后端团队的话,也可以使用平台即服务PaaS / 软件即服务SaaS这种完整解决方案(比如阿里云的云·企业官网、腾讯云的微搭低代码平台), 这种SaaS/PaaS化的建站工具使开发者不用再关心Nginx、Tomcat等服务实现,适合业务简单(如一个静态展示官网),需要在几天内快速上线一个原型来验证市场等情形。
浙公网安备 33010602011771号