背景:
1.双机房网络互通,数据库互通,redis不互通。
2.通过会话header里面的路由参数判断奇数调转机房一,偶数条状机房二。机房一,机房二应用服务做故障转移
nginx+lua+java+vue方式实现
nginx 配置:
机房一和机房二 nginx.conf 只需要修改 app-server 里面的主节点和备用节点互换
# nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
upstream datacenter1 {
server 192.168.10.100:8888;
}
upstream datacenter2 {
server 192.168.10.120:8888;
}
upstream app-server {
# app1-maser 机房一节点
server 192.168.10.101:8081 max_fails=3 fail_timeout=30s;
server 192.168.10.102:8081 max_fails=3 fail_timeout=30s;
# app2-backup 机房二节点
server 192.168.10.121:8081 backup;
server 192.168.10.122:8081 backup;
}
server {
listen 8080;
location / {
access_by_lua_block {
local header_val = ngx.req.get_headers()["X-Route-Key"]
if not header_val then
ngx.status = 400
ngx.say("Missing X-Route-Key header")
ngx.exit(400)
end
-- 尝试转为数字
local num = tonumber(header_val)
if not num then
ngx.status = 400
ngx.say("Invalid number in X-Route-Key")
ngx.exit(400)
end
-- 奇数去机房一,偶数去机房二
if num % 2 == 1 then
ngx.exec('@proxy_cd1');
else
ngx.exec('@proxy_cd2');
end
}
}
location @proxy_cd1 {
proxy_pass_header X-Route-Key;
proxy_pass http://proxy_cd1;
proxy_set_upstream error timout http_502;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-DataCenter $http_x_datacenter;
add_header Access-Control-Expose-Headers "X-Route-Key";
}
location @proxy_cd2 {
proxy_pass_header X-Route-Key;
proxy_pass http://proxy_cd2;
proxy_set_upstream error timout http_502;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-DataCenter $http_x_datacenter;
add_header Access-Control-Expose-Headers "X-Route-Key";
}
}
server {
listen 8088;
location / {
proxy_pass http://app-server;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
springboot 改造
HeaderTransferFilter.java
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.stereotype.Component;
@Component
public class HeaderTransferFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 获取请求头
String routeKey = httpRequest.getHeader("X-Route-Key");
if (routeKey != null) {
httpResponse.setHeader("X-Route-Key", routeKey);
}
// 继续处理请求
chain.doFilter(request, response);
}
}
web前端项目改造:vue
// src/utils/request.js 或 src/api/index.js
import axios from 'axios';
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 例如 '/api'
timeout: 30000,
});
// 全局变量存储 x-route-key
let routeKey = null;
// --- 响应拦截器:获取 X-ROUTE-KEY ---
service.interceptors.response.use(
(response) => {
// 从响应头中获取 X-ROUTE-KEY
const key = response.headers['x-route-key']; // 浏览器自动转为小写
if (key) {
routeKey = key;
console.log('[Header] X-ROUTE-KEY captured:', routeKey);
}
// 可选:也可以从 response.data 中提取(如果后端放在 body 中)
return response;
},
(error) => {
return Promise.reject(error);
}
);
// --- 请求拦截器:带上 X-ROUTE-KEY(如果已获取)---
service.interceptors.request.use(
(config) => {
if (routeKey) {
config.headers['X-ROUTE-KEY'] = routeKey;
console.log('[Header] X-ROUTE-KEY added to request:', routeKey);
}
// 注意:不要设置 Content-Type 等会被覆盖的头,除非必要
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default service;