一、什么是微服务
微服务简言之就是基于单体应用围绕业务进行服务拆分出来每一个服务独立应用,独立部署、独立运行在自己的计算机进程中,基于分布式管理。
二、如何解决微服务间通信
- HTTP Rest方式 :使用http协议进行数据传递 (JSON数据)
- RPC方式 : 远程过程调用 (二进制)
OSI七层:物理层 数据链路层 网络层 传输层(rpc) 会话层 表示层 应用层(http)
springcloud使用http协议传递数据
三、如何在Java中发起http方式的请求
- 网络编程
- spring框架提供HttpClient对象–RestTemplate
四、使用RestTemplate实现服务间通信
1. 创建springboot项目(ORDER、SHOP)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 注册到consul注册中心
引入依赖
<!--引入consul依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--健康检查依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改配置,向consul server 注册
# 向consul server 服务注册地址
spring.cloud.consul.host=192.168.0.133
spring.cloud.consul.port=8500
# 指定注册服务的服务名称,默认${spring.application.name}
spring.cloud.consul.discovery.service-name=${spring.application.name}
# 注册时按我的ip地址注册
spring.cloud.consul.discovery.prefer-ip-address=true
spring.cloud.consul.discovery.ip-address=192.168.0.119
3. 入口类加入服务注册client注解
@SpringBootApplication
@EnableDiscoveryClient
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
4. 启动查看consul web端
5. 测试controller
ShopController
@RestController
@RequestMapping("shopConApi")
public class ShopController {
private static final Logger log = LoggerFactory.getLogger(ShopController.class);
@GetMapping("shop")
public String shop() {
log.info("shop ok !!!");
return "shop shop shop";
}
}
OrderController
@RestController
@RequestMapping("orderConApi")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Resource
private RestTemplate restTemplate;
@GetMapping("order")
public String order() {
log.info("order ok !!!");
return "order order order";
}
}
order服务调用shop服务
@GetMapping("invokeShop")
public String invokeShop() {
String getResult = restTemplate.getForObject("http://192.168.0.119:8881/shopConApi/shop", String.class);
return "invoke success " + getResult;
}
五、使用RestTemplate对象实现服务间通信存在问题
String getResult = restTemplate.getForObject(“http://192.168.0.119:8881/shopConApi/shop”, String.class);
- 调用服务路径直接写死在代码url参数中,无法实现服务集群时请求负载均衡
- 服务路径发生变化时不利于后续维护
解决RestTemplate负载均衡问题
1.自定义负载均衡解决策略
@GetMapping("invokeShop")
public String invokeShop() {
String getResult = restTemplate.getForObject("http://"+randomHost()+"/shopConApi/shop", String.class);
return "invoke success " + getResult;
}
/**
* 自定义随机策略
* @return
*/
public String randomHost() {
List<String> hostList = new ArrayList<>();
hostList.add("192.168.0.119:8881");
hostList.add("192.168.0.119:8882");
// 生成随机数,在0-hostList.size()之间
return hostList.get(new Random().nextInt(hostList.size()));
}
存在问题:
- 无法实现服务健康检查
- 负载均衡策略过于单一
2.使用springcloud提供ribbon组件解决负载均衡调用
六、Ribbon组件
1. 说明
- 官网:https://github.com/Netflix/ribbon
- spring cloud ribbon 是一个基于Http和Tcp的客户端负载均衡工具,它基于Netflix ribbon实现。通过spring cloud的封装,可以让我们轻松的将面向服务的rest模板请求自动转换成客户端负载均衡的服务调用。
2. ribbon负载均衡原理
ribbon组件拿到需要调用的服务名orders,在consul服务注册中心去寻找orders的服务实例,将orders服务实例列表拉取到本地进行缓存,ribbon底层自动负载均衡(默认轮询策略)返回orders可用的服务实例。RestTemplate再向ribbon返回的实例发送请求调用服务。
3. 使用ribbon+RestTemplate实现请求负载均衡
引入ribbon依赖
说明:如果使用的是eureka client和consul client,无需引入依赖,因为再eureka、consul中默认集成了ribbon组件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stater-netflix-ribbon</artifactId>
</dependency>
查看consul client中依赖的ribbon
ribbon负载均衡调用
- 使用DiscoveryClient 进行客户端调用
- 使用LoadBalanceClient 进行客户端调用
- 使用@LoadBalance 进行客户端调用
a. DiscoveryClient
DiscoveryClient:服务发现客户端对象,根据服务ID去服务注册中心获取对应服务列表到本地。
@RestController
@RequestMapping("orderConApi")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Resource
private RestTemplate restTemplate;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("order")
public String order() {
log.info("order ok !!!");
return "order order order";
}
@GetMapping("invokeShop")
public String invokeShop() {
// DiscoveryClient
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("SHOP");
serviceInstanceList.forEach(serviceInstance -> {
log.info("当前service url==={}", serviceInstance.getUri());
});
String getResult = restTemplate.getForObject(serviceInstanceList.get(0).getUri() + "/shopConApi/shop", String.class);
return "invoke success=" + getResult;
}
}
缺点:没有负载均衡,需要自己实现
b. LoadBalancerClient
LoadBalancerClient:负载均衡客户端对象,根据服务ID去服务注册中心获取对应服务列表到本地,根据默认的负载均衡策略(轮询)选择列表中一个服务实例进行返回。
@RestController
@RequestMapping("orderConApi")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancerClient loadBalancerClient;
@GetMapping("order")
public String order() {
log.info("order ok !!!");
return "order order order";
}
@GetMapping("invokeShop")
public String invokeShop() {
// LoadBalancerClient // 默认轮询
ServiceInstance serviceInstance = loadBalancerClient.choose("SHOP");
log.info("当前service url==={}", serviceInstance.getUri());
String getResult = restTemplate.getForObject(serviceInstance.getUri() + "/shopConApi/shop", String.class);
return "invoke success=" + getResult;
}
}
缺点:使用时需要每次先根据服务ID获取一个负载均衡服务实例后再通过RestTemplate调用服务
c. @LoadBalanced
@LoadBalanced: 修饰在方法上,让当前方法对象具有ribbon负载均衡特性。
使用方式:restTemplate.getForObject(“http://{{ServiceID}}/shopConApi/shop”, String.class);
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@RequestMapping("orderConApi")
public class OrderController {
@Resource
private RestTemplate restTemplate;
@GetMapping("invokeShop")
public String invokeShop() {
// @LoadBalanced
String getResult = restTemplate.getForObject("http://SHOP/shopConApi/shop", String.class);
return "invoke success=" + getResult;
}
}
4. ribbon负载均衡策略
源码分析
- 查看 loadBalancerClient.choose(“SHOP”) 源码,得知 ServiceInstanceChooser 是 LoadBalancerClient 的父接口
- ServiceInstanceChooser 的choose() 方法默认实现 RibbonLoadBalancerClient
- 在 RibbonLoadBalancerClient 实现类中有一个choose方法带有两个参数,这里面进行负载均衡实现
- 查看 getServer方法的实现
- IRule是底层负载均衡父接口
ribbon负载均衡策略
- RoundRobinRule 轮询策略,按照循环顺序选择server
- RandomRule 随机策略,随机选择server
- AvailabilityFilteringRule 可用过滤策略,会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。
- WeightedResponseTimeRule 响应时间加权策略,根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会默认切换
- RetryRule 重试策略,先按照RoundRobinRule策略获取服务,如果获取失败则在指定时间内进行重试,获取可用的服务
- BestAvailableRule 最低并发策略,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
修改ribbon负载均衡策略
在服务调用方修改配置:
服务ID.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
5. ribbon组件状态
官方停止维护说明:https://github.com/Netflix/ribbon
ribbon-core、ribbon-loadbalancer依然在大规模生产实践中部署,意味着日后如果实现服务间通信负载均衡依然使用ribbon组件