一, 背景
在我上一篇文章《Spring Cloud 开发人员如何解决服务冲突和实例乱窜?》中提到使用服务的元数据来实现隔离和路由, 有朋友问到能不能直接通过 IP 来实现? 本文就和大家一起来讨论一下这个问题
二, 可行性分析
要实现通过 IP 来隔离和路由的话有一个非常关键的点需要解决, 就是怎样实现 IP 可辨识, 意思就是如何区分那个 IP 是服务器上的, 那个 IP 是开发人员本机的
如上图所示其实我们还是能找到规律可以辨识的, 所以这个是可以行的!
开发人员本机 IP - 其实就是客户端 IP, 也就是原始请求方的 IP:172.16.20.2
服务器 IP - 可以理解为服务器上的服务所在机器的 IP(有点绕):172.16.20.1
三, 路由规则逻辑
主要实现以下目标:
普通用户访问服务器上的页面时, 请求的所有路由只调用
服务器上的实例
开发 A 访问时, 请求的所有路由优先调用
开发 A 本机启动的实例
, 如果没有则调用
服务器上的实例
开发 B 访问时同上, 请求的所有路由优先调用
开发 B 本机启动的实例
, 如果没有则调用
服务器上的实例
在找到 IP 的辨识规律后, 推导出下面 3 个路由规则来实现上面的目标
优先匹配
原始请求方的 IP
的服务实例
再者匹配
上游服务所在机器 IP
的服务实例
上面 2 个逻辑都匹配不到的话使用轮询的方式找一个实例
具体的自定义负载均衡的对象怎么写我这里就不详细描述了, 可以参考我上一篇文章《Spring Cloud 开发人员如何解决服务冲突和实例乱窜?》
四, 获取原始请求方的 IP
获取原 IP 的代码片段如下, 只需要在网关上增加一个过滤器获取 IP, 然后添加到 header 里面一直传递下去就可以了
- /**
- * 获取 Ip 地址
- */
- private String getIpAddr(HttpServletRequest request){
- String ip = request.getHeader("X-Forwarded-For");
- if (isEmptyIP(ip)) {
- ip = request.getHeader("Proxy-Client-IP");
- if (isEmptyIP(ip)) {
- ip = request.getHeader("WL-Proxy-Client-IP");
- if (isEmptyIP(ip)) {
- ip = request.getHeader("HTTP_CLIENT_IP");
- if (isEmptyIP(ip)) {
- ip = request.getHeader("HTTP_X_FORWARDED_FOR");
- if (isEmptyIP(ip)) {
- ip = request.getRemoteAddr();
- if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
- // 根据网卡取本机配置的 IP
- try {
- ip = InetAddress.getLocalHost().getHostAddress();
- } catch (UnknownHostException e) {
- log.error("InetAddress.getLocalHost()-error", e);
- }
- }
- }
- }
- }
- }
- } else if (ip.length()> 15) {
- String[] ips = ip.split(",");
- for (int index = 0; index < ips.length; index++) {
- String strIp = ips[index];
- if (!isEmptyIP(ip)) {
- ip = strIp;
- break;
- }
- }
- }
- return ip;
- }
- private boolean isEmptyIP(String ip) {
- if (StrUtil.isEmpty(ip) || UNKNOWN_STR.equalsIgnoreCase(ip)) {
- return true;
- }
- return false;
- }
把原 IP 添加到 header 的 HTTP_X_FORWARDED_FOR 里面传递给下游服务
- RequestContext ctx = RequestContext.getCurrentContext();
- HttpServletRequest request = ctx.getRequest();
- String sourceIp = getIpAddr(request);
- ctx.getZuulRequestHeaders().put("HTTP_X_FORWARDED_FOR", sourceIp);
五, 获取服务器所在机器的 IP
直接使用 JDK 自带的 InetAddress 就可以了
String localIp = InetAddress.getLocalHost().getHostAddress()
六, 总结
通过 IP 的方案来实现开发环境服务实例隔离和策略路由后, 可以实现到开发完全无感知, 既不需要配置元数据, 也不需要自己去传 version 之类的参数了.
但是这个方案其实也是有局限性的
开发服务器必须是只用一台来部署所有的服务, 因为如果上游服务和下游服务不在同一个 IP 上就失去了辨识能力了
因为网络环境比较复杂, 不一定能获取到客户端的真实原 IP
开发人员启动客户端 / 前端的机器与启动后台服务必须是同一台电脑上才行; 例如如果是
前端开发人员 A
启动的客户端, 去调试
后台开发人员 B
启动的服务就不行了, 因为原 IP 与注册上去的服务实例 IP 匹配不上
最后可能大家会问原 IP 怎样全链路传递下去? 链路传递可以参考一下我的另外一篇文章《日志排查问题困难? 分布式日志链路跟踪来帮你》
来源: https://www.cnblogs.com/zlt2000/p/11474327.html