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

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.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);
}
}