一、搭建服务注册中心
1、搭建服务注册中心
当服务实例启动的时候,它会按照名称将自己注册到Eureka中。服务的名称为“some-service”。“some-service”可能会有多个完全等价的实例,但是在Eureka注册时,它们的名称是相同的。
在某个时间点,另一个服务(other-service)需要使用“some-service”的端点。“other-service”没有使用特定的主机和端口信息对“some-service”进行硬编码,而是根据名字从Eureka查找“some-service”。Eureka的回应中将会包含它所知道的“some-service”的所有实例。
现在other-service服务需要做决定。 它将使用some-service哪个实例? 如果它们都是等价的,那么这并不重要。 但是为了避免每次选择任何同一个实例,最好用一些客户端负载平衡算法来分散请求。 这就是另一个Netflix
项目——Ribbon
。
虽然other-service完全可以自行查找和选择“some-service”的实例,但在这里使用依赖Ribbon。
2)使用客户端负载均衡器
引入依赖:带有Eureka Server标签的复选框
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<properties>
<!-- -->
...
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
启用Eureka服务器,在应用的主引导类添加@EnableEurekaServer
,如:
@EnableEurekaServer
@SpringBootApplication
public class LearndemoApplication {
public static void main(String[] args) {
SpringApplication.run(LearndemoApplication.class, args);
}
}
此时启动应用,Eureka服务注册中心就会运行起来并监听8080.
在http://localhost:8080,,就可以看到web页面。但此时,在eureka日志中,每隔大约30秒就会打印一些异常。
需要配置Eureka。
2、配置Eureka
Eureka默认行为是与其他Eureka服务器建立关联。
可以通过配置,使其当前的接受一个的状态。
eureka:
instance:
hostname: localhost # (可选属性)如果不指定,Eureka会尝试通过环境变量确定它的主机。
# 如果指定了,会告诉Eureka它正运行在哪个主机(host)上。
client:
fetch-registry: false # 默认值是true
register-with-eureka: false # 默认值是true,
# true表明从其他的Eureka实例获取注册信息,并应该将自身注册为其他Eureka服务器中的服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
1)指定Eureka的服务器端口
server:
port: 8761
application.yml:
server:
port: 8761 # Eureka默认监听的端口
eureka:
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
2)禁用自我保护模式
在开发环境可以禁用自我保护模式,但是在投入生产环境时需要将其启用。
server:
enable-self-preservation: false
application.yml:
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
server:
enable-self-preservation: false # Eureka希望服务实例能够注册上来,并且每隔30秒向它发送一次注册更新的请求,
# 如果Eureka在3个更新周期(90秒)内没有收到服务的更新请求,就会将改服务注销。
# Eureka假设出现了网络问题,今日自我保护模式,所以不会注销服务实例。
3、扩展Eureka
在开发环境中,每个Eureka实例会更加便利;但在将应用投入生产环境时,我们可能至少需要两个Eureka实例,以实现高可用性。
1)生产环境可用的Spring Cloud Services
配置2个或以上Eureka实例最简单直接的方式是在application.yml中使用Spring profile
,然后针对两个profile
各启动一次。
或者在application.propreties定义。
①application.yml方式:
application.yml:
eureka:
client:
service-url:
defaultZone: http://${other.eureka.host}:${other.eureka.port}/eureka
# 是否将自己注册到Eureka Server上,默认为true
# register-with-eureka: false
#是否从Eureka Server上获取注册信息,默认为true
# fetch-registry: false
---
spring:
profiles: eureka-1
application:
name: eureka-server
server:
port: 8761
eureka:
instance:
hostname: eureka1.learneurekademo.com
other:
eureka:
host: eureka2.learneurekademo.com
port: 8762
---
spring:
profiles: eureka-2
application:
name: eureka-server
server:
port: 8762
eureka:
instance:
hostname: eureka2.learneurekademo.com
other:
eureka:
host: eureka1.learneurekademo.com
port: 8761
注:上面面这个配置,先启动的会报错,后启动不会。
在命令行执行:
java -jar demoeureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-1
java -jar demoeureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-2
访问:
http://eureka1.learneurekademo.com:8761/
http://eureka2.learneurekademo.com:8762/
②application.properties
新建application-eureka1.properties文件,新建application-eureka2.properties 文件。
配置项与application.yml中一样,以properties语法格式写即可。
然后打包jar,分别在终端执行:
java -jar demoeureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-1
java -jar demoeureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-2
二、注册和发现服务
1、配置Eureka客户端属性
1)引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
springcloud版本属性:
<properties>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
2)配置Eureka属性
server:
port: 0
将端口设置为0将导致应用程序在随机选择的可用端口上启动。
eureka:
client:
service-url:
defaultZone: http://eureka1.learneurekademo.com:8761/eureka/
最好使用两个或以上的Eureka实例,如:
eureka:
client:
service-url:
defaultZone: http://eureka1.learneurekademo.com:8761/eureka/
http://eureka2.learneurekademo.com:8762/eureka/
当服务启动的时候,会尝试使用zone中的第一个服务器进行注册,如果因为某种原因失败,会使用列表中的下一个服务器来进行注册。最终,如果出现故障的服务器重新恢复在线状态,它将会从对等的端上复制注册信息,这样就能将该服务的注册条目包含进来。
2、消费服务
有两种方式可以消费从Eureka中查找到的服务:
支持负载均衡的RestTemplate;
Feign生成的客户端接口。
1)使用RestTemplate消费服务
public Ingredient getIngredientById(String ingredientId) {
return rest.getForObject("http://localhost:8080/ingredients/{id}", Ingredient.class, ingredientId);
}
上面getForObject()的URL硬编码了特定的主机和端口。就算把这个提取到一个属性,如果配置的URL始终指向同一个特定实例,这样就没有负载均衡器请求分散到多个服务实例中了。
可以将应用变成Eureka客户端,就可以声明支持负载均衡的RestTemplate bean了。
①声明一个常规的RestTemplate bean;
②并为带有@Bean注解的方法在添上@LoadBalanced
如:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced注解,会告诉SpringCloud这个RestTemplate要能够通过Ribbon来查找服务。其次,会作为一个注入限定符,所有有两个或以上RestTemplate bean的话,可以在注入的地方声明此处想要支持负载均衡的RestTemplate。
如:
@Component
public class IngredientServiceClient {
private RestTemplate rest;
public IngredientServiceClient(@LoadBalanced RestTemplate rest) {
this.rest = rest;
}
// ...
}
修改上面的getIngredientById()方法,使用注册服务的注册名,而不再明确使用主机和端口:
public Ingredient getIngredientById(String ingredientId) {
return rest.getForObject(
"http://ingredient-service/ingredients/{id}",
Ingredient.class, ingredientId);
}
这里使用了服务名ingredient-service,RestTemplate会让Ribbon根据名称查找服务并从中选择一个实例。
2)使用WebClient消费服务
与RestTemplate类似的方式将WebClient作为支持负载均衡的客户端。
声明返回一个WebClient.Builder bean 的方法,方法要添加@LoadBalanced注解。
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
声明完之后,可以将WebClient.Builder 注入到任何需要它的地方。
如:
@Component
public class IngredientServiceClient {
private WebClient.Builder wcBuilder;
public IngredientServiceClient(@LoadBalanced WebClient.Builder webclientBuilder wcBuilder) {
this.wcBuilder = wcBuilder;
}
// ...
}
最后在需要使用它的时候,可以利用WebClient.Builde 构建一个WebClient,然后就能够使用Eureka注册的服务名来发送请求了。如:
public Mono<Ingredient> getIngredientById(String ingredientId) {
return wcBuilder.build()
.get()
.uri("http://ingredient-service/ingredients/{id}", ingredientId)
.retrieve().bodyToMono(Ingredient.class);
}
3)定义Feign客户端接口
Feign是一个REST客户端库,使用一种特殊的、接口驱动的方式来定义REST客户端。
①引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
②将@EnableFeignClients
添加到某个配置类上:
@Configuration
@EnableFeignClients
public RestClientConfiguration {
}
如,想通过注册在Eureka中名为ingredient-service的服务获取一个Ingredient,需要做的是定义以下接口:
package demo.ingredientclient.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import tacos.ingredientclient.Ingredient;
@FeignClient("ingredient-service")
public interface IngredientClient {
@GetMapping("/ingredients/{id}")
Ingredient getIngredient(@PathVariable("id") String id);
}
在运行期,当Feign发现它的时候,Feign会创建一个实现类并将其暴露为Spring应用上下文中的bean。
接口上的@FeignClient注解会制定该接口上的所有方法都会对名为ingredient-service的服务发送请求。在内部,服务将会通过Ribbon进行查找,这与支持负载均衡的RestTemplate运行方式是一样的。
getIngredient()方法的@GetMapping注解来自SpringMVC。@PathVariable也是来自SpringMVC。
接下来,就是讲Feign实现的接口注入需要的地方,并使用它。如:
@Controller
@RequestMapping("/ingredients")
public class IngredientController {
private IngredientClient client;
@Autowired
public IngredientController(IngredientClient client) {
this.client = client;
}
@GetMapping("/{id}")
public String ingredientDetailPage(@PathVariable("id") String id, Model model) {
model.addAttribute("ingredient", client.getIngredient(id));
return "ingredientDetail";
}
}