jgcs123

导航

 

23_actuator微服务信息完善

主机名称:服务名称修改(也就是将IP地址,换成可读性高的名字)

修改cloud-provider-payment8001,cloud-provider-payment8002

修改部分 - YML - eureka.instance.instance-id

 

eureka:
...
instance:
  instance-id: payment8001 #添加此处

 

eureka:
...
instance:
  instance-id: payment8002 #添加此处

修改之后

eureka主页将显示payment8001,payment8002代替原来显示的IP地址。

 

访问信息有IP信息提示,(就是将鼠标指针移至payment8001,payment8002名下,会有IP地址提示)

修改部分 - YML - eureka.instance.prefer-ip-address

eureka:
...
instance:
  instance-id: payment8001
  prefer-ip-address: true #添加此处

 

eureka:
...
instance:
  instance-id: payment8002
  prefer-ip-address: true #添加此处

 

24_服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

  • 修改cloud-provider-payment8001的Controller

 

@RestController
@Slf4j
public class PaymentController{
...
   
   @Resource
   private DiscoveryClient discoveryClient;

  ...

   @GetMapping(value = "/payment/discovery")
   public Object discovery()
  {
       List<String> services = discoveryClient.getServices();
       for (String element : services) {
           log.info("*****element: "+element);
      }

       List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
       for (ServiceInstance instance : instances) {
           log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
      }

       return this.discoveryClient;
  }
}

 

  • 8001主启动类

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentMain001 {

   public static void main(String[] args) {
       SpringApplication.run(PaymentMain001.class, args);
  }
}

 

  • 自测

先要启动EurekaSeryer

再启动8001主启动类,需要稍等一会儿

浏览器输入http://localhost:8001/payment/discovery

浏览器输出:

{"services":["cloud-payment-service"],"order":0}

 

后台输出:

*****element: cloud-payment-service
CLOUD-PAYMENT-SERVICE 192.168.199.218 8001 http://192.168.199.218:8001

 

25_Eureka自我保护理论知识

概述

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE

导致原因

一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。

属于CAP里面的AP分支。

为什么会产生Eureka自我保护机制?

为了EurekaClient可以正常运行,防止与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除

什么是自我保护模式?

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

img

 

自我保护机制∶默认情况下EurekaClient定时向EurekaServer端发送心跳包

 

如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。

 

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

 

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

 

26_怎么禁止自我保护

  • 在eurekaServer端7001处设置关闭自我保护机制

  • 出厂默认,自我保护机制是开启的

    使用eureka.server.enable-self-preservation = false可以禁用自我保护模式

 

eureka:
...
server:
   #关闭自我保护机制,保证不可用服务被及时踢除
  enable-self-preservation: false
  eviction-interval-timer-in-ms: 2000

 

关闭效果:

spring-eureka主页会显示出一句:

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

 

  • 生产者客户端eureakeClient端8001

默认:

eureka.instance.lease-renewal-interval-in-seconds=30

eureka.instance.lease-expiration-duration-in-seconds=90

 

eureka:
...
instance:
  instance-id: payment8001
  prefer-ip-address: true
   #心跳检测与续约时间
   #开发时没置小些,保证服务关闭后注册中心能即使剔除服务
   #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
  lease-renewal-interval-in-seconds: 1
   #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
  lease-expiration-duration-in-seconds: 2

 

  • 测试

    • 7001和8001都配置完成

    • 先启动7001再启动8001

结果:先关闭8001,马上被删除了

 

27_Eureka停更说明

https://github.com/Netflix/eureka/wiki

 

Eureka 2.0 (Discontinued)

The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.

Eureka 1.x is a core part of Netflix’s service discovery system and is still an active project.

 

我们用ZooKeeper代替Eureka功能。

 

28_支付服务注册进zookeeper

  • 注册中心Zookeeper

zookeeper是一个分布式协调工具,可以实现注册中心功能

关闭Linux服务器防火墙后,启动zookeeper服务器

用到的Linux命令行:

  • systemctl stop firewalld关闭防火墙

  • systemctl status firewalld查看防火墙状态

  • ipconfig查看IP地址

  • ping查验结果

 

zookeeper服务器取代Eureka服务器,zk作为服务注册中心

视频里是用虚拟机CentOS开启ZooKeeper,我打算在本机启动ZooKeeper,具体操作参考ZooKeeper学习笔记

 

  • 服务提供者

1.新建名为cloud-provider-payment8004的Maven工程。

2.POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
       <artifactId>LearnCloud</artifactId>
       <groupId>com.lun.springcloud</groupId>
       <version>1.0.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>

   <artifactId>cloud-provider-payment8004</artifactId>
   <dependencies>
       <!-- SpringBoot整合Web组件 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
           <groupId>com.lun.springcloud</groupId>
           <artifactId>cloud-api-commons</artifactId>
           <version>${project.version}</version>
       </dependency>
       <!-- SpringBoot整合zookeeper客户端 -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
           <!--先排除自带的zookeeper3.5.3 防止与3.4.9起冲突-->
           <exclusions>
               <exclusion>
                   <groupId>org.apache.zookeeper</groupId>
                   <artifactId>zookeeper</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
       <!--添加zookeeper3.4.9版本-->
       <dependency>
           <groupId>org.apache.zookeeper</groupId>
           <artifactId>zookeeper</artifactId>
           <version>3.4.9</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>

</project>

 

3.YML

#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004

#服务别名----注册zookeeper到注册中心名称
spring:
application:
  name: cloud-provider-payment
cloud:
  zookeeper:
    connect-string: 127.0.0.1:2181 # 192.168.111.144:2181 #

 

4.主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient//该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
   public static void main(String[] args) {
       SpringApplication.run(PaymentMain8004.class, args);
  }
}

 

5.Controller

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController
{
   @Value("${server.port}")
   private String serverPort;

   @RequestMapping(value = "/payment/zk")
   public String paymentzk()
  {
       return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
  }
}

6.启动8004注册进zookeeper(要先启动zookeeper的server)

[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services/cloud-provider-payment
[a4567f50-6ad9-47a3-9fbb-7391f41a9f3d]
[zk: localhost:2181(CONNECTED) 2] get /services/cloud-provider-payment/a4567f50-6ad9-47a3-9fbb-7391f41a9f3d
{"name":"cloud-provider-payment","id":"a4567f50-6ad9-47a3-9fbb-7391f41a9f3d","address":"192.168.199.218","port":8004,"ss
lPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","
name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1612811116918,"serviceType":"DYNAMIC","uriSpec":{"pa
rts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":"
:","variable":false},{"value":"port","variable":true}]}}
[zk: localhost:2181(CONNECTED) 3]

 

json格式化get /services/cloud-provider-payment/a4567f50-6ad9-47a3-9fbb-7391f41a9f3d的结果:

{
   "name": "cloud-provider-payment",
   "id": "a4567f50-6ad9-47a3-9fbb-7391f41a9f3d",
   "address": "192.168.199.218",
   "port": 8004,
   "sslPort": null,
   "payload": {
       "@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
       "id": "application-1",
       "name": "cloud-provider-payment",
       "metadata": { }
  },
   "registrationTimeUTC": 1612811116918,
   "serviceType": "DYNAMIC",
   "uriSpec": {
       "parts": [
          {
               "value": "scheme",
               "variable": true
          },
          {
               "value": "://",
               "variable": false
          },
          {
               "value": "address",
               "variable": true
          },
          {
               "value": ":",
               "variable": false
          },
          {
               "value": "port",
               "variable": true
          }
      ]
  }
}

 

29_临时还是持久节点

ZooKeeper的服务节点是临时节点,没有Eureka那含情脉脉。

 

30_订单服务注册进zookeeper

1.新建cloud-consumerzk-order80

2.POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
       <artifactId>LearnCloud</artifactId>
       <groupId>com.lun.springcloud</groupId>
       <version>1.0.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>

   <artifactId>cloud-consumerzk-order80</artifactId>

   <dependencies>
       <!-- SpringBoot整合Web组件 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!-- SpringBoot整合zookeeper客户端 -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
           <!--先排除自带的zookeeper-->
           <exclusions>
               <exclusion>
                   <groupId>org.apache.zookeeper</groupId>
                   <artifactId>zookeeper</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
       <!--添加zookeeper3.4.9版本-->
       <dependency>
           <groupId>org.apache.zookeeper</groupId>
           <artifactId>zookeeper</artifactId>
           <version>3.4.9</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>

</project>

 

3.YML

server:
port: 80

#服务别名----注册zookeeper到注册中心名称
spring:
application:
  name: cloud-consumer-order
cloud:
  zookeeper:
    connect-string: 127.0.0.1:2181 # 192.168.111.144:2181 #

 

4.主启动

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
   public static void main(String[] args) {
       SpringApplication.run(OrderZKMain80.class, args);
  }
}

 

5.业务类

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig
{
   @Bean
   @LoadBalanced
   public RestTemplate getRestTemplate()
  {
       return new RestTemplate();
  }
}

 

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderZKController
{
   public static final String INVOKE_URL = "http://cloud-provider-payment";

   @Resource
   private RestTemplate restTemplate;

   @GetMapping(value = "/consumer/payment/zk")
   public String paymentInfo()
  {
       String result = restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);
       return result;
  }
}

 

6.验证测试

运行ZooKeeper服务端,cloud-consumerzk-order80,cloud-provider-payment8004。

打开ZooKeeper客户端:

[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-consumer-order, cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 2]

 

7.访问测试地址 - http://localhost/consumer/payment/zk

31_Consul简介

 

Consul官网

Consul下载地址

What is Consul?

Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality. Each of these features can be used individually as needed, or they can be used together to build a full service mesh. Consul requires a data plane and supports both a proxy and native integration model. Consul ships with a simple built-in proxy so that everything works out of the box, but also supports 3rd party proxy integrations such as Envoy. link

Consul是一个服务网格解决方案,它提供了一个功能齐全的控制平面,具有服务发现、配置和分段功能。这些特性中的每一个都可以根据需要单独使用,也可以一起用于构建全服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul船与一个简单的内置代理,使一切工作的开箱即用,但也支持第三方代理集成,如Envoy。

 

Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp 公司用Go语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。

它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨平台,支持Linux、Mac、Windows。

 

The key features of Consul are:

Service Discovery: Clients of Consul can register a service, such as api or mysql, and other clients can use Consul to discover providers of a given service. Using either DNS or HTTP, applications can easily find the services they depend upon.

Health Checking: Consul clients can provide any number of health checks, either associated with a given service (“is the webserver returning 200 OK”), or with the local node (“is memory utilization below 90%”). This information can be used by an operator to monitor cluster health, and it is used by the service discovery components to route traffic away from unhealthy hosts.

 

KV Store: Applications can make use of Consul’s hierarchical key/value store for any number of purposes, including dynamic configuration,feature flagging, coordination, leader election, and more. The simple HTTP API makes it easy to use.

 

Secure Service Communication: Consul can generate and distribute TLS certificates for services to establish mutual TLS connections.Intentions can be used to define which services are allowed to communicate. Service segmentation can be easily managed with intentions thatcan be changed in real time instead of using complex network topologies and static firewall rules.

 

Multi Datacenter: Consul supports multiple datacenters out of the box. This means users of Consul do not have to worry about building additional layers of abstraction to grow to multiple regions.

link

 

能干嘛?

  • 服务发现 - 提供HTTP和DNS两种发现方式。

  • 健康监测 - 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化

  • KV存储 - Key、Value的存储方式

  • 多数据中心 - Consul支持多数据中心

  • 可视化Web界面

怎么玩

 

32_安装并运行Consul

官网安装说明

windows版解压缩后,得consul.exe,打开cmd

D:\位置 -v
Consul v1.9.3
Revision f55da9306
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

 

  • 开发模式启动consul agent -dev

浏览器输入 - http://localhost:8500/ - 打开Consul控制页。

 

33_服务提供者注册进Consul

1.新建Module支付服务provider8006

2.POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
       <artifactId>LearnCloud</artifactId>
       <groupId>com.lun.springcloud</groupId>
       <version>1.0.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>

   <artifactId>cloud-providerconsul-payment8006</artifactId>
   <dependencies>
       <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
       <dependency>
           <groupId>com.lun.springcloud</groupId>
           <artifactId>cloud-api-commons</artifactId>
           <version>${project.version}</version>
       </dependency>
       <!--SpringCloud consul-server -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-consul-discovery</artifactId>
       </dependency>
       <!-- SpringBoot整合Web组件 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
       <!--日常通用jar包配置-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>cn.hutool</groupId>
           <artifactId>hutool-all</artifactId>
           <version>RELEASE</version>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>cn.hutool</groupId>
           <artifactId>hutool-all</artifactId>
           <version>RELEASE</version>
           <scope>test</scope>
       </dependency>
   </dependencies>

</project>

 

3.YML

###consul服务端口号
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}

 

4.主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006
{
   public static void main(String[] args) {
           SpringApplication.run(PaymentMain8006.class, args);
  }
}

 

5.业务类Controller

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController
{
   @Value("${server.port}")
   private String serverPort;

   @RequestMapping(value = "/payment/consul")
   public String paymentConsul()
  {
       return "springcloud with consul: "+serverPort+"\t   "+ UUID.randomUUID().toString();
  }
}

 

6.验证测试

 

34_服务消费者注册进Consul

1.新建Module消费服务order80 - cloud-consumerconsul-order80

2.POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
       <artifactId>LearnCloud</artifactId>
       <groupId>com.lun.springcloud</groupId>
       <version>1.0.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>

   <artifactId>cloud-consumerconsul-order80</artifactId>
   <dependencies>
       <!--SpringCloud consul-server -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-consul-discovery</artifactId>
       </dependency>
       <!-- SpringBoot整合Web组件 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
       <!--日常通用jar包配置-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
</project>

 

3.YML

###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}

 

4.主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class OrderConsulMain80
{
   public static void main(String[] args) {
           SpringApplication.run(OrderConsulMain80.class, args);
  }
}

 

5.配置Bean

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
*/
@Configuration
public class ApplicationContextConfig
{
   @Bean
   @LoadBalanced
   public RestTemplate getRestTemplate()
  {
       return new RestTemplate();
  }
}

 

6.Controller

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@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 paymentInfo()
  {
       String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);
       return result;
  }
}

7.验证测试

运行consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80

http://localhost:8500/ 主页会显示出consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80三服务。

8.访问测试地址 - http://localhost/consumer/payment/consul

 

35_三个注册中心异同点

组件名语言CAP服务健康检查对外暴露接口Spring Cloud集成
Eureka Java AP 可配支持 HTTP
Consul Go CP 支持 HTTP/DNS
Zookeeper Java CP 支持客户端 已集成

 

CAP:

  • C:Consistency (强一致性)

  • A:Availability (可用性)

  • P:Partition tolerance (分区容错性)

 

img

 

最多只能同时较好的满足两个

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求

因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。

  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。

  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

 

 

AP架构(Eureka)

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

 

img

 

 

CP架构(ZooKeeper/Consul)

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。

img

 

CP 与 AP 对立同一的矛盾关系。

36_Ribbon入门介绍

 

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

 

简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

 

Github - Ribbon

Ribbon目前也进入维护模式。

Ribbon未来可能被Spring Cloud LoadBalacer替代。

 

LB负载均衡(Load Balance)是什么

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。

常见的负载均衡有软件Nginx,LVS,硬件F5等。

 

Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别

Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。

Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

 

集中式LB

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;

 

进程内LB

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

 

Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

一句话

负载均衡 + RestTemplate调用

 

37_Ribbon的负载均衡和Rest调用

架构说明

总结:Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例。

img

 

Ribbon在工作时分成两步:

  • 第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。

  • 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

 

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

POM

先前工程项目没有引入spring-cloud-starter-ribbon也可以使用ribbon。

<dependency>
   <groupld>org.springframework.cloud</groupld>
   <artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>

 

这是因为spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用。

二说RestTemplate的使用

RestTemplate Java Doc

 

getForObject() / getForEntity() - GET请求方法

getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json。

getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。

 

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
   ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);

   if(entity.getStatusCode().is2xxSuccessful()){
       return entity.getBody();//getForObject()
  }else{
       return new CommonResult<>(444,"操作失败");
  }
}

 

postForObject() / postForEntity() - POST请求方法

38_Ribbon默认自带的负载规则

lRule:根据特定算法中从服务列表中选取一个要访问的服务img

 

 

  • RoundRobinRule 轮询

  • RandomRule 随机

  • RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重

  • WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择

  • BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

  • AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例

  • ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器

 

39_Ribbon负载规则替换

1.修改cloud-consumer-order80

2.注意配置细节

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

也就是说不要将Ribbon配置类与主启动类同包

3.新建package - com.lun.myrule

4.在com.lun.myrule下新建MySelfRule规则类

 

 

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();
  }
}

 

5.主启动类添加@RibbonClient

import com.lun.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@EnableEurekaClient
//添加到此处
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80
{
   public static void main( String[] args ){
       SpringApplication.run(OrderMain80.class, args);
  }
}

 

6.测试

开启cloud-eureka-server7001,cloud-consumer-order80,cloud-provider-payment8001,cloud-provider-payment8002

 

浏览器-输入http://localhost/consumer/payment/get/1

返回结果中的serverPort在8001与8002两种间反复横跳。

 

 

40_Ribbon默认负载轮询算法原理

默认负载轮训算法: rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始

 

List<Servicelnstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

 

如:

  • List [0] instances = 127.0.0.1:8002

  • List [1] instances = 127.0.0.1:8001

 

8001+ 8002组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:

  • 当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001

  • 当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002

  • 当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001

  • 当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002

  • 如此类推…

 

41_RoundRobinRule源码分析

public interface IRule{
   /*
    * choose one alive server from lb.allServers or
    * lb.upServers according to key
    *
    * @return choosen Server object. NULL is returned if none
    * server is available
    */

   //重点关注这方法
   public Server choose(Object key);
   
   public void setLoadBalancer(ILoadBalancer lb);
   
   public ILoadBalancer getLoadBalancer();    
}

 

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis <nikos@netflix.com>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {

   private AtomicInteger nextServerCyclicCounter;
   private static final boolean AVAILABLE_ONLY_SERVERS = true;
   private static final boolean ALL_SERVERS = false;

   private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

   public RoundRobinRule() {
       nextServerCyclicCounter = new AtomicInteger(0);
  }

   public RoundRobinRule(ILoadBalancer lb) {
       this();
       setLoadBalancer(lb);
  }

   //重点关注这方法。
   public Server choose(ILoadBalancer lb, Object key) {
       if (lb == null) {
           log.warn("no load balancer");
           return null;
      }

       Server server = null;
       int count = 0;
       while (server == null && count++ < 10) {
           List<Server> reachableServers = lb.getReachableServers();
           List<Server> allServers = lb.getAllServers();
           int upCount = reachableServers.size();
           int serverCount = allServers.size();

           if ((upCount == 0) || (serverCount == 0)) {
               log.warn("No up servers available from load balancer: " + lb);
               return null;
          }

           int nextServerIndex = incrementAndGetModulo(serverCount);
           server = allServers.get(nextServerIndex);

           if (server == null) {
               /* Transient. */
               Thread.yield();
               continue;
          }

           if (server.isAlive() && (server.isReadyToServe())) {
               return (server);
          }

           // Next.
           server = null;
      }

       if (count >= 10) {
           log.warn("No available alive servers after 10 tries from load balancer: "
                   + lb);
      }
       return server;
  }

   /**
    * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
    *
    * @param modulo The modulo to bound the value of the counter.
    * @return The next value.
    */
   private int incrementAndGetModulo(int modulo) {
       for (;;) {
           int current = nextServerCyclicCounter.get();
           int next = (current + 1) % modulo;//求余法
           if (nextServerCyclicCounter.compareAndSet(current, next))
               return next;
      }
  }

   @Override
   public Server choose(Object key) {
       return choose(getLoadBalancer(), key);
  }

   @Override
   public void initWithNiwsConfig(IClientConfig clientConfig) {
  }
}

42_Ribbon之手写轮询算法

自己试着写一个类似RoundRobinRule的本地负载均衡器。

  • 7001/7002集群启动

  • 8001/8002微服务改造- controller

 

@RestController
@Slf4j
public class PaymentController{

  ...
   
@GetMapping(value = "/payment/lb")
   public String getPaymentLB() {
       return serverPort;//返回服务接口
  }
   
  ...
}

 

  • 80订单微服务改造

1.ApplicationContextConfig去掉注解@LoadBalanced,OrderMain80去掉注解@RibbonClient

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

   @Bean
   //@LoadBalanced
   public RestTemplate getRestTemplate(){
       return new RestTemplate();
  }

}

 

2.创建LoadBalancer接口

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
*/
public interface LoadBalancer
{
   ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

 

3.MyLB

实现LoadBalancer接口

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
*/
@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 >= 2147483647 ? 0 : current + 1;
      }while(!this.atomicInteger.compareAndSet(current,next));
       System.out.println("*****第几次访问,次数next: "+next);
       return next;
  }

   //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
   @Override
   public ServiceInstance instances(List<ServiceInstance> serviceInstances)
  {
       int index = getAndIncrement() % serviceInstances.size();

       return serviceInstances.get(index);
  }
}

 

4.OrderController

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import com.lun.springcloud.lb.LoadBalancer;

@Slf4j
@RestController
public class OrderController {

   //public static final String PAYMENT_URL = "http://localhost:8001";
   public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

...

   @Resource
   private LoadBalancer loadBalancer;

   @Resource
   private DiscoveryClient discoveryClient;

...

   @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 = loadBalancer.instances(instances);
       URI uri = serviceInstance.getUri();

       return restTemplate.getForObject(uri+"/payment/lb",String.class);

  }
}

 

5.测试 不停地刷新http://localhost/consumer/payment/lb,可以看到8001/8002交替出现。

 

posted on 2021-08-25 23:04  Dongdong98  阅读(37)  评论(0编辑  收藏  举报