文章目录
一、介绍
Cloud各种组件的停更/升级/替换
以前:
现在:
二、微服务框架构建
约定 > 配置 > 编码
(一) IDEA新建project工作空间
1、微服务cloud整体聚合父工程Project
(1)New Project
(2)字符编码
(3)注解生效激活
(4)java编译版本
(5)File Type 过滤
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxxx.springcloud</groupId>
<artifactId>SpringCloudStudy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Maven</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org/</url>
<inceptionYear>2001</inceptionYear>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<!--统一管理jar包版本-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!--子模块继承之后,提供作用:锁定版本+子module不用groupId和version-->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
3、Maven工程落地细节
Maven 中的 DependencyManagement 和 Dependencies
dependencyManagement:
Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。
使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。
Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个dependencyManagement 元素中指定的版本号。
这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version就可。
dependencyManagement 里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖
如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom
如果子项目中指定了版本号,那么会使用子项目中指定的jar版本
maven中跳过单元测试
4、Run Dashboard
没有Run Dashboard,可以在工程目录下找.idea文件夹下的workspace.xml,在其中增加如下组件
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
</component>
(二) Rest微服务工程构建
1、支付模块8001
cloud-provider-payment8001 微服务提供者支付Module模块
项目结构:
(1)pom.xml
<?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>SpringCloudStudy</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</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>
(2)application.yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.xxxx.springcloud.entities # 所有Entity别名类所在包
(3)主启动
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
(4)创建数据库和数据表
-- ----------------------------
-- Table structure for payment
-- ----------------------------
DROP TABLE IF EXISTS `payment`;
CREATE TABLE `payment` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of payment
-- ----------------------------
INSERT INTO `payment` VALUES (1, '123');
INSERT INTO `payment` VALUES (2, '234');
INSERT INTO `payment` VALUES (3, '345');
SET FOREIGN_KEY_CHECKS = 1;
(5)实体类
主实体 Payment
package com.xxxx.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
private Long id;
private String serial;
}
Json封装体CommonResult
这个类是传递给前端的,前端不管什么 payment,它只要响应状态码、message…
package com.xxxx.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
(6)dao
package com.xxxx.springcloud.dao;
import com.xxxx.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface PaymentDao {
/**
* 创建一个 payment
*
* @param payment
* @return
*/
public int create(Payment payment);
/**
* 根据 id 查询 payment
*
* @param id
* @return
*/
public Payment getPaymentById(@Param("id") Long id);
}
mapper文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxxx.springcloud.dao.PaymentDao">
<resultMap id="BaseResultMap" type="com.xxxx.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial)
values (#{serial})
</insert>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select id, serial
from payment
where id = #{id}
</select>
</mapper>
(7)service
PaymentService
package com.xxxx.springcloud.service;
import com.xxxx.springcloud.entities.CommonResult;
import com.xxxx.springcloud.entities.Payment;
public interface PaymentService {
/**
* 创建一个 payment
* @param payment
* @return
*/
CommonResult create(Payment payment);
/**
* 根据 id 查询 payment
* @param id
* @return
*/
CommonResult getPaymentById(Long id);
}
PaymentServiceImpl
package com.xxxx.springcloud.service.impl;
import com.xxxx.springcloud.dao.PaymentDao;
import com.xxxx.springcloud.entities.Payment;
import com.xxxx.springcloud.service.PaymentService;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
public int create(Payment payment){
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id){
return paymentDao.getPaymentById(id);
}
}
(8)controller
package com.xxxx.springcloud.controller;
import com.xxxx.springcloud.entities.CommonResult;
import com.xxxx.springcloud.entities.Payment;
import com.xxxx.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@PostMapping("/create")
public CommonResult create(Payment payment) {
return paymentService.create(payment);
}
@GetMapping("/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
return paymentService.getPaymentById(id);
}
}
(9)测试
Chrom浏览器可能不支持Post请求,可以使用PostMan工具测试。
总结:
1.建Module
2.改pom
3.写yml
4.主启动
5.业务类
2、热部署
(1)Adding devtools to your project
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
(2)Adding plugin to your pom.xml
我们在父工程中添加。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
(3)Enabling automatic build
(4)Update the value of
快捷键:ctrl+shift+alt+/
(5)重启IDEA
注意:开发阶段开启热部署,生产阶段必须关闭
3、订单模块80
cloud-consumer-order80 微服务消费者订单Module模块
(1)改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>SpringCloudStudy</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<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>
<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>
(2)写yml
server:
port: 80
(3)主启动
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
(4)entities
主实体 Payment
package com.xxxx.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
private Long id;
private String serial;
}
Json封装体CommonResult
这个类是传递给前端的,前端不管什么 payment,它只要响应状态码、message…
package com.xxxx.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
(5)首说RestTemplate
是什么:
RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
官网地址:
https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
使用:
使用restTemplate访问restful接口非常的简单粗暴无脑
url:REST请求地址
requestMap:请求参数
ResponseBean.class:HTTP响应转换被转换成的对象类型
(6)config配置类
package com.xxxx.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
(7)controller
package com.xxxx.springcloud.controller;
import com.xxxx.springcloud.entities.CommonResult;
import com.xxxx.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
public static final String PaymentSrv_URL = "http://localhost:8001";
@Resource
private RestTemplate restTemplate;
/**
* 客户端用浏览器是get请求,但是底层实质发送post调用服务端8001
*
* @param payment
* @return
*/
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PaymentSrv_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PaymentSrv_URL + "/payment/get/" + id,CommonResult.class,id);
}
}
注意:/payment/get/
路径中 get 后面的/
不要忘了加,否则会一直报错。
(8)测试
客户端用浏览器是get请求,但是底层实质发送post调用服务端8001
但是数据库中只有主键并没有数据
8001中的 create 不要忘记@RequestBody注解
//只传给前端CommonResult,不需要前端了解其他的组件
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0){
return new CommonResult(200,"插入数据成功",result);
}else{
return new CommonResult(444,"插入数据失败",null);
}
}
数据插入成功
4、工程重构
项目中存在相同的代码(entities包下的Payment.class和CommonResult.class),造成代码冗余,可以进行重构。
通过Maven聚合父工程,把相同重复的代码移到公开公用的工程里面,还可以放第三方接口、工具类,统一调配使用。
(1)建立公共Module
(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>cloud2020</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-api-commons</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<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>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>
(3)将entities包复制到cloud-api-commons
(4)使用Maven打包发布上传到公用本地库里
打开Maven窗口,执行clean测试一下,无误后出现BUILD SUCCESS,然后执行install
(5)删除重复entities,引入maven install的jar包坐标即可使用
<!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<dependency>
<groupId>com.xxxx.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
三、Eureka 服务注册与发现
(一) Eureka基础知识
1、什么是服务治理?
Springcloud 封装了Netflix 公司开发的Eureka模块来实现服务治理。
在传统的 rpc 远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
2、什么是服务注册?
Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)。
下左图是Eureka系统架构,右图是Dubbo的架构,请对比:
3、服务发现
服务之间不需要指定具体的实例地址,而是通过服务中心发起请求调用实现。所以调用方并不需要知道服务提供方具体的位置,只需向注册中心发起请求,从而获取所有服务的实例清单,才能实现对具体服务实例的访问。
实际应用中,不会每次都向注册中心获取服务,使用了缓存和服务剔除等不同的策略。
4、Eureka 两组件
Eureka包含两个组件:Eureka Server 和 Eureka Client
-
Eureka Server 提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。 -
EurekaClient 通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。
(二) 单机Eureka构建步骤
eurekaServer端cloud-eureka-server7001
服务注册中心
1、创建module
cloud-eureka-server7001
2、pom.xml
<?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>SpringCloudStudy</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入自己定义的api通用包,可以使用payment支付Entity-->
<dependency>
<groupId>com.xxxx.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<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>
<!--一般通用配置-->
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
1.X和2.X的对比说明:
以前的老版本(当前使用2018)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
现在新版本(当前使用2020.2)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3、application.yml
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4、主启动类
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
这是个服务注册中心,主要干的活就是服务注册,不需要写业务类。
但是注意:Eureka有两个组件,一定要标清楚哪个是Server,哪个是Client。@EnableEurekaServer代表服务注册中心。
5、测试
出现上面图标,表示Eureka 服务端安装成功。No instances available表示当前没有服务注册进来,红色警告是Eureka的自我保护机制
EurekaClient端cloud-provider-payment8001
将注册进EurekaServer成为服务提供者provider
1、在pom.xml将 Eureka-client 依赖引入
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、在application.yml添加Eureka相关配置
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
3、主启动类使用注解@EnableEurekaClient标注这是个Eureka Client端
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
4、测试
注意: 要先启动EurekaServer
这样就注册进来了,入住进Eureka服务器的名称就是8001yml中配置的spring.application.name。
EurekaClient端cloud-consumer-order80
将注册进EurekaServer成为服务消费者consumer
1、pom.xml
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、application.yml
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
3、主启动类
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
4、测试
先要启动EurekaServer——7001服务,再要启动服务提供者provider——8001服务
cloud-order-server服务已入住,查询功能也可以正常执行
(三) 集群Eureka构建步骤
Eureka集群原理说明
问题:微服务RPC远程服务调用最核心的是什么?
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)"了,会导致整个为服务环境不可用,所以解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错
eurekaServer端cloud-eureka-server7002
1、参考cloud-eureka-server7001,新建cloud-eureka-server7002
2、改pom
3、修改映射配置
找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改映射配置添加进hosts文件
4、改yml
以前单机版
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
7001:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7002.com:7002/eureka/
7002:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/
5、主启动
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
cloud-provider-payment8001发布到Eureka集群
application.yml
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.xxxx.springcloud.entities # 所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
cloud-consumer-order80发布到Eureka集群
application.yml
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
测试
支付服务提供者集群环境构建
参考cloud-provider-payment8001,新建cloud-provider-payment8002
修改8001和8002的controller,默认的负载均衡方式是轮询,看执行查询具体调用那台provider
package com.xxxx.springcloud.controller;
import com.xxxx.springcloud.entities.CommonResult;
import com.xxxx.springcloud.entities.Payment;
import com.xxxx.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
//@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
//只传给前端CommonResult,不需要前端了解其他的组件
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0){
return new CommonResult(200,"插入数据成功,serverPort:"+serverPort,result);
}else{
return new CommonResult(444,"插入数据失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****插入结果:"+payment);
if(payment != null){
return new CommonResult(200,"查询成功,serverPort:"+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
}
}
}
测试:
8001和8002也都访问正常,那如果我们用80访问呢?发现怎么刷新都是8001,这是因为我们的源程序地址是写死的:
单机版写死是没有问题的,但是现在有8001、8002了,所有不应该再关注具体的IP和端口,而是只认服务名称。代码修改一下再试。
发现报错了,现在对外暴露的不再是地址和端口,只认微服务名称了,可是微服务并不知道下面有几个,找不到这个主机名称,需要使用@LoadBalanced注解开启RestTemplate负载均衡功能。
提前说一下:这个就是后面要介绍的Ribbon负载均衡功能。
然后测试,多次刷新,就会发现8001、8002端口交替出现。
这样Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载均衡功能了。O(∩_∩)O
(四) actuator微服务信息完善
1、主机名称:服务名称修改
当前问题:含有主机名称
修改8001:application.xml
instance:
instance-id: payment8001
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.xxxx.springcloud.entities # 所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
8002同理
测试:
2、访问信息有IP信息提示
当前问题:没有ip提示
修改8001:application.yml
prefer-ip-address: true #访问路径可以显示IP地址
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.xxxx.springcloud.entities # 所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
8002同理
测试:
(五) 服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
1、修改8001的controller
加上如下代码
@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;
}
完整代码:
package com.xxxx.springcloud.controller;
import com.xxxx.springcloud.entities.CommonResult;
import com.xxxx.springcloud.entities.Payment;
import com.xxxx.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@Slf4j
//@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@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;
}
//只传给前端CommonResult,不需要前端了解其他的组件
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0){
return new CommonResult(200,"插入数据成功,serverPort:"+serverPort,result);
}else{
return new CommonResult(444,"插入数据失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****插入结果:"+payment);
if(payment != null){
return new CommonResult(200,"查询成功,serverPort:"+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
}
}
}
2、主启动类
@EnableDiscoveryClient//服务发现
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//服务发现
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
8002同理
3、测试
(六) Eureka自我保护
1、故障现象
概述:保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式
2、导致原因
为什么会产生Eureka自我保护机制?
为了防止可以正常运行的EurekaClient,但是与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
属于CAP里面的AP分支
3、禁止自我保护
现在可以不用集群版了,只用开一个,在 application.yml 中把集群配置改为单机版
(1)注册中心eureakeServer端7001
出厂默认,自我保护机制是开启的 eureka.server.enable-self-preservation=true,使用eureka.server.enable-self-preservation = false 可以禁用自我保护模式
application.yml:
server:
#关闭自我保护机制,保证不可用服务被及时踢除
enable-self-preservation: false
# 默认时间是90s,改为2s
eviction-interval-timer-in-ms: 2000
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/
server:
#关闭自我保护机制,保证不可用服务被及时踢除
enable-self-preservation: false
# 默认时间是90s,改为2s
eviction-interval-timer-in-ms: 2000
关闭效果:
(2)生产者客户端eureakeClient端8001
默认配置:
eureka.instance.lease-renewal-interval-in-seconds=30 #单位为秒(默认是30秒)
eureka.instance.lease-expiration-duration-in-seconds=90 #单位为秒(默认是90秒)
配置:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.xxxx.springcloud.entities # 所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
# 集群版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
测试:
7001和8001都配置完成,先启动7001再启动8001
http://eureka7001.com:7001/
模拟8001出现了故障关闭了,以前7001还会保留,但是现在:
九、GateWay
十、Config 分布式配置中心
(一) 概述
分布式系统面临的—配置问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理…/(ㄒoㄒ)/~~
1、是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
2、怎么玩
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
3、能干嘛
集中管理配置文件
不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
将配置信息以REST接口的形式暴露:post、curl访问刷新均可…
4、与GitHub整合配置
由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
(二) Config服务端配置与测试
1、服务端配置
-
用你自己的账号在Gitee上新建一个名为springcloud-config的新Repository
-
由上一步获得刚新建的git地址
- SSH:git@gitee.com:yangfan1814012468/springcloud-config.git
- HTTPS:https://gitee.com/yangfan1814012468/springcloud-config.git
-
本地硬盘目录上新建git仓库并clone
- 例:E:\临时文件夹\SpringCloud2020
- git clone https://gitee.com/yangfan1814012468/springcloud-config.git
-
此时在本地E盘符下E:\临时文件夹\SpringCloud2020\springcloud-config
- 表示多个环境的配置文件例如:config-test.yml、config-dev.yml、config-prod.yml
- 保存格式必须为UTF-8
- 如果需要修改,此处模拟运维人员操作git和github
- git add .
- git commit -m “init yml”
- git push origin master
config-test.yml
spring:
application:
name: springcloud-config-test
profiles:
active: test
config-dev.yml
spring:
application:
name: springcloud-config-dev
profiles:
active: dev
config-prod.yml
spring:
application:
name: springcloud-config-master
profiles:
active: master
2、配置模块 cloud-config-center-4444
新建Module模块cloud-config-center-4444,它即为Cloud的配置中心模块cloudConfig Center
(1)pom.xml
<?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>cloud2020</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-center-4444</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
<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>
(2)application.yml
server:
port: 4444
spring:
application:
name: config-center #注册进Eureka服务器的微服务名 config server
cloud:
config:
server:
git:
uri: https://gitee.com/llnan/springcloud-config.git #Gitee上面的git仓库名字
####搜索目录
search-paths:
- springcloud-config
####读取分支
label: master
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
(3)主启动类
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/4
*/
@SpringBootApplication
@EnableConfigServer //激活配置中心
public class ConfigCenterMain4444 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain4444.class, args);
}
}
(4)映射
windows下修改hosts文件,增加映射:127.0.0.1 config-4444.com
(5)测试
测试通过Config微服务是否可以从Gitee上获取配置内容
先启动7001、再启动微服务4444:http://config-4444.com:4444/master/config-dev.yml
成功实现了用SpringCloud Config 通过GitHub获取配置信息
3、配置读取规则
{application} 就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件
{profile} 就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以 application-{profile}.yml 加以区分,例如application-dev.yml、application-test.yml、application-prod.yml
{label} 表示 git 分支,默认是 master 分支,如果项目是以分支做区分也是可以的,那就可以通过不同的 label 来控制访问不同的配置文件了
官网:
(1)/{label}/{application}-{profile}.yml
- master分支
- http://config-3344.com:3344/master/config-dev.yml
- http://config-3344.com:3344/master/config-test.yml
- http://config-3344.com:3344/master/config-prod.yml
- dev分支
- http://config-3344.com:3344/dev/config-dev.yml
- http://config-3344.com:3344/dev/config-test.yml
- http://config-3344.com:3344/dev/config-prod.yml
(2)/{application}-{profile}.yml
- http://config-3344.com:3344/config-dev.yml
- http://config-3344.com:3344/config-test.yml
- http://config-3344.com:3344/config-prod.yml
- http://config-3344.com:3344/config-xxxx.yml(不存在的配置)
(3)/{application}/{profile}[/{label}]
- http://config-3344.com:3344/config/dev/master
- http://config-3344.com:3344/config/test/master
- http://config-3344.com:3344/config/test/dev
(三) Config客户端配置与测试
1、配置模块 cloud-config-client-5555
pox.xml
<?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>cloud2020</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-5555</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--不带server了,说明是客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
<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>
bootstrap.yml
applicaiton.yml是用户级的资源配置项;bootstrap.yml是系统级的,优先级更加高
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context
的父上下文。初始化的时候,Bootstrap Context
负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
。
Bootstrap
属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context
和Application Context
有着不同的约定,所以新增了一个bootstrap.yml
文件,保证Bootstrap Context
和Application Context
配置的分离。
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
server:
port: 5555
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:4444 #配置中心地址
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version
spring:
application:
name: springcloud-config-dev
cloud:
config:
info: master branch,springcloud-config/config-dev.xml version-1
profiles:
active: dev
主启动
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/5
*/
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain5555 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain5555.class, args);
}
}
业务类
package com.xxxx.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/5
*/
@RestController
public class ConfigClientController {
@Value("${spring.cloud.config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
2、测试
成功实现了客户端5555访问SpringCloud Config 4444通过GitHub获取配置信息
3、分布式配置的动态刷新问题
修改 gitee 上的配置:把 version 改为2
spring:
application:
name: springcloud-config-dev
cloud:
config:
info: master branch,springcloud-config/config-dev.xml version-2
profiles:
active: dev
刷新5555,发现没有变化!!!
重启5555
问题接踵而来,分布式配置的动态刷新问题
- Linux运维修改Gitee上的配置文件内容做调整
- 刷新3344,发现ConfigServer配置中心立刻响应
- 刷新3355,发现ConfigClient客户端没有任何响应
- 3355没有变化除非自己重启或者重新加载
- 难到每次运维修改配置文件,客户端都需要重启??噩梦
(四) Config动态刷新
避免每次更新配置都要重启客户端微服务5555,我们需要它能动态刷新,下面来修改5555模块
1、pom 引入 actuator 监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、修改YML,暴露监控端口
添加配置
server:
port: 5555
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:4444 #配置中心地址
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
3、业务类Controller修改
添加@RefreshScope注解
package com.xxxx.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/5
*/
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${spring.cloud.config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
4、问题与解决
此时修改gitee,再测:http://localhost:5555/configInfo,发现5555还是没有变化
需要运维人员发送Post请求刷新5555
curl -X POST "http://localhost:5555/actuator/refresh"
再次请求:http://localhost:5555/configInfo,成功实现了客户端5555刷新到最新配置内容,避免了服务重启
5、思考
想想还有什么问题?
假如有多个微服务客户端5555/6666/7777。。。。。。
每个微服务都要执行一次post请求,手动刷新?
可否广播,一次通知,处处生效?
我们想大范围的自动刷新,求方法
十一、Bus 消息总线
(一) 概述
上一讲解的加深和扩充,一言以蔽之,分布式自动刷新配置功能
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新
1、是什么
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus目前支持RabbitMQ和Kafka。
2、能干嘛
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
3、总线
什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
(二) RabbitMQ环境配置
安装Erlang,https://www.erlang.org/downloads
安装RabbitMQ,https://www.rabbitmq.com/install-windows.html
进入RabbitMQ安装目录下的sbin目录,输入以下命令启动管理功能
rabbitmq-plugins enable rabbitmq_management
可视化插件
开启服务
rabbitmq-server start
访问地址查看是否安装成功:http://localhost:15672/
tips:如果访问不到重启一下电脑就好了
输入账号密码并登录:guest guest
(三) 动态刷新全局广播
1、搭建客户端微服务 cloud-config-client-6666
必须先具备良好的RabbitMQ环境先,演示广播效果,增加复杂度,再以5555为模板再制作一个6666
新建 cloud-config-client-6666
(1) pom.xml
<?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>cloud2020</artifactId>
<groupId>com.xxxx.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-6666</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--不带server了,说明是客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
<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>
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
</project>
(2) bootstrap.yml
server:
port: 9999 #6666不可用,官方关了
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:4444 #配置中心地址
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
(3)主启动
package com.xxxx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/5
*/
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain6666 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain6666.class, args);
}
}
(4)controller
package com.xxxx.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 注释内容
*
* @author : li.linnan
* @create : 2022/8/5
*/
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${spring.cloud.config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
2、设计思想
(1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
(2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
图二的架构显然更加适合,图一不适合的原因如下:
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责
- 破坏了微服务各节点的对等性
- 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
3、配置中心4444添加消息总线支持
(1)pom.xml
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
(2)application.yml
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
4、客户端5555添加消息总线支持
给cloud-config-client-5555客户端添加消息总线支持
(1)pom.xml
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
(2)bootstrap.yml
server:
port: 5555
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:4444 #配置中心地址
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
5、客户端6666添加消息总线支持
给cloud-config-client-6666客户端添加消息总线支持
(1)pom.xml
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
(2)bootstrap.yml
server:
port: 9999 #6666不可用,官方关了
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:4444 #配置中心地址
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
6、测试
修改Gitee上配置文件增加版本号
发送POST请求
curl -X POST "http://localhost:4444/actuator/bus-refresh"
配置中心
客户端
一次修改,广播通知,处处生效
(四) 动态刷新定点通知
不想全部通知,只想定点通知:只通知5555,不通知6666
简单一句话:指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
案例:
我们这里以刷新运行在3355端口上的config-client为例:只通知5555,不通知6666
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:5555"
通知总结All: