Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
下载地址:https://www.consul.io/downloads
consul --version 查看版本号
consul agent -dev 启动
http://localhost:8500/ 访问控制台
cloud-providerconsul-payment8006
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 8006
spring:
application:
name: consul-provider-payment
###consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class,args);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentConsulController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/consul")
public String paymentConsul(){
return "Spring cloud with consul port: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
}
http://localhost:8006/payment/consul
查看sonsul 有服务入驻
cloud-consumerconsul-order80
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
### consul服务端口号
server:
port: 80
spring:
application:
name: cloud-consumer-order
###consul服务注册中心
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import javax.swing.*;
@SpringBootApplication
@EnableDiscoveryClient
public class OrderConsulMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain80.class,args);
}
}
配置Bean RestTemplate模板
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@RestController
@Slf4j
public class OrderConsulController {
public static final String INVOKE_URL ="http://consul-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/consul")
public String getPaymentInfo(){
return restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);
}
}
http://localhost/consumer/payment/consul
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据CAP原理将NoSql数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA:单点集群,满足一致性,可用性的系统,通常在扩展上并不强大。
CP:满足一致性,分区容忍性的系统,通常性能不是特别高。
AP:满足可用性,分区容忍性的系统,通常可能对一致性要求低一点。
结论:
最好同时满足两个,目前的系统分为两种 要么 CP 要么AP。
CAP理论关注力度是数据,而不是整体系统设计的策略。
满足AP Eureka
满足CP Zookeeper、Consul
C:Consistency (强一致性)
A:Available (可用性)
P:Partition tolerance (分区容错性)
AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP架构
当网络分区出现后,为了保证一致性,就必须拒绝请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端 负载均衡的工具。
Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出 Load Balancer(简称LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡有软件 Nginx,LVS,硬件F5 等。
Ribbon 就是 负载均衡 + RestTemplate调用,最终实现RPC的远程调用。
由于eureka天生集成了ribbon,所以可以不用添加依赖就可以用ribbon
@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getForEntity(@PathVariable("id") Long id){
ResponseEntity<CommonResult> forEntity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
log.info(entity.getStatusCode() + "\t" + entity.getHeaders());
if ((forEntity.getStatusCode().is2xxSuccessful())){
return forEntity.getBody();
}else {
return new CommonResult<>(444,"调用失败");
}
}
http://localhost/consumer/payment/getForEntity/1
官方文档明确给出警告:
这个自定义配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下,
否则自定义的配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //定义为随机
}
}
main方法上加注释
@RibbonClient(name = "cloud-payment-service", configuration = MySelfRule.class)
http://localhost/consumer/payment/getForEntity/1
在之前轮询的情况下端口是8001与8002交替出现,而负载均衡规则变为随机后,端口是随机出现的
在cloud-consumer-order80 项目中 注释 @LoadBalanced
@Configuration
public class ApplicationContextConfig {
@Bean
// @LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
新建LoadBalancer 接口
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
新建MyLB实现类:手写负载均衡算法
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("****第几次访问,次数next: " + next);
return next;
}
// 负载均衡轮询算法,rest接口第几次请求数 % 服务器集群总数 = 实际调用服务器位置下标
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
控制层OrderController的方法
@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {
// 通过容器中的 discoveryClient和服务名来获取服务集群
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
// 传入服务集群来计算出获取具体的服务实例
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
log.info("uri " + uri);
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
在cloud-provider-payment8001、cloud-provider-payment8002项目中新增方法
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;
}
http://localhost/consumer/payment/lb
Feign 是一个声明式 WebService 客户端。使用 Feign 能让编写Web Service 客户端更加简单。
Feign 旨在使编写Java Http 客户端变得更容易。
前面在使用 Ribbon+RestTemplate时,利用RestTemplate 对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign 在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign 的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注衣一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了Payment 的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
OpenFeign | Feign |
---|---|
OpenFeign是springcloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 | Feign是Springcloud组件中的一个轻量级Restful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务 |
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderOpenFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderOpenFeignMain80.class,args);
}
}
package com.atguigu.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.atguigu.springcloud.entities.CommonResult;
@FeignClient(value = "cloud-payment-service")
@Component
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.service.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
CommonResult paymentById = paymentFeignService.getPaymentById(id);
return paymentById ;
}
}
http://localhost/consumer/payment/get/1
由于Feign天生支持Ribbon所以在超时控制这块由Ribbon来控制
在cloud-provider-payment8001、cloud-provider-payment8002的PaymentController增加方法
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
在cloud-consumer-feign-order80的OrderFeignController里增加方法
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
return paymentFeignService.paymentFeignTimeout();
}
http://localhost/payment/feign/timeout
因为Ribbon默认超时时间是1s,所以访问抛出异常
#设置feign 客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 5000
http://localhost/payment/feign/timeout
日志功能
Feign 提供了日志打印功能,可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。
说白了就是对接口的调用情况进行监控和输出
日志级别
package com.atguigu.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignloggerLevel(){
return Logger.Level.FULL;
}
}
logging:
level:
#feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug