一.负载均衡Ribbon
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执
1.自定义负载均衡
1.1拷贝nacos_provider:
1.2application.yml
server:
port: 9090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.137.114:8848
application:
name: ribbon-provider
server:
port: 9091
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.137.114:8848
application:
name: ribbon-provider
1.3创建服务消费者
拷贝nacos_consumer:
1.4application.yml
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.137.114:8848
application:
name: ribbon-consumer
1.5controller
package com.bjpowernode.controller;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
private int currentIndex;
@RequestMapping(value="/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
List<ServiceInstance> serviceList =
discoveryClient.getInstances("ribbon-provider");
//随机方式获得服务
//int currentIndex = new Random().nextInt(serviceList.size());
//轮询方式获得服务
currentIndex = (currentIndex + 1) % serviceList.size();
ServiceInstance instance = serviceList.get(currentIndex);
String serviceUrl = instance.getHost() + ":" + instance.getPort();
System.out.println("serviceUrl:"+serviceUrl);
String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
return restTemplate.getForObject(url, User.class);
}
}
2.基于ribbon实现负载均衡
2.1修改ribbon_consumer
2.2ConfigBean
package com.bjpowernode.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
/**
* 1.在RestTemplate上添加@LoadBalanced后,ribbon会给RestTemplate的请求添加拦截器
* 2.在拦截器中根据serverId获得List<Service>,然后再使用ribbon的负载均衡算法从
* List<Service>获得 一个service
* 3.最后再把serverId换成ip和端口
*/
//@LoadBalanced //开启负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//随机策略
@Bean
public IRule iRule() {
return new RandomRule();
}
}
2.3controller
package com.bjpowernode.controller;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
//不使用ribbon:ip:port
//String serviceUrl = "127.0.0.1:9090";
//使用ribbon:不再使用ip:port的方式,而是改成了serviceId(即Nacos中注册的服务名称)
String serviceUrl = "ribbon-provider";
return restTemplate.getForObject("http://" + serviceUrl +
"/provider/getUserById/" + id, User.class);
}
}
二.声明式服务调用Feign
1.Feign的背景
当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻。
那么有没有更好的解决方案呢?答案是确定的有,Netflix已经为我们提供了一个框架:Feign。
2.Feign概述
Feign是Spring Cloud提供的声明式、模板化的HTTP客户端, 它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Spring Cloud集成Feign并对其进行了增强,使Feign支持了Spring MVC注解;Feign默认集成了Ribbon,所以Fegin默认就实现了负载均衡的效果。
3.Feign入门
3.1创建服务提供者
3.2创建工程
拷贝ribbon_provider_1
3.3application.yml
server:
port: 9090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.137.114:8848
application:
name: feign-provider
3.4创建feign接口
3.5创建工程
3.6pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_parent</artifactId>
<groupId>com.bjpowernode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign_interface</artifactId>
<dependencies>
<!--Spring Cloud OpenFeign Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.7feign
package com.bjpowernode.feign;
import com.bjpowernode.fallback.UserFeignFallback;
import com.bjpowernode.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value="sentinel-provider",fallbackFactory = UserFeignFallback.class)
@RequestMapping(value = "/provider")
public interface UserFeign {
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable(value="id") Integer id);
}
4.服务消费
4.1拷贝ribbon_consumer
4.2pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_parent</artifactId>
<groupId>com.bjpowernode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ribbon_consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--feign接口-->
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
4.4application.yml
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.137.114:8848
application:
name: feign-consumer
4.5Controller
package com.bjpowernode.controller;
import com.bjpowernode.feign.UserFeign;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private UserFeign userFeign;
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
return userFeign.getUserById(id);
}
}
4.6app
package com.bjpowernode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients(basePackages = "com.bjpowernode.feign")
@EnableFeignClients//开启feign接口扫描
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class);
}
}
5.feign实现原理
5.1将Feign接口注入到Spring容器中
@EnableFeignClients注解开启Feign扫描,先调用FeignClientsRegistrar.registerFeignClients()方法扫描@FeignClient注解的接口,再将这些接口注入到Spring IOC容器中,方便后续被调用。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
... ... ...
//扫描feign接口
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)
candidateComponent;
//获得UserFeign的详细信息
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注入Feign接口到Spring容器中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
5.2RequestTemplate封装请求信息
SynchronousMethodHandler.invoke(): 当定义的的Feign接口中的方法被调用时,通过JDK的代理方式为Feign接口生成了一个动态代理类,当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。该对象封装了HTTP请求需要的全部信息,如请url、参数,请求方式等信息都是在这个过程中确定的。
public Object invoke(Object[] argv) throws Throwable {
//创建一个RequestTemplate
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
//发出请求
return this.executeAndDecode(template);
} catch (RetryableException var8) {
... ... ...
}
}
}
package feign;
public final class RequestTemplate implements Serializable {
... ... ... ... ... ...
private UriTemplate uriTemplate;
private HttpMethod method;
private Body body;
... ... ... ... ... ...
}
5.3发起请求
SynchronousMethodHandler.executeAndDecode():
通过RequestTemplate生成Request,然后把Request交给Client去处理,Client可以是JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp,最后Client结合Ribbon负载均衡发起服务调用。
Object executeAndDecode(RequestTemplate template) throws Throwable {
//生成请求对象
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
//发起请求
response = this.client.execute(request, this.options);
} catch (IOException var15) {
... ... ...
throw FeignException.errorExecuting(request, var15);
}
}
6.Feign参数传递
传参方式:
-
restful风格:
feign接口:@PathVarible
【拼接restful形式的url】
-
?传参
feign接口:@RequestParam
【拼接?形式的url】
-
pojo参数
provider: @RequestBody User user
【获取请求体中的json串】
7.Feign请求超时
7.1模拟超时
修改feign_privider:
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUser() {
//模拟网络延迟
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new User(99,"hulu哇",18);
}
}
8.设置feign超时时间
ribbon:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间