尚硅谷周阳构建分布式微服务架构(Eureka篇) B 站视频:https://www.bilibili.com/video/BV18E411x7eT?from=search&seid=13583080742381804215
原 Blog 笔记文档:https://blog.csdn.net/qq_41211642/article/details/104772140 本篇文章大部分图片用到了参考的 blog 笔记中的图片。
本人代码下载:下载 本人代码 Gitee 仓库地址:https://gitee.com/Sevattal/springcloud_project_eureka
技术
版本
cloud
Hoxton.SR1
boot
2.2.2.RELEASE
cloud alibaba
2.1.0.RELEASE
java
java8
Maven
3.5及以上
Mysql
5.7及以上
整体工程项目架构 springcloud2021 父项目
cloud-api-commons 通用类项目
cloud-provider-payment8001 模拟支付接收接口项目
cloud-provider-payment8002 模拟支付接收接口项目(复制的cloud-provider-payment8001)
cloud-consumer-order80 模拟客户端调用后端接口项目
cloud-eureka-server7001 eureka注册中心项目
cloud-eureka-server7002 eureka注册中心项目 (复制的cloud-eureka-server7001)
Eureka url命令的补充知识点
Operation
HTTP action
Description
Register new application instance
POST /eureka/v2/apps/appID
Input: JSON/XML payload HTTP Code: 204 on success
De-register application instance
DELETE /eureka/v2/apps/appID/instanceID
HTTP Code: 200 on success
Send application instance heartbeat
PUT /eureka/v2/apps/appID/instanceID
HTTP Code:* 200 on success* 404 if instanceID doesn’t exist
Query for all instances
GET /eureka/v2/apps
HTTP Code: 200 on success Output: JSON/XML
Query for all appID instances
GET /eureka/v2/apps/appID
HTTP Code: 200 on success Output: JSON/XML
Query for a specific appID/instanceID
GET /eureka/v2/apps/appID/instanceID
HTTP Code: 200 on success Output: JSON/XML
Query for a specific instanceID
GET /eureka/v2/instances/instanceID
HTTP Code: 200 on success Output: JSON/XML
Take instance out of service
PUT /eureka/v2/apps/appID/instanceID/status?value=OUT_OF_SERVICE
HTTP Code:* 200 on success* 500 on failure
Move instance back into service (remove override)
DELETE /eureka/v2/apps/appID/instanceID/status?value=UP
(The value=UP is optional, it is used as a suggestion for the fallback status due to removal of the override) HTTP Code:* 200 on success* 500 on failure
Update metadata
PUT /eureka/v2/apps/appID/instanceID/metadata?key=value
HTTP Code:* 200 on success* 500 on failure
Query for all instances under a particular vip address
GET /eureka/v2/vips/vipAddress
* HTTP Code: 200 on success Output: JSON/XML * 404 if the vipAddress does not exist.
Query for all instances under a particular secure vip address
GET /eureka/v2/svips/svipAddress
* HTTP Code: 200 on success Output: JSON/XML * 404 if the svipAddress does not exist.
1. 新建Maven父工程 springcloud2021 1.1 maven架构不需要选择 1.2 pom.xml代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 <?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.sevattal.springcloud</groupId> <artifactId>springcloud2021</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!--子模块继承之后,提供作用:锁定版本+ 子modlue不用写groupId 和 version--> <!--dependencyManagement 里只声明依赖,并不实现引入,因此子项目需要显示声明需要的依赖--> <!--统一管理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> <spring.boot.devtools.version>2.3.10.RELEASE</spring.boot.devtools.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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>${spring.boot.devtools.version}</version> <scope>runtime</scope> <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>
2. 建立支付 module: cloud-provider-payment8001 2.1 在 springcloud2021 主项目下建立 cloud-provider-payment8001 模块(maven)
2.2 pom.xml如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <?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>springcloud2021</artifactId> <groupId>com.sevattal.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-payment8001</artifactId> <dependencies> <!--引入自己定义的api通用包,--> <dependency> <groupId>com.sevattal.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--eureka-client--> <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.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.3 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 8001 spring: application: name: cloud-provider-service datasource: type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型 driver-class-name: org.gjt.mm.mysql.Driver #mysql驱动包 url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding-utr-8&useSSL=false username: sevattal password: Lovemomo mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities #所有Entity别名类所在包
2.4 主启动类PaymentMain8001 1 2 3 4 5 6 7 8 9 10 11 12 13 package com.sevattal.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; <!-- PaymentMain8001 在多个微服务的情况下建议在开发的过程中 将SpringBoot的启动类,名称要有对应的含义以及对应的端口号 --> @SpringBootApplication public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
2.5 数据库 1 2 3 4 5 CREATE TABLE `payment`( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `serial` varchar(200) DEFAULT '', PRIMARY KEY (`id`) ) ENGING=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
2.6 业务类 1 2 3 4 5 6 7 8 9 10 11 12 13 // 主实体类 Payment package com.sevattal.springcloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor public class Payment implements Serializable { private Long id; private String serial; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //Json封装体CommentResult,传给前端,判断编码是否成功,成功才显示 package com.sevattal.springcloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { //泛型:如果装的payment 返回payment,装的order 返回order private Integer code; private String message; private T data; public CommonResult(Integer code,String message){ this(code,message,null); } }
1 2 3 4 5 6 7 8 9 10 //PaymentDao package com.atguigu.springcloud.dao; import com.atguigu.springcloud.entities.Payment; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface PaymentDao { int create(Payment payment); Payment getPaymentById(@Param("id") Long id); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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.sevattal.springcloud.dao.PaymentDao"> <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id"> insert into payment(serial) values(#{serial}) </insert> <resultMap id="BaseResultMap" type="com.sevattal.springcloud.entities.Payment"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="serial" property="serial" jdbcType="VARCHAR"/> </resultMap> <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap"> select * from payment where id=#{id}; </select> </mapper>
1 2 3 4 5 6 7 8 //PaymentService package com.sevattal.springcloud.service; import com.sevattal.springcloud.entities.Payment; import org.apache.ibatis.annotations.Param; public interface PaymentService { public int create(Payment payment); public Payment getPaymentById(@Param("id") Long id); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //PaymentServiceImpl package com.sevattal.springcloud.service; import com.sevattal.springcloud.dao.PaymentDao; import com.sevattal.springcloud.entities.Payment; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class PaymentServiceImpl implements PaymentService{ /* * @resource是基于j2ee的注解,默认是按名字进行注解,若不指定装配bean的名字, * 当注解写在字段上时,默认取字段名,按照名称查找通过set方法进行装配, * 倘若有多个子类,则会报错。若想不报错,只需加(required=false) * * @autowired是基于spring的注解org.springframework.beans.factory.annotation.Autowired, * 它默认是按类型进行的装配的,如果想要它按名字进行装配只需在@autowired * 下面加@qualifier("name")`注解 * * */ @Resource private PaymentDao paymentDao; public int create(Payment payment){ return paymentDao.create(payment); } public Payment getPaymentById(Long id){ return paymentDao.getPaymentById(id); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 //PaymentController package com.sevattal.springcloud.controller; import com.sevattal.springcloud.entities.CommonResult; import com.sevattal.springcloud.entities.Payment; import com.sevattal.springcloud.service.PaymentService; 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.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; //只传给前端CommonResult,不需要前端了解其他的组件 @PostMapping(value = "/payment/create") public CommonResult create(Payment payment){ int result = paymentService.create(payment); log.info("*****插入结果:"+result); if(result > 0){ return new CommonResult(200,"插入数据成功",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,"查询成功",payment); }else{ return new CommonResult(444,"没有对应记录,查询ID:"+id,null); } } }
2.7 测试 Chrome 浏览器可能不支持 Post 请求,可以使用 PostMan 工具测试
总结: 1. 建 module 2. 改 pom 3. 写 yml 4. 主启动 5. 业务类
3. 建立消费者订单 module: 3.1 在 springcloud2021 主项目下建立 cloud-consumer-order80 模块(maven) 3.2 pom.xml如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?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>springcloud2021</artifactId> <groupId>com.sevattal.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-order80</artifactId> <dependencies> <!--引入自己定义的api通用包,--> <dependency> <groupId>com.sevattal.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--eureka-client--> <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>
3.3 application.yml
3.4 主启动类 1 2 3 4 5 6 7 8 9 package com.sevattal.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); } }
3.5 业务类 订单也需要Payment、CommonResult实体类,但是不需要操作数据库,没有Service、Dao,只需添加Controller即可。
1 2 3 4 5 6 7 8 9 10 11 12 //Payment package com.sevattal.springcloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Payment { private Long id; private String serial; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //CommonResult package com.sevattal.springcloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { //泛型:如果装的payment 返回payment,装的order 返回order private Integer code; private String message; private T data; public CommonResult(Integer code,String message){ this(code,message,null); } }
首说 RestTemplate: RestTemplate 提供了多种便捷访问远程 Http 服务的方法,是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集,实现 80 到 8001 的远程调用。
官网地址:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
使用: 使用 restTemplate 访问 restful 接口非常的简单粗暴,(url、requestMap、ResponseBean.class)这三个参数分别代表 REST 请求地址、请求参数、HTTP 响应转换被转换成的对象类型。
将 RestTemplate 对象注册到容器中
1 2 3 4 5 6 7 8 9 10 11 package com.sevattal.springcloud.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.sevattal.springcloud.controller; import com.sevattal.springcloud.entities.CommonResult; import com.sevattal.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 { private static final String PAYMENT_URL="http://localhost:8001"; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class); } }
3.6 启动 80、8001 服务,测试 80 服务调用 8001 服务,实现效果如下:
查询
添加
浏览器并没有返回错误,但是我们来看数据库:
可以看到数据库只插入主键,并没有插入内容,要在8001的PaymentController加@RequestBody注解。
然后就可以插入了
4. 工程重构 项目中存在相同的代码(entities 包下的 Payment.class 和 CommonResult.class),造成代码冗余,可以进行重构。
通过 Maven 聚合父工程,把相同重复的代码移到公开公用的工程里面,还可以放第三方接口、工具类,统一调配使用。
4.1 建立公共module在springcloud2021 主项目下建立 cloud-api-commons 模块(maven) 4.2 pom.xml 如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?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>springcloud2021</artifactId> <groupId>com.sevattal.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api-commons</artifactId> <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> <!-- huto 工具包, 时间日期格式--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.1.0</version> </dependency> </dependencies> </project>
4.3 将 entities 包复制到 cloud-api-commons
4.4 使用 Maven 打包发布上传到公用本地库里 打开 Maven 窗口,执行 clean 测试一下,无误后出现 BUILD SUCCESS,然后执行 install
注:若 Maven 运行操作失败,请查看 Maven 配置的环境是否正确
4.5 删除重复 entities,引入 maven install 的 jar 包坐标即可使用。 1 2 3 4 5 6 <!--引入自己定义的api通用包,可以使用Payment支付Entity--> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency>
5. EurekaServer服务端安装 5.1 在 springcloud2021 主项目下建 cloud-eureka-server7001 模块(Maven) 5.2 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?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>springcloud2021</artifactId> <groupId>com.sevattal.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-eureka-server7001</artifactId> <dependencies> <!--eureka server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <!--引入自己定义的api通用包,可以使用payment支付Entity--> <dependency> <groupId>com.sevattal.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</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>
5.3 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 7001 eureka: instance: hostname: localhost # eureka服务端的实例名称 client: #false表示不向注册中心注册自己 register-with-eureka: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 fetch-registry: false service-url: # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址 defaultZ4 one: http://${eureka.instance.hostname}:${server.port}/eureka/
5.4 主启动类 1 2 3 4 5 6 7 8 9 10 11 package com.sevattal.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.5 测试
出现上面图标,表示 Eureka 服务端安装成功。No instances available 表示当前没有服务注册进来
6. 单机 Eureka 构建:支付微服务 8001 入驻进 eurekaServer 6.1 将 Eureka-client 依赖引入,便于使用注解@EnableEurekaClient标注这是个 Eureka Client 端 1 2 3 4 5 <!--eureka-client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
6.2 在 application.yml 添加 Eureka 相关配置 1 2 3 4 5 6 7 8 eureka: client: #表示是否将自己注册进EurekaServer默认为true register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
6.3 主启动类添加注解 @EnableEurekaClient
6.4 测试
这样就注册进来了,入住进 Eureka 服务器的名称就是 8001 yml 中配置的 spring.application.name。红色警告是 Eureka 的自我保护机制,后面会详细说。
7. 单机 Eureka 构建:订单微服务入驻进 eurekaServer (操作与支付微服务 8001 一样) 7.1 在 pom 添加 Eureka-client 依赖 1 2 3 4 5 <!--eureka-client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
7.2 在 application.yml 添加相关配置 1 2 3 4 5 6 7 8 9 10 spring: application: name: cloud-order-server eureka: client: #表示是否将自己注册进EurekaServer默认为true register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eu
7.3 主启动类添加注解 @EnableEurekaClient
7.4 测试 PS: 先启动 EurekaServer,7001 服务,再启动服务提供者 provider,8001 服务
cloud-order-server 服务以入住,查询功能也可以正常执行
8. EurekaServer 集群环境构建 8.1 创建 module cloud-eureka-server7002 (与创建 cloud-eureka-server7001 相似,除了端口改为 7002 外) 8.3 写 yml 之前修改 hosts 文件 找到 C:\Windows\System32\drivers\etc 路径下的 hosts 文件 添加如下主机名
8.4 修改 7001 和 7002 的 application.yml
8.5 测试
同时看到 Eureka 图标,且 7001 指着 7002,7002 指着 7001,说明 Eureka 集群搭建成功。
9. 将两个微服务发布到 Eureka 集群配置中 9.1 只需修改 application.yml
9.1 测试 PS: 先启动 EurekaServer,7001/7002 服务;再启动服务提供者 provider,8001;再启动消费者,80
现在,就已经把支付服务 8001、订单服务 80 注册进 Eureka 集群环境,调用也 OK。
10. 支付提供者 8001 集群环境搭建 10.1 创建 module cloud-provider-payment8002 (与创建 cloud-provider-payment8002 一样不做过多介绍) 10.2 修改 8001 和 8002 的 controller,默认的负载均衡方式是轮询,看执行查询具体调用那台 provider
10.3 测试 PS: 启动顺序:7001、7002、8001、8002、80
8001 和 8002 也都访问正常,那如果我们用 80 访问呢?发现怎么刷新都是 8001,这是因为我们的源程序地址是写死的:
单机版写死是没有问题的,但是现在有 8001、8002 了,所有不应该再关注具体的 IP 和端口,而是只认服务名称。代码修改为一下在试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RestController @Slf4j public class OrderController { //private static final String PAYMENT_URL="http://localhost:8001"; //通过在eureka上注册过的微服务名称调用 private static final String PAYMENT_URL = "http://CLOUD-PROVIDER-SERVICE"; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class); } }
发现报错了,现在对外暴露的不再是地址和端口,只认微服务名称了,可是微服务并不知道下面有几个,找不到这个主机名称,需要使用 # LoadBalanced 注解开启 RestTemplate 负载均衡功能。 提前说一下:这个就是后面要介绍的 Ribbon 负载均衡功能。
然后测试,多次刷新,就会发现 8001、8002 端口交替出现。
这样 Ribbon 和 Eureka 整合后 Consumer 可以直接调用服务而不用再关心地址和端口号,且该服务还有负载均衡功能了。O(∩_∩)O
11. Eureka 服务发现 11.1 SpringBoot 启动主类配置 @EnableDiscoveryClient 注解启动服务发现
11.2 Controller 中配置服务发现测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /* * 服务发现 * */ @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-PROVIDER-SERVICE"); for (ServiceInstance instance: instances){ log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" +instance.getUri()); } return this.discoveryClient; }
11.3 测试
从上述 json 以及后台日志的返回信息中可以看出,已经将 Eureka 中注册的微服务的对应信息都查询出来了。
12. Eureka 的自我保护机制 默认情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server 将会注销该实例(默认90秒)。 但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与 Eureka Server 之间无法正常通信,以上行为可能变得非常危险了。 因为微服务本身其实是健康的,此时不应该注销该微服务。Eureka 通过”自我保护模式”来解决这个问题。 当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
在自我保护模式中,Eureak Server 会保护服务注册表中的信息,不再注销任何服务实例。 它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。– 好死不如赖活着
以下示例为解决 Eureka 的自我保护机制
12.1 配置 Eureka application.yml 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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/ # 关闭eurka的自我保护机制,当发现微服务没有在指定时间内发送心跳直接剔除该微服务 server: # 关闭禁用自我保护机制 enable-self-preservation: false # 配置心跳时间为2秒钟 eviction-interval-timer-in-ms: 2000
12.2 测试查看
注:从上述信息可以看到,Erueka 已经关闭了自我保护机制
12.3 配置微服务 8001 application.yml 文件(以下只展示 eureka 部分,配置心跳间隔时间等) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 eureka: client: #表示是否将自己注册进EurekaServer默认为true register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 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
12.4 当关闭了微服务 8001 后,查看 Eureka Web 界面
注:可以很快的看到 8001 服务,已经在 Eureka 中剔除了。