SpringCloud

springcloud是分布式微服务架构一揽子的解决方案,有多种技术的落地

SpringCloud-第一部分 - 图1

git源码地址

SpringCloud-第一部分 - 图2

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes

通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本

SpringCloud-第一部分 - 图3

SpringCloud-第一部分 - 图4

git源码地址 https://github.com/spring-projects/spring-cloud

官网:https://spring.io/projects/spring-cloud

SpringCloud的版本关系

  1. SpringCloud的版本关系
  2. Spring Cloud 采用了英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本
  3. SpringCloud是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序。例如Angel是第一个版本, Brixton是第二个版本。
  4. 当SpringCloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个"service releases"版本,简称SRX版本,比如Greenwich.SR2就是SpringCloud发布的Greenwich版本的第2个SRX版本。

SpringCloud-第一部分 - 图5

Springcloud和Springboot之间的依赖关系如何看

https://spring.io/projects/spring-cloud#overview

SpringCloud-第一部分 - 图6

Finchley 是基于 Spring Boot 2.0.x 构建的不再 Boot 1.5.x
Dalston 和 Edgware 是基于 Spring Boot 1.5.x 构建的,不支持 Spring Boot 2.0.x
Camden 构建于 Spring Boot 1.4.x,但依然能支持 Spring Boot 1.5.x

SpringCloud-第一部分 - 图7

更详细的版本对应查看方法 :https://start.spring.io/actuator/info

SpringCloud-第一部分 - 图8

查看json串返回结果

SpringCloud-第一部分 - 图9

cloud Hoxton.SR1

boot 2.2.2.RELEASE

cloud alibaba 2.1.0.RELEASE

Java Java8

Maven 3.5及以上

Mysql 5.7及以上

只用boot,直接用最新,同时用boot和cloud,需要照顾cloud,由cloud决定boot版本

SpringCloud-第一部分 - 图10

SpringCloud和SpringBoot版本对应关系

SpringCloud-第一部分 - 图11

SpringCloud和SpringBoot版本对应关系

SpringCloud-第一部分 - 图12

Spring Cloud文档: https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/

Spring Cloud中文文档: https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md

Spring Boot文档: https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/

SpringCloud-第一部分 - 图13

一、工程构建

1、cloud-provider-payment8001

微服务提供者支付Module模块

1、new project

SpringCloud-第一部分 - 图14

2、聚合总父工程名字

SpringCloud-第一部分 - 图15

3、Maven选版本

SpringCloud-第一部分 - 图16

4、工程名字

SpringCloud-第一部分 - 图17

5、字符编码

SpringCloud-第一部分 - 图18

6、注解生效激活

SpringCloud-第一部分 - 图19

7、java编译版本选8

SpringCloud-第一部分 - 图20

8、File Type过滤

SpringCloud-第一部分 - 图21

pom依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.example</groupId>
  7. <artifactId>spring-cloud</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <!--统一管理jar包版本-->
  10. <properties>
  11. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  12. <maven.compiler.source>12</maven.compiler.source>
  13. <maven.compiler.target>12</maven.compiler.target>
  14. <junit.version>4.12</junit.version>
  15. <lombok.version>1.18.10</lombok.version>
  16. <log4j.version>1.2.17</log4j.version>
  17. <mysql.version>8.0.18</mysql.version>
  18. <druid.version>1.1.16</druid.version>
  19. <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version>
  20. </properties>
  21. <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
  22. <dependencyManagement>
  23. <dependencies>
  24. <!--spring boot 2.2.2-->
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-dependencies</artifactId>
  28. <version>2.2.2.RELEASE</version>
  29. <type>pom</type>
  30. <scope>import</scope>
  31. </dependency>
  32. <!--spring cloud Hoxton.SR1-->
  33. <dependency>
  34. <groupId>org.springframework.cloud</groupId>
  35. <artifactId>spring-cloud-dependencies</artifactId>
  36. <version>Hoxton.SR1</version>
  37. <type>pom</type>
  38. <scope>import</scope>
  39. </dependency>
  40. <!--spring cloud alibaba 2.1.0.RELEASE-->
  41. <dependency>
  42. <groupId>com.alibaba.cloud</groupId>
  43. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  44. <version>2.1.0.RELEASE</version>
  45. <type>pom</type>
  46. <scope>import</scope>
  47. </dependency>
  48. <!-- mysql-->
  49. <dependency>
  50. <groupId>mysql</groupId>
  51. <artifactId>mysql-connector-java</artifactId>
  52. <version>${mysql.version}</version>
  53. </dependency>
  54. <dependency>
  55. <groupId>com.alibaba</groupId>
  56. <artifactId>druid</artifactId>
  57. <version>${druid.version}</version>
  58. </dependency>
  59. <dependency>
  60. <groupId>org.mybatis.spring.boot</groupId>
  61. <artifactId>mybatis-spring-boot-starter</artifactId>
  62. <version>${mybatis.spring.boot.version}</version>
  63. </dependency>
  64. <dependency>
  65. <groupId>junit</groupId>
  66. <artifactId>junit</artifactId>
  67. <version>${junit.version}</version>
  68. </dependency>
  69. <dependency>
  70. <groupId>log4j</groupId>
  71. <artifactId>log4j</artifactId>
  72. <version>${log4j.version}</version>
  73. </dependency>
  74. <dependency>
  75. <groupId>org.projectlombok</groupId>
  76. <artifactId>lombok</artifactId>
  77. <version>${lombok.version}</version>
  78. <optional>true</optional>
  79. </dependency>
  80. </dependencies>
  81. </dependencyManagement>
  82. <build>
  83. <plugins>
  84. <plugin>
  85. <groupId>org.springframework.boot</groupId>
  86. <artifactId>spring-boot-maven-plugin</artifactId>
  87. <configuration>
  88. <fork>true</fork>
  89. <addResources>true</addResources>
  90. </configuration>
  91. </plugin>
  92. <plugin>
  93. <groupId>org.apache.maven.plugins</groupId>
  94. <artifactId>maven-compiler-plugin</artifactId>
  95. <version>3.1</version>
  96. <configuration>
  97. <source>1.8</source>
  98. <target>1.8</target>
  99. </configuration>
  100. </plugin>
  101. </plugins>
  102. </build>
  103. </project>

cloud-provider-payment8001-pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud</artifactId>
  7. <groupId>org.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-provider-payment8001</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-actuator</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.mybatis.spring.boot</groupId>
  23. <artifactId>mybatis-spring-boot-starter</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.alibaba</groupId>
  27. <artifactId>druid-spring-boot-starter</artifactId>
  28. <version>1.1.10</version>
  29. </dependency>
  30. <!--mysql-connector-java-->
  31. <dependency>
  32. <groupId>mysql</groupId>
  33. <artifactId>mysql-connector-java</artifactId>
  34. </dependency>
  35. <!--jdbc-->
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-jdbc</artifactId>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-devtools</artifactId>
  43. <scope>runtime</scope>
  44. <optional>true</optional>
  45. </dependency>
  46. <dependency>
  47. <groupId>org.projectlombok</groupId>
  48. <artifactId>lombok</artifactId>
  49. <optional>true</optional>
  50. </dependency>
  51. <dependency>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-starter-test</artifactId>
  54. <scope>test</scope>
  55. </dependency>
  56. </dependencies>
  57. </project>

微服务模块构建步骤:

  1. 1、建module
  2. 2、改pom
  3. 3、写yml
  4. 4、主启动
  5. 5、业务类

SpringCloud-第一部分 - 图22

application.yml

  1. server:
  2. port: 8001
  3. spring:
  4. application:
  5. name: cloud-payment-service
  6. datasource:
  7. type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
  8. driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包 com.mysql.jdbc.Driver
  9. url: jdbc:mysql://localhost:3306/crimsdbs?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
  10. username: root
  11. password: abc123
  12. mybatis:
  13. mapperLocations: classpath:mapper/*.xml
  14. type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包

启动类:

  1. @SpringBootApplication
  2. public class PaymentMain8001 {
  3. public static void main(String[] args) {
  4. SpringApplication.run(PaymentMain8001.class,args);
  5. }
  6. }

2、简单payment的工程的创建

目录结构:

SpringCloud-第一部分 - 图23

创数据库:

  1. create table `payment`(
  2. `id` bigint(20) not null auto_increment comment 'ID',
  3. `serial` varchar(200) default '',
  4. PRIMARY key (`id`)
  5. )engine=INNODB auto_increment=1 default charset=utf8

dao:

  1. @Mapper
  2. public interface PaymentDao {
  3. public int create(Payment payment);
  4. public Payment getPaymentById(@Param("id") Long id);
  5. }

entities:

  • 实体类
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Payment implements Serializable {
  5. private Long id;
  6. private String serial;
  7. }
  • result:
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T> {
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code,String message){
  9. this(code,message,null);
  10. }
  11. }

mapper:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
  4. <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
  5. <id column="id" property="id" jdbcType="BIGINT"/>
  6. <result column="serial" property="serial" jdbcType="VARCHAR"/>
  7. </resultMap>
  8. <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
  9. INSERT INTO payment(SERIAL) VALUES(#{serial});
  10. </insert>
  11. <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap" >
  12. SELECT * FROM payment WHERE id=#{id};
  13. </select>
  14. </mapper>

service:

  1. public interface PaymentService {
  2. public int create(Payment payment);
  3. public Payment getPaymentById(@Param("id") Long id);
  4. }

impl:

  1. @Service
  2. public class PaymentServiceImpl implements PaymentService {
  3. @Resource
  4. private PaymentDao paymentDao;
  5. @Override
  6. public int create(Payment payment) {
  7. return paymentDao.create(payment);
  8. }
  9. @Override
  10. public Payment getPaymentById(Long id) {
  11. return paymentDao.getPaymentById(id);
  12. }
  13. }

controller:

  1. @RestController
  2. @Slf4j
  3. @RequestMapping("/payment")
  4. public class PaymentController {
  5. @Autowired
  6. private PaymentService paymentService;
  7. @PostMapping("/save")
  8. public CommonResult<Object> create(Payment payment){
  9. int result = paymentService.create(payment);
  10. log.info("插入成功{}",result);
  11. if (result > 0){
  12. return new CommonResult(0,"插入数据库成功",result);
  13. }else {
  14. return new CommonResult(1,"插入数据库失败",null);
  15. }
  16. }
  17. @GetMapping("/get/{id}")
  18. public CommonResult<Object> getById(@PathVariable("id") Long id){
  19. Payment paymentById = paymentService.getPaymentById(id);
  20. log.info("插入成功{}",paymentById);
  21. if (paymentById != null){
  22. return new CommonResult(0,"插入数据库成功",paymentById);
  23. }else {
  24. return new CommonResult(1,"插入数据库失败",null);
  25. }
  26. }
  27. }

测试接口调用:

http://localhost:8001/payment/get/31

  1. {
  2. "code": 0,
  3. "message": "插入数据库成功",
  4. "data": {
  5. "id": 31,
  6. "serial": "aabbb01"
  7. }
  8. }

SpringCloud-第一部分 - 图24

chrome浏览器默认get请求,因此要用接口测试工具

SpringCloud-第一部分 - 图25

SpringCloud-第一部分 - 图26

3、热部署Devtools

1、Adding devtools to your project

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-devtools</artifactId>
  4. <scope>runtime</scope>
  5. <optional>true</optional>
  6. </dependency>

2、Adding plugin to your pom.xml

  1. <build>
  2. <finalName>你自己的工程名字</finalName>
  3. <plugins>
  4. <plugin>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-maven-plugin</artifactId>
  7. <configuration>
  8. <fork>true</fork>
  9. <addResources>true</addResources>
  10. </configuration>
  11. </plugin>
  12. </plugins>
  13. </build>

3、Enabling automatic build

SpringCloud-第一部分 - 图27

SpringCloud-第一部分 - 图28

4、Update the value of

SpringCloud-第一部分 - 图29

SpringCloud-第一部分 - 图30

重启idea

3、cloud-consumer-order80

微服务消费者订单Module模块

pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud</artifactId>
  7. <groupId>org.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-consumer-order80</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-actuator</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-devtools</artifactId>
  24. <scope>runtime</scope>
  25. <optional>true</optional>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.projectlombok</groupId>
  29. <artifactId>lombok</artifactId>
  30. <optional>true</optional>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. </dependencies>
  38. </project>

yml

  1. server:
  2. port: 80

目录结构
SpringCloud-第一部分 - 图31

entities略

RestTemplate

  1. RestTemplate提供了多种便捷访问远程Http服务的方法,
  2. 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

官网地址

https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

  1. 使用restTemplate访问restful接口非常的简单粗暴无脑。
  2. (url, requestMap, ResponseBean.class)这三个参数分别代表
  3. REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

配置类:

  1. @Configuration
  2. public class ApplicationContextConfig {
  3. @Bean
  4. public RestTemplate getRestTemplate(){
  5. return new RestTemplate();
  6. }
  7. }
  8. //applicationcontext.xml <bean id="" class="">

需要entities目录的两个类,CommonResult、Payment

controller:

  1. @RestController
  2. @Slf4j
  3. public class OrderController {
  4. public static final String PAYMENT_URL = "http://localhost:8001";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @GetMapping("/consumer/payment/create")
  8. public CommonResult<Payment> create(Payment payment){
  9. return restTemplate.postForObject(PAYMENT_URL + "/payment/create",payment,CommonResult.class);
  10. }
  11. @GetMapping("/consumer/payment/get/{id}")
  12. public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
  13. return restTemplate.getForObject(PAYMENT_URL + "/payment/get/"+id,CommonResult.class);
  14. }
  15. }

测试:get

SpringCloud-第一部分 - 图32

测试:save

SpringCloud-第一部分 - 图33

虽然请求成功,但是数据未携带过去,数据库存的是null

SpringCloud-第一部分 - 图34

8001save请求未加@RequestBody才能生效

SpringCloud-第一部分 - 图35

4、工程重构

SpringCloud-第一部分 - 图36

系统中有重复部分,重构
公共模块:cloud-api-commons —> 删除各自有的entities,导入依赖

clean install检查是否依赖引入正确 —-> 测试接口调用

SpringCloud-第一部分 - 图37

二、Eureka服务注册与发现

什么是服务治理

  1. SpringCloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
  2. 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

什么是服务注册与发现

  1. Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
  2. 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

下左图是Eureka系统架构,右图是Dubbo的架构,请对比

SpringCloud-第一部分 - 图38

Eureka包含两个组件:Eureka Server 和 Eureka Client

  1. Eureka Server提供服务注册服务
  2. 各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
  3. EurekaClient通过注册中心进行访问
  4. 是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

服务端7001

module: cloud-eureka-server7001

pom:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud</artifactId>
  7. <groupId>org.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-eureka-server7001</artifactId>
  12. <dependencies>
  13. <!--eureka-server-->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  17. </dependency>
  18. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  19. <dependency>
  20. <groupId>org.example</groupId>
  21. <artifactId>cloud-api-commons</artifactId>
  22. <version>1.0-SNAPSHOT</version>
  23. <scope>compile</scope>
  24. </dependency>
  25. <!--boot web actuator-->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-web</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-actuator</artifactId>
  33. </dependency>
  34. <!--一般通用配置-->
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-devtools</artifactId>
  38. <scope>runtime</scope>
  39. <optional>true</optional>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.projectlombok</groupId>
  43. <artifactId>lombok</artifactId>
  44. </dependency>
  45. <dependency>
  46. <groupId>org.springframework.boot</groupId>
  47. <artifactId>spring-boot-starter-test</artifactId>
  48. <scope>test</scope>
  49. </dependency>
  50. <dependency>
  51. <groupId>junit</groupId>
  52. <artifactId>junit</artifactId>
  53. </dependency>
  54. </dependencies>
  55. </project>

yml:

  1. server:
  2. port: 7001
  3. eureka:
  4. instance:
  5. hostname: localhost #eureka服务端的实例名称
  6. client:
  7. #false表示不向注册中心注册自己。
  8. register-with-eureka: false
  9. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  10. fetch-registry: false
  11. service-url:
  12. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  13. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类:

  1. @SpringBootApplication
  2. @EnableEurekaServer
  3. public class EurekaMain7001
  4. {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(EurekaMain7001.class,args);
  8. }
  9. }

测试:http://localhost:7001/

SpringCloud-第一部分 - 图39

单机服务端完成。。。

X和2.X的对比说明

  1. 以前的老版本(当前使用2018)
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-eureka</artifactId>
  5. </dependency>
  6. 现在新版本(当前使用2020.2)
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  10. </dependency>

客户端8001

使用刚创建的 cloud-provider-payment8001===>服务提供者provider

改pom文件

  1. <!--eureka-client-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>

改yml

  1. eureka:
  2. client:
  3. #表示是否将自己注册进EurekaServer默认为true。
  4. register-with-eureka: true
  5. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  6. fetchRegistry: true
  7. service-url:
  8. defaultZone: http://localhost:7001/eureka

启动类

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class PaymentMain8001
  4. {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(PaymentMain8001.class,args);
  8. }
  9. }

先要启动EurekaServer ===> 测试: http://localhost:7001/

SpringCloud-第一部分 - 图40

yml配置文件的spring.application.name 与之对应

SpringCloud-第一部分 - 图41

自我保护机制。

使用刚创建的cloud-consumer-order80作为===>服务消费者consumer

改pom

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>

yml加

  1. spring:
  2. application:
  3. name: cloud-order-service
  4. eureka:
  5. client:
  6. #表示是否将自己注册进EurekaServer默认为true。
  7. register-with-eureka: true
  8. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  9. fetchRegistry: true
  10. service-url:
  11. defaultZone: http://localhost:7001/eureka

启动类上加@EnableEurekaClient注解

  1. @EnableEurekaClient

先要启动EurekaServer,7001服务,再要启动服务提供者provider,8001服务

eureka服务器

SpringCloud-第一部分 - 图42

测试接口调用: http://localhost/consumer/payment/get/31

可能产生的 bug:

SpringCloud-第一部分 - 图43

Eureka集群原理说明

SpringCloud-第一部分 - 图44

问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)”了,会导致整个为服务环境不可用,所以 ; 解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错

EurekaServer集群环境构建步骤

参考cloud-eureka-server7001,新建cloud-eureka-server7002

改POM

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>mscloud03</artifactId>
  7. <groupId>com.atguigu.springcloud</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-eureka-server7002</artifactId>
  12. <dependencies>
  13. <!--eureka-server-->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  17. </dependency>
  18. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  19. <dependency>
  20. <groupId>com.atguigu.springcloud</groupId>
  21. <artifactId>cloud-api-commons</artifactId>
  22. <version>${project.version}</version>
  23. </dependency>
  24. <!--boot web actuator-->
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-web</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-actuator</artifactId>
  32. </dependency>
  33. <!--一般通用配置-->
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-devtools</artifactId>
  37. <scope>runtime</scope>
  38. <optional>true</optional>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.projectlombok</groupId>
  42. <artifactId>lombok</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. <dependency>
  50. <groupId>junit</groupId>
  51. <artifactId>junit</artifactId>
  52. </dependency>
  53. </dependencies>
  54. </project>

修改映射配置

找到C:\Windows\System32\drivers\etc路径下的hosts文件

SpringCloud-第一部分 - 图45

修改映射配置添加进hosts文件

127.0.0.1 eureka7001.com

127.0.0.1 eureka7002.com

写YML(以前单机)

  1. server:
  2. port: 7001
  3. eureka:
  4. instance:
  5. hostname: localhost #eureka服务端的实例名称
  6. client:
  7. #false表示不向注册中心注册自己。
  8. register-with-eureka: false
  9. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  10. fetch-registry: false
  11. service-url:
  12. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  13. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

7001

  1. server:
  2. port: 7001
  3. eureka:
  4. instance:
  5. hostname: eureka7001.com #eureka服务端的实例名称
  6. client:
  7. register-with-eureka: false #false表示不向注册中心注册自己。
  8. fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  9. service-url:
  10. defaultZone: http://eureka7002.com:7002/eureka/

7002

  1. server:
  2. port: 7002
  3. eureka:
  4. instance:
  5. hostname: eureka7002.com #eureka服务端的实例名称
  6. client:
  7. register-with-eureka: false #false表示不向注册中心注册自己。
  8. fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  9. service-url:
  10. defaultZone: http://eureka7001.com:7001/eureka/

主启动

  1. @SpringBootApplication
  2. @EnableEurekaServer
  3. public class EurekaMain7002
  4. {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(EurekaMain7002.class,args);
  8. }
  9. }

SpringCloud-第一部分 - 图46

SpringCloud-第一部分 - 图47

相互注册,相互守望

将支付服务8001微服务发布到上面2台Eureka集群配置中

  1. server:
  2. port: 8001
  3. spring:
  4. application:
  5. name: cloud-payment-service
  6. datasource:
  7. type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
  8. driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
  9. url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
  10. username: root
  11. password: 123456
  12. eureka:
  13. client:
  14. #表示是否将自己注册进EurekaServer默认为true。
  15. register-with-eureka: true
  16. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  17. fetchRegistry: true
  18. service-url:
  19. #defaultZone: http://localhost:7001/eureka
  20. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
  21. mybatis:
  22. mapperLocations: classpath:mapper/*.xml
  23. type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包

将订单服务80微服务发布到上面2台Eureka集群配置中

  1. server:
  2. port: 80
  3. spring:
  4. application:
  5. name: cloud-order-service
  6. eureka:
  7. client:
  8. #表示是否将自己注册进EurekaServer默认为true。
  9. register-with-eureka: true
  10. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  11. fetchRegistry: true
  12. service-url:
  13. #defaultZone: http://localhost:7001/eureka
  14. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版

测试01

  1. 先要启动EurekaServer7001/7002服务
  2. 再要启动服务提供者provider8001
  3. 再要启动消费者,80
  4. http://localhost/consumer/payment/get/31

支付服务提供者8001集群环境构建

  1. 参考cloud-provider-payment8001
  2. 新建cloud-provider-payment8002

pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud</artifactId>
  7. <groupId>org.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-provider-payment8002</artifactId>
  12. <dependencies>
  13. <!--eureka-client-->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-web</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-actuator</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.mybatis.spring.boot</groupId>
  28. <artifactId>mybatis-spring-boot-starter</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>com.alibaba</groupId>
  32. <artifactId>druid-spring-boot-starter</artifactId>
  33. <version>1.1.10</version>
  34. </dependency>
  35. <!--mysql-connector-java-->
  36. <dependency>
  37. <groupId>mysql</groupId>
  38. <artifactId>mysql-connector-java</artifactId>
  39. </dependency>
  40. <!--jdbc-->
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-starter-jdbc</artifactId>
  44. </dependency>
  45. <dependency>
  46. <groupId>org.springframework.boot</groupId>
  47. <artifactId>spring-boot-devtools</artifactId>
  48. <scope>runtime</scope>
  49. <optional>true</optional>
  50. </dependency>
  51. <dependency>
  52. <groupId>org.projectlombok</groupId>
  53. <artifactId>lombok</artifactId>
  54. <optional>true</optional>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-starter-test</artifactId>
  59. <scope>test</scope>
  60. </dependency>
  61. <!-- 热部署-->
  62. <dependency>
  63. <groupId>org.springframework.boot</groupId>
  64. <artifactId>spring-boot-devtools</artifactId>
  65. <scope>runtime</scope>
  66. <optional>true</optional>
  67. </dependency>
  68. <!-- 公共模块-->
  69. <dependency>
  70. <groupId>org.example</groupId>
  71. <artifactId>cloud-api-commons</artifactId>
  72. <version>1.0-SNAPSHOT</version>
  73. <scope>compile</scope>
  74. </dependency>
  75. <dependency>
  76. <groupId>org.example</groupId>
  77. <artifactId>cloud-provider-payment8001</artifactId>
  78. <version>1.0-SNAPSHOT</version>
  79. <scope>compile</scope>
  80. </dependency>
  81. </dependencies>
  82. </project>
  1. server:
  2. port: 8002
  3. spring:
  4. application:
  5. name: cloud-payment-service
  6. datasource:
  7. type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
  8. driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
  9. url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
  10. username: root
  11. password: 123456
  12. eureka:
  13. client:
  14. #表示是否将自己注册进EurekaServer默认为true。
  15. register-with-eureka: true
  16. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  17. fetchRegistry: true
  18. service-url:
  19. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
  20. #defaultZone: http://localhost:7001/eureka # 单机版
  21. mybatis:
  22. mapperLocations: classpath:mapper/*.xml
  23. type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包

启动类

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class PaymentMain8002
  4. {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(PaymentMain8002.class,args);
  8. }
  9. }

直接从8001粘

修改8001/8002的Controller

  1. @Value("${server.port}")
  2. private String serverPort;

在return new CommonResult上拼接个serverPort用以区分

负载均衡

SpringCloud-第一部分 - 图48

订单服务访问地址不能写死

使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

  1. //public static final String PAYMENT_SRV = "http://localhost:8001";
  2. // 通过在eureka上注册过的微服务名称调用
  3. public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";

RestTemplate ApplicationContextBean

  1. @Configuration
  2. public class ApplicationContextConfig {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

SpringCloud-第一部分 - 图49

SpringCloud-第一部分 - 图50

两次请求不同服务,轮询

Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了。

actuator微服务信息完善

主机名称:服务名称修改

含有主机名称:

SpringCloud-第一部分 - 图51

修改cloud-provider-payment8001 yml文件

  1. eureka:
  2. client:
  3. #表示是否将自己注册进EurekaServer默认为true。
  4. register-with-eureka: true
  5. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  6. fetchRegistry: true
  7. service-url:
  8. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
  9. #defaultZone: http://localhost:7001/eureka # 单机版
  10. instance:
  11. instance-id: payment8001

加instance.instance-id:

访问信息有IP信息提示:

  1. instance:
  2. instance-id: payment8001
  3. prefer-ip-address: true #访问路径可以显示IP地址

SpringCloud-第一部分 - 图52

服务发现Discovery

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

修改cloud-provider-payment8001的Controller

  1. public class PaymentController
  2. {
  3. @Autowired
  4. private DiscoveryClient discoveryClient;
  5. @GetMapping(value = "/payment/discovery")
  6. public Object discovery(){
  7. List<String> services = discoveryClient.getServices();
  8. for (String element : services) {
  9. System.out.println(element);
  10. }
  11. List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
  12. for (ServiceInstance element : instances) {
  13. System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
  14. + element.getUri());
  15. }
  16. return this.discoveryClient;
  17. }
  18. }

8001主启动类

@EnableDiscoveryClient //服务发现

自测:

  1. 先要启动EurekaServer
  2. 再启动8001主启动类,需要稍等一会儿
  3. http://localhost:8001/payment/discovery

Eureka自我保护机制

  1. 概述
  2. 保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,
  3. Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
  4. 如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
  5. EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.
  6. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE

SpringCloud-第一部分 - 图53

导致原因

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

属于CAP里面的AP分支

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

SpringCloud-第一部分 - 图54

  1. 在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。<br />

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

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

怎么禁止自我保护

注册中心eureakeServer端7001

  1. 出厂默认,自我保护机制是开启的
  2. eureka.server.enable-self-preservation=true

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

  1. server:
  2. port: 7001
  3. spring:
  4. application:
  5. name: eureka-cluster-server
  6. eureka:
  7. instance:
  8. hostname: eureka7001.com
  9. client:
  10. register-with-eureka: false
  11. fetch-registry: false
  12. service-url:
  13. #defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  14. defaultZone: http://eureka7001.com:7001/eureka
  15. server:
  16. #关闭自我保护机制,保证不可用服务被及时踢除
  17. enable-self-preservation: false
  18. eviction-interval-timer-in-ms: 2000

关闭效果

SpringCloud-第一部分 - 图55

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

生产者客户端eureakeClient端8001 单位为秒(默认是30秒)

  1. eureka.instance.lease-renewal-interval-in-seconds=30 #单位为秒(默认是30秒)
  2. eureka.instance.lease-expiration-duration-in-seconds=90 #单位为秒(默认是90秒)

配置

  1. server:
  2. port: 8001
  3. ###服务名称(服务注册到eureka名称)
  4. spring:
  5. application:
  6. name: cloud-provider-payment
  7. eureka:
  8. client: #服务提供者provider注册进eureka服务列表内
  9. service-url:
  10. register-with-eureka: true
  11. fetch-registry: true
  12. # cluster version
  13. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  14. # singleton version
  15. defaultZone: http://eureka7001.com:7001/eureka
  16. #心跳检测与续约时间
  17. #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
  18. instance:
  19. #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
  20. lease-renewal-interval-in-seconds: 1
  21. #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
  22. lease-expiration-duration-in-seconds: 2

测试

  1. 70018001都配置完成
  2. 先启动7001再启动8001

SpringCloud-第一部分 - 图56

先关闭8001

SpringCloud-第一部分 - 图57

马上被删除了

那么不显式的设置enable-self-preservation: true,即使不配置自我保护机制,也同样会被马上剔除掉,这里阳哥讲错了。

  1. eureka:
  2. instance:
  3. hostname: eureka7002.com #eureka服务端的实例名称
  4. client:
  5. #false表示不向注册中心注册自己。
  6. register-with-eureka: false
  7. #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
  8. fetch-registry: false
  9. service-url:
  10. #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
  11. # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  12. defaultZone: http://eureka7001.com:7001/eureka/
  13. server:
  14. #关闭自我保护机制,保证不可用服务被及时踢除
  15. enable-self-preservation: true
  16. eviction-interval-timer-in-ms: 200000

手动关掉8001服务

SpringCloud-第一部分 - 图58

超时时间过后才被剔除

SpringCloud-第一部分 - 图59

三、Zookeeper

Eureka停止更新了你怎么办 https://github.com/Netflix/eureka/wiki

第一章、基础知识

1、概述

  1. Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察
  2. 者做出相应的反应。

文件系统+通知机制

SpringCloud-第一部分 - 图60

2、特点

SpringCloud-第一部分 - 图61

  1. 1Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
  2. 2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器。
  3. 3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
  4. 4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
  5. 5)数据更新原子性,一次数据更新要么成功,要么失败。
  6. 6)实时性,在一定时间范围内,Client能读到最新数据。

3、数据结构

  1. ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个节点称做一个 ZNode。每一个 ZNode 默认能够存储 1MB 的数据,每个 ZNode 都可以通过其路径唯一标识。

SpringCloud-第一部分 - 图62

4 应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如:IP不容易记住,而域名容易记住。

统一命名服务:

SpringCloud-第一部分 - 图63

统一配置管理:

1)分布式环境下,配置文件同步非常常见。

(1)一般要求一个集群中,所有节点的配置信息是一致的,比如 Kafka 集群。

(2)对配置文件修改后,希望能够快速同步到各个节点上。

2)配置管理可交由ZooKeeper实现。

(1)可将配置信息写入ZooKeeper上的一个Znode。

(2)各个客户端服务器监听这个Znode。

(3)一 旦Znode中的数据被修改,ZooKeeper将通知各个客户端服务器。

SpringCloud-第一部分 - 图64

统一集群管理

1)分布式环境中,实时掌握每个节点的状态是必要的。

(1)可根据节点实时状态做出一些调整。

2)ZooKeeper可以实现实时监控节点状态变化

(1)可将节点信息写入ZooKeeper上的一个ZNode。

(2)监听这个ZNode可获取它的实时状态变化。

SpringCloud-第一部分 - 图65

服务器动态上下线

SpringCloud-第一部分 - 图66

软负载均衡

在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求

SpringCloud-第一部分 - 图67

5 下载地址

1)官网首页:

https://zookeeper.apache.org/

2)下载截图

SpringCloud-第一部分 - 图68

SpringCloud-第一部分 - 图69

SpringCloud-第一部分 - 图70

第二章、Zookeeper本地安装

将zookeeper安装包用xftp放到/opt/software 目录中,没有就创建一个。

改个名(名太长,但是一般留个版本号): mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7

去配置文件:conf/zoo_sample.cfg 改名 mv zoo_sample.cfg zoo.cfg

在zookeeper上创建个zkData目录,为了保存zookeeper的数据文件,在zoo.cfg上改文件

SpringCloud-第一部分 - 图71

把目录换成自己建的,可以进入目录用pwd命令获取目录绝对路径

启动zookeeper服务端

  1. bin/zkServer.sh start

启动zookeeper客户端

  1. bin/zkCli.sh

退出客户端

  1. quit

查看进程

  1. jps
  2. jps -l

查看状态

  1. bin/zkServer.sh status

关闭zk服务端命令

  1. bin/zkServer.sh stop

2.2 配置参数解读

Zookeeper中的配置文件zoo.cfg中参数含义解读如下:

1)tickTime = 2000:通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒

SpringCloud-第一部分 - 图72

2)initLimit = 10:LF初始通信时限

SpringCloud-第一部分 - 图73

Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)

默认initLimit*tickTime

3)syncLimit = 5:LF同步通信时限

SpringCloud-第一部分 - 图74

Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。

4)dataDir:保存Zookeeper中的数据

注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。

5)clientPort = 2181:客户端连接端口,通常不做修改。

第三章、Zookeeper集群操作

3.1 集群操作

3.1.1 集群安装

1)集群规划

在 hadoop102、hadoop103 和 hadoop104 三个节点上都部署 Zookeeper。

思考:如果是 10 台服务器,需要部署多少台Zookeeper?

2)解压安装

(1)在 hadoop102 解压 Zookeeper 安装包到/opt/module/目录下

[atguigu@hadoop102 software]$ tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/

(2)修改 apache-zookeeper-3.5.7-bin 名称为 zookeeper-3.5.7

[atguigu@hadoop102 module]$ mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7

3)配置服务器编号

(1)在/opt/module/zookeeper-3.5.7/这个目录下创建 zkData

[atguigu@hadoop102 zookeeper-3.5.7]$ mkdir zkData

(2)在/opt/module/zookeeper-3.5.7/zkData 目录下创建一个 myid 的文件

[atguigu@hadoop102 zkData]$ vi myid

在文件中添加与 server 对应的编号(注意:上下不要有空行,左右不要有空格)

注意:添加 myid 文件,一定要在 Linux 里面创建,在 notepad++里面很可能乱码

(3)拷贝配置好的 zookeeper 到其他机器上

[atguigu@hadoop102 module ]$ xsync zookeeper-3.5.7

并分别在 hadoop103、hadoop104 上修改 myid 文件中内容为 3、4

…………………..

Jdk环境变量

SpringCloud-第一部分 - 图75

部集群

改ip地址

vim /etc/sysconfig/network/network-scripts/ifcfg-ens33

改主机名

vim /etc/hostname

reboot重启

ifconfig检查ip修改情况

  1. hadoop100 192.168.200.188
  2. hadoop102 192.168.200.189
  3. hadoop103 192.168.200.190

搭集群,集群之间的文件同步配置:

xsync需要先安装rsync,去bin目录下安装cd /bin

yum install -y rsync

vim xsync 复制脚本文件

  1. exit 0
  2. fi
  3. #获取文件名称
  4. f=$1
  5. fname=`basename $f`
  6. echo $fname
  7. #获取文件绝对路径
  8. pdir=`cd -P $(dirname $f); pwd`
  9. echo $pdir
  10. #获取当前用户
  11. user=`whoami`
  12. echo "$user"
  13. #循环拷贝
  14. for host in 192.168.200.189 192.168.200.188 192.168.200.190
  15. do
  16. echo "**********$host*********"
  17. rsync -av $pdir/$fname $user@$host:$pdir
  18. done

修改权限

chmod 777 xsync

去配置文件怎加如下配置 vim /opt/moudle/zookeeper-3.5.7/conf/zoo.cfg

  1. #######################cluster##########################
  2. server.1=192.168.200.188:2888:3888
  3. server.2=192.168.200.189:2888:3888
  4. server.3=192.168.200.190:2888:3888

执行命令即可集群间传输数据

sudo xsync “xxxxx”

zk.sh脚本

  1. #!/bin/bash
  2. case $1 in
  3. "start"){
  4. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  5. do
  6. echo ---------- zookeeper $i 启动 ------------
  7. ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
  8. done
  9. };;
  10. "stop"){
  11. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  12. do
  13. echo ---------- zookeeper $i 停止 ------------
  14. ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
  15. done
  16. };;
  17. "status"){
  18. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  19. do
  20. echo ---------- zookeeper $i 状态 ------------
  21. ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
  22. done
  23. };;
  24. esac

jpsall.sh脚本

  1. #!/bin/bash
  2. # 执行jps命令查询每台服务器上的节点状态
  3. echo ======================集群节点状态====================
  4. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  5. do
  6. echo ====================== $i ====================
  7. ssh $i "/opt/moudle/jdk1.8.0_152/bin/jps"
  8. done
  9. echo ======================执行完毕====================

启动zookeeper集群,并查看状态

SpringCloud-第一部分 - 图76

启动第二个的时候,zookeeper服务器超过半数,集群启动成功,选出leader,follower

3.1.2 选举机制(面试重点)

第一次启动

SpringCloud-第一部分 - 图77

SpringCloud-第一部分 - 图78

非第一次启动

SpringCloud-第一部分 - 图79

3.1.3 ZK 集群启动停止脚本

1)在 hadoop102 的/home/atguigu/bin 目录下创建脚本

vim zk.sh

在脚本中编写如下内容

  1. #!/bin/bash
  2. case $1 in
  3. "start"){
  4. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  5. do
  6. echo ---------- zookeeper $i 启动 ------------
  7. ssh $i "/opt/moudle/zookeeper-3.5.7/bin/zkServer.sh start"
  8. done
  9. };;
  10. "stop"){
  11. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  12. do
  13. echo ---------- zookeeper $i 停止 ------------
  14. ssh $i "/opt/moudle/zookeeper-3.5.7/bin/zkServer.sh stop"
  15. done
  16. };;
  17. "status"){
  18. for i in 192.168.200.188 192.168.200.189 192.168.200.190
  19. do
  20. echo ---------- zookeeper $i 状态 ------------
  21. ssh $i "/opt/moudle/zookeeper-3.5.7/bin/zkServer.sh status"
  22. done
  23. };;
  24. esac

2)增加脚本执行权限

chmod u+x zk.sh

3)Zookeeper 集群启动脚本

zk.sh start

4)Zookeeper 集群停止脚本

zk.sh stop

bug:

Error: JAVA_HOME is not set and java could not be found in PATH.

在zookeeper目录下bin打开zkEnv.sh 添加一行jdk的安装路径

SpringCloud-第一部分 - 图80

脚本文件都需要用xsync同步一下。。。

3.2 客户端命令行操作

3.2.1 命令行语句
命令基本语法 功能描述
help 显示所有操作命令
ls path 使用 ls 命令来查看当前 znode 的子节点 [可监听]
-w 监听子节点变化
-s 附加次级信息
create 普通创建
-s 含有序列
-e 临时(重启或者超时消失)
get path 获得节点的值 [可监听]
-w 监听节点内容变化
-s 附加次级信息
set 设置节点的具体值
stat 查看节点状态
delete 删除节点
deleteall 递归删除节点

1)启动客户端

bin/zkCli.sh -server hadoop102:2181

2)显示所有操作命令

help

3.2.2 znode 节点数据信息

1)查看当前znode中所包含的内容

ls /

[zookeeper]

2)查看当前节点详细数据

  1. [zk: hadoop100:2181(CONNECTED) 3] ls -s /
  2. [zookeeper]cZxid = 0x0
  3. ctime = Thu Jan 01 08:00:00 CST 1970
  4. mZxid = 0x0
  5. mtime = Thu Jan 01 08:00:00 CST 1970
  6. pZxid = 0x0
  7. cversion = -1
  8. dataVersion = 0
  9. aclVersion = 0
  10. ephemeralOwner = 0x0
  11. dataLength = 0
  12. numChildren = 1

(1)czxid:创建节点的事务 zxid

  1. 每次修改 ZooKeeper 状态都会产生一个 ZooKeeper 事务 ID。事务 ID ZooKeeper 中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 zxid2 之前发生。

(2)ctime:znode 被创建的毫秒数(从 1970 年开始)

(3)mzxid:znode 最后更新的事务 zxid

(4)mtime:znode 最后修改的毫秒数(从 1970 年开始)

(5)pZxid:znode 最后更新的子节点 zxid

(6)cversion:znode 子节点变化号,znode 子节点修改次数

(7)dataversion:znode 数据变化号

(8)aclVersion:znode 访问控制列表的变化号

(9)ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0。

(10)dataLength:znode 的数据长度

(11)numChildren:znode 子节点数量

3.2.3 节点类型(持久/短暂/有序号/无序号)

分别创建2个普通节点(永久节点+不带序号)

  1. [zk: hadoop100:2181(CONNECTED) 4] ls /
  2. [zookeeper]
  3. [zk: hadoop100:2181(CONNECTED) 5] create /sanguo "diaochan"
  4. Created /sanguo
  5. [zk: hadoop100:2181(CONNECTED) 6] ls /
  6. [sanguo, zookeeper]
  7. [zk: hadoop100:2181(CONNECTED) 7] create /sanguo/shuguo "liubei"
  8. Created /sanguo/shuguo
  9. [zk: hadoop100:2181(CONNECTED) 8] ls /
  10. [sanguo, zookeeper]
  11. [zk: hadoop100:2181(CONNECTED) 9] ls /sanguo
  12. [shuguo]
  13. [zk: hadoop100:2181(CONNECTED) 10] get -s /sanguo
  14. diaochan
  15. cZxid = 0x400000002
  16. ctime = Sat Apr 23 10:44:06 CST 2022
  17. mZxid = 0x400000002
  18. mtime = Sat Apr 23 10:44:06 CST 2022
  19. pZxid = 0x400000003
  20. cversion = 1
  21. dataVersion = 0
  22. aclVersion = 0
  23. ephemeralOwner = 0x0
  24. dataLength = 8
  25. numChildren = 1
  26. [zk: hadoop100:2181(CONNECTED) 11]

获得节点的值

  1. [zk: hadoop100:2181(CONNECTED) 11] get -s /sanguo/shuguo
  2. liubei
  3. cZxid = 0x400000003
  4. ctime = Sat Apr 23 10:44:33 CST 2022
  5. mZxid = 0x400000003
  6. mtime = Sat Apr 23 10:44:33 CST 2022
  7. pZxid = 0x400000003
  8. cversion = 0
  9. dataVersion = 0
  10. aclVersion = 0
  11. ephemeralOwner = 0x0
  12. dataLength = 6
  13. numChildren = 0
  14. [zk: hadoop100:2181(CONNECTED) 12]

创建带序号的节点(永久节点 +带序号)

先创建一个普通的根节点/sanguo/weiguo

  1. [zk: localhost:2181(CONNECTED) 1] create /sanguo/weiguo "caocao"
  2. Created /sanguo/weiguo

创建带序号的节点

  1. [zk: hadoop100:2181(CONNECTED) 18] create -s /sanguo/weiguo/zhangliao "zhangliao"
  2. Created /sanguo/weiguo/zhangliao0000000001
  3. [zk: hadoop100:2181(CONNECTED) 19] create -s /sanguo/weiguo/zhangliao
  4. zhangliao zhangliao0000000001
  5. [zk: hadoop100:2181(CONNECTED) 19] create -s /sanguo/weiguo/zhangliao "zhangliao"
  6. Created /sanguo/weiguo/zhangliao0000000002
  7. [zk: hadoop100:2181(CONNECTED) 20] create -s /sanguo/weiguo/xuchu "xushu"
  8. Created /sanguo/weiguo/xuchu0000000003

如果原来没有序号节点,序号从 0 开始依次递增。如果原节点下已有 2 个节点,则再排序时从 2 开始,以此类推。

创建短暂节点(短暂节点 + 不带序号 or 带序号)

  1. 1)创建短暂的不带序号的节点
  2. [zk: localhost:2181(CONNECTED) 7] create -e /sanguo/wuguo "zhouyu"
  3. Created /sanguo/wuguo
  4. 2)创建短暂的带序号的节点
  5. [zk: localhost:2181(CONNECTED) 2] create -e -s /sanguo/wuguo "zhouyu"
  6. Created /sanguo/wuguo0000000001
  7. 3)在当前客户端是能查看到的
  8. [zk: localhost:2181(CONNECTED) 3] ls /sanguo
  9. [wuguo, wuguo0000000001, shuguo]
  10. 4)退出当前客户端然后再重启客户端
  11. [zk: localhost:2181(CONNECTED) 12] quit
  12. [atguigu@hadoop104 zookeeper-3.5.7]$ bin/zkCli.sh
  13. 5)再次查看根目录下短暂节点已经删除
  14. [zk: localhost:2181(CONNECTED) 0] ls /sanguo
  15. [shuguo]

5)修改节点数据值

[zk: localhost:2181(CONNECTED) 6] set /sanguo/weiguo “simayi”

3.2.4 监听器原理
  1. 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证 ZooKeeper 保存的任何的数据的任何改变都能快速的响应到监听了该节点的应用程序。

SpringCloud-第一部分 - 图81

1)节点的值变化监听

(1)在 hadoop104 主机上注册监听/sanguo 节点数据变化

[zk: localhost:2181(CONNECTED) 26] get -w /sanguo

(2)在 hadoop103 主机上修改/sanguo 节点的数据

[zk: localhost:2181(CONNECTED) 1] set /sanguo “xisi”

(3)观察 hadoop104 主机收到数据变化的监听

WATCHER::WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo

注意:在hadoop103再多次修改/sanguo的值,hadoop104上不会再收到监听。因为注册

一次,只能监听一次。想再次监听,需要再次注册。

2)节点的子节点变化监听(路径变化)

(1)在 hadoop104 主机上注册监听/sanguo 节点的子节点变化

[zk: localhost:2181(CONNECTED) 1] ls -w /sanguo

[shuguo, weiguo]

(2)在 hadoop103 主机/sanguo 节点上创建子节点

[zk: localhost:2181(CONNECTED) 2] create /sanguo/jin “simayi”

Created /sanguo/jin

(3)观察 hadoop104 主机收到子节点变化的监听

WATCHER::WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo

注意:节点的路径变化,也是注册一次,生效一次。想多次生效,就需要多次注册。

3.2.5 节点删除与查看

1)删除节点

[zk: localhost:2181(CONNECTED) 4] delete /sanguo/jin

2)递归删除节点

[zk: localhost:2181(CONNECTED) 15] deleteall /sanguo/shuguo

3)查看节点状态

  1. [zk: localhost:2181(CONNECTED) 17] stat /sanguo
  2. cZxid = 0x100000003
  3. ctime = Wed Aug 29 00:03:23 CST 2018
  4. mZxid = 0x100000011
  5. mtime = Wed Aug 29 00:21:23 CST 2018
  6. pZxid = 0x100000014
  7. cversion = 9
  8. dataVersion = 1
  9. aclVersion = 0
  10. ephemeralOwner = 0x0
  11. dataLength = 4
  12. numChildren = 1

3.3 客户端API操作

前提:保证 hadoop102、hadoop103、hadoop104 服务器上 Zookeeper 集群服务端启动。做好win的hosts映射

3.3.1 IDEA 环境搭建

1)创建一个工程:zookeeper

2)添加pom文件

  1. <dependencies>
  2. <dependency>
  3. <groupId>junit</groupId>
  4. <artifactId>junit</artifactId>
  5. <version>RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.logging.log4j</groupId>
  9. <artifactId>log4j-core</artifactId>
  10. <version>2.8.2</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.apache.zookeeper</groupId>
  14. <artifactId>zookeeper</artifactId>
  15. <version>3.5.7</version>
  16. </dependency>
  17. </dependencies>

3)拷贝log4j.properties文件到项目根目录

需要在项目的 src/main/resources 目录下,新建一个文件,命名为“log4j.properties”,在文件中填入。

  1. log4j.rootLogger=INFO, stdout
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
  5. log4j.appender.logfile=org.apache.log4j.FileAppender
  6. log4j.appender.logfile.File=target/spring.log
  7. log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
  8. log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

4)创建包名com.atguigu.zk

5)创建类名称zkClient

3.3.2 创建 ZooKeeper 客户端
  1. public class ZkClient {
  2. // 注意:都好左右不能有空格
  3. private String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  4. // 超时时间
  5. private int sessionTimeout = 2000;
  6. private ZooKeeper zkClient;
  7. @Before
  8. public void init() throws IOException {
  9. zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
  10. @Override
  11. public void process(WatchedEvent watchedEvent) {
  12. }
  13. });
  14. }
  15. @Test
  16. public void create(){
  17. try {
  18. String nodeCreated = zkClient.create("/atguigu", "ss.avi".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  19. } catch (KeeperException e) {
  20. e.printStackTrace();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }

3.3.3 创建子节点
  1. @Test
  2. public void create(){
  3. try {
  4. String nodeCreated = zkClient.create("/atguigu", "ss.avi".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  5. } catch (KeeperException e) {
  6. e.printStackTrace();
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }

测试:在 hadoop102 的 zk 客户端上查看创建节点情况 get -s /atguigu

3.3.4 获取子节点并监听节点变化
  1. @Test
  2. public void getChildren() throws KeeperException, InterruptedException {
  3. List<String> children = zkClient.getChildren("/", true);
  4. for (String child : children) {
  5. System.out.println(child);
  6. }
  7. //延时阻塞
  8. Thread.sleep(Long.MAX_VALUE);
  9. }
  1. public class ZkClient {
  2. // 注意:都好左右不能有空格
  3. private String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  4. // 超时时间
  5. private int sessionTimeout = 2000;
  6. private ZooKeeper zkClient;
  7. @Before
  8. public void init() throws IOException {
  9. zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
  10. @Override
  11. public void process(WatchedEvent watchedEvent) {
  12. System.out.println("-------------------------");
  13. List<String> children =null;
  14. try {
  15. children = zkClient.getChildren("/", true);
  16. for (String child : children) {
  17. System.out.println(child);
  18. }
  19. System.out.println("-------------------------");
  20. } catch (KeeperException e) {
  21. e.printStackTrace();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. });
  27. }
  28. @Test
  29. public void create(){
  30. try {
  31. String nodeCreated = zkClient.create("/atguigu", "ss.avi".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  32. } catch (KeeperException e) {
  33. e.printStackTrace();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. @Test
  39. public void getChildren() throws KeeperException, InterruptedException {
  40. List<String> children = zkClient.getChildren("/", true);
  41. for (String child : children) {
  42. System.out.println(child);
  43. }
  44. //延时阻塞
  45. Thread.sleep(Long.MAX_VALUE);
  46. }
  47. }

此时创建/删除节点,都有监听到,并打印才控制台

3.3.5 判断Znode是否存在
  1. @Test
  2. public void exist() throws KeeperException, InterruptedException {
  3. Stat stat = zkClient.exists("/atguigu", false);
  4. System.out.println(stat == null ? "not exist" : "exist");
  5. }

3.4 客户端向服务端写数据流程

写流程之写入请求直接发送给Leader节点

SpringCloud-第一部分 - 图82

写流程之写入请求发送给follower节点

SpringCloud-第一部分 - 图83

第四章服务器动态上下线监听案例

4.1 需求

某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。

4.2 需求分析

SpringCloud-第一部分 - 图84

4.3 具体实现

(1)先在集群上创建/servers 节点

create /servers “servers”

(2)在 Idea 中创建包名:com.atguigu.zkcase1

(3)服务器端向 Zookeeper 注册代码

  1. public class DistributeServer {
  2. // 注意:都好左右不能有空格
  3. private String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  4. // 超时时间
  5. private int sessionTimeout = 2000;
  6. private ZooKeeper zkClient;
  7. public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
  8. DistributeServer server = new DistributeServer();
  9. // 1 获取zk连接
  10. server.getConnect();
  11. // 2 注册服务器到zk集群
  12. server.registServer(args[0]);
  13. // 3 启动业务逻辑
  14. server.business();
  15. }
  16. private void business() {
  17. try {
  18. Thread.sleep(Long.MAX_VALUE);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. private void registServer(String hostname) throws KeeperException, InterruptedException {
  24. String created = zkClient.create("/servers/", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  25. System.out.println("hostname = " + hostname + " is online");
  26. }
  27. private void getConnect() throws IOException {
  28. zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
  29. @Override
  30. public void process(WatchedEvent watchedEvent) {
  31. }
  32. });
  33. }
  34. }

客户端代码:

  1. public class DistributeClient {
  2. // 注意:都好左右不能有空格
  3. private String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  4. // 超时时间
  5. private int sessionTimeout = 2000;
  6. private ZooKeeper zk;
  7. public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
  8. DistributeClient client = new DistributeClient();
  9. // 1 获取zk连接
  10. client.getConnect();
  11. // 2 监听/servers 下面子节点的增加和删除
  12. client.getServerList();
  13. // 3 业务逻辑(sleep)
  14. client.business();
  15. }
  16. private void business() {
  17. try {
  18. Thread.sleep(Long.MAX_VALUE);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. private void getServerList() throws KeeperException, InterruptedException {
  24. List<String> children = zk.getChildren("/servers", true);
  25. List<String> servers = new ArrayList<>();
  26. for (String child : children) {
  27. //System.out.println("child = " + child);
  28. byte[] data = zk.getData("/servers/" + child, false, null);
  29. servers.add(new String(data));
  30. }
  31. // 打印
  32. System.out.println(servers);
  33. }
  34. private void getConnect() throws IOException {
  35. zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {//ctrl + alt + f
  36. @Override
  37. public void process(WatchedEvent watchedEvent) {
  38. try {
  39. getServerList();
  40. } catch (KeeperException e) {
  41. e.printStackTrace();
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. });
  47. }
  48. }

4.4 测试

1)在 Linux 命令行上操作增加减少服务器

(1)启动 DistributeClient 客户端

(2)在 hadoop102 上 zk 的客户端/servers 目录上创建临时带序号节点

create -e -s /servers/hadoop100 “hadoop100”

create -e -s /servers/hadoop100 “hadoop102”

SpringCloud-第一部分 - 图85

(3)观察 Idea 控制台变化

SpringCloud-第一部分 - 图86

(4)执行删除操作

delete /servers/hadoop1000000000000

SpringCloud-第一部分 - 图87

2)在 Idea 上操作增加减少服务器

(1)启动 DistributeClient 客户端(如果已经启动过,不需要重启)

(2)启动 DistributeServer 服务

①点击 Edit Configurations…

SpringCloud-第一部分 - 图88

②在弹出的窗口中(Program arguments)输入想启动的主机,例如,hadoop100

SpringCloud-第一部分 - 图89

③回到 DistributeServer 的 main 方 法 , 右 键 , 在 弹 出 的 窗 口 中 点 击 Run“DistributeServer.main()”

还可以

SpringCloud-第一部分 - 图90

更换不同的参数,启动多个服务端

④观察 DistributeServer 控制台,提示 hadoop102 is working

⑤观察 DistributeClient 控制台,提示 hadoop102 已经上线

SpringCloud-第一部分 - 图91

5 章、ZooKeeper 分布式锁案例

  1. 什么叫做分布式锁呢?
  2. 比如说"进程 1"在使用该资源的时候,会先去获得锁,"进程 1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程 1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

SpringCloud-第一部分 - 图92

5.1 原生Zookeeper实现分布式锁案例

1)分布式锁实现

  1. public class DistributedLock {
  2. // 注意:都好左右不能有空格
  3. private String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  4. // 超时时间
  5. private int sessionTimeout = 2000;
  6. private final ZooKeeper zk;
  7. // Zookeeper 连接
  8. private CountDownLatch connectLatch = new CountDownLatch(1);
  9. private CountDownLatch waitLatch = new CountDownLatch(1);
  10. private String waitPath;
  11. private String currentMode;
  12. public DistributedLock() throws IOException, InterruptedException, KeeperException {
  13. // 获取连接
  14. zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
  15. @Override
  16. public void process(WatchedEvent watchedEvent) {
  17. // connectLatch 如果连接上zk 可以释放
  18. if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
  19. connectLatch.countDown();
  20. }
  21. // waitLatch 需要释放
  22. if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
  23. waitLatch.countDown();
  24. }
  25. }
  26. });
  27. // 等待zk 正常连接后,往下走程序
  28. connectLatch.await();
  29. // 判断根节点/locks 是否存在
  30. Stat stat = zk.exists("/locks", false);
  31. if (stat == null){
  32. // 创建一下根节点
  33. zk.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
  34. }
  35. }
  36. // 对zk加锁
  37. public void zkLock(){
  38. // 创建对应的临时带序号节点
  39. try {
  40. currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  41. Thread.sleep(10);
  42. // 判断当前节点是不是整个目录下最小的序号节点,如果是,获取到锁,如果不是,监听它前一个节点
  43. List<String> children = zk.getChildren("/locks", false);
  44. // 如果children 只有一个值,那就直接获取锁,如果有多个节点,需要判断,谁最小
  45. if (children.size() == 1){
  46. return;
  47. } else {
  48. Collections.sort(children);
  49. // 获取节点名称 seq-00000000
  50. String thisNode = currentMode.substring("/locks/".length());
  51. // 通过seq-000000000 获取该节点在children集合的位置
  52. int index = children.indexOf(thisNode);
  53. //判断
  54. if (index == -1){
  55. System.out.println("数据异常");
  56. } else if (index == 0){
  57. //就一个节点,可以获取锁了
  58. return;
  59. } else {
  60. // 需要监听 他前一个节点变化
  61. waitPath = "/locks/" + children.get(index - 1);
  62. zk.getData(waitPath,true,new Stat());
  63. //等待监听
  64. waitLatch.await();
  65. return;
  66. }
  67. }
  68. } catch (KeeperException e) {
  69. e.printStackTrace();
  70. } catch (InterruptedException e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. // 解锁
  75. public void unZkLock(){
  76. //删除节点
  77. try {
  78. zk.delete(currentMode,-1);
  79. } catch (InterruptedException e) {
  80. e.printStackTrace();
  81. } catch (KeeperException e) {
  82. e.printStackTrace();
  83. }
  84. }
  85. }

创两个线程测试:

  1. public class DistributedLockTest {
  2. public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
  3. final DistributedLock lock1 = new DistributedLock();
  4. final DistributedLock lock2 = new DistributedLock();
  5. new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. try {
  9. lock1.zkLock();
  10. System.out.println("线程1 启动,获取到锁");
  11. Thread.sleep(5 * 1000);
  12. lock1.unZkLock();
  13. System.out.println("线程1 启动,释放到锁");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }).start();
  19. new Thread(new Runnable() {
  20. @Override
  21. public void run() {
  22. try {
  23. lock2.zkLock();
  24. System.out.println("线程2 启动,获取到锁");
  25. Thread.sleep(5 * 1000);
  26. lock2.unZkLock();
  27. System.out.println("线程2 启动,释放到锁");
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }).start();
  33. }
  34. }

测试结果:

SpringCloud-第一部分 - 图93

5.2 Curator 框架实现分布式锁案例

1)原生的 Java API 开发存在的问题

(1)会话连接是异步的,需要自己去处理。比如使用 CountDownLatch

(2)Watch 需要重复注册,不然就不能生效

(3)开发的复杂性还是比较高的

(4)不支持多节点删除和创建。需要自己去递归

2)Curator 是一个专门解决分布式锁的框架,解决了原生JavaAPI 开发分布式遇到的问题。

详情请查看官方文档:https://curator.apache.org/index.html

3)Curator 案例实操

(1)添加依赖:

  1. <dependency>
  2. <groupId>org.apache.curator</groupId>
  3. <artifactId>curator-framework</artifactId>
  4. <version>4.3.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.curator</groupId>
  8. <artifactId>curator-recipes</artifactId>
  9. <version>4.3.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.curator</groupId>
  13. <artifactId>curator-client</artifactId>
  14. <version>4.3.0</version>
  15. </dependency>

(2)代码实现:

  1. public class CuratorLockTest {
  2. private String rootNode = "/locks";
  3. // 注意:都好左右不能有空格
  4. private static String connectString = "hadoop100:2181,hadoop102:2181,hadoop103:2181";
  5. // connection 超时时间
  6. private static int connectionTimeout = 2000;
  7. // session 超时时间
  8. private static int sessionTimeout = 2000;
  9. public static void main(String[] args) {
  10. //创建分布式锁 1
  11. InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");
  12. //创建分布式锁 2
  13. InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");
  14. new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. try {
  18. lock1.acquire();
  19. System.out.println("线程1 获取到锁");
  20. lock1.acquire();
  21. System.out.println("线程1 再次获取到锁");
  22. Thread.sleep(5 * 1000);
  23. lock1.release();
  24. System.out.println("线程1 释放锁");
  25. lock1.release();
  26. System.out.println("线程1 再次释放锁");
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }).start();
  32. new Thread(new Runnable() {
  33. @Override
  34. public void run() {
  35. try {
  36. lock2.acquire();
  37. System.out.println("线程2 获取到锁");
  38. lock2.acquire();
  39. System.out.println("线程2 再次获取到锁");
  40. Thread.sleep(5 * 1000);
  41. lock2.release();
  42. System.out.println("线程2 释放锁");
  43. lock2.release();
  44. System.out.println("线程2 再次释放锁");
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }).start();
  50. }
  51. private static CuratorFramework getCuratorFramework() {
  52. ExponentialBackoffRetry retry = new ExponentialBackoffRetry(3000, 3);
  53. CuratorFramework client = CuratorFrameworkFactory.builder().connectString(connectString)
  54. .connectionTimeoutMs(connectionTimeout)
  55. .sessionTimeoutMs(sessionTimeout)
  56. .retryPolicy(retry).build();
  57. // 启动客户端
  58. client.start();
  59. System.out.println("zookeeper 启动成功...");
  60. return client;
  61. }
  62. }

第六章、企业面试真题(面试重点)

6.1 选举机制

半数机制,超过半数的投票通过,即通过。

(1)第一次启动选举规则:

投票过半数时,服务器 id 大的胜出

(2)第二次启动选举规则:

①EPOCH 大的直接胜出

②EPOCH 相同,事务 id 大的胜出

③事务 id 相同,服务器 id 大的胜出

6.2 生产集群安装多少 zk 合适?

安装奇数台。

生产经验:

⚫ 10 台服务器:3 台 zk;

⚫ 20 台服务器:5 台 zk;

⚫ 100 台服务器:11 台 zk;

⚫ 200 台服务器:11 台 zk

服务器台数多:好处,提高可靠性;坏处:提高通信延时

6.3 常用命令

ls、get、create、delete

SpringCloud整合zookeeper

服务提供者

新建cloud-provider-payment8004

pom:

  1. <!-- SpringBoot整合Web组件 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  7. <groupId>com.atguigu.springcloud</groupId>
  8. <artifactId>cloud-api-commons</artifactId>
  9. <version>${project.version}</version>
  10. </dependency>
  11. <!-- SpringBoot整合zookeeper客户端 -->
  12. <dependency>
  13. <groupId>org.springframework.cloud</groupId>
  14. <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-devtools</artifactId>
  19. <scope>runtime</scope>
  20. <optional>true</optional>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.projectlombok</groupId>
  24. <artifactId>lombok</artifactId>
  25. <optional>true</optional>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-test</artifactId>
  30. <scope>test</scope>
  31. </dependency>

yml:

  1. #8004表示注册到zookeeper服务器的支付服务提供者端口号
  2. server:
  3. port: 8004
  4. #服务别名----注册zookeeper到注册中心名称
  5. spring:
  6. application:
  7. name: cloud-provider-payment
  8. cloud:
  9. zookeeper:
  10. connect-string: 192.168.200.188:2181

主启动类:

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

controller:

  1. @RestController
  2. public class PaymentController{
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @RequestMapping(value = "/payment/zk")
  6. public String paymentzk(){
  7. return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
  8. }
  9. }

启动8004注册进zookeeper

启动后问题

SpringCloud-第一部分 - 图94

解决zookeeper版本jar包冲突问题

SpringCloud-第一部分 - 图95

SpringCloud-第一部分 - 图96

排出zk冲突后的新POM

  1. <!-- SpringBoot整合zookeeper客户端 -->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
  5. <!--先排除自带的zookeeper3.5.3-->
  6. <exclusions>
  7. <exclusion>
  8. <groupId>org.apache.zookeeper</groupId>
  9. <artifactId>zookeeper</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>
  13. <!--添加zookeeper3.4.9版本-->
  14. <dependency>
  15. <groupId>org.apache.zookeeper</groupId>
  16. <artifactId>zookeeper</artifactId>
  17. <version>3.4.9</version>
  18. </dependency>

验证测试:

SpringCloud-第一部分 - 图97

SpringCloud-第一部分 - 图98

http://localhost:8004/payment/zk

SpringCloud-第一部分 - 图99

验证测试2

SpringCloud-第一部分 - 图100

获得json串后用在线工具查看试试

  1. {
  2. "name": "cloud-provider-payment",
  3. "id": "05ddbd78-2241-41e1-8b81-2ffbfa0354e8",
  4. "address": "DESKTOP-NK1QS30",
  5. "port": 8004,
  6. "sslPort": null,
  7. "payload": {
  8. "@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
  9. "id": "application-1",
  10. "name": "cloud-provider-payment",
  11. "metadata": {
  12. }
  13. },
  14. "registrationTimeUTC": 1650706247412,
  15. "serviceType": "DYNAMIC",
  16. "uriSpec": {
  17. "parts": [
  18. {
  19. "value": "scheme",
  20. "variable": true
  21. },
  22. {
  23. "value": "://",
  24. "variable": false
  25. },
  26. {
  27. "value": "address",
  28. "variable": true
  29. },
  30. {
  31. "value": ":",
  32. "variable": false
  33. },
  34. {
  35. "value": "port",
  36. "variable": true
  37. }
  38. ]
  39. }
  40. }

思考:服务节点是临时节点还是持久节点?

SpringCloud-第一部分 - 图101

是临时节点。。。一段时间以后,从注册表中清除

SpringCloud-第一部分 - 图102

服务消费者

新建cloud-consumerzk-order80

pom:

  1. <dependencies>
  2. <!-- SpringBoot整合Web组件 -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-web</artifactId>
  6. </dependency>
  7. <!-- SpringBoot整合zookeeper客户端 -->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
  11. <!--先排除自带的zookeeper-->
  12. <exclusions>
  13. <exclusion>
  14. <groupId>org.apache.zookeeper</groupId>
  15. <artifactId>zookeeper</artifactId>
  16. </exclusion>
  17. </exclusions>
  18. </dependency>
  19. <!--添加zookeeper3.5.7版本-->
  20. <dependency>
  21. <groupId>org.apache.zookeeper</groupId>
  22. <artifactId>zookeeper</artifactId>
  23. <version>3.5.7</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>

application.yml

  1. server:
  2. port: 80
  3. spring:
  4. application:
  5. name: cloud-consumer-order
  6. cloud:
  7. #注册到zookeeper地址
  8. zookeeper:
  9. connect-string: 192.168.200.188:2181

主启动:

  1. @SpringBootApplication
  2. public class OrderZK80{
  3. public static void main(String[] args){
  4. SpringApplication.run(OrderZK80.class,args);
  5. }
  6. }

业务类

配置Bean

  1. @Configuration
  2. public class ApplicationContextBean {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate getRestTemplate() {
  6. return new RestTemplate();
  7. }
  8. }

Controller

  1. @RestController
  2. public class OrderZKController {
  3. public static final String INVOKE_URL = "http://cloud-provider-payment";
  4. @Autowired
  5. private RestTemplate restTemplate;
  6. @RequestMapping(value = "/consumer/payment/zk")
  7. public String paymentInfo() {
  8. String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
  9. System.out.println("消费者调用支付服务(zookeeper)--->result:" + result);
  10. return result;
  11. }
  12. }

验证测试

SpringCloud-第一部分 - 图103

  1. {
  2. "name": "cloud-consumer-order",
  3. "id": "80cb7b07-1bb0-4fa0-a44e-e76ad37a1ec2",
  4. "address": "DESKTOP-NK1QS30",
  5. "port": 80,
  6. "sslPort": null,
  7. "payload": {
  8. "@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
  9. "id": "application-1",
  10. "name": "cloud-consumer-order",
  11. "metadata": {
  12. }
  13. },
  14. "registrationTimeUTC": 1650707226040,
  15. "serviceType": "DYNAMIC",
  16. "uriSpec": {
  17. "parts": [
  18. {
  19. "value": "scheme",
  20. "variable": true
  21. },
  22. {
  23. "value": "://",
  24. "variable": false
  25. },
  26. {
  27. "value": "address",
  28. "variable": true
  29. },
  30. {
  31. "value": ":",
  32. "variable": false
  33. },
  34. {
  35. "value": "port",
  36. "variable": true
  37. }
  38. ]
  39. }
  40. }

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

SpringCloud-第一部分 - 图104

四、Consul

Consul简介

SpringCloud-第一部分 - 图105

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

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

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

官网 https://www.consul.io/intro/index.html

Spring Cloud Consul 具有如下特性:

SpringCloud-第一部分 - 图106

特性:

  1. 服务发现:提供HTTPDNS两种发现方式。
  2. 健康监测: 支持多种方式,HTTPTCPDockerShell脚本定制化监控
  3. KV存储: KeyValue的存储方式
  4. 多数据中心: Consul支持多数据中心
  5. 可视化Web界面

下载地址: https://www.consul.io/downloads.html

文档: https://www.springcloud.cc/spring-cloud-consul.html

安装并运行Consul

官网安装说明 https://learn.hashicorp.com/consul/getting-started/install.html

下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本号信息

SpringCloud-第一部分 - 图107

SpringCloud-第一部分 - 图108

使用开发模式启动

consul agent -dev

通过以下地址可以访问Consul的首页:http://localhost:8500

结果页面

SpringCloud-第一部分 - 图109

  1. sudo yum install -y yum-utils
  2. sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
  3. sudo yum -y install consul

服务提供者

新建Module支付服务provider8006 cloud-providerconsul-payment8006

pom:

  1. <dependencies>
  2. <!--SpringCloud consul-server -->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  6. </dependency>
  7. <!-- SpringBoot整合Web组件 -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-actuator</artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-test</artifactId>
  19. <scope>test</scope>
  20. </dependency>
  21. </dependencies>

yml:

  1. ###consul服务端口号
  2. server:
  3. port: 8006
  4. spring:
  5. application:
  6. name: consul-provider-payment
  7. ####consul注册中心地址
  8. cloud:
  9. consul:
  10. host: localhost
  11. port: 8500
  12. discovery:
  13. #hostname: 127.0.0.1
  14. service-name: ${spring.application.name}

启动类:

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. public class PaymentMain8006 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(PaymentMain8006.class, args);
  6. }
  7. }

业务类Controller

  1. @RestController
  2. public class PaymentController {
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @GetMapping("/payment/consul")
  6. public String paymentInfo() {
  7. return "springcloud with consul : " + serverPort + "\t\t" + UUID.randomUUID().toString();
  8. }
  9. }

验证测试

SpringCloud-第一部分 - 图110

http://localhost:8006/payment/consul

SpringCloud-第一部分 - 图111

服务消费者

pom:

  1. <dependencies>
  2. <!--SpringCloud consul-server -->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  6. </dependency>
  7. <!-- SpringBoot整合Web组件 -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-actuator</artifactId>
  15. </dependency>
  16. </dependencies>

yml:

  1. ###consul服务端口号
  2. server:
  3. port: 80
  4. spring:
  5. application:
  6. name: consul-consumer-order
  7. ####consul注册中心地址
  8. cloud:
  9. consul:
  10. host: localhost
  11. port: 8500
  12. discovery:
  13. #hostname: 127.0.0.1
  14. service-name: ${spring.application.name}

启动类:

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. public class OrderConsulMain80 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(OrderConsulMain80.class,args);
  6. }
  7. }

配置bean:

  1. @Configuration
  2. public class ApplicationContextBean {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate getRestTemplate() {
  6. return new RestTemplate();
  7. }
  8. }

controller:

  1. @RestController
  2. public class OrderConsulController
  3. {
  4. //consul-provider-payment
  5. public static final String INVOKE_URL = "http://consul-provider-payment";
  6. @Autowired
  7. private RestTemplate restTemplate;
  8. @GetMapping(value = "/consumer/payment/consul")
  9. public String paymentInfo()
  10. {
  11. String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul", String.class);
  12. System.out.println("消费者调用支付服务(consule)--->result:" + result);
  13. return result;
  14. }
  15. }

验证测试:

SpringCloud-第一部分 - 图112

http://localhost:8006/payment/consul

访问测试地址:

http://localhost/consumer/payment/consul

SpringCloud-第一部分 - 图113

三个注册中心异同点

CAP:

  1. C:Consistency(强一致性)
  2. A:Availability(可用性)
  3. P:Partition tolerance(分区容错性)
  4. CAP理论关注粒度是数据,而不是整体系统设计的策略

经典CAP图

  1. 最多只能同时较好的满足两个。
  2. CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
  3. 因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
  4. CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  5. CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  6. AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

SpringCloud-第一部分 - 图114

SpringCloud-第一部分 - 图115

AP(Eureka)

AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

SpringCloud-第一部分 - 图116

CP(Zookeeper/Consul)

CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

SpringCloud-第一部分 - 图117

五、Ribbon

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

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

官网资料 https://github.com/Netflix/ribbon/wiki/Getting-Started

Ribbon目前也进入维护模式

SpringCloud-第一部分 - 图118

未来替换方案

SpringCloud-第一部分 - 图119

能干嘛

LB(负载均衡)

LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS,硬件 F5等。

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

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

集中式LB

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

进程内LB

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

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

前面我们碰到过了80通过轮询负载访问8001/8002

一句话 负载均衡+RestTemplate调用

Ribbon负载均衡演示

架构说明

SpringCloud-第一部分 - 图120

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

pom:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  4. </dependency>

之前写样例时候没有引入spring-cloud-starter-ribbon也可以使用ribbon,

二说RestTemplate的使用

官网 : https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

SpringCloud-第一部分 - 图121

getForObject方法/getForEntity方法

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

SpringCloud-第一部分 - 图122

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

SpringCloud-第一部分 - 图123

postForObject/postForEntity

SpringCloud-第一部分 - 图124

GET请求方法

  1. <T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
  2. <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
  3. <T> T getForObject(URI url, Class<T> responseType);
  4. <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
  5. <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
  6. <T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);

POST请求方法

  1. <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
  2. <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
  3. <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
  4. <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
  5. <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
  6. <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);

Ribbon核心组件IRule

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

SpringCloud-第一部分 - 图125

1、com.netflix.loadbalancer.RoundRobinRule——轮询

2、com.netflix.loadbalancer.RandomRule——随机

3、com.netflix.loadbalancer.RetryRule——先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

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

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

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

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

如何替换

修改cloud-consumer-order80

注意配置细节

官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

SpringCloud-第一部分 - 图126

新建package —> com.atguigu.myrule

SpringCloud-第一部分 - 图127

上面包下新建MySelfRule规则类

  1. @Configuration
  2. public class MySelfRule{
  3. @Bean
  4. public IRule myRule(){
  5. return new RandomRule();//定义为随机
  6. }
  7. }

主启动类添加@RibbonClient

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)
  4. public class OrderMain80{
  5. public static void main(String[] args){
  6. SpringApplication.run(OrderMain80.class,args);
  7. }
  8. }

测试

http://localhost/consumer/payment/get/31

Ribbon负载均衡算法

原理

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

List 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 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推……

RoundRobinRule源码

手写

自己试着写一个本地负载均衡器试试

7001/7002集群启动

8001/8002微服务改造

controller:

  1. @RestController
  2. @Slf4j
  3. public class PaymentController
  4. {
  5. @Value("${server.port}")
  6. private String serverPort;
  7. @Resource
  8. private PaymentService paymentService;
  9. @Resource
  10. private DiscoveryClient discoveryClient;
  11. @PostMapping(value = "/payment/create")
  12. public CommonResult create(@RequestBody Payment payment)
  13. {
  14. int result = paymentService.create(payment);
  15. log.info("*****插入操作返回结果:" + result);
  16. if(result > 0)
  17. {
  18. return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
  19. }else{
  20. return new CommonResult(444,"插入失败",null);
  21. }
  22. }
  23. @GetMapping(value = "/payment/get/{id}")
  24. public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
  25. {
  26. Payment payment = paymentService.getPaymentById(id);
  27. log.info("*****查询结果:{}",payment);
  28. if (payment != null) {
  29. return new CommonResult(200,"查询成功"+"\t 服务端口:"+serverPort,payment);
  30. }else{
  31. return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
  32. }
  33. }
  34. @GetMapping(value = "/payment/discovery")
  35. public Object discovery()
  36. {
  37. List<String> services = discoveryClient.getServices();
  38. for (String element : services) {
  39. System.out.println(element);
  40. }
  41. List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
  42. for (ServiceInstance element : instances) {
  43. System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
  44. + element.getUri());
  45. }
  46. return this.discoveryClient;
  47. }
  48. @GetMapping(value = "/payment/lb")
  49. public String getPaymentLB()
  50. {
  51. return serverPort;
  52. }
  53. }

80订单微服务改造

1、ApplicationContextBean去掉注解@LoadBalanced

  1. @Configuration
  2. public class ApplicationContextBean{
  3. @Bean
  4. //@LoadBalanced
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

2、LoadBalancer接口

  1. public interface LoadBalancer{
  2. ServiceInstance instances(List<ServiceInstance> serviceInstances);
  3. }

3、MyLB

  1. @Component
  2. public class MyLB implements LoadBalancer{
  3. private AtomicInteger atomicInteger = new AtomicInteger(0);
  4. public final int getAndIncrement(){
  5. int current;
  6. int next;
  7. do{
  8. current = this.atomicInteger.get();
  9. next = current >= 2147483647 ? 0 : current + 1;
  10. } while(!this.atomicInteger.compareAndSet(current, next));
  11. System.out.println("*****next: "+next);
  12. return next;
  13. }
  14. @Override
  15. public ServiceInstance instances(List<ServiceInstance> serviceInstances){
  16. int index = getAndIncrement() % serviceInstances.size();
  17. return serviceInstances.get(index);
  18. }
  19. }

4、OrderController

  1. @RestController
  2. public class OrderController
  3. {
  4. //public static final String PAYMENT_SRV = "http://localhost:8001";
  5. public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";
  6. @Resource
  7. private RestTemplate restTemplate;
  8. //可以获取注册中心上的服务列表
  9. @Resource
  10. private DiscoveryClient discoveryClient;
  11. @Resource
  12. private LoadBalancer loadBalancer;
  13. @GetMapping("/consumer/payment/create")
  14. public CommonResult<Payment> create(Payment payment)
  15. {
  16. return restTemplate.postForObject(PAYMENT_SRV+"/payment/create",payment,CommonResult.class);
  17. }
  18. @GetMapping("/consumer/payment/get/{id}")
  19. public CommonResult<Payment> getPayment(@PathVariable("id") Long id)
  20. {
  21. return restTemplate.getForObject(PAYMENT_SRV+"/payment/get/"+id,CommonResult.class);
  22. }
  23. @GetMapping("/consumer/payment/getForEntity/{id}")
  24. public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
  25. {
  26. ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_SRV+"/payment/get/"+id, CommonResult.class);
  27. if(entity.getStatusCode().is2xxSuccessful()){
  28. return entity.getBody();
  29. }else {
  30. return new CommonResult(444, "操作失败");
  31. }
  32. }
  33. @Resource
  34. private LoadBalancer loadBalancer;
  35. @GetMapping("/consumer/payment/lb")
  36. public String getPaymentLB()
  37. {
  38. List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
  39. if(instances == null || instances.size()<=0) {
  40. return null;
  41. }
  42. ServiceInstance serviceInstance = loadBalancer.instances(instances);
  43. URI uri = serviceInstance.getUri();
  44. return restTemplate.getForObject(uri+"/payment/lb",String.class);
  45. }
  46. }

5、测试

http://localhost/consumer/payment/lb