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

一 。客户端介绍

  在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")
	public 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;
    }
}
启动访问 发现日志成功输出





原文地址:https://www.cnblogs.com/liaomin416100569/p/9331187.html