远程调用的一种方式
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对应的返回值类型
}
@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对应的返回值类型
}
@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对应的返回值类型
}
@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);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@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;
@RestController
public class ProviderController {
@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),代码如下:
org.springframework.cloud spring-cloud-starter-openfeign
第二步:在启动类上添加@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(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接口的实现,代码如下:
package com.jt.servicer; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class RremoteproviderFallbackFactory implements FallbackFactory{ @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信息进行解析然后返回客户端。



