在传统的单体应用中,所有的业务都集中在一个服务器中,当浏览器发起请求时,通过前端请求调用后端接口,后端接口调用相应的业务并在前端进行响应,整个的调用就是从请求到响应的一条龙服务。所以不存在服务之间的中转,也就不存在注册中心。
但是随着项目越做越大,传统的单体项目已经无法满足我们的需求(用户数量增加,业务功能增多,服务器压力变大),所以我们需要用微服务思想,对项目进行拆分,拆分后的每个模块都会再一个服务器上独立的运行。虽然解决了一些单体项目所带来的的诸多瓶颈,但是又有一个新的问题产生,就是模块与模块之间的调用,一个模块的使用可能需要依赖很多模块,例如A调用B,那么就要在A中写上B的地址,也就意味着B的部署位置要稳定,不能变,如果B要进行集群化部署,那么A就需要修改,所以耦合度是较高的。引入注册中心,它的存在就能解决了这种高耦合度。
组成简介
Eureka由两部分组成,服务端和客户端,服务端是注册中心,用来接收其他服务的注册,客户端是java客户端,用开注册,并实现负载均衡,其中客户端根据业务又划分两部分,服务提供者,和服务消费者。
eureka的使用
官方文档:Spring Cloud Netflix
先父项目中导入spring cloud依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
再在新创建的Spring boot模块添加eureka的server依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
需要在主类上添加@EnableEurekaServer的注解,标记它是一EurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
再进行配置
server:
port: 8888
eureka:
client:
# 由于我们是作为服务端角色,所以不需要获取服务端,改为false,默认为true
fetch-registry: false #表示是否从eureka Server 上获取注册信息
register-with-eureka: false #false表示不向注册中心注册自己。
# 将eureka服务端指向自己
service-url:
defaultZone: http://localhost:8888/eureka
现在可以启动了,启动完成后,直接输入地址+端口即可访问Eureka的管理后台
现在还没有任何的服务注册到Eureka
再创建一个Spring boot项目并导入Eureka的client依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改配置文件
spring:
application:
name: userserviceeureka:
client:
# 需要指向Eureka服务端地址,这样才能进行注册
service-url:
defaultZone: http://localhost:8888/eureka
无需在启动类添加注解,直接启动就可以了
服务启动之后,会每隔一段时间跟Eureka发送一次心跳包,这样Eureka就能够感知到我们的服务是否处于正常运行状态
服务的调用
进行服务远程调用我们需要用到RestTemplate
来进行,这里以图书服务为例
@Service
public class BorrowServiceImpl implements BorrowService{
@Resource
BorrowMapper mapper;
@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
List<Borrow> borrow = mapper.getBorrowsByUid(uid);
//RestTemplate支持多种方式的远程调用
RestTemplate template = new RestTemplate();
//这里通过调用getForObject来请求其他服务,并将结果自动进行封装
//获取User信息
User user = template.getForObject("http://localhost:8082/user/"+uid, User.class);
//获取每一本书的详细信息
List<Book> bookList = borrow
.stream()
.map(b -> template.getForObject("http://localhost:8080/book/"+b.getBid(), Book.class))
.collect(Collectors.toList());
return new UserBorrowDetail(user, bookList);
}
}
这个这个服务间的调用需要知道服务的地址,但引入并注册eureka之后就可以直接用服务名称替代了
@Service
public class BorrowServiceImpl implements BorrowService {
@Resource
BorrowMapper mapper;
@Resource
RestTemplate template;
@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
List<Borrow> borrow = mapper.getBorrowsByUid(uid);
//这里不用再写IP,直接写服务名称userservice
User user = template.getForObject("http://userservice/user/"+uid, User.class);
//这里不用再写IP,直接写服务名称bookservice
List<Book> bookList = borrow
.stream()
.map(b -> template.getForObject("http://bookservice/book/"+b.getBid(), Book.class))
.collect(Collectors.toList());
return new UserBorrowDetail(user, bookList);
}
}
接着手动将RestTemplate声明为一个Bean,然后添加@LoadBalanced
注解,这样Eureka就会对服务的调用进行自动发现,并提供负载均衡
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
RestTemplate template(){
return new RestTemplate();
}
}
负载均衡
同一个服务器实际上是可以注册多个服务实例,但是它们的端口不同,请求会被均匀地分配到各个服务实例,达到负载均衡的目的,并且,一个服务实例挂了,只要存在其他同样的微服务实例在运行,那么就不会导致整个微服务不可用,极大地保证了安全性。
两种负载均衡策略:
RandomLoadBalancer - 随机分配策略
RoundRobinLoadBalancer - 轮询分配策略(默认)
指定负载策略
先创建随机分配策略的配置类(不用加@Configuration
)
public class LoadBalancerConfig {
//将官方提供的 RandomLoadBalancer 注册为Bean
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory){
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
为对应的服务指定负载均衡策略,直接使用注解即可
@Configuration
@LoadBalancerClient(value = "userservice", //指定为 userservice 服务,只要是调用此服务都会使用我们指定的策略
configuration = LoadBalancerConfig.class) //指定我们刚刚定义好的配置类
public class BeanConfig {
@Bean
@LoadBalanced
RestTemplate template(){
return new RestTemplate();
}
}
注册中心高可用
因为用了注册中心之后,所有的服务都要经过服务注册中心来进行信息交换,所以一旦注册中心出了问题,那么会影响到整个系统的稳定性。所以,在实际开发中,Eureka都是以集群的形式存在。
Eureka集群,实际上就是启动多个Eureka实例,多个实例之间相互注册,相互同步数据,共同组成一个 Eureka集群。
修改电脑的hosts文件,文件路径是C:\Windows\System32\drivers\etc\hosts,在这个配置文件的末尾加上127.0.0.1 eureka01 eureka02
再修改一下Eureka服务端的配置文件,这里需要两个配置文件
server:
port: 8801
spring:
application:
name: eurekaserver
eureka:
instance:
# 由于不支持多个localhost的Eureka服务器,但是又只有本地测试环境,所以就只能自定义主机名称了
# 主机名称改为eureka01
hostname: eureka01
client:#表示是否从eureka Server 上获取注册信息
fetch-registry: true
# 去掉register-with-eureka选项,让Eureka服务器自己注册到其他Eureka服务器,这样才能相互启用
service-url:
# 注意这里填写其他Eureka服务器的地址,不用写自己的
defaultZone: http://eureka02:8802/eureka
server:
port: 8802
spring:
application:
name: eurekaserver
eureka:
instance:
hostname: eureka02
client:
fetch-registry: true
service-url:
defaultZone: http://eureka01:8801/eureka
对创建的两个配置文件分别添加启动配置,直接使用spring.profiles.active
指定启用的配置文件
接着需要对各个微服务配置也进行修改
eureka:
client:
service-url:
# 将两个Eureka的地址都加入,这样就算有一个Eureka挂掉,也能完成注册
defaultZone: http://eureka01:8801/eureka, http://eureka02:8802/eureka
这样其他的服务可以在两个eureka实例中都注册了