服务之间的调用
上一篇搭建了一个Product服务,今天记录一下学习过程: 服务消费者ribbon和feign实战和注册中心高可用
笔记如下:
- 常用的服务间调用方式
- RPC:
1.1 远程过程调用,像调用本地服务(方法)一样调用服务器的服务
1.2 支持同步、异步调用
1.3 客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接
1.4 PRC数据包小
protobuf
thrift
rpc:编解码,序列化,链接,丢包,协议 - Rest(Http):
http请求,支持多种协议和功能
开发方便成本低
http数据包大
java开发:HttpClient,URLConnection
- RPC:
项目搭建:微服务调用方式之ribbon实战 订单调用商品服务
简介:实战电商项目 订单服务 调用商品服务获取商品信息
1、创建order_service项目
2、开发伪下单接口
3、使用ribbon. (类似httpClient,URLConnection)
启动类增加注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
4、根据名称进行调用商品,获取商品详情
一:order订单服务搭建:
搭建一个springboot应用,与搭建Product服务一样,依赖选择如下
实体类:
package com.xt.tools.erueka.order_server.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/*
* @description: 说明
* @author: 小谭
* @date: 2019/12/7 0:08
*/
@Data
public class ProductOrder implements Serializable {
private int id;
/**
* 商品名称
*/
private String productName
/**
* 订单号
*/
private String tradeNo;
/**
* 价格,分
*/
private int price;
private Date createTime;
private int userId;
private String userName;
}
Servic:
package com.xt.tools.erueka.order_server.service;
import com.xt.tools.erueka.order_server.domain.ProductOrder;
/*
* @description: 说明
* @author: 小谭
* @date: 2019/12/7 0:08
*/
public interface orderService {
ProductOrder save(int userId, int product);
}
实现类:
package com.xt.tools.erueka.order_server.service;
import com.xt.tools.erueka.order_server.domain.ProductOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
/*
* @description: 说明
* @author: 小谭
* @date: 2019/12/7 0:09
*/
@Service
public class orderServiceImpl implements orderService {
@Autowired
private RestTemplate restTemplate;
@Override
public ProductOrder save(int userId, int productId) {
//获取商品详情 http://localhost:8771/api/v1/product/find?id=1
String url="http://product-service/api/v1/product/find?id="+productId;
System.out.println(url);
Map<String,Object> productMap = restTemplate.getForObject(url, Map.class);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
productOrder.setProductName(productMap.get("name").toString());
productOrder.setPrice(Integer.parseInt(productMap.get("price").toString()));
System.out.println(productOrder);
return productOrder;
}
}
其中product-service代表着之前的Product服务
controller:
package com.xt.tools.erueka.order_server.controller;
import com.xt.tools.erueka.order_server.domain.ProductOrder;
import com.xt.tools.erueka.order_server.service.orderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/*
* @description: 说明
* @author: 小谭
* @date: 2019/12/7 0:06
*/
@RestController
@RequestMapping("/api/v1/order")
public class orderController {
@Autowired
private orderService OrderService;
//模拟下单接口
@RequestMapping("save")
public Object save(@RequestParam("userId") int userId,@RequestParam("productId") int product){
return OrderService.save(userId,product);
}
}
启动类:
package com.xt.tools.erueka.order_server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
// 可做负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
配置文件:
server:
port: 8781
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: order-service
最后,为了分辨服务在哪个端口调用,修改一下product服务代码
@Value("${server.port}")
private String port;
@RequestMapping("find")
public Object product(@RequestParam("id") int id){
Product product = productService.findById(id);
product.setName(product.getName()+"----"+port);
return product;
}
启动服务:
为了方便观察负载均衡现象,我这里创建了3个Product实例,当然也可以以其他方法运行多个实例来模拟集群
-Dserver.port=8773
刷新注册中心:
到此:各个服务都已经注册到注册中心:
访问订单order服务接口:
http://localhost:8781/api/v1/order/save?userId=123&productId=1
有数据返回,说明服务正常,并且调用端口是:8771,接下来不断刷新页面
一直在8771-8773之间轮流调用,因为默认采用的是负载均衡策略中的轮询策略
ribbon服务间调用负载均衡源码分析
1、分析@LoadBalanced
1)首先从注册中心获取provider的列表
2)通过一定的策略选择其中一个节点
3)再返回给restTemplate调用
接下来修改一下负载均衡的策略
自定义负载均衡策略:http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_customizing_the_ribbon_client_by_setting_properties
在配置文件yml里面,自定义负载均衡策略
#自定义负载均衡策略
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
策略选择:
1、如果每个机器配置一样,则建议不修改策略 (推荐)
2、如果部分机器配置强,则可以改为 WeightedResponseTimeRule
刷新页面:
第二次刷新:8773端口
第三次刷新:8772端口:
至此,默认的负载均衡策略修改完毕(当然也可以在源码上打断点调试证明):
二:微服务调用方式之feign
Feign: 伪RPC客户端(本质还是用http)
官方文档: https://cloud.spring.io/spring-cloud-openfeign/
1、使用feign步骤讲解(新旧版本依赖名称不一样)
加入依赖
启动类增加 @EnableFeignClients
增加一个接口 并 @FeignClient(name=“product-service”)
2、注意点:
1、路径
2、Http方法必须对应
3、使用requestBody,应该使用@PostMapping
4、多个参数的时候,通过@RequestParam(“id”) int id)方式调用
3.编码实战:
添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
新建service接口:
package com.xt.tools.erueka.order_server.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/*
* @description: 说明
* @author: 小谭
* @date: 2019/12/8 21:29
*/
@FeignClient(name="product-service")
public interface ProductClient {
@RequestMapping("/api/v1/product/find")
String findById(@RequestParam("id")int id);
}
修改service接口:
启动类:
数据正常返回
服务间的调用方式ribbon、feign选择
1、ribbon和feign两个的区别和选择
选择feign
默认集成了ribbon
写起来更加思路清晰和方便
采用注解方式进行配置,配置熔断等方式方便
2、超时配置
默认optons readtimeout是60,但是由于hystrix默认是1秒超时
#修改调用超时时间
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
模拟接口响应慢,线程睡眠新的方式
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
GitHub项目: https://github.com/XT962464oo/Eureka_demo