springcloud记录篇3-springcloud客户端ribbon和feign

来源:互联网 发布:浙江师范行知学院 编辑:程序博客网 时间:2024/05/21 21:20

一 。客户端介绍

  在springcloud中发布的服务一般为http服务 使用http服务客户端即可调用 最底层的http协议是使用它tcp协议实现 清晰理解http协议请求响应模型可以

使用Socket来进行请求 这种方式开发成本太大,java.net包提供了 HttpURLConnection类来处理http协议  该类可以发送get和post请求,但是没有自动重连以及

自动解析 以及不同数据格式的处理 功能 apache提供的 common-net 机 github开源项目 okhttp 都可以很好的解决以上问题 springboot 引入这两个框架


二 。ribbon配置和演示

ribbon所有的配置  需要在spring的配置文件中 <client>.ribbon.* 也就是调用的客户端类全路径 .ribbon.*  

 *的部分可以参考 github官网https://github.com/Netflix/ribbon/wiki/Getting-Started

  比如ribbon的连接超时(sample-client.ribbon.ConnectTimeout=3000) 读超时(sample-client.ribbon.ReadTimeout=1000

ribbon使用RestTemplate实现http调用 提供了客户端负载均衡的能力

 1》引入ribbon maven依赖

groupid org.springframework.cloud artifactid spring-cloud-starter-ribbon
 2》测试调用和负载均衡

   测试环境为 《springcloud记录篇2-服务注册和发现》文章中的环境

eurekaserver    本机ip为 192.168.88.20  主机名 mymaster 端口 8761  这里为了简单就不开启eurekaserver的从服务eurekabackup服务提供和服务消费配置    eurekapub  本机ip  192.168.88.20 端口8086 应用名称 idserver  eurekapub  本机ip  192.168.88.20 端口8085 应用名称 idserver  eurekaconsumer 本机ip  192.168.88.20 端口8888
因为客户端eurekaconsumer(订阅者)需要调用eurekapub提供的服务 eurekapub需要做负载处理 开启两台设置不同端口

》》eurekapub配置如下

server:  port: 8086eureka:  client:    serviceUrl:        defaultZone: http://jiaozi:jiaozi123@mymaster:8761/eureka/#每个服务必须有一个唯一的应用名称     该服务的负载均衡 可以通过相同名称的一组服务 做集群 spring:  application:    name:   idserver  
运行主类

package cn.et;import java.util.UUID;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * 服务提供则  @EnableDiscoveryClient 将当前服务注册到Eureka服务器 * @author jiaozi * */@EnableDiscoveryClient(autoRegister=true)@SpringBootApplication@RestControllerpublic class EurekapubApplication {@RequestMapping("/getId")public String getId() {return UUID.randomUUID().toString();}public static void main(String[] args) {SpringApplication.run(EurekapubApplication.class, args);}}
同一个项目将 application.yml中server.port=8085 后启动  然后 改成8086后启动 
访问localhost:8761 输入eurekaserver的用户名和密码 


》》eurekaconsume配置如下

主类 EurekaconsumerApplication

package cn.et;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.netflix.ribbon.RibbonClient;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;import cn.et1.RuleSetting;@SpringBootApplication@EnableDiscoveryClient(autoRegister=true)@Configuration@RibbonClient(value="IDSERVER") //表示使用ribbon客户端 value表示发布方的服务名称public class EurekaconsumerApplication {@LoadBalanced  //启动负载均衡    @Bean    RestTemplate restTemplate() {        return new RestTemplate();    }public static void main(String[] args) {SpringApplication.run(EurekaconsumerApplication.class, args);}}
添加Controller类测试TestController

package cn.et;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import com.netflix.appinfo.InstanceInfo;import com.netflix.discovery.EurekaClient;import com.netflix.loadbalancer.IRule;@RestControllerpublic class TestController {@AutowiredEurekaClient client;@Autowired    private RestTemplate restTemplate;/** * 直接通过服务方地址 http://localhost:8080/getId 调用这种方式没有集群 * //@LoadBalanced不能添加 * @return */@RequestMapping("invokeService")public String invokeService() {String uuid=restTemplate.getForObject("http://localhost:8080/getId", String.class);return uuid;}/** * 通过在eureka server注册的 应用名称 直接来访问 * @LoadBalanced必须添加 * @return */@RequestMapping("invokeServiceBalance")public String invokeServiceBalance() {String uuid=restTemplate.getForObject("http://IDSERVER/getId",String.class);//getid是服务发布方提供的http方法return uuid;}@Autowired    private LoadBalancerClient loadBalancer;/** * 启动多个发布者 端口不一致 程序名相同  * 使用 * @LoadBalanced必须添加 * @return */@RequestMapping("choosePub")public String choosePub() {StringBuffer sb=new StringBuffer();for(int i=0;i<=10;i++) {ServiceInstance ss=loadBalancer.choose("IDSERVER");//从两个idserver中选择一个 这里涉及到选择算法sb.append(ss.getUri().toString()+"<br/>");}return sb.toString();}}
以上定义LoadBalancerClient 可以用于测试选择的服务器的算法 负载的算法的类需要实现 IRule 接口 以下列表摘自网络

策略名

策略描述

BestAvailableRule

选择一个最小的并发请求的server

AvailabilityFilteringRule

过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)

WeightedResponseTimeRule

根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。

RetryRule

对选定的负载均衡策略机上重试机制。

RoundRobinRule

roundRobin方式轮询选择server

RandomRule

随机选择一个server

ZoneAvoidanceRule

复合判断server所在区域的性能和server的可用性选择server

ribbon默认的配置类为 RibbonClientConfiguration 其中配置IRule的bean为

@Bean@ConditionalOnMissingBeanpublic IRule ribbonRule(IClientConfig config) {if (this.propertiesFactory.isSet(IRule.class, name)) {return this.propertiesFactory.get(IRule.class, config, name);}ZoneAvoidanceRule rule = new ZoneAvoidanceRule();rule.initWithNiwsConfig(config);return rule;}
默认使用的是ZoneAvoidanceRule  代码第一行判断是否存在IRule的规则 说明可以自己来设置
自定义ribbon负载规则两种方式 

方式1  自动bean 覆盖IRule (假设这里永远选择第一个服务器)

创建类 MyRule类 继承AbstractLoadBalancerRule 

package cn.et1;import java.util.List;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.AbstractLoadBalancerRule;import com.netflix.loadbalancer.ILoadBalancer;import com.netflix.loadbalancer.Server;public class MyRule extends AbstractLoadBalancerRule {@Overridepublic Server choose(Object key) {//获取负载均衡接口ILoadBalancer lb=getLoadBalancer();//获取所有的服务器 包括不可用和可用List<Server> allServers=lb.getAllServers();//获取所有可用服务器 List<Server> avaServers=lb.getReachableServers();//永远返回第一台return avaServers.get(0);}private IClientConfig clientConfig=null;@Overridepublic void initWithNiwsConfig(IClientConfig arg0) {this.clientConfig=arg0;}}
创建bean定义类RuleSetting(该类不能被扫描到 要么去掉 @Configuration注解要么放在 非main方法的包和子包中 否则会出错)

package cn.et1;import org.springframework.context.annotation.Bean;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.IRule;import com.netflix.loadbalancer.RandomRule;public class RuleSetting {@Beanpublic IRule ribbonRule(IClientConfig config) {return new MyRule(); //这里是自定义的规则//return new RandomRule();//也可以返回自带一些规则 比如随机选择等}}

修改主类 EurekaconsumerApplication注解RibbonClinet

@RibbonClient(value="IDSERVER",configuration=RuleSetting.class)
访问 客户端 choosePub 发现随机选择10次发现每次都只出现第一个服务器的ip和端口


方式2  使用配置文件设置规则的类(开始IDSERVER表示是调用发布者名称)

IDSERVER.ribbon.NFLoadBalancerRuleClassName=cn.et1.MyRule

其他具体参考(http://cloud.spring.io/spring-cloud-static/Dalston.SR4/multi/multi_spring-cloud-ribbon.html)

三 。feign配置和演示

 feign同是github上开源项目可以轻松实现 调用http服务,是一个声明式的rest调用框架  项目地址为:https://github.com/OpenFeign/feign

以下实例测试环境 同 ribbon  不同的是 将ribbon调用换成了feign

springboot封装了ribbon将原始ribbon的用法替换成了springmvc的方式

feign具体用法参考 http://cloud.spring.io/spring-cloud-static/Dalston.SR4/multi/multi_spring-cloud-feign.html

修改服务发布方 eurekapub 提供一个get和post的服务 

1》发布方修改

启动主类 EurekapubApplication

package cn.et;import java.util.UUID;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * 服务提供则  @EnableDiscoveryClient 将当前服务注册到Eureka服务器 * @author jiaozi * */@EnableDiscoveryClient(autoRegister=true)@SpringBootApplication@RestControllerpublic class EurekapubApplication {@RequestMapping("/getId")public String getId() {return UUID.randomUUID().toString();}/** * 测试get方法 * @param id * @return */@GetMapping("/getUser/{id}")public User getId(@PathVariable String id) {User user=new User();user.setUserName("zs_"+id);user.setPassword("ls_"+id);return user;}/** * 测试post方法 post方法获取内容 必须添加@RequestBody注解否则无法获取 * @param user * @return */@PostMapping("/saveUser")public User getId(@RequestBody User user) {return user;}public static void main(String[] args) {SpringApplication.run(EurekapubApplication.class, args);}}
测试的用户类

package cn.et;import lombok.Data;@Datapublic class User {private String userName;private String password;}
这里用到了 lombok 引用maven依赖

<dependency>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId></dependency>
找到jar包后 运行  java -jar lombok-1.16.18.jar 选择eclipse 安装目录 自动安装插件 该包原理是 通过插件 伪造get和set方法 不添加get set 可以使用get和

set方法 编译时自动添加get和set方法

修改完成后 依次启动 8085和8086两个实例

2》订阅方eurekaconsume修改

》》springmvc调用方式
 拷贝发布方的User到到当前项目

添加调用客户端类TestClient

package cn.feign;import org.springframework.cloud.netflix.feign.FeignClient;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.RequestMethod;@FeignClient("idserver") //使用FeignClient 告知发布方的应用名称 默认使用ribbon进行负载均衡public interface TestClient {@RequestMapping(method = RequestMethod.GET, value = "/getUser/{id}") //因为是调用必须明确告诉是GET方式传入的参数是idpublic User getId(@PathVariable("id") String id) ;@RequestMapping(method = RequestMethod.POST, value = "/saveUser",consumes="application/json")public User saveUser(@RequestBody User user) ;//post请求必须添加@RequestBody}
添加Controller类

package cn.feign;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import com.netflix.loadbalancer.Server;@RestControllerpublic class TestController {@Autowiredprivate TestClient client;@Autowiredprivate LoadBalancerClient flb;@RequestMapping("chooseFeign")public String chooseFeign() {StringBuffer sb=new StringBuffer();for(int i=0;i<=10;i++) {ServiceInstance ss=flb.choose("IDSERVER");sb.append(ss.getUri()+"<br/>");}return sb.toString();}/** * 使用Feign调用client的get方式 * @return */@RequestMapping("getUser")public User getUser(String id) {User user=client.getId(id);return user;}/** * 使用Feign调用client的post方法 * @return */@RequestMapping("saveUser")public User saveUser(User user) {User muser=client.saveUser(user);return muser;}}
添加主类EurekaconsumerApplication
package cn.feign;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.netflix.feign.EnableFeignClients;import org.springframework.cloud.netflix.feign.FeignClientsConfiguration;import org.springframework.cloud.netflix.ribbon.RibbonClient;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;import cn.et1.FeignConf;import cn.et1.RuleSetting;@SpringBootApplication@EnableDiscoveryClient(autoRegister=true)@EnableFeignClients(defaultConfiguration=FeignClientsConfiguration.class)@Configurationpublic class EurekaconsumerApplication {public static void main(String[] args) {SpringApplication.run(EurekaconsumerApplication.class, args); }}
启动服务 测试get方法(consume端口 设置8888)
http://localhost:8888/getUser?id=20

成功调用输出

{"userName":"zs_20","password":"ls_20"}


启动服务 测试get方法(consume端口 设置8888)
http://localhost:8888/saveUser?userName=zs&password=123456

成功调用输出

{"userName":"zs","password":"123456"}

》》feign调用方式

具体feign的语法参考https://github.com/OpenFeign/feign

添加一个配置类 告知springcloud 使用feign原始的操作方式

package cn.et1;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import feign.Contract;import feign.Logger;@Configurationpublic class FeignConf {@Beanpublic Contract feignContract() {return new feign.Contract.Default(); //返回原始的解析方式} }


添加客户端调用类 TestClient2

package cn.feign;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import cn.et1.FeignConf;import feign.Param;import feign.RequestLine;//这里注意一点 name 如果设置了一次 他的配置类 所有相同name的都会使用这个配置类 这里为了掩饰直接用url  给个特殊的名字 名字只是标识@FeignClient(name="mytest",url="http://localhost:8086",configuration=FeignConf.class)public interface TestClient2 {@RequestLine("GET /getUser/{id}")public User getId(@Param("id") String id) ;@RequestLine("POST /saveUser")public User saveUser(User user) ;}
添加 TestController1 用于测试

package cn.feign;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import com.netflix.loadbalancer.Server;@RestControllerpublic class TestController {@Autowiredprivate TestClient2 client2;/** * 使用Feign调用 * @return */@RequestMapping("getUser2")public User getUser2(String id) {User user=client2.getId(id);return user;}/** * 使用Feign调用 * @return */@RequestMapping("saveUser2")public User saveUser2(User user) {User muser=client2.saveUser(user);return muser;}}

主类相同 然后运行 同样访问方式 访问 测试ok

》》日志添加

springboot允许客户端在调用http协议时打印调用http日志信息

application.yml中添加(logging.level.声明调用的客户端类  只支持debug一种方式

logging.level.cn.feign.TestClient2: DEBUG
修改 FeignConf类 添加打印所有日志

package cn.et1;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import feign.Contract;import feign.Logger;@Configurationpublic class FeignConf {@Beanpublic Contract feignContract() {return new feign.Contract.Default();} @Bean    Logger.Level feignLoggerLevel() {        return Logger.Level.FULL;    }}
启动访问 发现日志成功输出