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接口. ...
随机推荐
- JWT对SpringCloud进行系统认证和服务鉴权
JWT对SpringCloud进行系统认证和服务鉴权 一.为什么要使用jwt?在微服务架构下的服务基本都是无状态的,传统的使用session的方式不再适用,如果使用的话需要做同步session机制,所 ...
- 数据分析之sklearn
一,介绍 Python 中的机器学习库 简单高效的数据挖掘和数据分析工具 可供大家使用,可在各种环境中重复使用 建立在 NumPy,SciPy 和 matplotlib 上 开放源码,可商业使用 - ...
- Python 软件安装
安装Python解释器 Python目前已支持所有主流操作系统,在Linux,Unix,Mac系统上自带Python环境,在Windows系统上需要安装一下,超简单 打开官网https://www.p ...
- xtrbackup备份,及恢复数据
模拟定时任务周日备份数据,周一数据变化,周一crontab定时任务增量备份,周二数据变化,周二crontabl增量备份,然后有人删库,我们进行恢复数据 模拟crontab 里的定时任务周日全备 [ro ...
- 19.centos7基础学习与积累-005-命令总结01
从头开始积累centos7系统运用 大牛博客:https://blog.51cto.com/yangrong/p5 1.查看命令帮助的方法: --help 适用于一般命令,非内置命令 man 适用于 ...
- Python入门篇-装饰器
Python入门篇-装饰器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.装饰器概述 装饰器(无参) 它是一个函数 函数作为它的形参 返回值也是一个函数 可以使用@functi ...
- 讨论---MySQL数据库中null、''、' '区别
今天在写代码的时候,遇到了一个关于MySQL数据库的问题,发现在表中插入‘’数据的时候插入不进去.发现这张表中的数据没有数据的时候,默认显示的null,当时自己又重复的额试了几次,但是始终没有成功,从 ...
- Node.js是什么?提供了哪些内容?
什么是Node.js? Node.js是基于Chrome V8 引擎的 JavaScript运行时(运行环境). Node.js提供了哪些内容? Node.js运行时,JavaScript代码运行时的 ...
- robot framework的使用方法
1.后台代码: 目录结构: 测试代码:Arithmetic.py 2.开始编写用例 直接在eclipse上新建一个txt文件即可,或者是通过ride编写用例. (1).首先在eclipse上新建目录T ...
- 学习Spring-Data-Jpa(十四)---自定义Repository
有些时候,我们需要自定义Repository实现一些特殊的业务场景. 1.自定义单个Repository 1.1.首先提供一个片段接口和实现(接口的实现默认要使用Impl为后缀,实现本身不依赖spri ...