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接口. ...
随机推荐
- MySQL/MariaDB数据库的存储引擎
MySQL/MariaDB数据库的存储引擎 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL体系结构 连接管理模块: 负责接收远程用户的连接. 线程管理模块: 维护 ...
- Python数据结构汇总
Python数据结构汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.线性数据结构 1>.列表(List) 在内存空间中是连续地址,查询速度快,修改也快,但不利于频繁新 ...
- Thinkphp 配置不用输入index.php
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u011186019/article/det ...
- CentOS7安装Supervisor3.1.4
supervisord 负责管理进程的server端,配置文件是/etc/supervisor/supervisord.conf supervisorctl client端的命令行工具,管理子进程,配 ...
- 2019年牛客多校第一场 C题Euclidean Distance 暴力+数学
题目链接 传送门 题意 给你\(n\)个数\(a_i\),要你在满足下面条件下使得\(\sum\limits_{i=1}^{n}(a_i-p_i)^2\)最小(题目给的\(m\)只是为了将\(a_i\ ...
- Alpha冲刺(7/10)——2019.4.30
所属课程 软件工程1916|W(福州大学) 作业要求 Alpha冲刺(7/10)--2019.4.30 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪 ...
- Spring和mybatis整合 org.mybatis.spring.mapper.MapperScannerConfigurer
在springmvc与mybatis整合时,需要对每一个mapper定义对应的一个MapperFactoryBean,可以使用MapperScannerConfigurer自动扫描mapper,然后自 ...
- 【游记】CSP2019 垫底记
考试时候的我: Day 1 做完 \(T1\) 和 \(T2\),还有 \(2.5 h\),我想阿克 \(Day1\).(\(T3\):不,你不想) 不过一会就想出来给每个点 dfs 贪心选一个点,然 ...
- linux /lib64/libc.so.6: version `GLIBC_2.17′ not found
使用root权限安装Glances,需要用到glibc,安装失败后所有命令都不好用了,执行回报“/lib64/libc.so.6: version `GLIBC_2.17′ not found ”的错 ...
- Tips on Java
1.JAVA种数组的两种定义方式. int[] nums; int nums[]. 2.整型默认为int,如果需要long,须加l或L.小数默认double,d或D可省略,但如果需要float,须加f ...