目录
一、引言
微服务的架构
面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据上下文,选择合适的语言、工具对其进行构建。
什么是分布式?
分布式系统能满足互联网对大数据存储、高并发和快响应的要求,采用了分而治之的思想。从实际成本来说,可以使用廉价的普通机器进行独立运算。然后通过相互协助来完成网站业务,这样就可以完成单个计算机节点无法完成的计算和存储任务了。
二、服务注册发现(Eureka)
2.1 Eureka概述
【服务注册发现的几个概念】
服务提供者(Provider Service):用于提供服务。它将自己提供的服务注册到服务注册中心,以供服务消费者发现。
服务消费者(Consumer Service):用于消费服务。它可以从服务注册中心获取服务列表,调用所需的服务。
服务注册中心(Register Service):它是一个 Eureka Server,用于提供服务注册和发现功能。
【eureka架构图】
【Eureka的常用组件】
Eureka Server: 当微服务启动时,会将各个的服务节点配置信息注册到 Eureka Server中。Eureka Server 维护了一个可用服务列表,存储了所有注册到 Eureka Server 的可用服务信息,这些可用服务可以在 Eureka Server 的管理界面中直观看到。
Eureka Client: 通常指的是微服务系统中各个服务,主要用于和 Eureka Server 进行交互的客户端。在微服务应用启动后,Eureka Client 会向 Eureka Server 发送心跳(默认周期为 30 秒)。若 Eureka Server 在多个心跳周期内没有接收到某个 Eureka Client 的心跳,Eureka Server 将它从可用服务列表中移除(默认 90 秒)。
2.2 搭建单机eureka服务
1、父工程pom.xml
<!-- 微服务模块 -->
<modules>
<module>cloud_payment_provider8081</module>
<module>cloud_order_consumer8080</module>
<module>cloud_api_commons</module>
<module>cloud_eureka_server7001</module>
<module>cloud_eureka_server7002</module>
<module>cloud_payment_provider8082</module>
</modules>
<packaging>pom</packaging>
<!-- 统一版本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.18.20</lombok.version>
<mysql.version>8.0.20</mysql.version>
<druid.version>1.1.18</druid.version>
<mybatis.spring.boot.version>2.1.2</mybatis.spring.boot.version>
<project.version>1.0-SNAPSHOT</project.version>
</properties>
<!-- 子模块继承 可以锁定版本 子module 不用写 groupId 和 version-->
<dependencyManagement>
<dependencies>
<!--spring boot 2.3.9 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud SR10 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud alibaba 2.2.5-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.9.RELEASE</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
2、建子模块:cloud_eureka_server7001
pom.xml
<dependencies>
<!-- eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- spring boot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 热部署 devtools -->
<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>
3、EurekaServer的配置文件application.yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001 #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://eureka7002.com:7002/eureka/
#单机就是7001自己
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4、Eureka主启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication7001.class, args);
}
}
【子模块支付服务注册到eureka-server中】PaymentApplication8081
pom.xml
<dependencies>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.hu</groupId>
<artifactId>cloud_api_commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- spring boot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- actuator -->
<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.2.11</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 热部署 devtools -->
<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>
application.yml
server:
port: 8081
spring:
application:
name: payment-service
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&charactEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.hu.cloud.domain # 所有Entity别名类所在包
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
主启动类PaymentApplication8081
@EnableEurekaClient
@SpringBootApplication
public class PaymentApplication8081 {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication8081.class, args);
}
}
controller/PaymentController
@Slf4j
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
/**
* 新增支付信息
*
* @param payment
*/
@PostMapping("/save")
public Result save(@RequestBody Payment payment) {
int result = paymentService.save(payment);
if (result > 0) {
return new Result(200, "serverPort添加成功" + serverPort, result);
} else {
return new Result(500, "添加失败", null);
}
}
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping("/findPaymentById/{id}")
public Result findPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.findPaymentById(id);
if (payment != null) {
return new Result(200, "serverPort查询成功" + serverPort, payment);
} else {
return new Result(500, "查询失败", null);
}
}
}
【子模块订单服务注册到eureka-server中】OrderApplication8080
pom.xml、application.yml、主启动类 同理支付服务手动实践加深理解。
controller/OrderController调用支付服务
@Slf4j
@RestController
@RequestMapping("/consumer")
public class OrderController {
private static final String PAYMENT_URL = "http://localhost:8081/payment";
@Resource
private RestTemplate restTemplate;
/**
* 添加支付
* @param payment
* @return
*/
@GetMapping("/save")
public Result<Payment> save(Payment payment){
return restTemplate.postForObject(PAYMENT_URL + "/save", payment, Result.class);
}
/**
* 查询支付
* @param id
* @return
*/
@GetMapping("/findPaymentById/{id}")
public Result<Payment> findPaymentById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL + "/findPaymentById/" + id, Result.class);
}
}
config/ApplicationContextConfig配置远程调用RestTemplate
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
【测试Eureka注册中心的微服务】
2.3 搭建集群Eureka服务
RPC远程调用的核心
高可用。由多个服务节点构成,就算有些节点挂掉也不影响正常运行,避免了单点故障。
【子模块eureka注册中心】EurekaApplication7002
1. pom.xml
<artifactId>cloud_eureka_server7002</artifactId>
<dependencies>
<!-- eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- spring boot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 热部署 devtools -->
<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>
2. 主启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication7002 {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication7002.class, args);
}
}
3. application.yml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://eureka7001.com:7001/eureka/
在本地模拟集群服务器修改本地的映射文件C:\Windows\System32\drivers\etc\host
【建子模块集群支付服务】PaymentApplication8082
application.yml
server:
port: 8082
spring:
application:
name: payment-service
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&charactEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.hu.cloud.domain # 所有Entity别名类所在包
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
instance:
instance-id: payment8082 # 修改主机id
prefer-ip-address: true # 访问路径显示IP
【建子模块集群支付服务】PaymentApplication8081
application.yml
server:
port: 8081
spring:
application:
name: payment-service
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&charactEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.hu.cloud.domain # 所有Entity别名类所在包
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
instance:
instance-id: payment8081 # 修改主机id
prefer-ip-address: true # 访问路径显示IP
使用@LoadBalanced注解开启RestTemplate负载均衡
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
【测试集群下注册中心的微服务】
注册中心相互注册,互相守望一个服务宕机,另一个服务继续使用
2.4 Discovery服务发现
【修改支付服务PaymentApplication8081的controller】
@Resource
private DiscoveryClient discoveryClient;
/**
* 支付服务发现
* @return
*/
@GetMapping("/discovery")
public Object discovery(){
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("*********service" + service);
}
List<ServiceInstance> instances = discoveryClient.getInstances("PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
return this.discoveryClient;
}
【支付服务的主启动类加@EnableDiscoveryClient注解】
【测试支付服务发现】
2.5 Eureka自我保护
什么是自我保护?
当微服务客户端启动后,会把自身信息注册到Eureka注册中心,以供其他微服务进行调用。一般情况下,当某个服务不可用时(一段时间内没有检测到心跳或者连接超时等),那么Eureka注册中心就会将该服务从可用服务列表中剔除,但是在微服务架构中,因为服务数量众多,可能存在跨机房或者跨区域的情况,因此当某个服务心跳探测失败并不能完全说明其无法正常提供服务而将其剔除,并且服务一旦剔除后,再重新注册将会重新进行负载均衡等等一系列的操作,考虑到性能问题,eureka会将不可用的服务暂时断开,并期望能够在接下来一段时间内接收到心跳信号,而不是直接剔除,同时,新来的请求将不会分发给暂停服务的实例,这就是eureka的保护机制
产生自我保护的原因?
在网络不通畅的情况下,EurekaServer不会立刻剔除EurekaClient服务。
eureka自我保护禁止
修改EurekaApplication7001的application.yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://eureka7002.com:7002/eureka/
#单机就是7001自己
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false # 关闭自我保护机制
eviction-interval-timer-in-ms: 2000
修改PaymentApplication8081的application.yml
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
instance:
instance-id: payment8081 # 修改主机id
prefer-ip-address: true # 访问路径显示IP
lease-renewal-interval-in-seconds: 1 # 客户端向服务端发送心跳的间隔
lease-expiration-duration-in-seconds: 2 # 服务端收到最后一次心跳等待时间
总结自我保护模式:自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康和不健康)也不盲目注销任何健康的微服务。使用自我保护模式,可以让eureka集群更加的健壮、稳定。