一 。客户端介绍
在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: 8086
- eureka:
- 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
- @RestController
- public 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;
-
- @RestController
- public class TestController {
- @Autowired
- EurekaClient 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();
- }
- }
gu
关于RestTemplate的用法 遵循rest风格
比如get请求 比如发布方的地址是 /user/{userId}
调用方的代码为:
- String result=restTemplate.getForObject("http://SENDMAIL/user/{id}", String.class,id);
比如 post请求 比如发布方的代码是 (post使用请求体获取参数 所以可以通过互相传递map的方式)
- @PostMapping("/s")
- String send(@RequestBody Map<String,Object> map){}
调用方的代码为:
- HttpHeaders requestHeaders=new HttpHeaders();
- requestHeaders.setContentType(MediaType.APPLICATION_JSON);
- requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
- Map<String, Object> map=new HashMap<String, Object>();
- map.put("email_to", email_to);
- map.put("email_subject", email_subject);
- map.put("email_content", email_content);
- HttpEntity<Map> request=new HttpEntity<Map>(map, requestHeaders);
- restTemplate.postForObject("http://SENDMAIL/send", request, String.class);
以上定义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
- @ConditionalOnMissingBean
- public 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 {
-
- @Override
- public 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;
- @Override
- public 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 {
- @Bean
- public 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
- @RestController
- public 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;
-
- @Data
- public 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方式传入的参数是id
- public 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;
-
- @RestController
- public class TestController {
- @Autowired
- private TestClient client;
- @Autowired
- private 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)
- @Configuration
- public 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;
- @Configuration
- public class FeignConf {
- @Bean
- public 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;
-
- @RestController
- public class TestController {
- @Autowired
- private 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;
- @Configuration
- public class FeignConf {
- @Bean
- public Contract feignContract() {
- return new feign.Contract.Default();
- }
- @Bean
- Logger.Level feignLoggerLevel() {
- return Logger.Level.FULL;
- }
- }
启动访问 发现日志成功输出