负载均衡-Ribbon-Openfeign
想想,什么叫负载均衡,就是把大量请求分散均衡的放在各个节点,不会让单个节点负载太大而崩掉。
Ribbon
方法一:硬编码分发端口实现负载均衡
在controller处,实现两个端口的轮询转发,通过奇偶数的规律,给两个端口依次分发,但是这样不好的是,端口定死了,如果后台节点宕机,那么程序就执行不了,数据就会访问失败 。
@RestController public class UserController { //定义一个全局变量 private int count = 0; @GetMapping("/user/findps") public String findps(){ System.out.println("正在调用客户端"); //http请求工具去访问 RestTemplate restTemplate = new RestTemplate(); //访问另一台机器上的端口 每次执行函数都会+1 String ps = restTemplate.getForObject("http://localhost:"+ randomPort()+"/product/findps", String.class); return ps; } private String randomPort() { int result = this.count++%2; //每次执行完后+1 return result == 0?"9996":"9997"; //能被2整除的话 就得到9996 不能得到2整除的话就是9997 } }
新增restTemplateConfig配置类
@Configuration //配置类 public class RestTemplateConfig { //在工厂中创建一个restTemplate对象 调用的时候具备负载均衡能力的工具 @Bean @LoadBalanced //代表ribbon负载均衡的restTemplate 客户端对象 public RestTemplate getRestTemplate() { return new RestTemplate(); } }
在后台同一个启动类,开启两个启动类端口,模拟两个服务同时在不同机器上运行。开启另一个端口。
访问同一个端口,会打印出经过的端口和数据
这个打印的数据对应的products中的模拟数据源
方法二:使用负载均衡策略进行分发请求
1、查看请求的主机名,端口,地址
@Resource //客户端服务发现
private DiscoveryClient discoveryClient;
// 访问路由
@GetMapping("/user/findps2") public List<ServiceInstance> getPs2() { List<ServiceInstance> products = discoveryClient.getInstances("product9997"); for(ServiceInstance product: products) { //节点 System.out.println(product.getHost()); //端口 System.out.println(product.getPort()); //uri System.out.println(product.getUri()); } return products; }
访问效果
2、 只显示当下分发的端口信息
@Resource
private LoadBalancerClient loadBalancerClient;
// 测试3的访问路径 @GetMapping("/user/findps3") public String getProduct() { //根据负载均衡策略来拉取一个服务调用 ServiceInstance product = loadBalancerClient.choose("product9997"); //节点 System.out.println(product.getHost()); //端口 System.out.println(product.getPort()); //uri System.out.println(product.getUri()); return product.toString(); }
一次访问结果:
再次访问结果,serviceId代表在consule注册中心进行服务名称,我这里端口不一样的就是因为我同一个启动类开启了两个启动端口程序。
3、只显示分发的端口数据
//测试4的访问路径 @GetMapping("/user/findps4") public String getPruduct2() { ServiceInstance product = loadBalancerClient.choose("product9997"); //服务名称 //使用远程调用工具 RestTemplate restTemplate = new RestTemplate(); //获取后台负载均衡后的uri地址拼接 String uri = "http://" + product.getHost() + ":" + product.getPort() + "/product/findps"; //发送请求 String forObjects = restTemplate.getForObject(uri, String.class); return forObjects; //这个是拿到请求返回的值 }
一次访问效果
第二次访问效果
4、简化步骤
@GetMapping("/user/findps5") public String getProduct3() { //发送请求到负载均衡 String forObject = restTemplate.getForObject("http://product9997/product/findps", String.class); return forObject; }
一次访问效果
二次访问效果
Ribbon负载均衡算法
-RoundRobinRule:轮询策略,按顺序循环选择Server
-RandomRule:随机策略,随机选择server
-AvailabilityFilteringRule:可用过滤策略,会先过滤由于多次访问故障而处于断路器跳闸状态的服务,一个服务的连接数到达了规定的阈值,就过滤掉这个服务,将剩下的服务进行轮询访问。
-weightedResponseTimeRule:响应时间加权策略,根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大,被选中的概率就越高,换句话说,就是谁抢的快,谁的权重越高,访问次数越多。刚启动时如果统计信息不足,则使用RoundRobinRule。
-RetryRule:重试策略,先安装RoundRobRule策略获取服务,如果获取失败则制定时间内进行重试,获取可用的服务。就是说服务获取失败,规定时间内重新尝试获取该服务。
-BestAviableRule:最低并发策略,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务。先过滤掉那种多次访问后崩掉的服务,在选择那种访问量小的服务悄悄溜走。
更换策略
前面RestTemplate类,要补充一点内容
@Configuration //配置类 public class RestTemplateConfig { //在工厂中创建一个restTemplate对象 调用的时候具备负载均衡能力的工具 @Bean @LoadBalanced //代表ribbon负载均衡的restTemplate 客户端对象 public RestTemplate getRestTemplate() { return new RestTemplate(); } @Bean public IRule ribbonRule() { return new RandomRule();//这里配置策略,和配置文件对应 } }
在访问端userproduct的properties中进行策略的更换配置
Openfign
ribbon在使用的过程中有很多代码的耦合,所以使用Openfign去进行优化访问。Openfeign默认继承了ribbon。通过访问user服务的连接,查询到相同的product数据,但是访问的是不同的接口。
实现步骤:1、引入依赖
<!--引入openfeign依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2、client访问客户端
//调用服务-value指定服务调用名称 @FeignClient("product9997") //被调用的服务名称 server_id public interface ProductClient { @GetMapping("/product/findps") //调用客户端的指定url public Map<String, Object> findPs(); }
我这里client包不能乱放,springbootApplication扫描不了,如果你的也是出现类不能被容器发现,请查看该地址:https://blog.csdn.net/qq_44349459/article/details/113408191
3、controller中访问,在类上需要加上两个注解
添加访问路由
@Resource private ProductClient productClient; @GetMapping("/user/findps6") public String findAlls() { Map<String, Object> ps = productClient.findPs(); return ps.toString(); }
4、效果,第一次访问
第二次访问结果
Openfeign实现参数传递-Get
user服务传递参数到product中,然后product得到该参数查询相应的数据返回给浏览器显示。
开发步骤:1、product服务中,在productcontroller中添加模拟数据源
@GetMapping("/product/findById") //这里需要访问路由 和user服务对接 public String findById(String productId) { System.out.println("port--->" + port); System.out.println("productId--->" + productId); //用hashmap来模拟数据源 HashMap<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "product-server port------>" + port); map.put("productId", productId); return map.toString(); }
2、在user服务中,productclient添加接口
@GetMapping("/product/findById") //访问路由 public String findById(@RequestParam("productId")String productId); //这里是传递参数
3、在user服务中,Usercontroller中添加访问路由
@Resource private ProductClient productClients; //用户通过id及访问product @GetMapping("/user/findPById") public String getTest1(String productid) { String msg = productClients.findById(productid); System.out.println(msg); return msg; }
4、浏览器访问
http://localhost:9998/user/findPById?productid=10
第一次访问
第二次访问
Openfeign实现参数传递-Post
使用浏览器Get得到参数值,但是user服务向product服务请求数据时,采用的是post请求方式。
开发步骤:1、在product服务中productController进行模拟数据源
@PostMapping("/product/persist") //持久化操作 ==save public String persist(String name) { //这里需要访问路由 和user对接 System.out.println("port" + port); System.out.println("name" + name); HashMap<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "psot-->product-server port-->" + port); map.put("name", name); return map.toString(); }
2、在user服务中,productClient类中添加接口方法
@PostMapping("/product/persist") //这里是对接user 和product服务的 public String persist(@RequestParam("name")String name); //传参数记得给requestParam
3、在user服务中,userController进行路径访问,使用GetMapping去接收浏览器传过来的参数
@Resource private ProductClient productClients; @GetMapping("/user/saveProduct") //这里也可以使用浏览器框来接收 到后台用post进行提交 public String postTest1(String name) { String msg = productClients.persist(name); //调用接口内的持久化操作 return msg; }
4、访问效果,第一次访问
第二次访问
Openfeign实现参数传递-Post对象
user服务向product服务传递对象,product接收并且返回显示到浏览器。
实现步骤:1、在product服务和user服务中,都创建Vo类对象
@Data public class ProductVo { private Integer id; private String name; private String date; }
2、product服务中模拟数据源
@PostMapping("/product/save") //post发送对象 public String save(@RequestBody ProductVo productVo){ HashMap<String, Object> map = new HashMap<>(); map.put("Status", true); map.put("msg", "object-product-server port--->:" + port); map.put("productVo", productVo); return map.toString(); }
3、user服务中productclient接口方法
@PostMapping("/product/save") //对应的product后台路由 public String save(@RequestBody ProductVo productVo);//
4、user服务中controller访问
@Resource private ProductClient productClients; //这个对接的是浏览器的路由 @GetMapping("/user/persistProduct") //这里也可以使用浏览器框来接收 到后台用post进行提交 public String postTest2(ProductVo productVo) { String msg = productClients.save(productVo); //接口访问 return msg; }
5、访问效果,第一次访问:http://localhost:9998/user/persistProduct?id=1&name=aaa&date=20200202
第二次访问
Openfeign的超时管理
openfeign连接请求默认是1秒,如果超过1秒就访问失败。
1、做个小测试:在productcontroller内,增加线程3秒的休眠时间,保证请求超过三秒
@PostMapping("/product/persist") //持久化操作 ==save public String persist(String name) { //这里需要访问路由 和user对接 //让后台这个服务停止3秒 //openfeign有个默认请求时间为1秒 // 这里模拟的有时候访问数据时间超过1秒 是3秒 try{ Thread.sleep(3000); }catch(InterruptedException e) { e.printStackTrace(); } System.out.println("port" + port); System.out.println("name" + name); HashMap<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "psot-->product-server port-->" + port); map.put("name", name); return map.toString(); }
在浏览器访问的结果就是,超时报错
2、openfeign的时间管理
在user服务中,properties配置文件中添加超时设置
feign.client.config.product9997.connect-timeout=5000
feign.client.config.product9997.read-timeout=5000
浏览器再次访问,成功
3、openfeign默认时间
以上是对单个服务模块进行时间管理设置,有默认default属性可以将所有服务都设置成该时间
#default feign.client.config.default.connect-timeout=5000 feign.client.config.default.read-timeout=5000
这个访问也是没有问题的
Openfeign日志功能
1、日志的级别
#日志级别:
#openfeign 默认对debug级别信息做出响应
#none 不记录任何事情
#basic 请求方法,url,响应状态码,执行时间
#header 在basic基础上,记录请求和响应时间的header
#full 请求,响应的header body 元数据
2、设置方式
在user服务模块的properties中设置日志的访问级别。user作为访问入口,调用debug级别。product服务作为被访问采用full级别。
#设置方式 #分模块 服务进行设置 feign.client.config.product9997.logger-level=full #指定feign调用的客户端对象包所在的包 必须是debug级别 logging.level.com.demo.user9998.controller.client=debug
访问效果
2021-01-30 12:34:09.356 INFO 508 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: product9997.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] <--- HTTP/1.1 200 (3358ms) 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] connection: keep-alive 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] content-length: 62 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] content-type: text/plain;charset=UTF-8 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] date: Sat, 30 Jan 2021 04:34:11 GMT 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] keep-alive: timeout=60 2021-01-30 12:34:11.510 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] 2021-01-30 12:34:11.512 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] {msg=psot-->product-server port-->9996, name=pig, status=true} 2021-01-30 12:34:11.512 DEBUG 508 --- [nio-9998-exec-2] c.d.u.controller.client.ProductClient : [ProductClient#persist] <--- END HTTP (62-byte body)
如果单个的级别设置麻烦,就使用全局配置
#全局设置 #feign.client.config.default.logger-level=full
总结
本章演示了ribbon负载均衡的算法端口切换、使用负载均衡策略、其他负载均衡策略(轮询、随机、可用过滤、重试、响应时间加权、最低并发等策略),openfeign传参的请求方式Get、Post,openfeign时间管理、openfeign日志功能