一. Eureka介绍

1.什么是Eureka

微服务的其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用服务的通信地址,试问当微服务数量成百上千之多,该如何管理众多的服务通信地址,对于随时新增加的微服务和下线的微服务,又应该如何去动态添加和删除这些微服务的通信地址呢?所以手工管理服务的通信地址是一件遥不可及的事情,我们需要借助一个强大的工具帮我们实现这一功能 - Eureka,同类型的组件还有 zookeeper,consul等。

2.服务注册

Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址的组件,它包含了EurekaServer 服务端(也叫注册中心)和EurekaClient客户端两部分组成,EurekaServer是独立的服务,而EurekaClient需要集成到每个微服务中。

微服务(EurekaClient)在启动的时候会向EurekaServer提交自己的服务信息(通信地址如:服务名,ip,端口等),在 EurekaServer会形成一个微服务的通信地址列表存储起来。 —- 这叫服务注册

3.服务发现

微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。—-这叫服务发现。

4.服务续约

另外,微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送“心跳”请求向EurekaServer发请求进行服务续约,其实就是定时向 EurekaServer发请求报告自己的健康状况,告诉EurekaServer自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。

5.服务下线

微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除

image-20221012185842388

二.Eureka Server实例

1.在父项目导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!--1.管理 SpringBoot的jar包-->
<!--SpringBoot-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
</parent>

<!--2.管理 SpringCloud的jar包-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<!--3.这里是所有子项目都可以用的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

<!-- 使用lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

2.搭建Eureka Server

创建模块后导入以下依赖

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<!--spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 10086 #端口
eureka:
instance:
hostname: localhost #主机
client: #客户端配置
registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:10086/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false #关闭自我保护警告
spring:
application:
name: eureka-server

提示:这里配置了EurekaServer的端口为 10086,主机名 localhost ,需要特别说明的是我们再引入EurekaServer的基础依赖spring-cloud-starter-netflix-eureka-server时,这个依赖即引入了EurekaServer所需要的包,也引入了EurekaClient的包,换句话说,现在的springcloud-eureka-server-10086工程既是一个 EurekaServer,也是一个EurekaClient。

我们这里暂时把EurekaClient的功能屏蔽掉 ,即关闭它的服务注册和发现功能,让他做好EurekaServer该做的事情即可。

  • serviceUrl是服务注册地址,EurekaClient需要注册到EurekaServer就得跟上该地址。
  • registerWithEureka=false :禁止自己向自己注册
  • fetchRegistry=false : 禁止拉取服务注册列表

启动类用于启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/
* 注册中心启动类
* @EnableEurekaServer : 开启EurekaServer服务端
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication1010
{
public static void main( String[] args )
{
SpringApplication.run(EurekaServerApplication1010.class);
}
}

提示:在主配置类上通过 @EnableEurekaServer 注解开启了EurekaServer端的功能。

3.启动测试

启动springcloud-eureka-server工程,浏览器访问 http://localhost:10086,出现如下界面代码EurekaServer集成成功:

image-20221012191518547

3.Eureka自我保护

默认情况下,当EurekaServer接收到服务续约的心跳失败比例在15分钟之内低于85%,EurekaServer会把这些服务保护起来,即不会把该服务从服务注册地址清单中剔除掉,但是在此种情况下有可能会出现服务下线,那么消费者就会拿到一个无效的服务,请求会失败,那我们需要对消费者服务做一些重试,或在熔断策略。

当EurekaServer开启自我保护时,监控主界面会出现红色警告信息,我们可以使用eureka.server.enable-self-preservation=false来关闭EurekaServer的保护机制,这样可以确保注册中心中不可用的实例被及时的剔除,但是不推荐。

三.Eureka Clien实例-User

1.创建实体模块(包含订单和用户)

image-20221012192015868

User

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String address;
private String desc;
}

Order

1
2
3
4
5
6
7
8
9
10
11
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}

2.创建用户模块

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<!-- 引入eureka客户端 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入实体 -->
<dependency>
<groupId>com.lqs</groupId>
<artifactId>springcloud-entity</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

3.主配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/
* 用户的启动类
* @EnableEurekaClient: 标记该应用是 Eureka客户端
*/
@SpringBootApplication
@EnableEurekaClient
public class UserServerApp
{

public static void main( String[] args )
{
SpringApplication.run(UserServerApp.class);
}
}

提示:主配置类通过打@EnableEurekaClient注解开启EurekaClient客户端功能,当然如果不打这个标签也能实现功能,因为导入spring-cloud-starter-netflix-eureka-client 依赖后,默认就开启了EurekaClient

4.application.yml配置

默认情况下EurekaClient使用hostname进行注册到EurekaServer,我们希望使用ip进行注册,可以通过配置eureka.instance.prefer-ip-address=true来指定,同时为了方便区分和管理服务实例,我们指定服务的实例ID,通过eureka.instance.instance-id为user-serer:8081来指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10086/eureka
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: user-server:${server.port} #实例ID

spring:
application:
name: user-server

server:
port: 8081


四.Eureka Clien实例-Order

1.与User服务一样导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入entity模块 -->
<dependency>
<groupId>com.lqs</groupId>
<artifactId>springcloud-entity</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

2. 主配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApp {
public static void main(String[] args) {
SpringApplication.run(OrderServerApp.class);
}

@LoadBalanced // 实现负载均衡
@Bean
public RestTemplate getRestTemplate() {// 实现服务之间的通信
return new RestTemplate();
}

/**
* 配置随机的负载均衡算法
* @return RandomRule
*/
@Bean
public RandomRule randomRule() {
return new RandomRule();
}
}

3.application.yml配置

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8088
spring:
application:
name: order-server
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
instance: #实例
prefer-ip-address: true
instance-id: order-server:${server.port} #唯一的实例ID

五.RestTemplate介绍

微服务的通信协议主流的有RPC,Http,SpringCloud是基于Http Restful 风格 ,在Java中发起一个Http请求的方式很多,比如 Apache的HttpClient , OKHttp等等 。Spring为我们封装了一个基于Restful的使用非常简单的Http客户端工具 RestTemplate ,我们就用它来实订单服务和用户服务的通信。需要注意的是,RestTmplate本身不具备服务发现和负载均衡器的功能。

六.实例

user模块

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("/user")
public class UserController {
@Value("${server.port}")
private String port;

@GetMapping("{id}")
public User getByUserId(@PathVariable("id") Long id) {
return new User(1L, "coderyeah", "二仙桥", port + ":这是返回user信息的描述");
}
}

order模块

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;

@GetMapping("{id}")
public Order getOrderById(@PathVariable("id") Long id) {
// 发送的http请求
String url = "http://user-server/user/" + id;
final User user = restTemplate.getForObject(url, User.class);
return new Order(1L, 88L, "芋泥啵啵奶茶", 2, 1L, user);
}

七.什么是Ribbon

我们知道,为了防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,如果我们对user-server(用户服务)做了集群 ,那么这个时候回衍生出一些问题:现在有两个user-server(用户服务)就意味着有两个user-server(用户服务)的通信地址,我的order-server(订单服务)在向user-server(用户服务)发起调用的时候该访问哪个?如何访问?这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个 - 客户端负载均衡器。

Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法

客户端导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

开启负载均衡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 订单的启动类
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApp
{

//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
//@LoadBalanced :让RestTemplate有负载均衡的功能
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
//省略...

Ribbon内置算法

Ribbon内置7种负载均衡算法,每种算法对应了一个算法类如下:

image-20221012195253383

配置负载均衡算法

Ribbon可以进行全局负载均衡算法配置,也可以针对于具体的服务做不同的算法配置。同时可以使用注解方式和yml配置方式来实现上面两种情况。

  1. 注解全局配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 订单的启动类
    */
    @SpringBootApplication
    @EnableEurekaClient
    public class OrderServerApp
    {

    //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
    //@LoadBalanced :让RestTemplate有负载均衡的功能
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }

    //负载均衡算法
    @Bean
    public RandomRule randomRule(){
    return new RandomRule();
    }
    //省略...
  2. yml方式配置负载均衡算法

    1
    2
    ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  3. 配置某个服务的Ribbon算法

    1
    2
    3
    user-server:
    ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  4. Ribbon调优配置

    使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制

    1
    2
    3
    4
    5
    6
    ribbon:
    ReadTimeout: 3000 #读取超时时间
    ConnectTimeout: 3000 #链接超时时间
    MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数
    MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数
    OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
  5. 饥饿加载

    我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。

    1
    2
    3
    4
    ribbon:
    eager-load:
    enabled: true #开启饥饿加载
    clients: user-server #针对于哪些服务需要饥饿加载