前言
之前给原生Servlet应用集成过Eureka,现在想想加入了多个依赖让应用更加“胖”!便想着Eureka有没有开放的协议,我直接实现原生的Eureka协议实现Eureka的注册和心跳。我们先看看Eureka的Github研究一下源码。
Eureka开放协议:
Eureka REST operations · Netflix/eureka Wiki · GitHub
简单解释:其他非Java应用可以通过这些RestApi,进行Eureka服务支持。
实操
服务规划
服务名 | 说明 |
EUREKA-SERVER | 原生EurekaServer |
EUREKA-CLIENT | 原生EurekaClient |
Eureka-Cleint-Api | 通过开放协议实现的Eureka协议 |
Server代码
/**
* 启动注册中心
*/
@SpringBootApplication
@EnableEurekaServer
@ServletComponentScan
public class RegistrationCenterApplication {
public static void main(String[] args) {
SpringApplication.run(RegistrationCenterApplication.class, args);
}
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> rg = new FilterRegistrationBean<>();
rg.setFilter(new BodyLogFilter());
rg.addUrlPatterns("/eureka/*");
rg.addServletRegistrationBeans();
return rg;
}
}
package org.example.listener;
import com.netflix.appinfo.InstanceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaServerStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 监听SpringEvent 获取EurekaServer 发生的事情
*/
@Component
public class ClientChangeListener {
private final static Logger log = LoggerFactory.getLogger(ClientChangeListener.class);
@EventListener
public void registerListen(EurekaInstanceRegisteredEvent event) {
InstanceInfo instanceInfo = event.getInstanceInfo();
StringBuilder sb = new StringBuilder();
sb.append("服务名=").append(instanceInfo.getAppName()).append("服务的host名=").
append(instanceInfo.getHostName()).append("服务的ip地址=").
append(instanceInfo.getIPAddr()).append("服务的端口=").append(instanceInfo.getPort()).append("服务ID=").
append(instanceInfo.getInstanceId());
log.info(sb.toString());
}
@EventListener
public void canceledListen(EurekaInstanceCanceledEvent event) {
log.info("服务名={}下线了", event.getAppName());
log.info("server地址信息{}", event.getServerId());
}
@EventListener
public void renewedListen(EurekaInstanceRenewedEvent event) {
log.info("服务名={}进行续约", event.getServerId() +" "+ event.getAppName());
}
@EventListener
public void listen(EurekaRegistryAvailableEvent event) {
log.info("注册中心启动,{}", System.currentTimeMillis());
}
@EventListener
public void listen(EurekaServerStartedEvent event) {
log.info("注册中心服务端启动,{}", System.currentTimeMillis());
}
}
//打印请求参数官方没有给Json示例自己写太麻烦等下发布直接Copy Eureka-Client的注册参数
package example.filter;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
try (InputStream inputStream = request.getInputStream()) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
body = byteArrayOutputStream.toByteArray();
}
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
public byte[] getBody() {
return this.body;
}
}
package example.filter;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
public class ResponseWrapper extends HttpServletResponseWrapper {
/**
* This class implements an output stream in which the data is written into a byte array.
* The buffer automatically grows as data is written to it. The data can be retrieved using toByteArray() and toString().
Closing a ByteArrayOutputStream has no effect. The methods in this class can be called after the stream has been closed without generating an IOException.
*/
private ByteArrayOutputStream buffer = null;//输出到byte array
private ServletOutputStream out = null;
private PrintWriter writer = null;
public ResponseWrapper(HttpServletResponse resp) throws IOException {
super(resp);
buffer = new ByteArrayOutputStream();// 真正存储数据的流
out = new WapperedOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding()));
}
/** 重载父类获取outputstream的方法 */
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
/** 重载父类获取writer的方法 */
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
}
/** 重载父类获取flushBuffer的方法 */
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
if (writer != null) {
writer.flush();
}
}
@Override
public void reset() {
buffer.reset();
}
/** 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 */
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
/** 内部类,对ServletOutputStream进行包装 */
private class WapperedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = null;
public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException {
bos = stream;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public void write(byte[] b) throws IOException {
bos.write(b, 0, b.length);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {
}
}
}
package example.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(filterName = "bodyLogFilter" )
public class BodyLogFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
String contentType = servletRequest.getContentType();
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String url = request.getRequestURI();
String method = request.getMethod();
if (contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
log.info("接口 {} 文件上传 不过滤", url);
filterChain.doFilter(servletRequest, servletResponse);
} else if (HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) ) {
RequestWrapper requestWrapper = new RequestWrapper(request);
String body = new String(requestWrapper.getBody(), servletRequest.getCharacterEncoding());
log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, body);
filterChain.doFilter(requestWrapper,
new ResponseWrapper((HttpServletResponse) servletResponse));
} else if (HttpMethod.GET.matches(method)) {
String queryString = request.getQueryString();
log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, queryString);
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
Eureka-Client-Api
核心API实现
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@RequestMapping("eureka")
@Slf4j
public class EurekaClientApi {
static Map<String,String> headers = new LinkedHashMap<>();
static {
headers.put("ACCEPT","application/json");
}
@Autowired
private HttpClientUtil httpClientUtil;
@Value("${eurekaServerAddress}")
private String URL ;
/**
* 获取某一个服务的全部实例
* @param applictionName
* @return
*/
@GetMapping("getAppInfoByName")
public String getAppInfoByName(@RequestParam("name") String applictionName){
String url = URL +"/apps/"+applictionName ;
//127.0.0.1:8081/eureka/getAppInfoByName?name=EUREKA-CLIENT
return httpClientUtil.doGet(url,null ,headers) ;
}
/**
* 注销xx应用
* @param name
* @param id
* @return
*/
@GetMapping("deleteAppByNameAndId")
public String deleteAppByNameAndId(@RequestParam("name") String name ,@RequestParam("id")String id){
String url = URL +"/apps/"+name+"/"+id ;
//127.0.0.1:8081/eureka/deleteAppByNameAndId?name=EUREKA-CLIENT&id=192.168.2.106:EUREKA-CLIENT:8080
return httpClientUtil.delete(url,headers) ;
}
/**
* 发送心心跳
* @param name
* @param id
* @return
*/
@GetMapping("heartBeat")
public String heartBeat(@RequestParam("name") String name ,@RequestParam("id")String id){
String url = URL +"/apps/"+name+"/"+id ;
//127.0.0.1:8081/eureka/heartBeat?name=EUREKA-CLIENT&id=192.168.2.106:EUREKA-CLIENT:8080
return httpClientUtil.put(url,headers);
}
/**
* 注册应用
* @return
*/
@PostMapping
public String registerApp( String s){
String url = URL +"/apps/"+"EUREKA-API-CLIENT";
Map<String,String> info = new LinkedHashMap<>();
/*
{
"instance": {
"instanceId": "192.168.2.106:EUREKA-API-CLIENT:8081",
"app": "EUREKA-API-CLIENT",
"appGroupName": null,
"ipAddr": "192.168.2.106",
"sid": "na",
"homePageUrl": "http://192.168.2.106:8081/",
"statusPageUrl": "http://192.168.2.106:8081/actuator/info",
"healthCheckUrl": "http://192.168.2.106:8081/actuator/health",
"secureHealthCheckUrl": null,
"vipAddress": "EUREKA-API-CLIENT",
"secureVipAddress": "EUREKA-API-CLIENT",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.2.106",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 0,
"lastRenewalTimestamp": 0,
"evictionTimestamp": 0,
"serviceUpTimestamp": 0
},
"isCoordinatingDiscoveryServer": false,
"lastUpdatedTimestamp": 1723301723465,
"lastDirtyTimestamp": 1723301725975,
"actionType": null,
"asgName": null,
"port": {
"$": 8081,
"@enabled": "true"
},
"securePort": {
"$": 443,
"@enabled": "false"
},
"metadata": {
"management.port": "8081"
}
}
}
*/
return httpClientUtil.doPost(url,info,headers);
}
}
通过开放协议获取EUREKA-CLIENT全部实例
2024-08-11 11:54:56.329 INFO 3556 --- [nio-8761-exec-6] example.filter.BodyLogFilter : 接口 /eureka/apps/EUREKA-CLIENT 请求方法 GET 过滤参数 null
通过开放协议注销EUREKA-CLIENT
2024-08-11 11:53:00.482 INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener : 服务名=EUREKA-CLIENT下线了
2024-08-11 11:53:00.482 INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener : server地址信息192.168.2.106:EUREKA-CLIENT:8080
2024-08-11 11:53:00.482 INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener : 服务名=EUREKA-CLIENT下线了
2024-08-11 11:53:00.482 INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener : server地址信息192.168.2.106:EUREKA-CLIENT:8080
2024-08-11 11:53:00.482 INFO 3556 --- [nio-8761-exec-8] c.n.e.registry.AbstractInstanceRegistry : Cancelled instance EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 (replication=false)
通过开放协议发EUREKA-CLIENT心跳
2024-08-11 11:57:38.788 INFO 3556 --- [nio-8761-exec-3] example.filter.BodyLogFilter : 接口 /eureka/apps/EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 请求方法 PUT 过滤参数
2024-08-11 11:57:38.788 INFO 3556 --- [nio-8761-exec-3] o.example.listener.ClientChangeListener : 服务名=192.168.2.106:EUREKA-CLIENT:8080 EUREKA-CLIENT进行续约
2024-08-11 11:57:39.541 INFO 3556 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 10ms
2024-08-11 11:57:40.744 INFO 3556 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 2ms
2024-08-11 11:57:41.230 INFO 3556 --- [nio-8761-exec-2] example.filter.BodyLogFilter : 接口 /eureka/apps/delta 请求方法 GET 过滤参数 null
2024-08-11 11:57:41.245 INFO 3556 --- [nio-8761-exec-4] example.filter.BodyLogFilter : 接口 /eureka/apps/EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 请求方法 PUT 过滤参数
2024-08-11 11:57:41.245 INFO 3556 --- [nio-8761-exec-4] o.example.listener.ClientChangeListener : 服务名=192.168.2.106:EUREKA-CLIENT:8080 EUREKA-CLIENT进行续约
通过开放协议注册EUREKA-CLIENT-API
//参数太多了写的麻烦直接API访问吧
2024-08-11 12:20:00.670 INFO 15052 --- [nio-8761-exec-2] example.filter.BodyLogFilter : 接口 /eureka/apps/EUREKA-API-CLIENT 请求方法 POST 过滤参数 {"instance":{
"instanceId":"192.168.2.106:EUREKA-API-CLIENT:8081",
"app":"EUREKA-API-CLIENT",
"appGroupName":null,
"ipAddr":"192.168.2.106",
"sid":"na",
"homePageUrl":"http://192.168.2.106:8081/",
"statusPageUrl":"http://192.168.2.106:8081/actuator/info",
"healthCheckUrl":"http://192.168.2.106:8081/actuator/health",
"secureHealthCheckUrl":null,"vipAddress":"EUREKA-API-CLIENT",
"secureVipAddress":"EUREKA-API-CLIENT","countryId":1,
"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},
"hostName":"192.168.2.106","status":"UP",
"overriddenStatus":"UNKNOWN",
"leaseInfo":{"renewalIntervalInSecs":30,
"durationInSecs":90,"registrationTimestamp":0,
"lastRenewalTimestamp":0,"evictionTimestamp":0,
"serviceUpTimestamp":0},
"isCoordinatingDiscoveryServer":false,
"lastUpdatedTimestamp":1723301723465,
"lastDirtyTimestamp":1723301725975,"actionType":null,
"asgName":null,"port":{"$":8081,"@enabled":"true"},
"securePort":{"$":443,"@enabled":"false"},
"metadata":{"management.port":"8081"}}}
2024-08-11 12:20:00.671 INFO 15052 --- [nio-8761-exec-2] o.example.listener.ClientChangeListener : 服务名=EUREKA-API-CLIENT服务的host名=192.168.2.106服务的ip地址=192.168.2.106服务的端口=8081服务ID=192.168.2.106:EUREKA-API-CLIENT:8081
2024-08-11 12:20:00.671 INFO 15052 --- [nio-8761-exec-2] c.n.e.registry.AbstractInstanceRegistry : Registered instance EUREKA-API-CLIENT/192.168.2.106:EUREKA-API-CLIENT:8081 with status UP (replication=false)
2024-08-11 12:20:01.456 I
Other
.......TODO ......
总结
原态Servlet应用与其他语言应用即可以通过当前Rest Api 实现EurekaClient。