在我之前的文章  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来实现平滑发布的更多相关文章

  1. 通过Nginx、Consul、Upsync实现动态负载均衡和服务平滑发布

    前提 前段时间顺利地把整个服务集群和中间件全部从UCloud迁移到阿里云,笔者担任了架构和半个运维的角色.这里详细记录一下通过Nginx.Consul.Upsync实现动态负载均衡和服务平滑发布的核心 ...

  2. 系统架构设计:平滑发布和ABTesting

    平滑发布的介绍 背景 单位的云办公相关系统没有成熟的平滑发布方案,导致每一次发布都是直接发布,dll文件或配置文件的变更会引起站点的重启. 云办公系统的常驻用户有10000+,即使短短半分多钟,也会收 ...

  3. springcloud如何实现服务的平滑发布

    在之前的文章中我们提到服务的优雅下线,见: SpringCloud服务如何在Eureka安全优雅的下线 但这个对于ribbon调用其实是不平滑的,shutdown请求到后服务就马上关闭了,服务消费此时 ...

  4. SpringCloud服务如何在Eureka安全优雅的下线

    如果直接KILL SpringCloud的服务,因为Eureka采用心跳的机制来上下线服务,会导致服务消费者调用此已经kill的服务提供者然后出错,处理这种情况有2中方案. 如需平滑的发布服务请参考: ...

  5. springcloud灰度发布实现方案

    Nepxion Discovery是一款对Spring Cloud Discovery服务注册发现.Ribbon负载均衡.Feign和RestTemplate调用.Hystrix或者阿里巴巴Senti ...

  6. CXF整合Spring发布WebService实例

    一.说明: 上一篇简单介绍了CXF以及如何使用CXF来发布一个简单的WebService服务,并且介绍了客户端的调用. 这一篇介绍如何使用CXF与spring在Web项目中来发布WebService服 ...

  7. JAVA 程序发布引发性能抖动

    发布或重启线上服务时抖动问题解决方案 一.问题描述       在发布或重启某线上某服务时(jetty8作为服务器),常常发现有些机器的load会飙到非常高(高达70),并持续较长一段时间(5分钟)后 ...

  8. SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)使用详解

    前言 本篇主要介绍的是SpringCloud中的服务消费者(Feign)和负载均衡(Ribbon)功能的实现以及使用Feign结合Ribbon实现负载均衡. SpringCloud Feign Fei ...

  9. 在spring boot微服务中使用JWS发布webService

    发布时间:2018-11-22   技术:Java+spring+maven   概述 在springboot微服务中使用JWS发布webService,在服务启动时自动发布webservice接口. ...

随机推荐

  1. RSA加密&解密【Java&Scala】

    一.简介 RSA加密算法是一种非对称加密算法.在公开密钥加密和电子商业中RSA被广泛使用. RSA公开密钥密码体制.所谓公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解 ...

  2. 【书评:Oracle查询优化改写】第二章

    [书评:Oracle查询优化改写]第二章 BLOG文档结构图 在上一篇中http://blog.itpub.net/26736162/viewspace-1652985/,我们主要分析了一些单表查询的 ...

  3. Centos7安装DockerCE

    1. 说明 以下使用的系统为centos7,64位,镜像为CentOS-7-x86_64-Minimal-1804,所有操作以root用户操作 2. 安装Docker官方源 2.1 安装yum工具集 ...

  4. MySQL MHA工作原理

    MHA工作组件 MHA(Master High Availability)是一种MySQL高可用解决方案,由日本DeNA公司开发,主要用于在故障切换和主从提升时进行快速切换,并最大程度保证数据一致性. ...

  5. QuickStart系列:Docker部署PostgreSQL

    docker镜像地址: https://hub.docker.com/_/postgres/ https://www.widuu.com/chinese_docker/examples/postgre ...

  6. 【CMDB】API传输验证

    客户端向服务器发送请求时,在请求头添加自定义的字符串 客户端的加密方式 1.对key+time进行md5加密 2.发送的时候的格式为md5_key|time,将时间也发送过去 服务器端验证 1.获取加 ...

  7. 云计算与大数据实验:Hbase shell操作用户表

    [实验目的] 1)了解hbase服务 2)学会hbase shell命令操作用户表 [实验原理] HBase是一个分布式的.面向列的开源数据库,它利用Hadoop HDFS作为其文件存储系统,利用Ha ...

  8. windows10 进入BIOS

    windows10开机进入不了BIOS 原因 上网查了电脑固件所应该有的进入键,什么F1.F2.F12.Delete以及什么要配置Fn+F1...等等方法就是开机进入不了BIOS. 解决办法 最后发现 ...

  9. Matplotlib:mpl_toolkits.mplot3d工具包

    简介 mpl_toolkits.mplot3d是Matplotlib里面专门用来画三维图的工具包,官方指南请点击此处<mplot3d tutorial> 使用 导入 使用from mpl_ ...

  10. vs调试的时候无法命中断点

    visual studio 调试的时候如果没有命中断点,可能是启动的时候就出错了. 在文件Global.asax中, protected void Application_Error(object s ...