springcloud添加⾃定义的endpoint来实现平滑发布
在我之前的⽂章⾥介绍了基于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=\
fig.PublishEndpointAutoConfiguration
PublishEndpointAutoConfiguration.java
package fig;
import javax.servlet.Filter;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.dpoint.Endpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.dition.ConditionalOnClass;
import org.springframework.dition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import t.annotation.Bean;
import t.annotation.Configuration;
import dpoint.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 dpoint;
import java.util.Collections;
import java.util.Map;
import org.springframework.dpoint.AbstractEndpoint;
import org.t.event.ApplicationPreparedEvent;
import org.t.properties.ConfigurationProperties;
import t.ApplicationListener;
import t.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 (t == 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"));
springcloud和springbootreturn shutdownMessage;
}
finally {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
t.stop();
Thread.sleep(waitSeconds * 1000);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
t.close();
}
});
thread.setContextClassLoader(getClass().getClassLoader());
thread.start();
}
}
@Override
public void onApplicationEvent(ApplicationPreparedEvent input) {
if (t == null) {
}
}
}
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;
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 = IpAddress(request);
log.info("访问publish的机器的原始IP:{}", ip);
if (!isMatchWhiteList(ip)) {
sresponse.setContentType("application/json");
sresponse.setCharacterEncoding("UTF-8");
PrintWriter writer = Writer();
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 ");
}
/**
* 匹配是否是⽩名单
* @param ip
* @return
*/
private boolean isMatchWhiteList(String ip) {
ains(ip)) {
return true;
}
if(null == IpWhiteList() || 0 == IpWhiteList().length) {
return false;
}
List<String> list = Arrays.IpWhiteList());
return list.stream().anyMatch(item -> ip.startsWith(item));
}
/
**
* 获取⽤户真实IP地址,不使⽤RemoteAddr();的原因是有可能⽤户使⽤了代理软件⽅式避免真实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 = Header("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = Header("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = Header("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = Header("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = Header("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = RemoteAddr();
}
return ip;
}
}
PublishProperties.java
package com.longge.filter;
import org.t.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⾥配置如下:
abled = false
# 开启⾃定义的publish端点
abled = true
# 警⽤密码校验
endpoints.publish.sensitive = false
# 服务暂停时间
endpoints.publish.waitSeconds = 7
# 发布endpoint⽩名单
publish.ip-white-list=172.17.,172.16.
# 2秒拉取最新的注册信息
istry-fetch-interval-seconds=2
# 2秒刷新ribbon中的缓存信息
ribbon.ServerListRefreshInterval=2000
然后GET访问 IP:端⼝/publish,服务将先成注册中⼼下线,然后等待waitSeconds秒,然后再shutdown
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论