微服务的代码由两部分构成,一部分是根据模型定义自动生成的,这部分代码不可修改,另外一部分代码是留给开发人员实现业务逻辑的,可以修改。
防止开发人员的本地环境污染注册中心最开始的解决方法是,在自动生成的代码中,强制指定了微服务运行的IP,模板文件applicationProperties.ftl中的相关片段如下:
eureka.instance.prefer-ip-address=true
eureka.instance.ip-address=${serviceDeployServerIp}
当服务开发完毕,需要部署到测试环境的时候,需要执行一个发布服务审核流程,发布服务的操作会给服务打一个版本,在发布服务的时候,去掉了如上用于开发环境的强制指定的IP,发布服务脚本文件service_release.sh的部分相关片段如下:
if [[ $1 = '' ]]
then
echo "没有指定服务名称"
exit
fi
cat gi-service/gi-service-$1/standard/src/main/resources/application.properties | awk '{if($0!~/eureka.instance.ip-address=/){print $0}}' > gi-service/gi-service-$1/standard/src/main/resources/application-release.properties
mv gi-service/gi-service-$1/standard/src/main/resources/application-release.properties gi-service/gi-service-$1/standard/src/main/resources/application.properties
用这种解决方案,满足了我们很长一段时间的需求,开发人员自己在本地启动服务向注册中心注册服务并不会把自己的本地内网地址注册上去,注册的地址其实是云端的服务的运行地址,不会对网关以及跨服务调用产生影响。
然而,接下来事情发生了变化。
微服务不再是一直固定运行在一台机器上了,开始引入了机器资源池的概念,资源池中的机器用来运行微服务,每一个微服务在启动的时候会自动地从资源池中挑选一台机器来执行,也就是说,微服务不会有固定的运行IP,这样,上面的解决方案就失效了。
该怎么解决这个问题呢?
思路是,改造注册中心,只允许机器资源池中的机器进行服务的注册,其他的未知机器则只能做服务的查找而不能执行注册操作,这样,开发人员的本地服务就不会注册到注册中心中去了。
注册中心本身是一个web应用,服务的注册和查找也是通过http请求进行的,那么改造注册中心的方式可以使用一个过滤器,具体实现代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 防止开发人员的本地环境污染注册中心
* @date 2018-09-11
* @author 杨尚川
*/
@Service
public class LimitRegisterFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(LimitRegisterFilter.class);
@Value("${allow.ips.url}")
private String allowIpsUrl;
@Value("${refresh.time.ms}")
private long refreshTimeInMs;
private String allowIps="127.0.0.1,0:0:0:0:0:0:0:1,";
private static AtomicLong accessCount = new AtomicLong();
private static AtomicLong registerCount = new AtomicLong();
private static AtomicLong rejectRegisterCount = new AtomicLong();
private static AtomicLong noNeedRegisterCount = new AtomicLong();
private static AtomicLong nullProjectNameAndServiceNameRegisterCount = new AtomicLong();
private static Map<String, Long> lastRegisterTimeMap = new ConcurrentHashMap<>();
@PostConstruct
public void init(){
LOGGER.info("初始化过滤器, 获取允许的IP地址的路径: {}", allowIpsUrl);
try {
HttpURLConnection conn = (HttpURLConnection) new URL(allowIpsUrl).openConnection();
conn.setRequestMethod("GET");
conn.setDoOutput(true);
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
String line = null;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
}
allowIps += result.toString();
}catch (Exception e){
LOGGER.error("获取允许的IP地址失败", e);
}
LOGGER.info("初始化过滤器, 允许的IP地址: {}", allowIps);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
LOGGER.info("初始化过滤器");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
accessCount.incrementAndGet();
HttpServletRequest req = (HttpServletRequest)request;
String uri = req.getRequestURI();
String method = req.getMethod();
String ip = request.getRemoteAddr();
LOGGER.debug("访问信息, ip: {}, method: {}, uri: {}", ip, method, uri);
if (!"GET".equals(method.toUpperCase())
&& (uri.contains("eureka/apps") || uri.contains("eureka/v2/apps"))) {
LOGGER.debug("注册服务, ip: {}, method: {}, uri: {}", ip, method, uri);
String[] attr = uri.split("/");
String projectNameAndServiceName = null;
if(uri.contains("eureka/apps") && attr.length>3){
projectNameAndServiceName = attr[3];
}
if(uri.contains("eureka/v2/apps") && attr.length>4){
projectNameAndServiceName = attr[4];
}
registerCount.incrementAndGet();
if (allowIps == null || !allowIps.contains(ip)) {
LOGGER.warn("不允许的IP注册: {}, 总的访问次数: {}, 注册次数: {}, 无需注册次数: {}, 拒绝注册次数: {}, 获取不到项目名称和服务名称数: {}", ip, accessCount.get(), registerCount.get(), noNeedRegisterCount.get(), rejectRegisterCount.get(), nullProjectNameAndServiceNameRegisterCount.get());
rejectRegisterCount.incrementAndGet();
return;
}
if(projectNameAndServiceName==null){
nullProjectNameAndServiceNameRegisterCount.incrementAndGet();
return;
}
String key = ip+"_"+projectNameAndServiceName+"_"+method;
Long lastRegisterTime = lastRegisterTimeMap.get(key);
if(lastRegisterTime != null){
if((System.currentTimeMillis()-lastRegisterTime) < refreshTimeInMs){
noNeedRegisterCount.incrementAndGet();
return;
}
}
lastRegisterTimeMap.put(key, System.currentTimeMillis());
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
LOGGER.info("销毁过滤器");
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
LimitRegisterFilter limitRegisterFilter = new LimitRegisterFilter();
limitRegisterFilter.allowIps = allowIps;
limitRegisterFilter.refreshTimeInMs = refreshTimeInMs;
LOGGER.info("注册过滤器ServerRequestAuthFilter, 允许的IP地址: {}", allowIps);
FilterRegistrationBean registration = new FilterRegistrationBean(limitRegisterFilter);
registration.addUrlPatterns("/*");
return registration;
}
}
所有评论(0)