乐趣区

Spring-Cloud开发人员如何解决服务冲突和实例乱窜IP实现方案

一、背景

在我上一篇文章《Spring Cloud 开发人员如何解决服务冲突和实例乱窜?》中提到使用服务的 元数据 来实现隔离和路由,有朋友问到能不能直接通过 IP 来实现?本文就和大家一起来讨论一下这个问题

 

二、可行性分析

要实现通过 IP 来隔离和路由的话有一个非常关键的点需要解决,就是怎样实现 IP 可辨识,意思就是如何区分那个IP服务器 上的,那个 IP开发人员本机

如上图所示其实我们还是能找到规律可以辨识的,所以这个是可以行的!

  • 开发人员本机 IP – 其实就是 客户端 IP,也就是 原始请求方的 IP172.16.20.2
  • 服务器 IP – 可以理解为服务器上的服务所在机器的 IP(有点绕):172.16.20.1

 

三、路由规则逻辑

主要实现以下目标:

  1. 普通用户 访问服务器上的页面时,请求的所有路由只调用 服务器上的实例
  2. 开发 A 访问时,请求的所有路由优先调用 开发 A 本机启动的实例 ,如果没有则调用 服务器上的实例
  3. 开发 B 访问时同上,请求的所有路由优先调用 开发 B 本机启动的实例 ,如果没有则调用 服务器上的实例

在找到 IP 的辨识规律后,推导出下面 3 个 路由规则 来实现上面的目标

  1. 优先匹配 原始请求方的 IP的服务实例
  2. 再者匹配 上游服务所在机器 IP的服务实例
  3. 上面 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 添加到 headerHTTP_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 之类的参数了。
但是这个方案其实也是有 局限性

  1. 开发服务器必须是只用一台来部署所有的服务,因为如果上游服务和下游服务不在同一个 IP 上就失去了辨识能力了
  2. 因为网络环境比较复杂,不一定能获取到客户端的真实 原 IP
  3. 开发人员启动客户端 / 前端的机器与启动后台服务必须是同一台电脑上才行;例如如果是 前端开发人员 A 启动的客户端,去调试 后台开发人员 B 启动的服务就不行了,因为 原 IP与注册上去的 服务实例 IP匹配不上

 
最后可能大家会问 原 IP怎样全链路传递下去?链路传递可以参考一下我的另外一篇文章《日志排查问题困难?分布式日志链路跟踪来帮你》

 

推荐阅读

  • zuul 集成 Sentinel 最新的网关流控组件
  • 阿里注册中心 Nacos 生产部署方案
  • Spring Boot 自定义配置项在 IDE 里面实现自动提示
  • Spring Cloud Zuul 的动态路由怎样做?集成 Nacos 实现很简单

 
扫码关注有惊喜!

退出移动版