赞
踩
在之前我们做过的一个远程调用的一个小案例中,出现了一些的问题,比如我们的服务IP地址和端口都是硬编码在代码中的,首先硬编码这种方式就不应该存在,并且,我们在微服务中,哪怕是单一的服务,也会有多个服务器进行维护,这时候要写哪一个服务器的地址呢,这些都是我们之前在远程调用中出现的问题。
首先,我们的业务运行,需要有一个服务的提供者和服务的消费者,这些可能存在于不同服务器上的服务,在Eureks的环境中,统统被称为【Eureks-client】,即客户端。我们所有的客户端都会被注册到EurekaServer维护的一个表单中,当某一个服务需要另一个服务的时候,EurekaServer就会将自己维护的表单中,所属服务的服务器的IP以及端口号发送到需要请求服务的服务器,如果服务有多个,那么这台服务器再根据【负载均衡】的方法,选择其中一个服务器请求对应的服务。这就是Eureka做注册中心的基本流程。
其中一些细节,EurekaServer会每隔三十秒向服务端发送一次心跳续约,当某个机器当机下线没有向EurekaServer回应这个心跳,则EurekaServer就会将这台服务器从自己维护的表单中删除。同样的,如果有一台新的服务器加入到集群中,则EurekaServer会将新的服务器添加到自己维护的表单中,当其他服务请求时,就知道有一台新的服务器可以选择。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>EurkaServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>EurkaServer</name>
<description>EurkaServer</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<mysql.version>5.1.47</mysql.version>
<mybatis.version>2.1.1</mybatis.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
package Eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurkaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurkaServerApplication.class, args);
}
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://localhost:10086/eureka
其中比较重要的就是中间部分,这里显示的是服务的注册情况,也就是当我们之后有了服务之后,注册到Eureka之后,会在这里显示,现在只有一个,就是刚才我们注册的Eureka本身。
<!-- Eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
spring:
application:
name: userserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka/
当配置文件写好之后,重启服务,可以看到在Eureka的UI界面出现了两个新的服务:
在IDEA中,我么可以将某个服务复制一份,然后将复制的服务作为一个单独的服务启动,也就是说,我们可以在不编写代码的情况下启动多个服务。
选择复制服务,快捷键Ctrl+D,然后就会弹出一个编辑界面:
在这个界面我们设置新的服务的名称,最主要的是,我们要修改服务的启动端口,负责会产生端口冲突错误:
勾选之后,在之前的窗口中就出现了一个新的输入栏,上面显示【虚拟机选项】,对应的英文名叫做【VM Option】然后我们在这里写入配置:
最后面的数字表示端口号,这个端口号只要是自己机器上没有被占用的端口号都可以随意更改,然后点击【确定】,就可以看到刚才复制的服务,然后直接启动就可以了。
然后在Eureka的UI界面中,就可以看到对应复制的服务出现了两个可选服务器:
<!-- Eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
spring:
application:
name: userserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka/
package cn.itchase.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用RestTemplate发起HTTP请求
// 2.1 url地址
String url = "http://userservice/user/" + order.getUserId();
// 2.2 发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
// 封装user到Order
order.setUser(user);
// 4.返回
return order;
//
}
这样做的目的,是避免了直接写IP的硬编码,并且也解决了当同一服务有多个实例的时候,要如何去连接的问题,直接写对应服务的服务名,剩下的细节问题,就全部都交给Eureka去解决即可。
首先,在某个服务中需要用到另一个服务,就会发起对另一个服务暴露出的接口的请求,但是由于之前我们配置的Eureka,所以我们的请求地址并不是一个可用的地址,而是使用服务名代替IP地址的一个假地址,当负责负载均衡的Ribbon拦截下这个请求之后,他首先需要向Eureka-server去拉取服务名对应的IP地址和端口号,这个IP地址和端口号所对应的就是我们实际服务所在的服务器的完整的访问地址,然后Ribbon就可以根据负载均衡的程序去选择其中的一个服务去完成服务的请求。
首先,Ribbon的工作流程都是在【LoadBalancerInterceptor负载均衡器】中完成,第一步,【RibbonLoadBanlancerClient】会将我们的请求信息拦截下来,然后【DynamicServerListLoadBanlancer】会获取url中的服务id,比如上面的userservice,然后向Eureka-server中拉取userservice对应的服务列表,也就是对应服务的IP和端口号,然后使用IRule完成服务负载均衡,从多个服务列表中选择一个可用的,然后将服务的IP地址和端口号传回给【RibbonLoadBanlancerClient】,然后在这里修改之前的URL,将服务名称替换成之间选出来的IP地址和端口号,发送请求,然后将请求发送到对应的服务器中:
在之前我们分析负载均衡策略的时候,我们可以知道,实际的服务选择,也就是负载均衡是由IRule完成的,而IRule是一个接口,具体的功能还是根据具体的实现类去实现的。而这次我们就看一下IRule接口具体有什么实现类。
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:
其中比较重要的就是ZoneAvoidanceRule,即区域轮询,这个配置的关键在于,可以对服务做区域配置,即首先轮询与请求服务在同一区域内的服务,但是之前我们的演示中并没有做相关的配置,也就是默认所有的服务器都在同一区域内,理解为同一个机房。没有区域的分别,其实就是直接轮询。
@Bean
public IRule randomRule(){
retutn new RandomRule();
返回值的类型是IRule,但是具体的对象可以是任意的IRule的实现类。这种方式针对的服务是全局的,也就是所有的微服务都会更改策略,第二种方法是针对于某一个微服务的配置:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
这样的配置最终只会影响某一个微服务,也就是当别人来访问你的时候,你的均衡负载规则就是你的yml文件中设定的规则。
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面的配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
其中clients是一个集合类型,如果你有多个服务要做饥饿加载,那么你可以写作如下配置:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 配置多个开启饥饿加载的服务名称
- servername1
- servername2
饥饿加载的意思就是说,之前是我们在用到什么的时候才会去创建对应的对象调用对应的资源,但是饥饿加载会将所有的用到的用不到的对象和资源都一起给你创建好,这样就省去了我们在第一次访问时候的对象加载时间。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。