目录
问题1 :注册到Spring boot admin 服务器上的项目,在项目关闭或者重启的时候不会自动注销。
问题3 : 如果注册在spring boot admin server中的项目显示offline 但是这个项目的确运行着。
问题4 :注册到spring boot admin server的项目是加了 spring web security的。
问题 6:Spring-boot-admin-server项目的CPU和Memory占用高,
解决方法: 加下面参数,spring boot admin client 这个值默认是空,也就是false
spring.boot.admin.client.auto-deregistration=true
源码在这RegistrationApplicationListener个类onClosedContext 方法:
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onClosedContext(ContextClosedEvent event) {
if (event.getApplicationContext().getParent() == null
|| "bootstrap".equals(event.getApplicationContext().getParent().getId())) {
stopRegisterTask();
if (autoDeregister) {
registrator.deregister();
}
}
}
实现这个项目重启或下线调用额外逻辑的是下面方法,Runtime.getRuntime().addShutdownHook(this.shutdownHook);类是AbstractApplicationContext
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
方法一: 直接spring的配置文件配置下面参数
spring.boot.admin.client.username=xxx
spring.boot.admin.client.password=xxx
方法二:在BlockingRegistrationClient实例里设置username或password。
@Configuration
@ConditionalOnClass({SpringBootAdminClientAutoConfiguration.class})
@ConditionalOnWebApplication
public class SpringBootAdminClientWebSecurityAutoConfiguration {
private static final Log LOG = LogFactory.getLog(SpringBootAdminClientWebSecurityAutoConfiguration.class);
@Bean
@Conditional(SpringBootAdminClientEnabledCondition.class)
public BlockingRegistrationClient registrationClient(ClientProperties client) {
LOG.info("Instatiating BlockingRegistrationClient in SpringBootAdminClientWebSecurityAutoConfiguration");
RestTemplateBuilder builder = new RestTemplateBuilder().setConnectTimeout(client.getConnectTimeout())
.setReadTimeout(client.getReadTimeout());
if (client.getUsername() != null && client.getPassword() != null) {
builder = builder.basicAuthentication(client.getUsername(), client.getPassword());
return new BlockingRegistrationClient(builder.build());
}else {
RestTemplate resetTemplate = builder.build();
resetTemplate.getInterceptors().add(new AspenBasicAuthorizationInterceptor());
return new BlockingRegistrationClient(resetTemplate);
}
}
}
方法 一: 检查这个项目的Health的endpoint看看状态是不是up.
如果不是up, 那个是这个项目本身的Heath有问题. 解决了它就可以了。
方法 二: 如果检查这个项目的Health的endpoint看看状态是up 但是你发现它返回时间很长。
加大health的timeout时间。
spring.boot.admin.monitor.timeout.health=100000
因为spring boot admin sever 也要在它注册的项目上发送请求并抓取相应的数据,所以要在connection的Header上面加username 和password.
方法 一:
spring.security.user.name=client
spring.security.user.password=client
spring.boot.admin.client.instance.metadata.user.name=${spring.security.user.name}
spring.boot.admin.client.instance.metadata.user.password=${spring.security.user.password}
方法 二:
创建一个类让它继承HttpHeadersProvider, 并且实现getHeaders方法
public class testBasicAuthHttpHeaderProvider implements HttpHeadersProvider{
@Override
public HttpHeaders getHeaders(Instance instance) {
String username = WebSecurityInforHolder.getIntance().getSystemUser();//username
String password = WebSecurityInforHolder.getIntance().getPassword();// password
HttpHeaders headers = new HttpHeaders();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
headers.set(HttpHeaders.AUTHORIZATION, encode(username, password));
}
return headers;
}
protected String encode(String username, String password) {
String token = Base64Utils
.encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
return "Basic " + token;
}
}
用spring实例化它。
@Bean
public HttpHeadersProvider httpHeadersProvider() {
return new TestBasicAuthHttpHeaderProvider();
}
方法: Spring boot Admin 集成Eureka项目搭建。 说白了就是把 spring boot admin项目本身当成其中一个Eureka client 项目注册到Eureka server上,从而可以利用Eureka获取上面的注册信息。
1. 在 spring boot admin server的pom.xml里加 eureka client 的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 在启动类上增加 @EnableDiscoveryClient 注解开启注册功能,代码如下所示。
@EnableDiscoveryClient
@EnableAdminServer
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3. 配置 Eureka 注册信息:
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVICE_URL:http://localhost:10001}/eureka/
eureka.client.registryFetchIntervalSeconds=5
eureka.instance.leaseRenewalIntervalInSeconds=10
#eureka.instance.health-check-url-path=/actuator/health
4. 暴露所有端点信息
management.endpoints.web.exposure.include=*
相关代码
调用下面代码,加了一下逻辑。然后看web security项目里每个Request所用的密码和数据库里Hash码是否匹配。
public class TestBCryptEncoder implements PasswordEncoder {
private BCryptPasswordEncoder passwordEncoder;
...
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String rpwd = rawPassword.toString();
...
return passwordEncoder.matches(rpwd, encodedPassword);
}
}
经过Heap dump后发现很多线程都停在了BCrypt.key这个方法上面。也试了加下面参数,但是没用。
-Djava.security.egd=file:/dev/urandom
参考资料
java - BCrypt performance deterioration - Stack Overflow
at org.springframework.security.crypto.bcrypt.BCrypt.key([BZI)V (BCrypt.java:438)
at org.springframework.security.crypto.bcrypt.BCrypt.crypt_raw([B[BIZI)[B (BCrypt.java:546)
at org.springframework.security.crypto.bcrypt.BCrypt.hashpw([BLjava/lang/String;)Ljava/lang/String; (BCrypt.java:636)
at org.springframework.security.crypto.bcrypt.BCrypt.hashpw(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (BCrypt.java:577)
at org.springframework.security.crypto.bcrypt.BCrypt.checkpw(Ljava/lang/String;Ljava/lang/String;)Z (BCrypt.java:743)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.matches(Ljava/lang/CharSequence;Ljava/lang/String;)Z (BCryptPasswordEncoder.java:133)
at ....TestBCryptEncoder.matches(Ljava/lang/CharSequence;Ljava/lang/String;)Z (AspenBCryptEncoder.java:50) | | |
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(Lorg/springframework/security/core/userdetails/UserDetails;Lorg/springframework/security/authentication/UsernamePasswordAuthenticationToken;)V (DaoAuthenticationProvider.java:76
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(Lorg/springframework/security/core/Authentication;)Lorg/springframework/security/core/Authentication; (AbstractUserDetailsAuthenticationProvider.java:147)
at org.springframework.security.authentication.ProviderManager.authenticate(Lorg/springframework/security/core/Authentication;)Lorg/springframework/security/core/Authentication; (ProviderManager.java:182)
at org.springframework.security.authentication.ProviderManager.authenticate(Lorg/springframework/security/core/Authentication;)Lorg/springframework/security/core/Authentication; (ProviderManager.java:201)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;Ljavax/servlet/FilterChain;)V (BasicAuthenticationFilter.java:156)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;Ljavax/servlet/FilterChain;)V (OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V (FilterChainProxy.java:336)
at org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter.doFilterInternal(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;Ljavax/servlet/FilterChain;)V (DefaultLogoutPageGeneratingFilter.java:58)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;Ljavax/servlet/FilterChain;)V (OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V (FilterChainProxy.java:336)
at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;Ljavax/servlet/FilterChain;)V (DefaultLoginPageGeneratingFilter.java:237)
你可以通过看spring-boot-admin-server 里 Event Journal view就知道,那些错误的components一直不同的发注册信息进来. 注册信息如下:
{ "registration": { "name": "xxxx", "managementUrl": "https://xxx/actuator", "healthUrl": "xxx/actuator/health", "serviceUrl": "xxx", "source": "http-api", "metadata": { "startup": "2022-02-21T02:20:47.662Z" } } }
原因:
很多项目注册到spring admin server时报错。报错后,这些项目会隔一段时候在尝试注册一遍。因为项目太多,所以造成这样的请求很多,而且每个请求都会调用TestBCryptEncoder 做密码比对。但是因为BCrypt.key()这个方法很慢,所以很多线程就阻塞了。最终造成CPU usage 很高和Memory也很高。使得linux server都很慢。
为什么BCrypt.key()这个方法这慢,待调查。
解决方法
方法一:
1. spring boot server项目去掉spring web security。
方法二:
找到注册spring boot server 错误的原因。解决掉后这样的线程就少了。间接的解决了这个问题。
方法三:
由于strength数值越大,耗时越严重,所以就减少这个值或换加密算法,比如换成MD5.
记一次bcrypt加密引起的性能调优过程_xiaolaoban212的博客-CSDN博客
https://segmentfault.com/q/1010000003054250?bd_source_light=4746641
记一次使用BCryptPasswordEncoder,设置了不合理参数导致耗时严重的坑_mb607022e25a607的技术博客_51CTO博客
相关系列博客
Spring boot升级到2.3.2.Release和Spring framework升级到5.28.Release踩过的坑_keeppractice的博客-CSDN博客