Spring cloud服务发现过程

基本使用示例

基础结构

  • 服务注册中心 :Eureka提供的服务端,提供服务注册与发现的功能。
  • 服务提供者:提供服务的应用,可以是Spring boot应用,也可以是其他技术平台且遵循 Eureka 通信机制的应用。它将自己提供的服务注册到 Eureka 以供其他应用发现。
  • 服务消费者: 消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务。

很多时候,客户端既是服务提供者也是服务消费者。

下面我们创建一个基本的Spring cloud模式的微服务工程,用来说明服务的注册和发现以及微服务的调用流程以及方式。

  • eureka-server 注册中心
  • scm-message 消息推送服务
  • scm-form 申请单服务

具体的搭建以及代码实现过程如下:

创建一个maven工程,引入Springboot以及Springcloud的依赖,定义好统一的版本号。

<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 http://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.1.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
  
	<groupId>com.starnetuc</groupId>
    <artifactId>scm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    
  	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.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>
	<modules>
		<module>eureka-server</module>
		<module>scm-form</module>
		<module>scm-message</module>
		<module>scm-message-api</module>
	</modules>
</project>
eureka-server

pom依赖

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.starnetuc</groupId>
    <artifactId>scm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>eureka-server</artifactId>
  
  	<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
	</dependencies>
</project>

配置文件

server.port=8761
#不需要将自己注册到自己的注册中心(集群的时候需要true)
eureka.client.registerWithEureka=false
#不拉取注册服务信息
eureka.client.fetchRegistry=false

启动文件

@SpringBootApplication
@EnableEurekaServer //启动eureka server的注解
public class EurekaServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}	
}
scm-message

pom 文件

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.starnetuc</groupId>
    <artifactId>scm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>scm-message</artifactId>
  
    
  	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

配置文件

server.port=8004
#服务名称  
spring.application.name=eureka-client-message
eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka/

启动文件

@SpringBootApplication
@EnableDiscoveryClient  //开启eureka client的注解
public class ScmMessageApplication {
	
	public static void main(String[] args) {
		SpringApplication.run(ScmMessageApplication.class, args);
	}
	
}

接口文件

@RestController
public class ScmMessageController {

	Logger logger = LoggerFactory.getLogger(ScmMessageController.class);
	
	@GetMapping("/message/send/{content}")
	public String getForm(@PathVariable("content") String content) {
		logger.debug("send message, content: {}", content);
		return "send message complete, " + content;
	}
}
scm-form

pom文件

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.starnetuc</groupId>
    <artifactId>scm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>scm-form</artifactId>
  
  	<dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

配置文件

server.port=8003
#服务名称  
spring.application.name=eureka-client-form
eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka/

启动文件

@SpringBootApplication
@EnableDiscoveryClient
public class ScmFormApplication {

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

接口文件

@RestController
public class ScmFormController {


	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
	

	@Autowired
	RestTemplate restTemplate;
	
	@GetMapping("/message/send/{content}")
	public String sendMsg(@PathVariable("content") String content) {
		return restTemplate.getForEntity("http://eureka-client-message/message/send/{content}", String.class, content).getBody();
		
	}

}

测试访问结果

访问 http://127.0.0.1:8003/message/send/hello%20world

结果send message complete, hello world

服务的注册与发现

"message" "eurekaServer" "form" message eurekaServer form message服务启动 获取注册中心的服务列表 返回注册中心的服务列表 提交注册信息 每隔30秒发送一个保活报文,进行服务续约 每隔30秒请求获取服务列表,获取其变化的数据 返回服务列表的变化数据 form服务启动 服务调用 message服务停止 发送注销请求 检查服务列表,剔除失效服务 "message" "eurekaServer" "form" message eurekaServer form

服务注册:

“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上自身服务的一些元数据信息

eureka server接收到这个REST请求之后,会将注册上来的服务维护在自己的服务集合当中。

POST /eureka/apps/EUREKA-CLIENT-MESSAGE HTTP/1.1
Accept-Encoding: gzip
Content-Type: application/json
Accept: application/json
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.0.112
Transfer-Encoding: chunked
Host: 127.0.0.1:8761
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.9.8
{
	"instance": {
		"instanceId": "192.168.0.112:eureka-client-message:8004",
		"hostName": "192.168.0.112",
		"app": "EUREKA-CLIENT-MESSAGE",
		"ipAddr": "192.168.0.112",
		"status": "UP",
		"overriddenStatus": "UNKNOWN",
		"port": {
			"$": 8004,
			"@enabled": "true"
		},
		"securePort": {
			"$": 443,
			"@enabled": "false"
		},
		"countryId": 1,
		"dataCenterInfo": {
			"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
			"name": "MyOwn"
		},
		"leaseInfo": {
			"renewalIntervalInSecs": 30,
			"durationInSecs": 90,
			"registrationTimestamp": 0,
			"lastRenewalTimestamp": 0,
			"evictionTimestamp": 0,
			"serviceUpTimestamp": 0
		},
		"metadata": {
			"management.port": "8004"
		},
		"homePageUrl": "http://192.168.0.112:8004/",
		"statusPageUrl": "http://192.168.0.112:8004/actuator/info",
		"healthCheckUrl": "http://192.168.0.112:8004/actuator/health",
		"vipAddress": "eureka-client-message",
		"secureVipAddress": "eureka-client-message",
		"isCoordinatingDiscoveryServer": "false",
		"lastUpdatedTimestamp": "1566229507268",
		"lastDirtyTimestamp": "1566229508949"
	}
}

服务同步:

服务提供者发送注册请求到其中一个注册中心时,会将请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。

服务续约:

注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka server服务的状态。防止Eureka server的“剔除任务”将该服务实例从服务列表中排除出去。

eureka.instance.lease-renewal-interval-in-seconds=30 #服务续约任务的调用间隔时间
eureka.instance.lease-expiration-duration-in-seconds=90 #服务的失效时间
PUT /eureka/apps/EUREKA-CLIENT-MESSAGE/192.168.0.112:eureka-client-message:8004?status=UP&lastDirtyTimestamp=1566229508949 HTTP/1.1
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.0.112
Accept-Encoding: gzip
Content-Length: 0
Host: 127.0.0.1:8761
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.9.8

lastDirtyTimestamp:服务最后更新的时间。如果eureka server判断自己维护的实例已经过期,那么会返回404要求client重新注册

服务获取:

当启动服务消费者的时候,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑Eureka server会维护一份只读的服务清单返回给客户端,同时该缓存定期刷新。

eureka.client.fetch-registry=false #开启从注册中心获取服务列表
eureka.client.registry-fetch-interval-seconds=30 #刷新服务列表的间隔时间

先启动message,后启动form。form得到的服务列表内容

GET /eureka/apps/ HTTP/1.1
Accept: application/json
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.0.112
Accept-Encoding: gzip
Host: 127.0.0.1:8761
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.9.8

HTTP/1.1 200 
Content-Encoding: gzip
Content-Type: application/json
Content-Length: 543
Date: Mon, 19 Aug 2019 15:56:28 GMT

{
	"applications": {
		"versions__delta": "1",
		"apps__hashcode": "UP_1_",
		"application": [{
			"name": "EUREKA-CLIENT-MESSAGE",
			"instance": [{
				"instanceId": "192.168.0.112:eureka-client-message:8004",
				"hostName": "192.168.0.112",
				"app": "EUREKA-CLIENT-MESSAGE",
				"ipAddr": "192.168.0.112",
				"status": "UP",
				"overriddenStatus": "UNKNOWN",
				"port": {
					"$": 8004,
					"@enabled": "true"
				},
				"securePort": {
					"$": 443,
					"@enabled": "false"
				},
				"countryId": 1,
				"dataCenterInfo": {
					"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
					"name": "MyOwn"
				},
				"leaseInfo": {
					"renewalIntervalInSecs": 30,
					"durationInSecs": 90,
					"registrationTimestamp": 1566229509143,
					"lastRenewalTimestamp": 1566230229121,
					"evictionTimestamp": 0,
					"serviceUpTimestamp": 1566229509144
				},
				"metadata": {
					"management.port": "8004"
				},
				"homePageUrl": "http://192.168.0.112:8004/",
				"statusPageUrl": "http://192.168.0.112:8004/actuator/info",
				"healthCheckUrl": "http://192.168.0.112:8004/actuator/health",
				"vipAddress": "eureka-client-message",
				"secureVipAddress": "eureka-client-message",
				"isCoordinatingDiscoveryServer": "false",
				"lastUpdatedTimestamp": "1566229509144",
				"lastDirtyTimestamp": "1566229508949",
				"actionType": "ADDED"
			}]
		}]
	}
}

增量获取,先启动message,后启动form。message得到的增量内容

GET /eureka/apps/delta HTTP/1.1
Accept: application/json
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.0.112
Accept-Encoding: gzip
Host: 127.0.0.1:8761
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.9.8

HTTP/1.1 200 
Content-Encoding: gzip
Content-Type: application/json
Content-Length: 532
Date: Mon, 19 Aug 2019 15:56:39 GMT

{
	"applications": {
		"versions__delta": "5",
		"apps__hashcode": "UP_2_",
		"application": [{
			"name": "EUREKA-CLIENT-FORM",
			"instance": [{
				"instanceId": "192.168.0.112:eureka-client-form:8003",
				"hostName": "192.168.0.112",
				"app": "EUREKA-CLIENT-FORM",
				"ipAddr": "192.168.0.112",
				"status": "UP",
				"overriddenStatus": "UNKNOWN",
				"port": {
					"$": 8003,
					"@enabled": "true"
				},
				"securePort": {
					"$": 443,
					"@enabled": "false"
				},
				"countryId": 1,
				"dataCenterInfo": {
					"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
					"name": "MyOwn"
				},
				"leaseInfo": {
					"renewalIntervalInSecs": 30,
					"durationInSecs": 90,
					"registrationTimestamp": 1566230188521,
					"lastRenewalTimestamp": 1566230188521,
					"evictionTimestamp": 0,
					"serviceUpTimestamp": 1566230188521
				},
				"metadata": {
					"management.port": "8003"
				},
				"homePageUrl": "http://192.168.0.112:8003/",
				"statusPageUrl": "http://192.168.0.112:8003/actuator/info",
				"healthCheckUrl": "http://192.168.0.112:8003/actuator/health",
				"vipAddress": "eureka-client-form",
				"secureVipAddress": "eureka-client-form",
				"isCoordinatingDiscoveryServer": "false",
				"lastUpdatedTimestamp": "1566230188521",
				"lastDirtyTimestamp": "1566230188428",
				"actionType": "ADDED"
			}]
		}]
	}
}

服务调用:

消费者在获取服务清单后,通过服务名可以获得具体提供的服务的实例名和该实例的元数据信息。因为有了这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例。

这里会涉及到客户端对于调用的服务提供者的选择和负载均衡。

服务下线:

服务实例进行正常的关闭操作时,会触发一个服务下线的REST请求给Eureka server,告诉服务注册中心该服务下线了。服务端在接受到请求之后,将该服务状态置为下线(DOWN),并把下线事件传播出去。

DELETE /eureka/apps/EUREKA-CLIENT-FORM/192.168.0.112:eureka-client-form:8003 HTTP/1.1
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.0.112
Accept-Encoding: gzip
Host: 127.0.0.1:8761
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.9.8

HTTP/1.1 204 
Content-Type: application/json
Date: Tue, 20 Aug 2019 14:45:43 GMT

服务注册中心

失效剔除

有些时候,服务实例并不一定会正常下线,由于一些异常的原因,服务无法正常发出下线请求。Eureka server为了将这些服务剔除,会启动一个定时任务,会在默认的间隔时间里把续约超时的服务剔除出去。

自我保护

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

Eureka server 在运行期间,会统计心跳失败率率在15分钟以内是否高于85%,如果低于这种情况,Eureka Server会将这些实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。

eureka.server.enable-self-preservation=false #关闭自我保护机制

服务调用的基本原理

前文提到每个服务会从注册中心同步一份服务列表。有了这份服务列表服务调用的时候可以从列表中通过负载均衡策略获取到合适的服务提供者,然后从服务信息中得到正确的URI地址,进行访问。

Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。

Eureka和Ribbon会触发自动配置,将服务列表的维护交给Eureka的服务治理机制来维护,Ribbon要查询服务列表就可以从Eureka维护的服务列表中获取。

Ribbon怎么做到负载均衡
@RestController

public class ScmFormController {
	
	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	@Autowired
	RestTemplate restTemplate;
		
	@GetMapping("/message/send/{content}")
	public String sendMsg(@PathVariable("content") String content) {
		return restTemplate.getForEntity("http://eureka-client-message/message/send/{content}", String.class, content).getBody();
	}
}

再来看看上文提供的服务调用代码

RestTemplate 是 Spring 提供的对 http 请求封装的工具类。提供一些供http访问的API如下

  • getForEntity(…)
  • postForEntity(…)
  • put(…)
  • delete(…)

可以看出 RestTemplate 构造时添加了一个注解 @LoadBalanced。

这里就引入了 Ribbon 对于服务调用时的负载均衡。也就是上述代码进行服务调用时需要先通过 Ribbon 进行服务的选择和URL地址的构造 才能够确定服务提供方的地址。然后通过 RestTemplate 才能够发起http的请求。

大致的一个流程如下:

  1. 使用 @LoadBalanced 注解的 RestTemplate 会被 LoadBalancerClient 自动装配。

  2. LoadBalancerAutoConfiguration 关于负载均衡的自动配置,在这里的自动配置

    创建了一个 LoadBalancerInterceptor 的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端的负载均衡。

    创建了一个 RestTemplateCustomizer 的Bean,用于给 RestTemplate 增加 LoadBalancerInterceptor 拦截器。

    维护了一个被 @LoadBalanced 注解修饰的 RestTemplate 对象列表,并在这里进行初始化。通过调用 RestTemplateCustomizer 的实例来给客户端负载均衡的 RestTemplate 增加LoadBalancerInterceptor 拦截器。

  3. LoadBalancerInterceptor 是一个拦截器,当 restTamplate 对象向外发起 HTTP 请求时,会被 LoadBalancerInterceptor 的 intercept 函数所拦截。拦截函数InterceptingClientHttpRequest

    		@Override
    		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    			if (this.iterator.hasNext()) {
    				ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
    				return nextInterceptor.intercept(request, body, this);
    			}
    			else {
    				HttpMethod method = request.getMethod();
    				Assert.state(method != null, "No standard HTTP method");
    				ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
    				request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
    				if (body.length > 0) {
    					if (delegate instanceof StreamingHttpOutputMessage) {
    						StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
    						streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
    					}
    					else {
    						StreamUtils.copy(body, delegate.getBody());
    					}
    				}
    				return delegate.execute();
    			}
    		}
    	}
    
	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

​ 此处可见loadBalancer.execute() 可以根据服务名来选择实例并发起实际的请求。

  1. 而 Ribbon 提供了 LoadBalancerClient 的具体实现。org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

    在 Ribbon 的具体实现当中,Ribbon 使用了 ILoadBalancer 接口进行服务的选择。Ribbon对这个接口有多个实现,如 BaseLoadBalancer, DynamicServerListLoadBalancer 和 ZoneAwareLoadBalancer等等。这些具体实现都实现了不同的负载均衡策略。通过这些负载均衡的实现可以选择到合适的服务。

    而在 RibbonClientConfiguration配置类中,可知默认采用了 ZoneAwareLoadBalancer 来实现负载均衡器。

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
    	throws IOException {
    	ILoadBalancer loadBalancer = getLoadBalancer(serviceId);  //获取负载均衡实现类
    	Server server = getServer(loadBalancer, hint); //通过负载均衡得到合适的server
    	if (server == null) {
    		throw new IllegalStateException("No instances available for " + serviceId);
    	}
    	RibbonServer ribbonServer = new RibbonServer(serviceId, server,
    				isSecure(server, serviceId),
    				serverIntrospector(serviceId).getMetadata(server));
    	return execute(serviceId, ribbonServer, request);
    }
    
    @Override
    public <T> T execute(String serviceId, ServiceInstance serviceInstance,
    		LoadBalancerRequest<T> request) throws IOException {
    	Server server = null;
    	if (serviceInstance instanceof RibbonServer) {
    		server = ((RibbonServer) serviceInstance).getServer();
    	}
    	if (server == null) {
    		throw new IllegalStateException("No instances available for " + serviceId);
    	}
    	RibbonLoadBalancerContext context = this.clientFactory
    			.getLoadBalancerContext(serviceId);
    	RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
    	try {
    		T returnVal = request.apply(serviceInstance); //执行request
    		statsRecorder.recordStats(returnVal);
    		return returnVal;
    	}
    	// catch IOException and rethrow so RestTemplate behaves correctly
    	catch (IOException ex) {
    		statsRecorder.recordStats(ex);
    		throw ex;
    	}
    	catch (Exception ex) {
    		statsRecorder.recordStats(ex);
    		ReflectionUtils.rethrowRuntimeException(ex);
    	}
    	return null;
    }
    

    最后通过 request.apply(serviceInstance);执行请求

  2. request还会被包装一次

    HttpRequest serviceRequest = new ServiceRequestWrapper(request,
    								instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
    return execution.executeAsync(serviceRequest, body);
    

    ServiceRequestWrapper 重写了 getUrl方法

    @Override
    public URI getURI() {
    	URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
    	return uri;
    }
    

    这里看到通过 loadBalancer构造URI。这里的loadBalancer就是RibbonLoadBalancerCLient。

    通过ribbon的负载均衡,最后得到一个合适的访问的URI。

Feign的作用

Feign的使用

@FeignClient(value="eureka-client-message")
public interface ScmMessageFeignClient {

	@GetMapping("/message/send/{content}")
	public String sendMessage(@PathVariable("content") String content);
	
}

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients

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

@RestController

public class ScmFormController {
	@Autowired
	ScmMessageFeignClient scmMessageFeignClient;
	
	
	@GetMapping("/form/message/send/{content}")
	public String sendMsg(@PathVariable("content") String content) {
		return scmMessageFeignClient.sendMessage("hello");
	}	
}

Feign 声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。

Spring cloud当中Feign集成了 Ribbon以及Hystrix等。使得Feign具有负载均衡以及融断的功能。

Feign使用的一个基本原理:

  1. 启动类中添加@EnableFeignClient,它的作用是自动扫描注册标记为@FeignClient的用户定义接口,动态创建代理类并注入到IOC容器当中。
  2. ReflectiveFeign内部使用了jdk的动态代理为目标接口生成了一个动态代理类,这里会生成一个InvocationHandler(jdk动态代理原理)统一的方法拦截器,同时为接口的每个方法生成一个SynchronousMethodHandler拦截器,并解析方法上的 元数据,生成一个http请求模板。
  3. 发送http请求的时候会通过 ribbon 使用负载均衡策略发现服务,得到最终的请求URI。

总结一下,Spring cloud的服务调用

Eureka负责维护服务列表,以供服务请求时候查找服务,选择服务。

Feign实现了声明式的,模板化的http客户端构造。

Feign的http客户端发出请求后会通过Ribbon进行负载均衡,根据一定的策略得到合适的请求URI。

最终使用http客户端的API 执行URI的请求访问得到结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值