SpringCloud使用Eureka服务注册中心
Eureka介绍
什么是服务治理?
Springcloud 封装了Netfix公司开发的Eureka模块来实现服务治理。在传统的RPC远程调用中,管理每个服务于服务之间依赖关系复杂,管理复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
什么是服务注册与发现?
Eureka采用了CS的设计架构,Eureka Server服务端作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka Client客户端连接到Eureka Server服务端并维持心跳的连接,这样系统的维护人员就可以通过ureka Server来监控系统中的各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的的信息,比如:服务地址通讯等以别名方式注册到注册中心上,另一方(消费者|生产者)以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用和RPC远程调用框架核心设计思想。这里的重点就是注册中心,因为注册中心管理每个服务于服务之间的依赖关系。在任何的RPC框架中,都会有一个注册中心,存放服务地址相关信息,也就是接口地址。
下图为Eureka的架构图:
Eureka 心跳检测的原理
我们知道 Eureka 注册中心是通过心跳检测机制来判断服务是否可用的,如果不可用,可能会把这个服务摘除。为什么是可能呢?因为 Eureka 有自我保护机制,如果达到自我保护机制的阀值,后续就不会自动摘除。
Eureka 心跳机制:每个服务每隔 30s 自动向 Eureka Server 发送一次心跳,Eureka Server 更新这个服务的最后心跳时间。如果 180 s 内(版本1.7.2)未收到心跳,则任务服务故障了。
Eureka 自我保护机制:如果上一分钟实际的心跳次数,比我们期望的心跳次数要小,就会触发自我保护机制,不会摘除任何实例。期望的心跳次数:服务实例数量 * 2 * 0.85。
Eureka 服务摘除机制:不是一次性将服务实例摘除,每次最多随机摘除 15%。如果摘除不完,1 分钟之后再摘除。
搭建Eureka服务端服务
新建一个module,是Eureka的服务。该module名称:cloud-eureka-server。
1、pom.xml添加依赖
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka-server, springcloud对一些常用模块都进行了版本管理,所以这里不需要引入版本号-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2、application.yml配置
server:
port: 7001
spring:
application:
name: eureka-server-7001
# eureka配置
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称, 需要修改系hosts,添加ip和域名的解析配置
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与eureka server交互的地址和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/ #单机就是指向自己
3、主启动类:因为是Eureka的服务端,所以要加该注解@EnableEurekaServer
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //开启Eureka Server
@SpringBootApplication
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class, args);
}
}
4、启动该项目,我们直接访问该地址http://localhost:7001/,端口7001就是该服务的端口,出现如下界面,说明启动成功。
现在Erueka的服务端已经启动成功,那我们现在就将我们的其他微服务注册到我们Erueka的注册中心。
搭建Eureka客户端服务
新建一个module。该module名称:cloud-user-service。
1、pom.xml添加依赖
<!--boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加eureka客户端的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、application.yml配置
server:
port: 9999
spring:
application:
name: cloud-user-service
#eureka配置
eureka:
client:
#表示是否将自己注册进eureka 默认为true
register-with-eureka: true
#是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
fetch-registry: true
service-url:
#单机配置
defaultZone: http://eureka7001.com:7001/eureka/
3、主启动类,要加注解@EnableEurekaClient 或 @EnableDiscoveryClient(推荐)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient //客户端开启服务注册和发现
//@EnableEurekaClient //该注解仅适用于注册中心是Eureka Server,我们使用另外一个注解
@SpringBootApplication
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class, args);
}
}
4、最后我们要先启动Eureka服务端的服务,然后再启动生产者。然后再访问:http://localhost:7001/,会出现如下的界面,我们可以看到服务列表中有就了cloud-user-service这个服务
我们再新建一个module。该module名称:cloud-order-service。
步骤跟上面创建cloud-user-service一样,application.yml配置如下:
server:
port: 8888
spring:
application:
name: cloud-order-service
#eureka配置
eureka:
client:
#表示是否将自己注册进eureka 默认为true
register-with-eureka: true
#是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
fetch-registry: true
service-url:
#单机配置
defaultZone: http://eureka7001.com:7001/eureka/
启动cloud-order-service,访问 http://localhost:7001/,看到该服务也注册上去了。
搭建Eureka集群
我们只是简单的搭建了Eureka的单机版,但是在真正的生产环境上,是远远不够的,微服务RPC远程服务调用最核心的就是高可用,如果一台Eureka宕机了,那我们整个服务就不能使用了,所以就需要我们的集群版,实现负载均衡与故障容错。
我们参考cloud-eureka-server,新建module我们命名为cloud-eureka-server02,这样可以防止与cloud-eureka-server区别开来。(其实实际开发中不用这么模块,只需要修改配置文件,重新打包接口)
1、cloud-eureka-server02的搭建过程
1)添加相关的依赖
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2)application.yml
server:
port: 7002
spring:
application:
name: eureka-server-7002
# eureka配置
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称, 需要修改系hosts,添加ip和域名的解析配置
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与eureka server交互的地址和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/ #集群就是指向其他的eureka
3)启动类添加注解@EnableEurekaServer
2、cloud-eureka-server的yml修改配置
server:
port: 7001
spring:
application:
name: eureka-server-7001
# eureka配置
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称, 需要修改系hosts,添加ip和域名的解析配置
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与eureka server交互的地址和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/ #集群就是指向其他的eureka
3、eureka 客户端修改(我们的微服务)
将yml中的配置读改为:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
例如:cloud-user-service的application.yml完整配置
server:
port: 9999
spring:
application:
name: cloud-user-service
#eureka配置
eureka:
client:
#表示是否将自己注册进eureka 默认为true
register-with-eureka: true
#是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
fetch-registry: true
service-url:
#集群配置
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
现在我们要先启动Eureka端口为7001的服务,然后再启动Eureka端口为7002的服务,再启动我们的其他微服务。
先看一下Eureka端口为7001的服务,看到服务都已经成功注册到了Eureka服务中心。如下图:
再看看一下Eureka端口为7002的服务,看到服务都已经成功注册到了Eureka服务中心。如下图:
微服务集群搭建
1、我们参照之前搭建的端口为8888的服务cloud-order-service,现在我们搭建cloud-order-service02服务,端口为8899。(实际开发中,不用搭建cloud-order-service02项目的,我们只修改相应的配置文件,重新构建打包即可)
cloud-order-service02的application.yml配置:
server:
port: 8899 # 如果集群中的服务都是部署在不同的机器,端口可以不用修改
spring:
application:
name: cloud-order-service # 集群的服务名都是这个, 只有一个
#eureka配置
eureka:
client:
#表示是否将自己注册进eureka 默认为true
register-with-eureka: true
#是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
fetch-registry: true
service-url:
#集群配置
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#instance:
# 我使用的eureka client版本是3.1.1, 该值默认是ip:应用名:端口
#instance-id: ${spring.application.name}:${server.port}
#prefer-ip-address: true
cloud-order-service的application.yml配置:
server:
port: 8888 # 如果集群中的服务都是部署在不同的机器,端口可以不用修改
spring:
application:
name: cloud-order-service # 集群的服务名都是这个, 只有一个
#eureka配置
eureka:
client:
#表示是否将自己注册进eureka 默认为true
register-with-eureka: true
#是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
fetch-registry: true
service-url:
#集群配置
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#instance:
# 我使用的eureka client版本是3.1.1, 该值默认是ip:应用名:端口
#instance-id: ${spring.application.name}:${server.port}
#prefer-ip-address: true
启动后,我们看到服务名为cloud-order-service的有两个实例,端口分别是8888和8899。
2、服务间的调用
说明:cloud-user-service调用cloud-order-service集群。
1)cloud-user-service
创建RestTemplate实例:
//如果是集群服务, 调用时使用了服务名请求,则必须添加@LoadBalanced注解,使用ip请求不用添加
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
//服务端单机配置
//public static final String ORDER_URL = "http://localhost:8888";
//服务端集群配置
public static final String ORDER_URL = "http://CLOUD-ORDER-SERVICE";
@RequestMapping("/findOrdersByUserId")
public Map<String, Object> findOrdersByUserId(@RequestParam("userId") Long userId) {
return restTemplate.getForObject(ORDER_URL + "/order/findOrdersByUserId?userId={userId}", HashMap.class, userId);
}
}
2)cloud-order-service
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/findOrdersByUserId")
public Map<String, Object> findOrdersByUserId(@RequestParam("userId") Long userId) {
Map<String, Object> resultMap = new HashMap();
resultMap.put("code", 200);
resultMap.put("port", 8888);
return resultMap;
}
}
3)cloud-order-service02
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/findOrdersByUserId")
public Map<String, Object> findOrdersByUserId(@RequestParam("userId") Long userId) {
Map<String, Object> resultMap = new HashMap();
resultMap.put("code", 200);
resultMap.put("port", 8899);
return resultMap;
}
}
相关服务都启动后,请求访问 http://localhost:9999/user/findOrdersByUserId?userId=1
我们不断刷新页面,会出现:
说明集群服务进行了负载均衡。
我们查看注解@LoadBalanced的源码:
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
用于标记要配置为使用LoadBalancerClient的RestTemplate bean的。
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
LoadBalancerClient是一个接口,该接口中有四个方法,我们来大概看一下这几个方法的作用:
- ServiceInstance choose(String serviceId)根据传入的服务名serviceId从客户端负载均衡器中挑选一个对应服务的实例。
- T execute() ,使用从负载均衡器中挑选出来的服务实例来执行请求。
- URI reconstructURI(ServiceInstance instance, URI original)表示为系统构建一个合适的URI。第一个参数ServiceInstance实例是一个带有host和port的具体服务实例,第二个参数URI则是使用逻辑服务名定义为host和port的URI,而返回的URI则是通过ServiceInstance的服务实例详情拼接出的具体的host:port形式的请求地址。一言以蔽之,就是把类似于http://HELLO-SERVICE/hello这种地址转为类似于http://195.124.207.128/hello地址(IP地址也可能是域名)。
DiscoveryClient获取注册的服务
private Logger logger = LoggerFactory.getLogger(UserController.class);
//org.springframework.cloud.client.discovery.DiscoveryClient
@Autowired
private DiscoveryClient discoveryClient;
//获取服务信息
@RequestMapping("/discovery")
public Object discovery() {
List<String> services = discoveryClient.getServices();
for (String s : services) {
logger.info("********注册到eureka中的服务中有:" + services);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-ORDER-SERVICE");
for (ServiceInstance s : instances) {
logger.info("当前服务的实例有" + s.getServiceId() + "\t" + s.getHost() + "\t" + s.getPort() + "\t" + s.getUri());
}
return this.discoveryClient;
}
控制台输出:
********注册到eureka中的服务中有:[cloud-user-service, cloud-order-service]
********注册到eureka中的服务中有:[cloud-user-service, cloud-order-service]
当前服务的实例有CLOUD-ORDER-SERVICE 192.168.1.103 8888 http://192.168.1.103:8888
当前服务的实例有CLOUD-ORDER-SERVICE 192.168.1.103 8899 http://192.168.1.103:8899
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!