远程调用的一种方式
RestTemplate
package com.jt.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Random;
@RestController
public class ConsumerController {
//可以实现远程调用
@Autowired
private RestTemplate restTemplate;
@Autowired
//此对象可以基于服务名从nacos中获取服务实例,然后在nacos客户端采用负载均衡实现
private LoadBalancerClient loadBalancerClient;//实现呢类RibbbonLoadBalancerClient
//Ribbbon调用IRrule下面的算法获取到实例
@Value("${spring.application.name:8090}")
private String appName;
//http://localhost:8090/consumer/do02
@GetMapping("/consumer/do02")
public String do02(){
String url = "http://localhost:8081/provider/8090";
return restTemplate.getForObject(url,//远端服务的URl
String.class);//远端读物url对应的返回值类型
}
/**方式1
* http://localhost:8090/consumer/do04
* 负载均衡实现
*/
@GetMapping("/consumer/do03")
public String do03(){
//不推荐使用 这个实现负载均衡
String url = "http://localhost:8081/provider/8090";
String url1 = "http://localhost:8082/provider/8090";
String[] strings = new String[]{url,url1};
int num = new Random().nextInt(2);
return restTemplate.getForObject(strings[num],//远端服务的URl
String.class);//远端读物url对应的返回值类型
}
/**方式2
* http://localhost:8090/consumer/do04
* 负载均衡实现
*/
@GetMapping("/consumer/do04")
public String do04(){
//基于服务名获取实例
ServiceInstance provider = loadBalancerClient.choose("provider");//服务名
String ip = provider.getHost();
int port = provider.getPort();
//构建远程服务url
String url = "http://"+ip+":"+port+"/provider/8090";
return restTemplate.getForObject(url,//远端服务的URl
String.class);//远端读物url对应的返回值类型
}
/**方式3
* http://localhost:8090/consumer/do04
* 负载均衡实现
*/
@Autowired
@Qualifier("LoadBalancedRestTemplate")//可以指定方法名另一种写法
private RestTemplate restTemplate2;
@GetMapping("/consumer/do05")
public String do05(){
// String serviceNeme="provider";
String url = String.format("http://%s/provider/%s","provider",appName);
return restTemplate2.getForObject(url,//远端服务的URl
String.class);//远端读物url对应的返回值类型
}
}
启动类
注入对象
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
/**
* spring web模块中提供了一个RestTemplate对象可以完成远程调用
*
* consumer--->resttemplare--->provider
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 当使用@LoadBalanced描述RestTemplate对象时
* 拦截请求基于服务名找到服务实例(负载均衡)
* @return
*/
@Bean
@LoadBalanced
public RestTemplate LoadBalancedRestTemplate(){
return new RestTemplate();
}
}
被调用放:启动多个服务
package com.jt.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 构建Controller对象,用于处理客户端请求
*/
@RestController
public class ProviderController {
/**
* 从项目配置文件中读取server.port的值,然后赋值给serverPort属性,
* 冒号右边的8080为一个默认值,假如从配置文件读取不到server.port
* ,此时会将默认值赋值给属性serverPort
*/
@Value("${server.port:8080}")
private String serverPort;
//http://localhost:8081/provider/echo/client
@GetMapping("/provider/{msg}")
public String doRestEcho1(@PathVariable("msg") String msg) throws InterruptedException {
//模拟耗时操作
//Thread.sleep(5000);
return serverPort+" say hello "+msg;
}
}
基于Feign的远程服务调用
背景分析
服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护.
Feign是什么
Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现。如图所示:
Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由一些社区进行维护,更名为 OpenFeign。
Feign应用实践(掌握)
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter),代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:在启动类上添加@EnableFeignClients注解,代码如下:
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {…}
第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务,代码如下:
package com.jt.servicer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* FeignClient描述feign远程服务调用接口
*/
@FeignClient(name = "provider")
public interface RemoteProviderService {
@GetMapping("/provider/{msg}")
String Message(@PathVariable("msg") String msg);
}
其中,@FeignClient描述的接口底层会为其创建实现类。
第四步:
package com.jt.controller;
import com.jt.servicer.RemoteProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("consumer")
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
//http://localhost:8090/consumer/do01/8090
@GetMapping("/do01/{msg}")
public String do01(@PathVariable("msg")String msg){
return remoteProviderService.Message(msg);//调用接口
}
}
Feign配置进阶实践
一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口,此时假如没有指定contextId,服务
启动就会失败,例如,假如在服务消费方再添加一个如下接口,消费方启动时就会启动失败,例如:
@FeignClient(name = "Provider",contextId = "OtherService")
public interface OtherService {
@GetMapping("/doSomeThing")
public String doSomeThing();
}
其启动异常如下:
The bean ‘optimization-user.FeignClientSpecification’, defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
此时我们需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识(这个标识是Bean对象的名字)即可,例如:
@FeignClient(name = "provider",
contextId = "RemoteProviderService",
)
public interface RemoteProviderService {
@GetMapping("/provider/{msg}")
String Message(@PathVariable("msg") String msg);
}
还有,当我们在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了,怎么办呢?一般服务消费端会给出具体的容错方案,例如,在Feign应用中通过FallbackFactory接口的实现类进行默认的相关处理,例如:
第一步:定义FallbackFactory接口的实现,代码如下:
/**
- 基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
*/
package com.jt.servicer;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 服务回调工厂,当远程服务不可用或者远程服务掉用超时,可以通过
* 配置的方式,调用本地服务返回一个结果
*/
@Component
public class RremoteproviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
@Override
public RemoteProviderService create(Throwable throwable) {
return new RemoteProviderService() {
@Override
public String Message(String msg) {
return "服务维护中,一会在访问";
}
} ;
}
}
第二步:在Feign访问接口中应用FallbackFactory对象,例如:
@FeignClient(name = "provider",
contextId = "RemoteProviderService",
fallbackFactory = RremoteproviderFallbackFactory.class)
public interface RemoteProviderService {
@GetMapping("/provider/{msg}")
String Message(@PathVariable("msg") String msg);
}
第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制.
#启动feign方式调用时的服务中断处理机制
feign:
hystrix:
enabled: false #false,true表示启动超时熔断机制 优先级高
第四步:在服务提供方对应的调用方法中添加Thread.sleep(5000)模拟耗时操作,然后启动服务进行访问测试.
Feign 调用过程分析(了解)
Feign应用过程分析(底层逻辑先了解):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) Feign接口被调用时,底层代理对象会将接口中的请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
4) Feign客户端请求对象会经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) Feign客户端会携带 Request 调用远端服务并返回一个响应。
6) Feign客户端对象对Response信息进行解析然后返回客户端。