我们讲解一下如何在我们的服务当中优雅的停服,我们先回顾一下我们讲过的内容,我们讲解的是如何关闭自我保护,
那么关闭自我保护指的是什么呢,是Eureka Server通过修改它的配置文件,完成在服务关闭时,让注册中心不再对他开启
自我保护,那我们讲的优雅停服呢,也是关闭自我保护的一种方式,但是不同的是什么呢,我们这个方式,我们现在讲的内容呢,
是在我们服务当中去做的,而不是在我们的注册中心去做,我们来看如何优雅停服,我们之前在服务端做的,有一个不好的地方,
在管理页面当中总有这个提示,那么我们能不能在关闭服务时,还是能够让注册中心能够把这个信息除掉,同时还没有这就话的
显示,也可以的,如何让服务优雅停服,他的步骤是什么呢,第一个如果要配置优雅停服的话,那么你的Eureka的注册中心当中,
就不需要关闭服务的优雅停服了,我们写的案例当中,服务端配置了这个配置,关闭了自我保护,所以我们现在要把它改过来,
找到我们注册中心的代码,然后打开他的配置文件,把关闭自我保护的配置去掉,我们需要将原来的服务停掉,我们需要重新打包,
然后我们再去打包,这个步骤我们已经写得很清楚了,那么刚才的案例是已经配置了,已经打完包了,Build success,然后把我们
新打开的程序,添加到我们虚拟机当中,然后135下,两个都有了,这样我们就把刚刚的jar包,已经放到了eureka目录下了,我们
还是来启动,现在正在启动着,我们刷新一下,他启动还有一个过程,注意我们现在讲的优雅停服,最大的区别就是,在优雅停服的
那个服务去做的,第二步,在我们服务中去添加actuator的jar包,回到我们的eclipse当中,比如我们现在就拿Provider做一个
演示,未来我要让Provider的服务,具备一个优雅停服的一个功能,这个时候跟注册中心没有关系了,怎么做呢,这里首先要加一个
actuator的jar包,actuator.jar包在哪里呢,看我们的pom文件,我们看以来注入这儿,我们现在注入了一个
<artifactId>spring-cloud-starter-eureka</artifactId>
我们添加了这么一个jar包,我们也说过了,这里注入的其实是eureka的client,他的客户端,那么actuator在哪儿呢,在他的服务端,
那怎么办呢,服务端的坐标是后面加-server,这个我们之前也说过,讲Eureka注册中心的时候,我们用过这个坐标,然后我们在这里
找一下,找谁呢,就找actuator,看能不能找到,在
spring-cloud-netflix-eureka-server
这个下面
所以这一步我们要先把他做好,一定要添加actuator.jar包,我们把它粘到笔记当中,然后我们给他标记一下,
pom文件修改以后呢,接下来还要在我们的服务配置文件当中,添加这样两个配置,找到Provider的application.properties,
在配置文件当中添加这么两个配置
#启用shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
这两个配置是什么意思呢,一个是启用shutdown的命令,来关闭服务,第二个是什么呢,禁用密码验证,也就是说,在关闭这个服务的时候
呢,他有一个密码验证的机制,我们禁用密码验证,不需要给他密码做验证,所以这个先关掉,这个不是我们要讲的,你先不用管它,这两个
加了以后,接下来我们再看,第四步,发送一个关闭URL的请求,这个咱们先别着急,现在我们先看我们的注册中心有没有启动好,已经启动
好了,节点已经启动好了,注册中心是没有问题的,然后你看你把服务器的关闭配置修改以后,你再启动注册中心的管理界面的时候,那一段
的红字,是不是就没有了,又恢复到原来的状态了,咱们再来看,第四个关闭服务的URL的请求,这是什么意思呢,现在我们再关闭服务的时候,
就不能像我们之前那么暴力了,直接点击停止运行来结束了,不行了,那就不叫优雅停服了,那我们怎么做到优雅停服呢,大家可以回想一下,
我们Dubbo当中是不是也有优雅停服,优雅关机,优雅关机是调用main方法来实现的,那么在SpringCloud的微服务当中呢,对于服务的优雅
停服呢,它是需要你发送一个URL请求的,然后URL请求,这个请求的地址,就是你要关闭服务的一个地址,然后后面给一个shutdown的命令,
这个时候它会根据shutdown的命令,因为已经开启了shutdown的执行,所以这个时候它会根据你的shutdown命令,来把Provider关闭掉,
那么这个请求怎么发送呢,这个shutdown命令并不属于我们自己能够处理的请求,因为在我们服务当中,并没有处理shutdown的服务,
所以我们还没法用浏览器去请求,这个时候我们还得借助一个工具类,这个工具类是什么呢,就是我们之前写电商项目的时候,我们用过一个
HttpClient的工具类,我们通过这个工具类,是可以发送一个请求的,而且对于URL请求,让你的服务去做优雅停服的时候,他的请求方式还
必须是POST,这也就是映射出了我们为什么不能用浏览器,浏览器所有的请求方式用的都是GET,只要地址栏里输入的永远是GET,所以我们还
必须得借助于一个工具类,HttpClient,在这里我们把这个工具类找到,我们把它放到test包下,这里能够发送doPost,doGet的,
然后这里有一个main方法,我们就可以通过执行main方法,我们工具类就可以发送URL请求,URL地址是什么呢
String url ="http://10.40.8.152:9090/shutdown";
就这个地址,我们写IP吧,IP更清晰一些,就是这个地址呢,发送给谁呢,发送你要停止哪个服务的服务的IP地址,你们先看我们
把Provider启动,我们看这个服务是不是出来了,现在我要对这个Provider,就是我们服务列表中显示的
SPRINGCLOUD-EUREKA-PROVIDER
这个服务做优雅停服,那么我们的HttpClientUtil发送请求呢,得向他发送请求,向Eureka-Provider发送请求,明白这意思吧,
让他来做优雅停服,这个时候和我们的注册中心没有一点关系,所以当前我们写的URL呢就是当前eureka-provider的地址,
然后再去看他的端口,他现在监听的端口是多少,是不是9090,所以我们在这里,配上他的端口,然后接下来我们要对她做什么,
做停服,刚才我们在配置文件里是不是启用了shutdown,那么这就有作用了,我们就加一个URI叫/shutdown,这是向他发送一个请求,
去做优雅停服的一个命令,然后大家注意,我加到笔记当中,该URL必须要使用doPost方式来发送,不能用doGet,这是我们没法
用浏览器去发送请求,明白我的意思吧,我们得用一个发送请求的一个doPost的工具类,这个HttpClient,然后我们就可以
调用我们工具类里面的方法了,在我们这里有一个doPost方法,不用传任何参数,直接把URL放进去就可以了,你看我们现在
服务是不是还在运行着的,接下来我们就对工具类,就是HttpClient,我们看现在这个服务的运行状态是不是就是停止的了,
然后我们回过来,刷新,看到了吗,是不是这个服务也没有了,而且这里也没有显示服务过期了,关闭自我保护这样的提示,
所以很显然,这个停服的方式,要比我们讲的在服务端注册中心配置关闭自我保护的机制更人性化一些,所以这是eureka
当中优雅停服的一个讲解,我把这个工具类粘过来,放到我们的笔记当中,那么在这里我们需要注意的就是,一个是地址,
地址是要向我们需要优雅停服的服务,发送请求,其次这个请求只能用POST方式去调,不能用浏览器,用浏览器它是没有任何效果的
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learn.cloud</groupId>
<artifactId>springcloud-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server.port=8761
eureka.instance.hostname=eureka-server
#spring.application.name=eureka
#eureka.server.evictionIntervalTimerInMs=60000
eureka.client.serviceUrl.defaultZone=http://admin:1234@localhost:8761/eureka
#eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
#eureka.server.enableSelfPreservation=true
security.basic.enabled=true
security.user.name=admin
security.user.password=1234
eureka.datacenter=cloud
eureka.environment=product
#eureka.server.enable-self-preservation=false
package com.learn.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learn.cloud</groupId>
<artifactId>springcloud-eureka-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> -->
</dependencies>
<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server.port=9090
eureka.client.serviceUrl.defaultZone=http://admin:1234@localhost:8761/eureka
spring.application.name=springcloud-eureka-provider
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
endpoints.shutdown.enabled=true
endpoints.shutdown.sensitive=false
package com.learn.provider;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpClientUtil {
public static String doGet(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList,"utf-8");
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
public static void main(String[] args) {
String url ="http://10.40.8.152:9090/shutdown";
//该url必须要使用dopost方式来发送
HttpClientUtil.doPost(url);
}
}
package com.learn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class EurekaProviderApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}