springcloud添加自定义的endpoint来实现平滑发布
在我之前的文章 springcloud如何实现服务的平滑发布 里介绍了基于pause的发布方案。
平滑发布的核心思想就是:所有服务的调用者不再调用该服务了就表示安全的将服务kill掉。
另外actuator提供了优雅停机方式的endpoint:shutdown,那我们就可以结合 pause + 等待服务感知下线 + shutdown到一个endpoint里来提供优雅的停机发布方案。
之前的方案有一个不完美的地方,那就是IP白名单的filter是要在应用的application里加
@ServletComponentScan
注解,这样对应用程序的将是不透明的(有侵入性)。
故有了下面这我认为是相对完美的方案:
先建一个common模块,其他项目引用该模块。
在src/main/resources下新建 META-INF文件夹,然后新建:spring.factories文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.longge.config.PublishEndpointAutoConfiguration
PublishEndpointAutoConfiguration.java
package com.longge.config; import javax.servlet.Filter; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import com.longge.endpoint.PublishEndpoint;
import com.longge.filter.PublishFilter;
import com.longge.filter.PublishProperties; @Configuration
@ConditionalOnClass(Endpoint.class)
@AutoConfigureAfter(EndpointAutoConfiguration.class)
public class PublishEndpointAutoConfiguration { @Bean
public PublishEndpoint publishEndpoint() {
return new PublishEndpoint();
} @Bean
@ConditionalOnProperty("publish.ip-white-list")
public PublishProperties publishProperties() {
return new PublishProperties();
} @Bean
@ConditionalOnProperty("publish.ip-white-list")
@ConditionalOnClass(Filter.class)
public FilterRegistrationBean testFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new PublishFilter(publishProperties()));
registration.addUrlPatterns("/publish");
registration.setName("publishFilter");
registration.setOrder(1);
return registration;
} }
PublishEndpoint.java
package com.longge.endpoint; import java.util.Collections;
import java.util.Map; import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import lombok.Getter;
import lombok.Setter; @ConfigurationProperties(prefix = "endpoints.publish")
public class PublishEndpoint extends AbstractEndpoint<Map<String, Object>> implements ApplicationListener<ApplicationPreparedEvent> { private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections
.unmodifiableMap(Collections.<String, String>singletonMap("message",
"No context to publish.")); /**
* 等待多时秒
*/
@Getter
@Setter
private Integer waitSeconds; public PublishEndpoint() {
super("publish", true, false);
} private ConfigurableApplicationContext context; @Override
public Map<String, Object> invoke() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
}
try {
if(null == waitSeconds) {
waitSeconds = 10;
}
Map<String, Object> shutdownMessage = Collections
.unmodifiableMap(Collections.<String, Object>singletonMap("message", "Service will exit after "+waitSeconds+" seconds"));
return shutdownMessage;
}
finally {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
PublishEndpoint.this.context.stop();
Thread.sleep(waitSeconds * 1000);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
PublishEndpoint.this.context.close();
}
});
thread.setContextClassLoader(getClass().getClassLoader());
thread.start();
}
} @Override
public void onApplicationEvent(ApplicationPreparedEvent input) {
if (this.context == null) {
this.context = input.getApplicationContext();
}
}
}
PublishFilter.java
package com.longge.filter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List; import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; /**
* shutdown和pause的管理端点的ip白名单过滤
* @author yangzhilong
*
*/
@Slf4j
public class PublishFilter implements Filter {
private static final String UNKNOWN = "unknown";
/**
* 本机ip
*/
private List<String> localIp = Arrays.asList("0:0:0:0:0:0:0:1","127.0.0.1"); private PublishProperties properties; @Override
public void destroy() {
} public PublishFilter() {} public PublishFilter(PublishProperties properties) {
super();
this.properties = properties;
} @Override
public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) srequest; String ip = this.getIpAddress(request);
log.info("访问publish的机器的原始IP:{}", ip); if (!isMatchWhiteList(ip)) {
sresponse.setContentType("application/json");
sresponse.setCharacterEncoding("UTF-8");
PrintWriter writer = sresponse.getWriter();
writer.write("{\"code\":401}");
writer.flush();
writer.close();
return;
} filterChain.doFilter(srequest, sresponse);
} @Override
public void init(FilterConfig arg0) throws ServletException {
log.info("shutdown filter is init.....");
} /**
* 匹配是否是白名单
* @param ip
* @return
*/
private boolean isMatchWhiteList(String ip) {
if(localIp.contains(ip)) {
return true;
}
if(null == properties.getIpWhiteList() || 0 == properties.getIpWhiteList().length) {
return false;
}
List<String> list = Arrays.asList(properties.getIpWhiteList());
return list.stream().anyMatch(item -> ip.startsWith(item));
} /**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
PublishProperties.java
package com.longge.filter;
import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.Getter;
import lombok.Setter; /**
* publish白名单配置
* @author yangzl
* @data 2019年4月26日
*
*/
@Getter
@Setter
@ConfigurationProperties(prefix="publish")
public class PublishProperties {
/**
* 白名单
*/
private String[] ipWhiteList;
}
properties里配置如下:
management.security.enabled = false
# 开启自定义的publish端点
endpoints.publish.enabled = true
# 警用密码校验
endpoints.publish.sensitive = false
# 服务暂停时间
endpoints.publish.waitSeconds =
# 发布endpoint白名单
publish.ip-white-list=172.17.,172.16. # 2秒拉取最新的注册信息
eureka.client.registry-fetch-interval-seconds=
# 2秒刷新ribbon中的缓存信息
ribbon.ServerListRefreshInterval=
然后GET访问 http://IP:端口/publish,服务将先成注册中心下线,然后等待waitSeconds秒,然后再shutdown
springcloud添加自定义的endpoint来实现平滑发布的更多相关文章
- 通过Nginx、Consul、Upsync实现动态负载均衡和服务平滑发布
前提 前段时间顺利地把整个服务集群和中间件全部从UCloud迁移到阿里云,笔者担任了架构和半个运维的角色.这里详细记录一下通过Nginx.Consul.Upsync实现动态负载均衡和服务平滑发布的核心 ...
- 系统架构设计:平滑发布和ABTesting
平滑发布的介绍 背景 单位的云办公相关系统没有成熟的平滑发布方案,导致每一次发布都是直接发布,dll文件或配置文件的变更会引起站点的重启. 云办公系统的常驻用户有10000+,即使短短半分多钟,也会收 ...
- springcloud如何实现服务的平滑发布
在之前的文章中我们提到服务的优雅下线,见: SpringCloud服务如何在Eureka安全优雅的下线 但这个对于ribbon调用其实是不平滑的,shutdown请求到后服务就马上关闭了,服务消费此时 ...
- SpringCloud服务如何在Eureka安全优雅的下线
如果直接KILL SpringCloud的服务,因为Eureka采用心跳的机制来上下线服务,会导致服务消费者调用此已经kill的服务提供者然后出错,处理这种情况有2中方案. 如需平滑的发布服务请参考: ...
- springcloud灰度发布实现方案
Nepxion Discovery是一款对Spring Cloud Discovery服务注册发现.Ribbon负载均衡.Feign和RestTemplate调用.Hystrix或者阿里巴巴Senti ...
- CXF整合Spring发布WebService实例
一.说明: 上一篇简单介绍了CXF以及如何使用CXF来发布一个简单的WebService服务,并且介绍了客户端的调用. 这一篇介绍如何使用CXF与spring在Web项目中来发布WebService服 ...
- JAVA 程序发布引发性能抖动
发布或重启线上服务时抖动问题解决方案 一.问题描述 在发布或重启某线上某服务时(jetty8作为服务器),常常发现有些机器的load会飙到非常高(高达70),并持续较长一段时间(5分钟)后 ...
- SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)使用详解
前言 本篇主要介绍的是SpringCloud中的服务消费者(Feign)和负载均衡(Ribbon)功能的实现以及使用Feign结合Ribbon实现负载均衡. SpringCloud Feign Fei ...
- 在spring boot微服务中使用JWS发布webService
发布时间:2018-11-22 技术:Java+spring+maven 概述 在springboot微服务中使用JWS发布webService,在服务启动时自动发布webservice接口. ...
随机推荐
- Hive性能优化【严格模式、join优化、Map-Side聚合、JVM重用】
一.严格模式 通过设置以下参数开启严格模式: >set hive.mapred.mode=strict;[默认为nonstrict非严格模式] 查询限制: 1.对于分区表,必须添加where查询 ...
- IntelliJ IDEA快速自动生成Junit测试类
1.背景 测试是保证代码必不可少的环节,自己构建测试方法太慢,并且命名也不规范,idea中提供了,一键构建测试结构的功能...废话不多说,直接写步骤 2.步骤 1.在需要做测试的类的当前窗口,直接按快 ...
- ffmpeg基础使用
https://www.jianshu.com/p/ddafe46827b7
- 【索引】Oracle之不可见索引和虚拟索引的比对
[索引]Oracle之不可见索引和虚拟索引的比对 Oracle之不可见索引 :http://blog.itpub.net/26736162/viewspace-2124044/ Oracle之虚 ...
- DELL R720针对磁盘故障面板信息误报解决
现象: 面板报警信息显示 PDR1101 fault detected on drive 0. Check drive... 经查资料是磁盘故障的原因,而r720的idrac似乎我们没有安装,我不能通 ...
- ansible中的常用循环模块with_items
ansible中的循环模块有很多,不过with_items最为常用,且较为简单,循环模块最多的功能就是将重复性的任务简单化,如下例子所示: - hosts: all remote_user: root ...
- PAT甲级1007题解——贪心
题目分析:对于每一个点来说,如果选择合并入包含前一个点的序列那么只有在前一个点的序列不为负数(这里指的是包含前一个位置的数的一个连续序列的和不为负数),当前点才会将自己也加入这个子序列,否则,当前点则 ...
- 加速Github访问
Github 仓库的数据传输很慢,甚至可能导致仓库拉取失败.例如: remote: Enumerating objects: , done. remote: Counting objects: % ( ...
- win10在ip变动之后重启ftp
自己的笔记本拿去公司工作一周后,回到家里发现自己的ftp服务器没办法访问了. 首先,确定自己的ftp服务是打开的 接着搜索internet information service打开 接着点击网站查看 ...
- php导出表格两种方法 ——PhpExcel的列子
php常用的导出表格有两种方法,第一种是输出表格,这种方法打开的时候有警告提示,一般导出表格会用phpexcel,这个导出比较灵活,而且还可以设置表格的样式. 第一种导出例子 /** * 执行导出 * ...