https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集;
使用
使用restTemplate访问restful接口非常的简单粗暴无脑。
(url,requestMapResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
@RestController
@Slf4j
public class OrderController {
//需要访问的目标项目地址
public static final String PARAMENT_URL = "http://localhost:8001";
//在注入之前,别忘了在配置类中注入RestTemplate
@Autowired
private RestTemplate restTemplate;
//因为时浏览器客户端,只有get请求
@GetMapping("/consumer/addPay")
public ResponResult<Void> addPay(Pay pay) {
return restTemplate.postForObject(PARAMENT_URL+"/pay/addPay", pay, ResponResult.class);
}
@GetMapping("/consumer/queryPay")
public ResponResult<List<Pay>> queryPay() {
return restTemplate.getForObject(PARAMENT_URL+"/pay/queryPay", ResponResult.class);
}
}
@RestController
@RequestMapping("pay")
@Slf4j
public class PayController {
@Autowired
private PayService payService;
@GetMapping("queryPay")
public ResponResult<List<Pay>> queryAll() {
return payService.queryAll();
}
@PostMapping("addPay")
public ResponResult<Void> addPay(
@RequestBody Pay pay
) {
return payService.addPay(pay);
}
}
@RestController
@Slf4j
public class OrderController {
//如果是负载均衡,端口写死是个Bug,所以我们采用服务名,不对外暴露ip和端口
// public static final String PARAMENT_URL = "http://localhost:8001";
public static final String PARAMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/addPay")
public ResponResult<Void> addPay(Pay pay) {
return restTemplate.postForObject(PARAMENT_URL+"/pay/addPay", pay, ResponResult.class);
}
@GetMapping("/consumer/queryPay")
public ResponResult<List<Pay>> queryPay() {
return restTemplate.getForObject(PARAMENT_URL+"/pay/queryPay", ResponResult.class);
}
}
开启负载均衡
@Configuration
public class OrderConfig {
@Bean("restTemplate")
@LoadBalanced //开启负载均衡,默认轮询的方式
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/discovery")
public Object queryDiscovery() {
//获取所的已经注册的服务名
List<String> services = discoveryClient.getServices();
//获取指定服务名下的具体实列,比如获取CLOUD-PAYMENT-SERVICE
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
String host = instance.getHost();
int port = instance.getPort();
URI uri = instance.getUri();
String instanceId = instance.getInstanceId();
String serviceId = instance.getServiceId();
}
HashMap<String, Object> maps = new HashMap<>();
maps.put("services", services);
maps.put("instances", instances);
return maps;
}
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
SpringCloudRibbon是基于NetflixRibbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡街算法和服务调用。
Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
简单的说,就是在配置文件中列出Load Balance er(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
我们很容易使用Rib obon实现自定义的负载均衡算法。
什么叫Ribbon: 一句话就是 RestTemplate+负载均衡
官网: https://github.com/Netflix/ribbon/wiki/Getting-Started
未来趋势可能会被 LoadBalancer
Ribbon 主要负载均衡,它和nginx有什么区别,区别在于,打个比方,nginx是拦在最前面的,比如医院 nginx是医院大门,ribbon是里面某个小科室
LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
常见的负载均衡有软件Nginx,LVS,硬件F5等。
Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请青求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务5列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
Nginx和F5就属于集中式负载均衡
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5也可以是软件 如nginx),由该设施负责把访问请求通过某种策 略转发至服务的提供方;
Ribbon就属于进程内负载均衡
进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB
,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
负载均衡分类
负载均衡策略
1、随机策略——RandomRule
2、轮询策略——RoundRobinRule
注:Ribbon默认策略
3、重试策略——RetryRule
4、最低并发策略——BestAvailableRule
5、可用过滤策略——AvailabilityFilteringRule
过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值
性能仅次于最低并发策略。
6、响应时间加权策略——WeightedResponseTimeRule
每隔30秒计算一次服务器响应时间,以响应时间作为权重,响应时间越短的服务器被选中的概率越大。
7、区域权衡策略——ZoneAvoidanceRule
Ribbon的负载均衡策略使用建议
一般情况下,推荐使用最低并发策略,这个性能比默认的轮询策略高很多。
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了
3.4.1自定义一个接口
public interface ILoadBalancer {
ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances);
}
3.4.2 自定义一个实现类组件
@Component
public class MyILoadBalancer implements ILoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
//通过CAS加自旋锁方式保证不被改动
private final int getAndIncrement(){
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("***********第几次访问,次数next为:" + next);
return next;
}
@Override
public ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances) {
if (serviceInstances == null || serviceInstances.size() <= 0) {
return null;
}
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
3.4.3 controller层
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = myILoadBalancer.getServiceInstance(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/pay/lb",String.class);
}
远程调用 Dubbo 与 Feign 的区别
RPC,全称为Remote Procedure Call,即远程过程调用,是一种计算机通信协议。
单体项目时:一次服务调用发生在同一台机器上的同一个进程内部,也就是说调用发生在本机内部,因此也被叫作本地方法调用。
微服务项目时:服务提供者和服务消费者运行在两台不同物理机上的不同进程内,它们之间的调用相比于本地方法调用,可称之为远程方法调用,简称 RPC;
远程调用的过程中会涉及到建立网络链接(http、socket)、进行网络通信(开放协议、私有协议)、进行数据传输(序列化和反序列化)。
需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架
可靠的寻址方式(主要是提供服务的发现),比如可以使用Zookeeper来注册服务等等
序列化
当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。
反序列化
当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用, 通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。
需要有比较高效的序列化框架,比如谷歌的ProtoStuff 序列化框架
B机器进行本地调用(通过代理Proxy)之后得到了返回值,此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,最后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。
Rpc调用包含以下几个部分
而RPC框架是把调用、编码/解码的过程给封装起来,让用户感觉上像调用本地服务一样的调用远程服务。
在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
序列化和反序列化是用于网络传输过程中的操作,那么规定序列化操作的我们可以称之为序列化协议
序列化方式最简单的一种就是直接实现JDK自带的序列化接口Serializable就可以了,但是这种方式不支持跨语言调用,而且性能比较低。现在常用的序列化协议有 Hessian,Kyro,Protostuff。
另外还有JSON和XML这种文本类序列化方式,可读性比较好,但是性能也比较差。
BIO
):客户端发一次请求,服务端生成一个对应线程去处理。当客户端同时发起的请求很多时,服务端需要创建 多个线程去处理每一个请求,当达到了系统最大的线程数时,新来的请求就无法处理了;NIO
):客户端发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过 I/O 多路复用技术进行处理。就是把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销;RPC框架选择NIO进行传输
AIO
):客户端发起一个 I/O 操作然后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题;