安全下线服务,调整服务状态接口编写
This commit is contained in:
+96
-182
@@ -2,33 +2,42 @@ package com.micro.fast.eureka.server.controller;
|
||||
|
||||
import com.micro.fast.boot.starter.common.response.ServerResponse;
|
||||
import com.netflix.appinfo.AmazonInfo;
|
||||
import com.netflix.appinfo.ApplicationInfoManager;
|
||||
import com.netflix.appinfo.DataCenterInfo;
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
|
||||
import com.netflix.discovery.shared.Application;
|
||||
import com.netflix.discovery.shared.Pair;
|
||||
import com.netflix.eureka.EurekaServerContext;
|
||||
import com.netflix.eureka.EurekaServerContextHolder;
|
||||
import com.netflix.eureka.cluster.PeerEurekaNode;
|
||||
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
|
||||
import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl;
|
||||
import com.netflix.eureka.resources.StatusResource;
|
||||
import com.netflix.eureka.util.StatusInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* restful操作eureka的接口
|
||||
*
|
||||
* 服务的注册 :
|
||||
* 当eureka的客户端向eureka服务端注册的时候会提供自身的ip port info health等信息
|
||||
* 服务的续约 :
|
||||
* Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。
|
||||
* 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
|
||||
* 获取注册列表信息 :
|
||||
* Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。
|
||||
* 该注册列表信息定期(每30秒钟)更新一次。
|
||||
* 服务下线 :
|
||||
* Eureka客户端在程序关闭时向Eureka服务器发送取消请求。
|
||||
* 服务剔除 :
|
||||
* 在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除.
|
||||
* Eureka默认不会二次注册 :
|
||||
* 当有服务的注册信息传递到Eureka的时候,eureka会判断isReplication是否是true.当isReplication为true时候eureka就不会将服务信息传递
|
||||
* 给其他注册中心
|
||||
* 如何安全热发布:
|
||||
* 将服务状态调整为OUT_OF_SERVICE,经过几次心跳之后zuul会感知服务的状态,不在有新的请求进入下线的服务
|
||||
* 灰度发布:
|
||||
* 通过动态修改服务的matedate信息来实现灰度发布,可以将新发布的机器设置为灰度机器。灰度机器是指接收少量指定请求的机器
|
||||
* @author lsy
|
||||
*/
|
||||
@RestController
|
||||
@@ -36,65 +45,46 @@ import java.util.*;
|
||||
@Api(description = "注册中心的restful接口")
|
||||
public class RestfulEurekaController {
|
||||
|
||||
|
||||
@Value("${eureka.restful.path:/restful/eureka}")
|
||||
private String restfulPath = "/restful/eureka";
|
||||
|
||||
/**
|
||||
* 实例管理对象
|
||||
*/
|
||||
private ApplicationInfoManager applicationInfoManager;
|
||||
|
||||
public RestfulEurekaController(ApplicationInfoManager applicationInfoManager) {
|
||||
this.applicationInfoManager = applicationInfoManager;
|
||||
}
|
||||
|
||||
@GetMapping("/apps")
|
||||
@ApiOperation("获取所有的服务实例的简单信息")
|
||||
public ServerResponse apps(HttpServletRequest request, HttpServletResponse response) {
|
||||
public ServerResponse apps() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
//获取所有服务的信息
|
||||
populateApps(map);
|
||||
return ServerResponse.successMsgData("获取所有实例成功", map);
|
||||
}
|
||||
|
||||
@GetMapping("/apps/{instanceId}")
|
||||
@ApiOperation("根据域名获取某个实例的信息")
|
||||
public ServerResponse getInstance(@PathVariable("instanceId") String instanceId) {
|
||||
InstanceInfo instanceInfo = null;
|
||||
@GetMapping("/apps/{id}")
|
||||
@ApiOperation("根据id获取实例的信息,模糊查询")
|
||||
public ServerResponse getInstance(@PathVariable("id") String instanceId) {
|
||||
Set<InstanceInfo> instanceInfos = new HashSet<>(10);
|
||||
if (StringUtils.isNotBlank(instanceId)){
|
||||
List<Application> sortedApplications = getRegistry().getSortedApplications();
|
||||
boolean isBreak = false;
|
||||
for (Application sortedApplication : sortedApplications) {
|
||||
List<InstanceInfo> instances = sortedApplication.getInstances();
|
||||
for (InstanceInfo instance : instances) {
|
||||
String hostName = instance.getHostName();
|
||||
if (hostName.startsWith(instanceId)){
|
||||
instanceInfo = instance;
|
||||
isBreak = true;
|
||||
break;
|
||||
if (instance.getId().contains(instanceId)){
|
||||
instanceInfos.add(instance);
|
||||
}
|
||||
}
|
||||
if (isBreak){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else {
|
||||
return ServerResponse.errorMsg("instanceId不能为空");
|
||||
}
|
||||
|
||||
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfo);
|
||||
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfos);
|
||||
}
|
||||
@GetMapping("/apps/{appId}")
|
||||
@ApiOperation("根据appId查询实例,可能有多个")
|
||||
public ServerResponse getInstancesByAppId(@PathVariable("appId") String appId) {
|
||||
|
||||
@GetMapping("/apps/cluster/{appName}")
|
||||
@ApiOperation("根据appId,也就是application.name查询实例,可能有多个")
|
||||
public ServerResponse getInstancesByAppId(@PathVariable("appName") String appName) {
|
||||
Set<InstanceInfo> instanceInfos = new HashSet<>(10);
|
||||
if (StringUtils.isNotBlank(appId)){
|
||||
if (StringUtils.isNotBlank(appName)){
|
||||
List<Application> sortedApplications = getRegistry().getSortedApplications();
|
||||
for (Application sortedApplication : sortedApplications) {
|
||||
List<InstanceInfo> instances = sortedApplication.getInstances();
|
||||
for (InstanceInfo instance : instances) {
|
||||
if (appId.equals(instance.getAppName())){
|
||||
if (StringUtils.equalsIgnoreCase(instance.getAppName(),appName)){
|
||||
instanceInfos.add(instance);
|
||||
}
|
||||
}
|
||||
@@ -103,91 +93,82 @@ public class RestfulEurekaController {
|
||||
}else {
|
||||
return ServerResponse.errorMsg("instanceId不能为空");
|
||||
}
|
||||
|
||||
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfos);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public Map<String, Object> status(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
populateBase(request, map);
|
||||
populateApps(map);
|
||||
StatusInfo statusInfo;
|
||||
try {
|
||||
//获取当前注册中心的本身的相信信息
|
||||
statusInfo = new StatusResource().getStatusInfo();
|
||||
} catch (Exception e) {
|
||||
//获取当前注册中心的本身的相信信息
|
||||
statusInfo = StatusInfo.Builder.newBuilder().isHealthy(false).build();
|
||||
}
|
||||
map.put("statusInfo", statusInfo);
|
||||
populateInstanceInfo(map, statusInfo);
|
||||
filterReplicas(map, statusInfo);
|
||||
return map;
|
||||
@GetMapping("/apps/app/{appName}/{id}")
|
||||
@ApiOperation("根据appName和id获取服务的信息")
|
||||
public ServerResponse getApplication(@PathVariable("appName") String appName,@PathVariable("id") String id){
|
||||
PeerAwareInstanceRegistry registry = getRegistry();
|
||||
InstanceInfo instanceByAppAndId = registry.getInstanceByAppAndId(appName, id);
|
||||
return ServerResponse.successData(instanceByAppAndId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后注册的实例
|
||||
* @param request
|
||||
* 主动下线服务,并传递到其他注册中心
|
||||
* 此接口主要用于下线异常终止的服务
|
||||
* @param appName
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/lastn")
|
||||
public Map<String, Object> lastn(HttpServletRequest request) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
populateBase(request, map);
|
||||
PeerAwareInstanceRegistryImpl registry = (PeerAwareInstanceRegistryImpl) getRegistry();
|
||||
ArrayList<Map<String, Object>> lastNCanceled = new ArrayList<>();
|
||||
List<Pair<Long, String>> list = registry.getLastNCanceledInstances();
|
||||
for (Pair<Long, String> entry : list) {
|
||||
lastNCanceled.add(registeredInstance(entry.second(), entry.first()));
|
||||
@DeleteMapping("/{appName}/{id}/{isReplication}")
|
||||
@ApiOperation("下线服务")
|
||||
public ServerResponse unregister(@PathVariable("appName") String appName,@PathVariable("id") String id,@PathVariable("isReplication") Boolean isReplication){
|
||||
PeerAwareInstanceRegistry registry = getRegistry();
|
||||
// 当isReplication是true的时候表示这个实例的信息是从别的eureka复制过来的,eureka不会进行二次传递
|
||||
boolean cancel = registry.cancel(appName, id, isReplication);
|
||||
if (cancel){
|
||||
return ServerResponse.successMsg("下线服务成功,服务会在下次心跳时注册");
|
||||
}
|
||||
map.put("lastNCanceled", lastNCanceled);
|
||||
list = registry.getLastNRegisteredInstances();
|
||||
ArrayList<Map<String, Object>> lastNRegistered = new ArrayList<>();
|
||||
for (Pair<Long, String> entry : list) {
|
||||
lastNRegistered.add(registeredInstance(entry.second(), entry.first()));
|
||||
}
|
||||
map.put("lastNRegistered", lastNRegistered);
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> registeredInstance(String id, long date) {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("id", id);
|
||||
map.put("date", new Date(date));
|
||||
return map;
|
||||
}
|
||||
|
||||
protected void populateBase(HttpServletRequest request, Map<String, Object> model) {
|
||||
populateHeader(model);
|
||||
populateNavbar(request, model);
|
||||
return ServerResponse.successMsg("下线服务失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加注册中心基础信息
|
||||
*
|
||||
* @param model
|
||||
* 调整服务的状态,可以用于在无感知的情况下热部署服务
|
||||
* @param appName
|
||||
* @param id
|
||||
* @param status
|
||||
* @return
|
||||
*/
|
||||
private void populateHeader(Map<String, Object> model) {
|
||||
model.put("currentTime", StatusResource.getCurrentTimeAsString());
|
||||
model.put("upTime", StatusInfo.getUpTime());
|
||||
model.put("environment", ConfigurationManager.getDeploymentContext()
|
||||
.getDeploymentEnvironment());
|
||||
model.put("datacenter", ConfigurationManager.getDeploymentContext()
|
||||
.getDeploymentDatacenter());
|
||||
@PutMapping("/{appName}/{id}/{status}/{isReplication}")
|
||||
@ApiOperation("调整服务状态")
|
||||
public ServerResponse updateStatus(@PathVariable("appName") String appName,@PathVariable("id")String id,@PathVariable("status")String status,@PathVariable("isReplication")Boolean isReplication){
|
||||
PeerAwareInstanceRegistry registry = getRegistry();
|
||||
model.put("registry", registry);
|
||||
model.put("isBelowRenewThresold", registry.isBelowRenewThresold() == 1);
|
||||
DataCenterInfo info = applicationInfoManager.getInfo().getDataCenterInfo();
|
||||
if (info.getName() == DataCenterInfo.Name.Amazon) {
|
||||
AmazonInfo amazonInfo = (AmazonInfo) info;
|
||||
model.put("amazonInfo", amazonInfo);
|
||||
model.put("amiId", amazonInfo.get(AmazonInfo.MetaDataKey.amiId));
|
||||
model.put("availabilityZone",
|
||||
amazonInfo.get(AmazonInfo.MetaDataKey.availabilityZone));
|
||||
model.put("instanceId", amazonInfo.get(AmazonInfo.MetaDataKey.instanceId));
|
||||
switch (status){
|
||||
case "OUT_OF_SERVICE":
|
||||
boolean b = registry.statusUpdate(appName, id, InstanceStatus.OUT_OF_SERVICE, String.valueOf(System.currentTimeMillis()), isReplication);
|
||||
if (b){
|
||||
return ServerResponse.successMsg("下线服务成功");
|
||||
}
|
||||
break;
|
||||
case "DOWN":
|
||||
boolean b1 = registry.statusUpdate(appName, id, InstanceStatus.DOWN, String.valueOf(System.currentTimeMillis()), isReplication);
|
||||
if (b1){
|
||||
return ServerResponse.successMsg("设置服务为down成功");
|
||||
}
|
||||
break;
|
||||
case "UP" :
|
||||
boolean b2 = registry.statusUpdate(appName, id, InstanceStatus.UP, String.valueOf(System.currentTimeMillis()), isReplication);
|
||||
if (b2){
|
||||
return ServerResponse.successMsg("上线服务成功");
|
||||
}
|
||||
break;
|
||||
case "UNKNOWN" :
|
||||
boolean b3 = registry.statusUpdate(appName, id, InstanceStatus.UNKNOWN, String.valueOf(System.currentTimeMillis()), isReplication);
|
||||
if (b3){
|
||||
return ServerResponse.successMsg("设置服务状态为UNKNOWN");
|
||||
}
|
||||
break;
|
||||
case "STARTING" :
|
||||
boolean b4 = registry.statusUpdate(appName, id, InstanceStatus.STARTING, String.valueOf(System.currentTimeMillis()), isReplication);
|
||||
if (b4){
|
||||
return ServerResponse.successMsg("设置服务为正在启动中");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ServerResponse.errorMsg("请传入正确的状态");
|
||||
}
|
||||
return ServerResponse.errorMsg("修改状态失败");
|
||||
}
|
||||
|
||||
private PeerAwareInstanceRegistry getRegistry() {
|
||||
@@ -198,23 +179,8 @@ public class RestfulEurekaController {
|
||||
return EurekaServerContextHolder.getInstance().getServerContext();
|
||||
}
|
||||
|
||||
private void populateNavbar(HttpServletRequest request, Map<String, Object> model) {
|
||||
Map<String, String> replicas = new LinkedHashMap<>();
|
||||
List<PeerEurekaNode> list = getServerContext().getPeerEurekaNodes().getPeerNodesView();
|
||||
for (PeerEurekaNode node : list) {
|
||||
try {
|
||||
URI uri = new URI(node.getServiceUrl());
|
||||
String href = scrubBasicAuth(node.getServiceUrl());
|
||||
replicas.put(uri.getHost(), href);
|
||||
} catch (Exception ex) {
|
||||
// ignore?
|
||||
}
|
||||
}
|
||||
model.put("replicas", replicas.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 填所有实例的简单信息
|
||||
* 填充所有实例的简单信息
|
||||
* @param model
|
||||
*/
|
||||
private void populateApps(Map<String, Object> model) {
|
||||
@@ -289,57 +255,5 @@ public class RestfulEurekaController {
|
||||
}
|
||||
model.put("apps", apps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充当前注册中心的信息
|
||||
* @param model
|
||||
* @param statusInfo
|
||||
*/
|
||||
private void populateInstanceInfo(Map<String, Object> model, StatusInfo statusInfo) {
|
||||
InstanceInfo instanceInfo = statusInfo.getInstanceInfo();
|
||||
Map<String, String> instanceMap = new HashMap<>();
|
||||
instanceMap.put("ipAddr", instanceInfo.getIPAddr());
|
||||
instanceMap.put("status", instanceInfo.getStatus().toString());
|
||||
if (instanceInfo.getDataCenterInfo().getName() == DataCenterInfo.Name.Amazon) {
|
||||
AmazonInfo info = (AmazonInfo) instanceInfo.getDataCenterInfo();
|
||||
instanceMap.put("availability-zone",
|
||||
info.get(AmazonInfo.MetaDataKey.availabilityZone));
|
||||
instanceMap.put("public-ipv4", info.get(AmazonInfo.MetaDataKey.publicIpv4));
|
||||
instanceMap.put("instance-id", info.get(AmazonInfo.MetaDataKey.instanceId));
|
||||
instanceMap.put("public-hostname",
|
||||
info.get(AmazonInfo.MetaDataKey.publicHostname));
|
||||
instanceMap.put("ami-id", info.get(AmazonInfo.MetaDataKey.amiId));
|
||||
instanceMap.put("instance-type",
|
||||
info.get(AmazonInfo.MetaDataKey.instanceType));
|
||||
}
|
||||
model.put("instanceInfo", instanceMap);
|
||||
}
|
||||
|
||||
protected void filterReplicas(Map<String, Object> model, StatusInfo statusInfo) {
|
||||
Map<String, String> applicationStats = statusInfo.getApplicationStats();
|
||||
if (applicationStats.get("registered-replicas").contains("@")) {
|
||||
applicationStats.put("registered-replicas", scrubBasicAuth(applicationStats.get("registered-replicas")));
|
||||
}
|
||||
if (applicationStats.get("unavailable-replicas").contains("@")) {
|
||||
applicationStats.put("unavailable-replicas", scrubBasicAuth(applicationStats.get("unavailable-replicas")));
|
||||
}
|
||||
if (applicationStats.get("available-replicas").contains("@")) {
|
||||
applicationStats.put("available-replicas", scrubBasicAuth(applicationStats.get("available-replicas")));
|
||||
}
|
||||
model.put("applicationStats", applicationStats);
|
||||
}
|
||||
|
||||
private String scrubBasicAuth(String urlList) {
|
||||
String[] urls = urlList.split(",");
|
||||
String filteredUrls = "";
|
||||
for (String u : urls) {
|
||||
if (u.contains("@")) {
|
||||
filteredUrls += u.substring(0, u.indexOf("//") + 2) + u.substring(u.indexOf("@") + 1, u.length()) + ",";
|
||||
} else {
|
||||
filteredUrls += u + ",";
|
||||
}
|
||||
}
|
||||
return filteredUrls.substring(0, filteredUrls.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user