安全下线服务,调整服务状态接口编写

This commit is contained in:
winLsy
2018-02-08 21:45:53 +08:00
parent c57a2cf2fa
commit c70ced8fee
@@ -2,33 +2,42 @@ package com.micro.fast.eureka.server.controller;
import com.micro.fast.boot.starter.common.response.ServerResponse; import com.micro.fast.boot.starter.common.response.ServerResponse;
import com.netflix.appinfo.AmazonInfo; import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.InstanceInfo; 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.Application;
import com.netflix.discovery.shared.Pair; import com.netflix.discovery.shared.Pair;
import com.netflix.eureka.EurekaServerContext; import com.netflix.eureka.EurekaServerContext;
import com.netflix.eureka.EurekaServerContextHolder; import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.cluster.PeerEurekaNode;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry; 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.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.util.*; import java.util.*;
/** /**
* restful操作eureka的接口 * 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 * @author lsy
*/ */
@RestController @RestController
@@ -36,65 +45,46 @@ import java.util.*;
@Api(description = "注册中心的restful接口") @Api(description = "注册中心的restful接口")
public class RestfulEurekaController { 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") @GetMapping("/apps")
@ApiOperation("获取所有的服务实例的简单信息") @ApiOperation("获取所有的服务实例的简单信息")
public ServerResponse apps(HttpServletRequest request, HttpServletResponse response) { public ServerResponse apps() {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
//获取所有服务的信息 //获取所有服务的信息
populateApps(map); populateApps(map);
return ServerResponse.successMsgData("获取所有实例成功", map); return ServerResponse.successMsgData("获取所有实例成功", map);
} }
@GetMapping("/apps/{instanceId}") @GetMapping("/apps/{id}")
@ApiOperation("根据域名获取某个实例的信息") @ApiOperation("根据id获取实例的信息,模糊查询")
public ServerResponse getInstance(@PathVariable("instanceId") String instanceId) { public ServerResponse getInstance(@PathVariable("id") String instanceId) {
InstanceInfo instanceInfo = null; Set<InstanceInfo> instanceInfos = new HashSet<>(10);
if (StringUtils.isNotBlank(instanceId)){ if (StringUtils.isNotBlank(instanceId)){
List<Application> sortedApplications = getRegistry().getSortedApplications(); List<Application> sortedApplications = getRegistry().getSortedApplications();
boolean isBreak = false; boolean isBreak = false;
for (Application sortedApplication : sortedApplications) { for (Application sortedApplication : sortedApplications) {
List<InstanceInfo> instances = sortedApplication.getInstances(); List<InstanceInfo> instances = sortedApplication.getInstances();
for (InstanceInfo instance : instances) { for (InstanceInfo instance : instances) {
String hostName = instance.getHostName(); if (instance.getId().contains(instanceId)){
if (hostName.startsWith(instanceId)){ instanceInfos.add(instance);
instanceInfo = instance;
isBreak = true;
break;
} }
} }
if (isBreak){
break;
}
} }
}else { }else {
return ServerResponse.errorMsg("instanceId不能为空"); return ServerResponse.errorMsg("instanceId不能为空");
} }
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfos);
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfo);
} }
@GetMapping("/apps/{appId}")
@ApiOperation("根据appId查询实例,可能有多个") @GetMapping("/apps/cluster/{appName}")
public ServerResponse getInstancesByAppId(@PathVariable("appId") String appId) { @ApiOperation("根据appId,也就是application.name查询实例,可能有多个")
public ServerResponse getInstancesByAppId(@PathVariable("appName") String appName) {
Set<InstanceInfo> instanceInfos = new HashSet<>(10); Set<InstanceInfo> instanceInfos = new HashSet<>(10);
if (StringUtils.isNotBlank(appId)){ if (StringUtils.isNotBlank(appName)){
List<Application> sortedApplications = getRegistry().getSortedApplications(); List<Application> sortedApplications = getRegistry().getSortedApplications();
for (Application sortedApplication : sortedApplications) { for (Application sortedApplication : sortedApplications) {
List<InstanceInfo> instances = sortedApplication.getInstances(); List<InstanceInfo> instances = sortedApplication.getInstances();
for (InstanceInfo instance : instances) { for (InstanceInfo instance : instances) {
if (appId.equals(instance.getAppName())){ if (StringUtils.equalsIgnoreCase(instance.getAppName(),appName)){
instanceInfos.add(instance); instanceInfos.add(instance);
} }
} }
@@ -103,91 +93,82 @@ public class RestfulEurekaController {
}else { }else {
return ServerResponse.errorMsg("instanceId不能为空"); return ServerResponse.errorMsg("instanceId不能为空");
} }
return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfos); return ServerResponse.successMsgData("根据主机名获取实例信息成功", instanceInfos);
} }
@GetMapping @GetMapping("/apps/app/{appName}/{id}")
@ResponseBody @ApiOperation("根据appName和id获取服务的信息")
public Map<String, Object> status(HttpServletRequest request, HttpServletResponse response) { public ServerResponse getApplication(@PathVariable("appName") String appName,@PathVariable("id") String id){
Map<String, Object> map = new HashMap<>(); PeerAwareInstanceRegistry registry = getRegistry();
populateBase(request, map); InstanceInfo instanceByAppAndId = registry.getInstanceByAppAndId(appName, id);
populateApps(map); return ServerResponse.successData(instanceByAppAndId);
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;
} }
/** /**
* 获取最后注册的实例 * 主动下线服务,并传递到其他注册中心
* @param request * 此接口主要用于下线异常终止的服务
* @param appName
* @param id
* @return * @return
*/ */
@GetMapping("/lastn") @DeleteMapping("/{appName}/{id}/{isReplication}")
public Map<String, Object> lastn(HttpServletRequest request) { @ApiOperation("下线服务")
Map<String, Object> map = new HashMap<>(); public ServerResponse unregister(@PathVariable("appName") String appName,@PathVariable("id") String id,@PathVariable("isReplication") Boolean isReplication){
populateBase(request, map); PeerAwareInstanceRegistry registry = getRegistry();
PeerAwareInstanceRegistryImpl registry = (PeerAwareInstanceRegistryImpl) getRegistry(); // 当isReplication是true的时候表示这个实例的信息是从别的eureka复制过来的,eureka不会进行二次传递
ArrayList<Map<String, Object>> lastNCanceled = new ArrayList<>(); boolean cancel = registry.cancel(appName, id, isReplication);
List<Pair<Long, String>> list = registry.getLastNCanceledInstances(); if (cancel){
for (Pair<Long, String> entry : list) { return ServerResponse.successMsg("下线服务成功,服务会在下次心跳时注册");
lastNCanceled.add(registeredInstance(entry.second(), entry.first()));
} }
map.put("lastNCanceled", lastNCanceled); return ServerResponse.successMsg("下线服务失败");
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);
} }
/** /**
* 添加注册中心基础信息 * 调整服务的状态,可以用于在无感知的情况下热部署服务
* * @param appName
* @param model * @param id
* @param status
* @return
*/ */
private void populateHeader(Map<String, Object> model) { @PutMapping("/{appName}/{id}/{status}/{isReplication}")
model.put("currentTime", StatusResource.getCurrentTimeAsString()); @ApiOperation("调整服务状态")
model.put("upTime", StatusInfo.getUpTime()); public ServerResponse updateStatus(@PathVariable("appName") String appName,@PathVariable("id")String id,@PathVariable("status")String status,@PathVariable("isReplication")Boolean isReplication){
model.put("environment", ConfigurationManager.getDeploymentContext()
.getDeploymentEnvironment());
model.put("datacenter", ConfigurationManager.getDeploymentContext()
.getDeploymentDatacenter());
PeerAwareInstanceRegistry registry = getRegistry(); PeerAwareInstanceRegistry registry = getRegistry();
model.put("registry", registry); switch (status){
model.put("isBelowRenewThresold", registry.isBelowRenewThresold() == 1); case "OUT_OF_SERVICE":
DataCenterInfo info = applicationInfoManager.getInfo().getDataCenterInfo(); boolean b = registry.statusUpdate(appName, id, InstanceStatus.OUT_OF_SERVICE, String.valueOf(System.currentTimeMillis()), isReplication);
if (info.getName() == DataCenterInfo.Name.Amazon) { if (b){
AmazonInfo amazonInfo = (AmazonInfo) info; return ServerResponse.successMsg("下线服务成功");
model.put("amazonInfo", amazonInfo); }
model.put("amiId", amazonInfo.get(AmazonInfo.MetaDataKey.amiId)); break;
model.put("availabilityZone", case "DOWN":
amazonInfo.get(AmazonInfo.MetaDataKey.availabilityZone)); boolean b1 = registry.statusUpdate(appName, id, InstanceStatus.DOWN, String.valueOf(System.currentTimeMillis()), isReplication);
model.put("instanceId", amazonInfo.get(AmazonInfo.MetaDataKey.instanceId)); 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() { private PeerAwareInstanceRegistry getRegistry() {
@@ -198,23 +179,8 @@ public class RestfulEurekaController {
return EurekaServerContextHolder.getInstance().getServerContext(); 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 * @param model
*/ */
private void populateApps(Map<String, Object> model) { private void populateApps(Map<String, Object> model) {
@@ -289,57 +255,5 @@ public class RestfulEurekaController {
} }
model.put("apps", apps); 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);
}
} }