搭建服务提供方集群
将服务注册至eureka集群中,文中用到了http://euk1.com,是本地服务,需要修改hosts文件
127.0.0.1 euk1.com
127.0.0.1 euk2.com
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
-
application.properties
spring.profiles.active=provider1 spring.application.name=provider eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/ #自定义节点信息,可以根据节点信息来判断服务器,做个性化需求 eureka.instance.metadata-map.info=provider #Actuator 开启哪些端点,为* 表示端点全部开启 management.endpoints.web.exposure.include=* #Actuator 可以远程关闭服务节点,方便运维人员 management.endpoint.shutdown.enabled=true # 每隔这个时间会主动心跳一次,默认值为30s,更新自己的状态。Eureka Server收到心跳后,会通知集群里的其它Eureka Server更新此实例的状态。 eureka.instance.lease-renewal-interval-in-seconds = 5 #服务剔除需要在Eureka Server端把自我保护关闭掉,注意是Server端关闭,不是client端 #erueka.server.enable-self-preservation=false #设置服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除 eureka.instance.lease-expiration-duration-in-seconds = 10 #可以上报服务的真实健康状态 eureka.client.healthcheck.enabled=true
-
application-provider1.properties
server.port=81
-
application-provider2.properties
server.port=82
MainController
随意写两个方法来测试服务是否注册成功
package com.ls.eureka.eurekaprovider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.Map;
@RestController
public class MainController {
@Value("${server.port}")
String port;
@GetMapping("/getHi")
public String getHi() {
return "Hi!,我的port:" + port;
}
}
启动项目
使用 http://localhost:81/getHi,http://localhost:82/getHi能正常访问服务。
访问 eureka服务端 http://localhost:7001/ 或者 http://localhost:7002/ ,provider项目作为服务提供方已经注册在eureka了
Rest服务调用
可以使用Rest API 去获取注册在eureka的服务(https://github.com/Netflix/eureka/wiki/Eureka-REST-operations)
注册到eureka的服务信息查看
http://localhost:7001/eureka/apps
get: {ip:port}/eureka/apps
注册到eureka的具体的服务查看
http://localhost:7002/eureka/apps/PROVIDER/WIN-2BA4UGIGEK3:provider:81
get: {ip:port}/eureka/apps/{appname}/{id}
服务续约 put:{ip:port}/eureka/apps/{appname}/{id}?lastDirtyTimestamp={}&status=up
更改服务状态 put:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}
删除状态更新 delete:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}
删除服务 delete: {ip:port}/eureka/apps/{appname}/{id}
Spring Boot Actuator:健康检查、审计、统计和监控
SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、环境变量、日志信息、线程信息等(这有一篇介绍文章spring boot actuator监控详细介绍一(超级详细))
由于server和client通过心跳保持 服务状态,而只有状态为UP的服务才能被访问。看eureka界面中的status。http://localhost:7001/eureka/status
比如心跳一直正常,服务一直UP,但是此服务DB连不上了,无法正常提供服务。
此时,我们需要将微服务的健康状态提交到server端。需要启动eureka的健康检查,这样微服务就会将自己的健康状态同步到server端。
开启手动控制
pom.xml
<!-- Spring Boot Actuator:健康检查、审计、统计和监控。用于上报节点信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.properties
## 可以上报服务的真实健康状态
eureka.client.healthcheck.enabled=true
#Actuator 开启哪些端点,为* 表示端点全部开启
management.endpoints.web.exposure.include=*
#Actuator 可以远程关闭服务节点,方便运维人员
management.endpoint.shutdown.enabled=true
改变健康状态的Service
@Service
public class HealthStatusService implements HealthIndicator {
private Boolean status = true;
public void setStatus(Boolean status) {
this.status = status;
}
@Override
public Health health() {
// TODO Auto-generated method stub
if(status)
return new Health.Builder().up().build();
return new Health.Builder().down().build();
}
public String getStatus() {
// TODO Auto-generated method stub
return this.status.toString();
}
}
测试用的Controller
@GetMapping("/health")
public String health(@RequestParam("status") Boolean status) {
healthStatusSrv.setStatus(status);
return healthStatusSrv.getStatus();
}
测试修改服务状态
http://localhost:81/health?status=false
provider中看到日志
Saw local status change event StatusChangeEvent [timestamp=1605522480678, current=DOWN, previous=UP]
Eureka Server中看到日志
Registered instance PROVIDER/WIN-2BA4UGIGEK3:provider:81 with status DOWN (replication=true)
此时服务列表任然显示provider服务,不会将服务剔除,只会标注为服务下线
http://localhost:81/health?status=true 会将服务重新上线
安全配置
开启Eureka安全连接
application.properties
如果没有配置账号密码,则会再启动的时候输出一串随机密码,打印在控制台,
默认账号是user
Using generated security password: 178c5cf8-7f2a-4b38-a25b-6aed7a11c77d
开启之后,访问eureka服务就需要输入账号密码才行,且注册的时候需要将账号密码也加载链接上
如下:
eureka.client.service-url.defaultZone=http://ls:123@euk2.com:7002/eureka/
spring.security.user.name=ls
spring.security.user.password=123
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
关闭防止跨域攻击
spring.security带有防止跨域攻击的模块,需要手动关闭
javax.ws.rs.WebApplicationException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'timestamp' does not match expected ('instance') for type [simple type, class com.netflix.appinfo.InstanceInfo]
手动关闭
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.csrf().disable();
super.configure(http);
}
}
在服务端增加配置类
服务使用方
从服务端获取可以使用的服务列表,进行调用
配置文件
server.port=83
spring.application.name=consumer
eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/
#自定义节点信息
eureka.instance.metadata-map.info=consumer
#可以远程关闭服务节点
management.endpoint.shutdown.enabled=true
#可以上报服务的真实健康状态
#eureka.client.healthcheck.enabled=true
MainController
package com.ls.eureka;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class MainController {
@Value("${server.port}")
String port;
@Autowired
DiscoveryClient discoveryClient;
@Autowired
LoadBalancerClient loadBalancerClient;
@GetMapping("/getHi")
public String getHi() {
return "Hi!,我的port:" + port;
}
/**
* 获取服务列表
* @return
*/
@GetMapping("/getServices")
public String getServices() {
List<String> services = discoveryClient.getServices();
return services.toString();
}
/**
* 根据服务名称获取服务列表
* @return
*/
@GetMapping("/getInstances")
public Object getInstances() {
return discoveryClient.getInstances("provider");
}
/**
* 根据服务名称获取服务列表,测试服务访问
* @return
*/
@GetMapping("/getInstancesList")
public String getInstancesList() {
//discoveryClient Spring cloud 的client只会返回Up状态下的实例
List<ServiceInstance> instancesList = discoveryClient.getInstances("provider");
System.out.println("instancesList.size:==========="+instancesList.size());
for (ServiceInstance instance : instancesList) {
System.out.println(instance.toString());
}
if(instancesList.size() > 0){
ServiceInstance instance = instancesList.get(0);
String url = instance.getUri().toString()+"/getHi";
System.out.println(url);
RestTemplate restTemplate = new RestTemplate();
String respStr = restTemplate.getForObject(url, String.class);
System.out.println(respStr);
return respStr;
}
return "";
}
/**
* 根据服务名称获取服务列表,使用Ribbon做负载均衡策略 测试服务访问
* @return
*/
@GetMapping("/getInstancesListByLb")
public String getInstancesListByLb() {
//discoveryClient Spring cloud 的client只会返回Up状态下的实例
List<ServiceInstance> instancesList = discoveryClient.getInstances("provider");
ServiceInstance instance = loadBalancerClient.choose("provider");
String url = instance.getUri().toString()+"/getHi";
System.out.println(url);
RestTemplate restTemplate = new RestTemplate();
String respStr = restTemplate.getForObject(url, String.class);
System.out.println(respStr);
return respStr;
}
}
getInstancesListByLb
使用了Ribbon作为负载均衡,会根据负载策略将请求分发到不同的provider上。