1.负载均衡Ribbon
1.1.什么是负载均衡
通俗的讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
1.2.自定义实现负载均衡
1.2.1.创建服务提供者
1.2.1.1.创建工程
拷贝nacos_provider:
1.2.1.2.application.yml
server:
port: 9090 #配置tomcat端口号
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848 #配置注册中心的地址端口号
application:
name: ribbon-provider #配置服务的名字 注意:要实现负载均衡的服务名字必须一致
server:
port: 9091 #配置tomcat端口号不要和provider_1配置的一致 避免冲突
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848 #配置Nacos注册中心的地址和端口号
application:
name: ribbon-provider #配置注册中心服务的名字此模块要实现负载均衡,所以名字要和
#provider_1一致
注意:要对这个模块进行负载均衡的话Nacos注册中心的环境,分组,名字必须保持一致
1.2.2.创建服务消费者
1.2.2.1.创建工程
拷贝nacos_consumer:
1.2.2.2.application.yml
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
application:
name: ribbon-consumer
1.2.2.3.controller
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){
//此方法是根据注册中心服务的名字,获取到服务的集合
//因为provider1和provider2的yml的文件配置的application:name都是ribbon-provider
//那么从注册中心中取到的ribbon-provider的服务是有两个的分别是provider1和provider2放
//进集合中
List<ServiceInstance> serviceList =
discoveryClient.getInstances("ribbon-provider");
//随机方式获得服务serviceList.size() ==2 那么根据Random()类随机数currentIndex的范
//围就是0-1,从集合中取出provider1或者provider2对象消费该服务
//int currentIndex = new Random().nextInt(serviceList.size());
//轮询方式获得服务,因为是单例模式所以每次调用currentIndex(默认=0)都会在上次的基础上
//+1而currentIndex 经过公式运算得出的值这次是0那么下次调用就是1,每次消费的都是不同的
//服务
currentIndex = (currentIndex + 1) % serviceList.size();
//根据List集合通过角标获取服务
ServiceInstance instance = serviceList.get(currentIndex);
//根据服务对象获取到服务的IP地址和端口号
String serviceUrl = instance.getHost() + ":" + instance.getPort();
System.out.println("serviceUrl:"+serviceUrl);
//通过String类型拼接方式获取服务提供者路径
String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
//通过restTemplate对象getForObject(路径,返回值类型)方法调用服务
return restTemplate.getForObject(url, User.class);
}
}
1.2.3.测试
分别使用轮询和随机策略调用服务提供者
结论:
轮询:第一次:9090
第二次:9091
第三次:9090
......
随机策略:第一次:9090
第二次:9090
第三次:9091
第四次:9090
无规律
1.3.Ribbon介绍
1.3.1.什么是Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
我们不需要去引入ribbon的依赖,因为在nacos里面已经集成了ribbon的依赖
Ribbon默认提供 很多种负载均衡算法,例如轮询、随机 等等。
1.3.2.负载均衡策略
负载均衡接口:com.netflix.loadbalancer.IRule
1.3.2.1.随机策略
com.netflix.loadbalancer.RandomRule
:该策略实现了从服务清单中随机选择一个服务实例的功能
1.3.2.2.轮询策略
com.netflix.loadbalancer.RoundRobinRule
:该策略实现按照线性轮询的方式依次选择实例的功能。具体实现如下,在循环中增加了一个count计数变量,该变量会在每次轮询之后累加并求余服务总数
总结
根据以上的源码可以看得出来IRule这个接口下面的实现类跟自己用的工具类原理还是大似相同的
1.4.基于ribbon实现负载均衡
1.4.1.修改ribbon_consumer
1.4.1.1.ConfigBean
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
/**
* 添加了@LoadBalanced注解之后,Ribbon会给restTemplate请求添加一个拦截器,在拦截器中获取
* 注册中心的所有可用服务,通过获取到的服务信息(ip,port)替换 serviceId 实现负载请求。
*/
//@LoadBalanced //开启负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//随机策略,返回对象是IRule返回射门么样的实现类,Ribbon就会用什么样的负载均衡机制
@Bean
public IRule iRule() {
return new RandomRule();
}
}
1.3.1.2.controller
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中注册的服务名称)
//使用ribbon后根据Nacos中注册的服务名称会生成一个拦截器,根据配置类里面的实现类进行相
//对应的服务调用
String serviceUrl = "ribbon-provider";
return restTemplate.getForObject("http://" + serviceUrl +
"/provider/getUserById/" + id, User.class);
}
}
1.3.2.测试
-
分别使用轮询和随机策略调用服务提供者
问题:
使用Ribbon时路径拼接和参数拼接显得很低级,那么可以使用Feign解决
2.1Feign介绍
2.1.1什么是Feign?
Feign之前是netflix公司的产品,netflix公司在开源了一段时间后闭源了,但是Spring在Feign的基础上进行了升级为OpenFeign,但是由于习惯现在都叫Feign
Feign是spring cloud提供的声明式的http客户端,工作在consumer端
feign支持springmvc注解
feign集成了Ribbon也支持负载均衡
(ribbon+restTemplate)+优化=feign
2.1.2 Feign的启动器
spring-cloud-starter-openfeign
2.2.Feign入门
2.2.1.创建服务提供者
2.2.1.1.创建工程
拷贝ribbon_provider_1
server:
port: 9090 #tomcat端口号
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848 #配置nacos地址和端口号
application:
name: feign-provider #配置nacos注册中心的服务名字
Controller
package com.bjpowernode.controller;
import com.bjpowernode.pojo.User;
import com.bjpowernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/provider")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
@RequestMapping("/deleteUserById")
public User deleteUserById(Integer id){
return userService.deleteUserById(id);
}
@RequestMapping("/addUser")
public User addUser(@RequestBody User user){
return userService.addUser(user);
}
}
Service
package com.bjpowernode.service;
import com.bjpowernode.pojo.User;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Override
public User getUserById(Integer id) {
return new User(id, "zhangsan",67);
}
@Override
public User deleteUserById(Integer id) {
return new User(id,"删除的用户",89);
}
@Override
public User addUser(User user) {
return user;
}
}
2.2.2.创建feign接口
2.2.2.1.创建工程
2.2.2.2.pom.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 是Feign的启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--导入公共的pojo模块-->
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2.2.3.feign
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;
//value="sentinel-provider"的值为服务提供者在nacos注册的服务名
@FeignClient(value="sentinel-provider")
//一级的请求路径,要和服务服务提供者的一级路径保持一致
@RequestMapping(value = "/provider")
//创建一个Feign接口此处必须是接口
public interface UserFeign {
//二级路径,必须要和服务提供者的路径保持一致
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable(value="id") Integer id);
@RequestMapping("/deleteUserById")
User deleteUserById(@RequestParam("id") Integer id);
@RequestMapping("/addUser")
User addUser(@RequestBody User user);
}
2.2.3.创建服务消费者
2.2.3.1.创建工程
拷贝ribbon_consumer
2.2.3.2.pom.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>
2.2.3.3.application.yml
server:
port: 80 #配置tomcat端口
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848 #配置nacos注册中心的端口
application:
name: feign-consumer #配置nasos中服务的名字
2.2.3.4.Controller
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 {
//注入Feign接口调用其方法根据方法上面的路径消费服务提供者
@Autowired
private UserFeign userFeign;
//@PathVariable此时接收参数的路径是:12.0.0.1/consumer/getUserById/5
//添加@PathVariable注解那么接收的路径为路径传参
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
return userFeign.getUserById(id);
}
//此处参数前面没添加@PathVariable那么接收的路径为:
//127.0.0.1/consumer/getUserById?id=5
//如果没有添加@PathVariable注解那么接收的路径为拼接传参
@RequestMapping("/deleteUserById")
public User deleteUserById(Integer id){
return userService.deleteUserById(id);
}
//此处接收的路径为127.0.0.1/consumer/getUserById?id=2&name=zhangsan&age=18
//接收的是json串,可以是对象,集合... ...
@RequestMapping("/addUser")
User addUser(@RequestBody User user){
return userService.addUser(user);
}
}
2.2.3.4.App启动类
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);
}
}
注意:
启动类上面添加了@EnableFeignClients默认扫描的是启动类下面的所有包及其子包,
但是Feign接口模块中的java目录下包的前缀要和consumer启动类包保持一致
解释:
2.2.4.测试
2.3.Feign原理流程
2.3.1.将Feign接口代理类注入到Spring容器中
1. @EnableFeignClients开启feign注解扫描:FeignClientsRegistrar.registerFeignClients()扫描被 @FeignClient标识的接口生成代理类,
并把接口和代理类交给Spring的容器管理。
2.为接口的方法创建RequestTemplate
当consumer调用feign代理类时,代理类会调用SynchronousMethodHandler.invoke()创建RequestTemplate(url,参数)
3.发出请求
代理类会通过RequestTemplate创建Request,然后client(URLConnetct、HttpClient、OkHttp)使用Request发送请求
2.4.问题:
有哪点不同呢?
接下来我们打个断点看看
放行之后
打断点再次访问getUSerById这个路径
由此可以看得出在接口模块中代理类方法上面的注解跟以前认知的已经不是一回事了
综上所述debug模式下template指定的路径和参数就是这么来的
3.Feign优化
1.开启feign日志
在consumer模块中application.yml文件中添加
feign:
client:
config:
feign-provider/default: #指定服务(来自cacos中的服务名字)/如果是default则配置全部模块输出
loggerLevel: full #配置feign输出内容
logging:
level:
com.bjpowernode.feign: debug #日志输出级别
启动服务并且访问路径控制台可以看得出:
2.接口模块添加http连接池依赖(默认打开)
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
原理:http请求交互的数据比较多所以我们需要一个连接池(数据库连接池同理)
3.gzip压缩
介绍
gzip是一种数据格式,采用deflate算法压缩data,当gzip压缩纯文本的文件时,效果非常明显,大约减少70%以上的文件大小
开启前:
添加gzip压缩
server:
compression:
enabled: true #开启gzip压缩
gzip默认压缩的文件类型
默认的已经够用了,不用再配置了
测试成功
4.feign超时
方法1
在yml文件中添加
ribbon:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
方法2
在yml文件中添加
feign:
client:
config:
feign-provider:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
默认Ribbon的全局超时时间为1秒,可以设置feign的全局时间和指定服务器超时时间,防止一场出现