Spring Cloud 是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶。
参考版本约束关系:https://start.spring.io/actuator/info
{
"spring-cloud": {
"Finchley.M2": "Spring Boot >=2.0.0.M3 and <2.0.0.M5",
"Finchley.M3": "Spring Boot >=2.0.0.M5 and <=2.0.0.M5",
"Finchley.M4": "Spring Boot >=2.0.0.M6 and <=2.0.0.M6",
"Finchley.M5": "Spring Boot >=2.0.0.M7 and <=2.0.0.M7",
"Finchley.M6": "Spring Boot >=2.0.0.RC1 and <=2.0.0.RC1",
"Finchley.M7": "Spring Boot >=2.0.0.RC2 and <=2.0.0.RC2",
"Finchley.M9": "Spring Boot >=2.0.0.RELEASE and <=2.0.0.RELEASE",
"Finchley.RC1": "Spring Boot >=2.0.1.RELEASE and <2.0.2.RELEASE",
"Finchley.RC2": "Spring Boot >=2.0.2.RELEASE and <2.0.3.RELEASE",
"Finchley.SR4": "Spring Boot >=2.0.3.RELEASE and <2.0.999.BUILD-SNAPSHOT",
"Finchley.BUILD-SNAPSHOT": "Spring Boot >=2.0.999.BUILD-SNAPSHOT and <2.1.0.M3",
"Greenwich.M1": "Spring Boot >=2.1.0.M3 and <2.1.0.RELEASE",
"Greenwich.SR6": "Spring Boot >=2.1.0.RELEASE and <2.1.999.BUILD-SNAPSHOT",
"Greenwich.BUILD-SNAPSHOT": "Spring Boot >=2.1.999.BUILD-SNAPSHOT and <2.2.0.M4",
"Hoxton.SR9": "Spring Boot >=2.2.0.M4 and <2.3.8.BUILD-SNAPSHOT",
"Hoxton.BUILD-SNAPSHOT": "Spring Boot >=2.3.8.BUILD-SNAPSHOT and <2.4.0.M1",
"2020.0.0-M3": "Spring Boot >=2.4.0.M1 and <=2.4.0.M1",
"2020.0.0-M4": "Spring Boot >=2.4.0.M2 and <=2.4.0-M3",
"2020.0.0": "Spring Boot >=2.4.0.M4 and <2.4.2-SNAPSHOT",
"2020.0.1-SNAPSHOT": "Spring Boot >=2.4.2-SNAPSHOT"
}
}
初始项目仅包含两个微服务且逻辑简单:订单模块(Order)需要调用支付模块(Payment)模块的 restful 服务。
源码参考:spring-cloud-2021-get-start 提取码:rays
创建一个空的 Maven 父项目即可,它仅用作整个项目的版本依赖管理。
父项目本身以
spring-boot-starter-parent
作为自己的父项目。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<packaging>pompackaging>
<groupId>com.simwor.springcloudgroupId>
<artifactId>spring-cloud-2021artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>spring-cloud-2021name>
<description>Learning project for Spring Clouddescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.1version>
<relativePath/>
parent>
<modules>
<module>payment-8001module>
<module>order-80module>
<module>apimodule>
modules>
<properties>
<java.version>15java.version>
<spring-cloud.version>2020.0.0spring-cloud.version>
<mybatis-plus.version>3.4.1mybatis-plus.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis-plus.version}version>
dependency>
dependencies>
dependencyManagement>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
repository>
repositories>
project>
订单模块(order-80)
和 支付模块(payment-8001)
;api
,用作提取各个微服务公用的类。[root@simwor ~]# cd /opt/docker/mysql/compose/
[root@simwor compose]# cat docker-compose.yml
version: "3.8"
services:
spring-test-db:
image: mysql
container_name: spring-test-db
ports:
- 33060:3306
environment:
MYSQL_ROOT_PASSWORD: abcd1234..
MYSQL_DATABASE: simwor
MYSQL_USER: simwor
MYSQL_PASSWORD: abcd1234..
volumes:
- /opt/docker/mysql/mysql33060:/var/lib/mysql
[root@simwor compose]# docker-compose up -d
[root@simwor ~]# mysql -h127.0.0.1 -P33060 -usimwor -p
Enter password:
mysql> use simwor;
Database changed
mysql> CREATE TABLE payment(
-> id bigint(20) NOT NULL AUTO_INCREMENT,
-> serial varchar(200),
-> PRIMARY KEY(id)
-> );
Query OK, 0 rows affected, 1 warning (0.09 sec)
mysql>
mysql> INSERT INTO payment(serial) values('wx20200110121543089');
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM payment;
+----+---------------------+
| id | serial |
+----+---------------------+
| 1 | wx20200110121543089 |
+----+---------------------+
1 row in set (0.00 sec)
mysql>
支付模块直接与 MySQL 数据库进行交互,向外提供两个 restful 接口。
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
url: jdbc:mysql://simwor.com:33060/simwor
username: simwor
password: abcd1234..
driver-class-name: com.mysql.cj.jdbc.Driver
package com.simwor.springcloud.payment;
import com.simwor.springcloud.entity.CommonResult;
import com.simwor.springcloud.entity.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class PaymentController {
@Autowired
PaymentService paymentService;
@GetMapping("/payment/get")
public CommonResult<Payment> getPaymentById(@RequestParam("id") Long id) {
Payment payment = paymentService.getById(id);
if (payment == null)
return new CommonResult<>(400, "fail", null);
return new CommonResult<>(200, "success", payment);
}
@PostMapping("/payment/create")
public CommonResult<Payment> createPayment(@RequestBody Payment payment) {
boolean result = paymentService.save(payment);
if (!result)
return new CommonResult<>(400, "fail", null);
return new CommonResult<>(200, "success", payment);
}
}
订单模块通过远程调用功能,间接通过支付模块的接口实现数据库的读写操作。
server:
port: 80
spring:
application:
name: cloud-order-service
url requestMap ResponseBean.class
,分别代表 REST 请求地址、请求参数、HTTP响应转换被转换成的对象类型。package com.simwor.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanRegistration {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
package com.simwor.springcloud.order;
import com.simwor.springcloud.entity.CommonResult;
import com.simwor.springcloud.entity.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer/payment/get")
public CommonResult<Payment> getPayment(@RequestParam("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL+"/payment/get?id="+id, CommonResult.class);
}
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> createPayment(@RequestBody Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create", payment, CommonResult.class);
}
}
共用接口模块仅作用为各个微服务共有部分的提取,如订单模块和支付模块都须使用类
Payment
和CommonResult
,将其抽取出来放在这里维护。
package com.simwor.springcloud.entity;
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;
}
package com.simwor.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private int code;
private String message;
private T data;
public CommonResult(int code, String message) {
this(code, message,null);
}
}
注:公用接口模块POM文件中不可包含
spring-boot-maven-plugin
打包插件。
订单模块 和 支付模块 必须生命引用 公用接口模块。
<dependency>
<groupId>com.simwor.springcloudgroupId>
<artifactId>apiartifactId>
<version>${project.version}version>
dependency>
源码参考:spring-cloud-2021-eureka 提取码:rays
服务治理:在传统的 RPC 远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,服务治理可以简化依赖关系的管理工作,实现服务发现于注册、服务调用、负载均衡、容错等。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
server:
port: 7001
eureka:
instance:
hostname: localhost # 服务端的实例名称
client:
# 服务端声明自己不需要注册、不需要检索服务。
register-with-eureka: false
fetch-registry: false
service-url:
# 客户端依赖的注册地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class Eureka7001Application {
public static void main(String[] args) {
SpringApplication.run(Eureka7001Application.class, args);
}
}
访问测试:http://localhost:7001/
以同样的步骤注册 订单模块。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class Payment8001Application {
public static void main(String[] args) {
SpringApplication.run(Payment8001Application.class, args);
}
}
单节点Eureka故障会导致整个项目不可用,Eureka集群的各个节点 互相注册、相互守望 以达到高可用的目的。
此步前需提前修改
hosts
文件。
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # 服务端的实例名称
client:
# 服务端声明自己不需要注册、不需要检索服务。
register-with-eureka: false
fetch-registry: false
service-url:
# 客户端依赖的注册地址
defaultZone: http://eureka7002.com:7002/eureka/
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com # 服务端的实例名称
client:
# 服务端声明自己不需要注册、不需要检索服务。
register-with-eureka: false
fetch-registry: false
service-url:
# 客户端依赖的注册地址
defaultZone: http://eureka7001.com:7001/eureka/
不再通过具体的地址而是通过服务提供者注册的别名来访问接口。
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
package com.simwor.springcloud.order;
import com.simwor.springcloud.entity.CommonResult;
import com.simwor.springcloud.entity.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
// public static final String PAYMENT_URL = "http://localhost:8001";
//不再配置具体的地址,而是服务提供者的注册别名。
public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer/payment/get")
public CommonResult<Payment> getPayment(@RequestParam("id") Long id) {
return restTemplate.getForObject(PAYMENT_SRV+"/payment/get?id="+id, CommonResult.class);
}
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> createPayment(@RequestBody Payment payment) {
return restTemplate.postForObject(PAYMENT_SRV+"/payment/create", payment, CommonResult.class);
}
}
RestTemplate
接口地址访问方式的变化package com.simwor.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanRegistration {
@Bean
@LoadBalanced //告诉RestTemplate以服务发现的方式调用远程接口
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
多个微服务以相同的别名注册到 Eureka,Eureka 默认会采用轮询的方式调用该多个服务提供者(比如此例中的支付模块),且只要有一个服务提供者存活,服务就可正常调用。
目前这种结构并不优美(可能是因为部署在同一台主机上的缘故,一直会提示两个克隆模块的类冲突),待寻找其它合适的解决方案。
完全克隆
payment-8001
到payment-8002
,再作模块适配。
# 修改模块的服务器端口号,避免端口冲突。
# 如果有多台服务器主机,这项操作通常是不必要的。
server:
port: 8002
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
//修改主应用名 `Payment8002Application` 到 `Payment8002Application` 避免类名冲突。
public class Payment8002Application {
public static void main(String[] args) {
SpringApplication.run(Payment8002Application.class, args);
}
}
主机名 + 服务名 + 端口号
eureka:
instance:
instance-id: payment8001
实例ID + 鼠标移入浏览器左下角显示主机IP
源码参考:spring-cloud-2021-zookeeper 提取码:rays
docker-compose.yml
[root@simwor ~]# cd /opt/docker/zookeeper/compose/
[root@simwor compose]# cat docker-compose.yml
version: "3.8"
services:
zoo1:
image: zookeeper
container_name: zoo1
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo2:
image: zookeeper
container_name: zoo2
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo3:
image: zookeeper
container_name: zoo3
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181
[root@simwor compose]# docker-compose up -d
[root@simwor compose]# docker inspect zoo1 | grep IPA
"IPAddress": "192.168.64.4",
[root@simwor compose]# docker inspect zoo2 | grep IPA
"IPAddress": "192.168.64.3",
[root@simwor compose]# docker inspect zoo3 | grep IPA
"IPAddress": "192.168.64.2",
[root@simwor compose]# docker container exec -it zoo1 /bin/bash
root@zoo1:/apache-zookeeper-3.6.2-bin# bin/zkServer.sh status
Mode: follower
root@zoo2:/apache-zookeeper-3.6.2-bin# bin/zkServer.sh status
Mode: follower
root@zoo3:/apache-zookeeper-3.6.2-bin# bin/zkServer.sh status
Mode: leader
root@zoo3:/apache-zookeeper-3.6.2-bin#
新创建模块
payment-8004
模拟服务提供者。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zookeeper-discoveryartifactId>
dependency>
server:
port: 8004
spring:
application:
name: payment-8004
cloud:
zookeeper:
connect-string: simwor.com:2181,simwor.com:2182,simwor.com:2183
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
//该注解表明将consul或者zookeeper作为服务注册中心
@EnableDiscoveryClient
public class Payment8004Application {
public static void main(String[] args) {
SpringApplication.run(Payment8004Application.class, args);
}
}
package com.simwor.springcloud.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping
public String getHelloInfo() {
return "SpringCloud with Zookeeper!";
}
}
root@zoo3:/apache-zookeeper-3.6.2-bin# bin/zkCli.sh
[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[payment-8004]
[zk: localhost:2181(CONNECTED) 2] ls /services/payment-8004
[05b304aa-e5dc-4909-91bd-fe44e4f12f61]
[zk: localhost:2181(CONNECTED) 4] get /services/payment-8004/05b304aa-e5dc-4909-91bd-fe44e4f12f61
{
"name": "payment-8004",
"id": "05b304aa-e5dc-4909-91bd-fe44e4f12f61",
"address": "localhost",
"port": 8004,
"sslPort": null,
"payload": {
"@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
"id": "payment-8004",
"name": "payment-8004",
"metadata": {
"instance_status": "UP"
}
},
"registrationTimeUTC": 1610348725315,
"serviceType": "DYNAMIC",
"uriSpec": {
"parts": [
{
"value": "scheme",
"variable": true
},
{
"value": "://",
"variable": false
},
{
"value": "address",
"variable": true
},
{
"value": ":",
"variable": false
},
{
"value": "port",
"variable": true
}
]
}
}
[zk: localhost:2181(CONNECTED) 5]
新创建模块
order-81
模拟服务提供者。
server:
port: 81
spring:
application:
name: order-81
cloud:
zookeeper:
connect-string: simwor.com:2181,simwor.com:2182,simwor.com:2183
package com.simwor.springapplication.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanRegistration81 {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Order81Application {
public static void main(String[] args) {
SpringApplication.run(Order81Application.class, args);
}
}
package com.simwor.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderZKController {
public static final String PAYMENT_SRV="http://payment-8004";
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer/payment/zk")
public String getHelloInfoFromPaymentWithZookeeper() {
return restTemplate.getForObject(PAYMENT_SRV+"/payment/zk", String.class);
}
}
CAP理论:一个分布式系统不能同时很好的满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三个需求。
Eureka == AP,Zookeeper == CP
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡工具。
spring-cloud-starter-netflix-eureka-client
中默认包含了 Ribbon,所以当有多个实例同时注册到 Eureka 同个别名时,消费者调用服务时就是 自动负载均衡 的。
Ribbon 在工作时分成两步:
Feign 是一个声明式的 WebService 客户端,它通过定义一个服务接口与远程服务接口对应然后在上面添加注解,调用服务时只需要调用该本地接口即可。
Feign | OpenFeign |
---|---|
Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,其内置了 Ribbon 用来做客户端负载均衡;Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 | Open Feign 是 Spring Cloud 在 Feign 的基础上支持了 Spring MVC 的注解,如 @RequestMapping 等等;OpenFeign 的 @FeignClient 可以解析 SpringMVC @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其它服务 |
源码参考:spring-cloud-2021-openfeign 提取码:rays
新创建一个模块
order-feign-82
用来演示采用 接口 + 注解 的方式调用远程服务。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
server:
port: 82
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
package com.simwor.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class Order82Application {
public static void main(String[] args) {
SpringApplication.run(Order82Application.class, args);
}
}
package com.simwor.springcloud.feign;
import com.simwor.springcloud.entity.CommonResult;
import com.simwor.springcloud.entity.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get")
public CommonResult<Payment> getPaymentById(@RequestParam("id") Long id);
}
package com.simwor.springcloud.controller;
import com.simwor.springcloud.entity.CommonResult;
import com.simwor.springcloud.entity.Payment;
import com.simwor.springcloud.feign.PaymentFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get")
public CommonResult<Payment> getPaymentById(@RequestParam("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
已停止更新:Hystrix is no longer in active development, and is currently in maintenance mode.
Hystrix 是一个用于处理分布式系统 延迟和容错 的开源库。在分布式系统中,许多依赖不可避免地会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
服务雪崩:单个服务实例的失效,造成所有调用方的异常,如何解决对故障和延迟的隔离和管理?
fallback
break
服务降级
的方法返回友好提示。flow-limit
Gateway 旨在为微服务架构提供一种简单、有效和统一的API路由管理方式,以及提供一些强大的过滤器功能,例如:熔断、限流、重试、安全、监控/指标等。
java.util.function.Predicate
,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;源码参考:spring-cloud-2021-gateway 提取码:rays
需要同时添加
spring-cloud-starter-netflix-eureka-client
,不用来注册服务而是用来发现服务。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
gateway.discovery.locator.enable=true
:开启从注册中心动态创建路由的功能,利用微服务名进行路由gateway.routes.uri
:哪个微服务下的接口需要开启路由功能gateway.routes.predicates
:用来匹配请求特定的条件(更多官网参考)server:
port: 9527
spring:
application:
name: gateway-9527
cloud:
gateway:
discovery:
locator:
locator: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment # 路由的ID
uri: lb://CLOUD-PAYMENT-SERVICE # 微服务注册名
predicates:
- Path=/** # 断言,路径相匹配的进行路由
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
package com.simwor.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 Gateway9527App {
public static void main(String[] args) {
SpringApplication.run(Gateway9527App.class, args);
}
}
更多官网实例参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
package com.simwor.springcloud.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class GatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String idStr = exchange.getRequest().getQueryParams().getFirst("id");
int id = Integer.parseInt(idStr);
// 只有当query中包含“id”且值为小于10时才放行
if(id > 10){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 过滤器的优先级,越小优先级越大
return 0;
}
}
Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
Spring Cloud Stream 用于屏蔽消息中间件的差异,降低切换成本,统一消息的编程模型。
inputs
管道接收消息中间件中存放的消息、 outputs
管道向消息中间件推送消息;Binder
绑定器上;Middleware
就是具体的消息中间件实现了,不同的消息中间件需要引入不同的依赖包。应用中可以同时定义多个绑定器,不同的绑定器可以是不同的消息中间件。
spring.cloud.stream.binders:
myrabbit1: # 自定义绑定器名称,用于与管道绑定
type: rabbit
environment:
spring:
rabbitmq:
host: simwor.com
port: 5672
username: guest
password: guest
myrabbit2:
....
mykafka:
....
用 @Bean 标注
)中,方法的返回类型标识着其是发送消息的Supplier 或是处理消息的 Function 或是接收消息的 Consumer;send-out-0
,其中 send 为方法名,out 标识其消息相对于应用的传输方向,0 指定着管道的序号;Function, Flux> gather()
则会生成两条绑定 gather-in-0 and gather-in-1
。一条完整的绑定信息应包含:绑定名、消息存放的Topic名、绑定器。
spring:
cloud:
stream:
function: # 哪些方法(@Bean)里面包含消息收发逻辑,在此列出其方法名。
definition: sendMethod;processMethod;receiveMethod
bindings:
sendMethod-out-0: # 生成的绑定名
destination: message # 消息中间件中存放消息的Topic名
binder: myrabbit # 绑定器,对应具体的消息中间件
processMethod-in-0:
destination: message
binder: myrabbit
processMethod-out-0: # 处理消息包含两条绑定,消息哪里来,处理完哪里去
destination: processed-message
binder: myrabbit
receiveMethod-in-0:
destination: processed-message
binder: myrabbit
normalBinding: # 自定义的绑定名
destination: some-topic
binder: mykafka
如果消息是用户/事件驱动而不是 Stream 流式产生的,可以使用普通的方法发送消息。
@Autowired
private StreamBridge streamBridge;
@GetMapping("/rabbitmq/send")
public String sendMessage(@RequestParam("msg") String msg) {
//"msg-out-0" : 自定义的绑定名
streamBridge.send("msg-out-0", "Web Message From 8801 >> " + msg);
return "result: success, msg: " + msg;
}
源码参考:spring-cloud-2021-stream 提取码:rays
[root@simwor compose]# pwd
/opt/docker/rabbitmq/compose
[root@simwor compose]# cat docker-compose.yml
version: "3.8"
services:
rabbitmq:
image: rabbitmq
container_name: rabbitmq
ports:
- 5672:5672
[root@simwor compose]# docker-compose up -d
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
package com.simwor.springcloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.function.Function;
import java.util.function.Supplier;
@SpringBootApplication
@Slf4j
@RestController
public class RabbiMQProducer8801Application {
public static void main(String[] args) {
SpringApplication.run(RabbiMQProducer8801Application.class, args);
}
// Stream 默认情况下每秒调用一次该方法
@Bean
public Supplier<String> sendData() {
return () -> {
String now = LocalDateTime.now().toString();
log.info("Date info sent : " + now);
return now;
};
}
// 处理原始发送的消息
@Bean
public Function<String, String> processBeforeSendData() {
return s -> "Send From 8801 >> " + s;
}
@Autowired
private StreamBridge streamBridge;
// 处理网页的消息发送请求
@GetMapping("/rabbitmq/send")
public String sendMessage(@RequestParam("msg") String msg) {
streamBridge.send("msg-out-0", "Web Message From 8801 >> " + msg);
log.info("Web Message From 8801 >> " + msg);
return "result: success, msg: " + msg;
}
}
server:
port: 8801
spring:
# 该展示中就一个消息中间件,直接配置在 spring 下而不用在 spring.cloud.stream.binders 下
rabbitmq:
host: simwor.com
port: 5672
username: guest
password: guest
cloud:
stream:
function:
definition: sendData;processBeforeSendData
bindings:
sendData-out-0:
destination: echos
binder: rabbit # 指向应用的 rabbitmq 配置
processBeforeSendData-in-0:
destination: echos
binder: rabbit
processBeforeSendData-out-0:
destination: src-echos # 处理后将其存在另一个topic中
binder: rabbit
msg-out-0:
destination: web-message
binder: rabbit
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
package com.simwor.springcloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.function.Consumer;
import java.util.function.Function;
@SpringBootApplication
@Slf4j
public class RabbitMQConsumer8802Application {
public static void main(String[] args) {
SpringApplication.run(RabbitMQConsumer8802Application.class, args);
}
@Bean
public Function<String, String> processBeforeSinkData() {
return s -> s + " >> Received At 8802";
}
@Bean
public Consumer<String> sinkData() {
return s -> log.info(s);
}
@Bean
public Consumer<String> sinkWebMsg() {
return s -> log.info(s + " >> Sink Web Message At 8802");
}
}
server:
port: 8802
spring:
rabbitmq:
host: simwor.com
port: 5672
username: guest
password: guest
cloud:
stream:
function:
definition: processBeforeSinkData;sinkData;sinkWebMsg
bindings:
processBeforeSinkData-in-0:
destination: src-echos
binder: rabbit
processBeforeSinkData-out-0:
destination: dest-echos
binder: rabbit
sinkData-in-0:
destination: dest-echos
binder: rabbit
sinkWebMsg-in-0:
destination: web-message
binder: rabbit
图形化展示微服务之间接口的调用链路图,方便追踪和解决问题。
Github 主页:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
功能 | 说明 |
---|---|
服务限流降级 | 默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 |
服务注册与发现 | 适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。 |
分布式配置管理 | 支持分布式系统中的外部化配置,配置更改时自动刷新。 |
消息驱动能力 | 基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 |
分布式事务 | 使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。 |
阿里云对象存储 | 阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。 |
分布式任务调度 | 提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。 |
阿里云短信服务 | 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 |
组件 | 说明 |
---|---|
Sentinel | 把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 |
Nacos | 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 |
RocketMQ | 一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 |
Dubbo | Apache Dubbo™ 是一款高性能 Java RPC 框架。 |
Seata | 阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。 |
Alibaba Cloud OSS | 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 |
Alibaba Cloud SchedulerX | 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。 |
Alibaba Cloud SMS | 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 |
Naming Configuration Service 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台(注册中心+配置中心 = Nacos = Eureka + Config + Bus)。
下载地址:https://github.com/alibaba/nacos/releases
第一次启动可能会报错,因为其会默认寻找 MySQL 连接;如果确实想
2021-01-18 22:07:01,261 INFO Nacos started successfully in stand alone mode. use embedded storage
使用Nacos本身存储,关闭窗口再次启动就可以了。
初始化 SQL 目录:
nacos-server-1.4.1\nacos\conf\nacos-mysql.sql
[root@simwor compose]# pwd
/opt/docker/mysql/compose
[root@simwor compose]# cat docker-compose.yml
version: "3.8"
services:
spring-test-db:
image: mysql
container_name: spring-test-db
ports:
- 33060:3306
environment:
MYSQL_ROOT_PASSWORD: abcd1234..
MYSQL_DATABASE: simwor
MYSQL_USER: simwor
MYSQL_PASSWORD: abcd1234..
volumes:
- ../sql:/tmp/sql
[root@simwor compose]# ls ../sql
nacos-mysql.sql
[root@simwor compose]# docker exec -it spring-test-db /bin/bash
root@b74dc68a672e:/# mysql -usimwor -pabcd1234..
mysql> use nacos;
Database changed
mysql> source /tmp/sql/nacos-mysql.sql
Nacos 配置文件路径:
nacos-server-1.4.1\nacos\conf\application.properties
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://simwor.com:33060/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=simwor
db.password.0=abcd1234..
C:\Users\m1553\Desktop\nacos-server-1.4.1\nacos\bin>set JAVA_HOME=C:\Program Files\Java\jdk-15.0.1
C:\Users\m1553\Desktop\nacos-server-1.4.1\nacos\bin>startup.cmd -m standalone
默认账号密码:
nacos/nacos
源码参考:spring-cloud-2021-nacos-discovery 提取码:rays
<properties>
<spring-cloud-alibaba.version>2.2.4.RELEASEspring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencyManagement>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos 服务地址
management:
endpoints:
web:
exposure:
include: '*' # 以 web 方式暴露所有监控端点
package com.simwor.springcloud.nacos;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class NacosPaymentProvider9001Application {
public static void main(String[] args) {
SpringApplication.run(NacosPaymentProvider9001Application.class, args);
}
@Value("${server.port}")
private String serverPort;
@GetMapping("/nacos/payment/get-server-port")
public String getServerPort() {
return "This micro-service's server port is " + serverPort;
}
}
POM依赖与服务端完全相同
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos 服务地址
management:
endpoints:
web:
exposure:
include: '*' # 以 web 方式暴露所有监控端点
service-url:
nacos-payment: http://nacos-payment-provider
package com.simwor.springcloud.nacos;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class NacosOrder83Application {
public static void main(String[] args) {
SpringApplication.run(NacosOrder83Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
@Value("${service-url.nacos-payment}")
public String serverUrl;
@GetMapping("/nacos/order/invoke-payment")
public String invokePayment() {
return restTemplate.getForObject(serverUrl + "/nacos/payment/get-server-port", String.class);
}
}
C 是所有节点在同一时间看到的数据是一致的,而 A 的定义是所有的请求都会收到响应。
切换命令:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
nacos-client
注册,并能够保持心跳上报,那就可以选择 AP 模式;Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。