SpringCloud总结笔记
楠哥教你学Java SpringCloud+SpringCloudAlibaba总结笔记
SpringCloud
为什么需要微服务
传统开发模式下,绝大部分的Web应用都是采用单体架构的风格来进行构建的,这意味着 Web应用是作为单个可部署的软件制品进行交付的,所有的接口、业务逻辑、持久层都被打包在一个Web应用中,并且部署在一台服务器上,这种开发模式会带来诸多不便,多团队协同开发的成本极高。
单体应用存在问题
- 随着业务的发展,开发变得越来越复杂。
- 修改、新增某个功能,需要对整个系统进行测试、重新部署。
- 一个模块出现问题,很可能导致整个系统崩溃。
- 多个开发团队同时对数据进行管理,容易产生安全漏洞。
- 各个模块使用同一种技术进行开发,各个模块很难根据实际情况选择更合适的技术框架,局限性很大。
- 模块内容过于复杂,如果员工离职,可能需要很长时间才能完成工作交接。
分布式、集群
集群:一台服务器无法负荷高并发的数据访问量,那么就设置十台服务器一起分担压力,十台不行就设置一百台**(物理层面)。很多人干同一件事情,来分摊压力。
分布式:将一个复杂问题拆分成若干个简单的小问题,将一个大型的项目架构拆分成若干个微服务来协同完成。(软件设计层面)**。将一个庞大的工作拆分成若干个小步骤,分别由不同的人完成这些小步骤,最终将所有的结果进行整合实现大的需求。
微服务的优点
-
各个服务的开发、测试、部署都相互独立,比如用户服务就可以拆分作为一个单独的服务,而它的开发也不用依赖于其他服务,如果用户量很大,我们可以很容易的对其进行负载。
-
当一个新需求出现时,特别是在一个庞大的项目系统中,你得去考虑各方的问题,兼容性、影响度等等,而使用微服务则可以直接跳过这些废时又烧脑的环节。
-
使用微服务将项目进行拆分之后,各服务之间就消除了诸多限制,只需要保证对外提供的接口正常可用,至于使用什么语言、什么框架通通不用关心。
微服务的不足
- 微服务的拆分是基于业务的,不是我们随心所欲,想怎么拆就怎么拆的,那么问题来了,由谁来拆,怎么拆?这就给团队协作沟通带来了很多挑战。
- 当服务调用方需要使用某服务的接口时,首先需要找到该服务的提供方,通常在一个大公司中,这种场景是跨部门的,沟通成本可想而知。同时,如果服务的提供方需要对某个接口进行修改,也得和各个服务调用方进行沟通。
- 由于各个服务相互独立,它们的数据也是独立的,这就会带来一个问题,当调用多个服务接口来进行操作时,如何保证各个服务的数据一致性,这既是问题,也是难点。
为什么选择SpringClould
- Spring Cloud完全基于Spring BootI服务调用方式是基于REST API,整合了各种成熟的产品和架构,同时基于Spring Boot也使得整体的开发、配置、部署都非常方便。
- Spring 系的产品集功能齐全、简单好用、性能优越、文档规范等等于一身,因此Spring Cloud还是微服务架构中一个十分优越的实现方案。
SpringCloud 核心组件
-
服务治理 Eureka
-
服务通信 Ribbon
-
服务通信 Feign
-
服务网关 Zuul
-
服务容错 Hystrix
-
服务配置 Config
-
服务监控 Actuator
-
服务跟踪 Zipkin
服务治理
服务治理的核心有三部分组成:服务提供者、服务消费者、注册中心
在分布式系统架构中,每个微服务在启动时,将自己的信息存储在注册中心,叫做服务注册。
服务消费者从注册中心获取服务提供者的网络信息,通过该信息调用服务,叫做服务发现。
Spring Cloud的服务治理使用Eureka来实现,Eureka是Netflix开源的基于REST的服务治理解决方案。Spring Cloud集成了Eureka,提供==服务注册和服务发现的功能==,可以和基于Spring Boot搭建的微服务应用轻松完成整合,开箱即用,Spring Cloud Eureka。
Spring Cloud Eureka
-
Eureka Server:注册中心
-
Eureka Client:所有要进行注册的微服务通过Eureka Client连接到Eureka Server,完成注册。
注册中心 代码实现
Eureka Server
- 父工程pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 子模块 eurekaserver ,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml,添加 Eureka Server 相关配置
server:
port: 8888
eureka:
client:
register-with-eureka: false #是否在注册中心将自己当成一个微服务操作
fetch-registry: false #是否同步其他注册中心数组
service-url:
defaultZone: http://localhost:8888/eureka/
属性说明
server-port
:当前Eureka Server服务端口。
eureka.client.register-with-eureka
:是否将当前的Eureka Server服务作为客户端进行注册。
eureka.client.fetch-fegistry
:是否获取其他Eureka Server 服务的数据。
eureka.client.service-url.defaultzone
:注册中心的访问地址。
- 创建启动类
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
注解说朗
@SpringBootpplication
:声明该类是Spring Boot服务的入口。
@EnableEarekaserver
:声明该类是一个Eureka Server微服务,提供服务注册和服务发现功能,即注册中心。
服务提供者 provider 代码实现
Eureka Client
- 子模块 eurekaclient ,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml,添加 Eureka Server 相关配置
server:
port: 9999
spring:
application:
name: provider
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true #是否将当前服务注册到注册中心
属性说明
spring.application.name
:当前服务注册在Eureka Server上的名称。
eureka.client.service-url.defaultzone
:注册中心的访问地址。
eureka.instance.prefer-ip-address
:是否将当前服务的IP注册到Eureka Server。
- 创建启动类
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
- 创建Student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
}
- StudentRepository
public interface StudentRepository {
public Collection<Student> findAll();
public Student findById(long id);
public void saveOrUpdate(Student student);
public void deleteById(long id);
}
- StudentRepositoryImpl
@Repository
public class StudentRepositoryImpl implements StudentRepository {
private static Map<Long,Student> studentMap;
static {
studentMap = new HashMap<>();
studentMap.put(1L,new Student(1l,"小王",23));
studentMap.put(2L,new Student(2l,"小刘",12));
studentMap.put(3L,new Student(3l,"小东",24));
}
@Override
public Collection<Student> findAll() {
return studentMap.values();
}
@Override
public Student findById(long id) {
return studentMap.get(id);
}
@Override
public void saveOrUpdate(Student student) {
studentMap.put(student.getId(), student);
}
@Override
public void deleteById(long id) {
studentMap.remove(id);
}
}
- StudentHandler
@RestController
@RequestMapping("/student")
public class StudentHandler {
@Autowired
private StudentRepository studentRepository;
@GetMapping("/findAll")
public Collection<Student> findAll() {
return studentRepository.findAll();
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id) {
return studentRepository.findById(id);
}
@PostMapping("/save")
public void save(@RequestBody Student student) {
studentRepository.saveOrUpdate(student);
}
@PutMapping("/update")
public void update(@RequestBody Student student) {
studentRepository.saveOrUpdate(student);
}
@DeleteMapping("/delete/{id}")
public void delete(@PathVariable("id") long id) {
studentRepository.deleteById(id);
}
}
RestTemplate
RestTemplate是Spring框架提供的基于REST的服务组件,底层是对HTTP请求及响应进行了封装,提供了很多访问RETS服务的方法,可以简化代码开发。
- 启动类
@SpringBootApplication
public class RestTemplateApplication {
public static void main(String[] args) {
SpringApplication.run(RestTemplateApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
relurn new RestTemplate();
}
}
- RestHandler
@RestController
@RequestMapping("/rest")
public class RestHandler {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return restTemplate.getForEntity(" http://localhost:9999/student/findAll",Collection.class).getBody();
}
@GetMapping("/findAll2")
public Collection<Student> findAll2(){
return restTemplate.getForObject(" http://localhost:9999/student/findAll",Collection.class);
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id) {
return restTemplate.getForEntity(" http://localhost:9999/student/findById/{id}",Student.class,id).getBody();
}
@GetMapping("/findById2/{id}")
public Student findById2(@PathVariable("id") long id) {
return restTemplate.getForObject(" http://localhost:9999/student/findById2/{id}",Student.class,id);
}
@PostMapping("/save")
public void save(@RequestBody Student student) {
restTemplate.postForEntity("http://localhost:9999/student/save",student,null).getBody();
}
@PostMapping("/save2")
public void save2(@RequestBody Student student) {
restTemplate.postForObject("http://localhost:9999/student/save",student,null);
}
@PutMapping("/update")
public void update(@RequestBody Student student) {
restTemplate.put("http://localhost:9999/student/update",student);
}
@DeleteMapping("/delete/{id}")
public void delete(@PathVariable("id") long id) {
restTemplate.delete("http://localhost:9999/student/delete/{id}",id);
}
}
服务消费者 consumer 代码实现
- 子模块 consumer,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml,添加 Eureka Server 相关配置
server:
port: 7777
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true #是否将当前服务注册到注册中心
- 创建启动类
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
relurn new RestTemplate();
}
}
- 创建Student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
}
- ConsumerHandler
@RestController
@RequestMapping("/consumer")
public class StudentHandler {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return restTemplate.getForEntity(" http://localhost:9999/student/findAll",Collection.class).getBody();
}
@GetMapping("/findAll2")
public Collection<Student> findAll2(){
return restTemplate.getForObject(" http://localhost:9999/student/findAll",Collection.class);
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id) {
return restTemplate.getForEntity(" http://localhost:9999/student/findById/{id}",Student.class,id).getBody();
}
@GetMapping("/findById2/{id}")
public Student findById2(@PathVariable("id") long id) {
return restTemplate.getForObject(" http://localhost:9999/student/findById2/{id}",Student.class,id);
}
@PostMapping("/save")
public void save(@RequestBody Student student) {
restTemplate.postForEntity("http://localhost:9999/student/save",student,null).getBody();
}
@PostMapping("/save2")
public void save2(@RequestBody Student student) {
restTemplate.postForObject("http://localhost:9999/student/save",student,null);
}
@PutMapping("/update")
public void update(@RequestBody Student student) {
restTemplate.put("http://localhost:9999/student/update",student);
}
@DeleteMapping("/delete/{id}")
public void delete(@PathVariable("id") long id) {
restTemplate.delete("http://localhost:9999/student/delete/{id}",id);
}
}
服务网关
Spring Cloud集成了Zuul组件,实现服务网关。
Zuul
Zuul是Netflix提供的一个开源的API网关服务器,是客户端和网站后端所有请求的中间层,对外开放一个API,将所有请求导入统一的入口,屏蔽了服务端的具体实现逻辑,Zuul可以实现反向代理的功能,在网关内部实现动态路由、身份认证、IP过滤、数据监控等。
- 子模块 zuul,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml
server:
port: 8899
spring:
application:
name: gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
zuul:
routes:
provider: /p/**
属性说明
zuul.routes.provider
:给服务提供者provider设置映射
- 创建启动类
@EnableZuulProxy
@EnableAutoConfiguration
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
注解说明
@EnableZuulProxy
:包含了@EnableZuulProxy
,设置该类是网关的启动类。
@EnableAutoConfiguration
:可以帮助Spring Boot应用将所有符合条件的@Configuration
配置加载到当前Spring Boot创建并使用的IoC容器中。
- Zuul自带了负载均衡功能,修改provider 的代码
@RestController
@RequestMapping("/student")
public class StudentHandler {
@Value(${server.port})
private String port;
@GetMapping("/index")
public String index(){
return "当前端口:"+this.port;
}
}
负载均衡
RIbbon
Spring Cloud Ribbon是一个负载均衡解决方案,Ribbon是 Netflix发布的负载均衡器,Spring Cloud Ribbon是基于Netflix Ribbon实现的,是一个用于对HTTP请求进行控制的负载均衡客户端。
在注册中心对Ribbon进行注册之后,Ribbon就可以基于某种负载均衡算法,如轮询、随机、加权轮询、加权随机等自动帮助服务消费者调用接口,开发者也可以根据具体需求自定义Ribbon 负载均衡算法。实际开发中,Spring Cloud Ribbon需要结合Spring Cloud Eureka来使用,Eureka Server提供所有可以调用的服务提供者列表,Ribbon基于特定的负载均衡算法从这些服务提供者中选择要调用的具体实例。
- 子模块 ribbon,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml
server:
port: 9988
spring:
application:
name: ribbon
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true
- 创建启动类
@SpringBootApplication
public class RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class,args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
relurn new RestTemplate();
}
}
注解说明
@LoadBalanced
:声明一个基于Ribbon的负载均衡。
- 创建Student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
}
- RibbonHandler
@RestController
@RequestMapping("/ribbon")
public class RibbonHandler {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return restTemplate.getForObject(" http://provider/student/findAll",Collection.class);
}
@GetMapping("/index")
public String index(){
return restTemplate.getForObject(" http://provider/student/index",String.class);
}
}
Feign
与Ribbon一样,Feign也是由Netflix提供的,Feign是一个声明式、模版化的Web Service 客户端,它简化了开发者编写Web服务客户端的操作,开发者可以通过简单的接口和注解来调用HTTP API,Spring Cloud Feign,它==整合了Ribbon和 Hystrix,具有可插拔、基于注解、负载均衡、服务熔断==等一系列便捷功能。
相比较于Ribbon + RestTemplate 的方式,Feign 大大简化了代码的开发,Feign 支持多种注解,包括Feign注解、JAX-RS注解、Spring MVC注解等,Spring Cloud对 Feing进行了优化,整合了Ribbon和Eureka,从而让Feign的使用更加方便。
Ribbon和Feign的区别
Ribbon是一个通用的HTTP客户端工具,Feign是基于Ribbon实现的。
Feign的特点
-
Feign是一个声明式的 Web Service客户端。
-
支持Feign注解、Spring MVC注解、JAX-RS注解。
-
Feign 基于Ribbon 实现,使用起来更加简单。
-
Feign集成了Hystrix,具备服务熔断的功能。
-
子模块 feign,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml
server:
port: 9898
spring:
application:
name: feign
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true
- 创建启动类
@SpringBootApplication
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class,args);
}
}
- 创建Student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
}
- FeignProviderClient接口
@FeignClient(value = "provider")
public interface FeignProviderClient{
@GetMapping("/student/findAll")
public Collection<Student> findAll();
@GetMapping("/student/index")
public String index();
}
- FeignHandler
@RestController
@RequestMapping("/feign")
public class FeignHandler {
@Autowired
private FeignProviderClient feignProviderClient;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return feignProviderClient.findAll();
}
@GetMapping("/index")
public String index(){
return feignProviderClient.index();
}
}
- 服务熔断,application.yml添加熔断机制。
server:
port: 9898
spring:
application:
name: feign
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
属性说明
feign.hystrix.enabled
:是否开启熔断器。
- 创建FeignProviderClient 接口的实现类 FeignError,定义容错处理逻辑,通过
@Component
注解将FeignError实例注入loC容器中。
@Component()
public class FeignError implements FeignProviderClient{
@Override
public Collection<Student> findAll(){
return null;
}
@Override
public String index(){
return "服务器维护中......";
}
}
- 在FeignProviderClient定义处通过
@Feignclient
的fallback属性设置映射。
@FeignClient(value = "provider",fallback = FeignError.class)
public interface FeignProviderClient{
@GetMapping("/student/findAll")
public Collection<Student> findAll();
@GetMapping("/student/index")
public String index();
}
容错机制
在不改变各个微服务调用关系的前提下,针对错误情况进行预先处理。
设计原则
-
服务隔离机制
-
服务降级机制
-
熔断机制
-
提供实时的监控和报警功能
-
提供实时的配置修改功能
Hystrix
Hystrix数据监控需要结合Spring Boot Actuator来使用,Actuator提供了对服务的健康健康、数据统计,可以通过hystrix.stream 节点获取监控的请求数据,提供了可视化的监控界面。
- 子模块 hystrix ,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml
server:
port: 9998
spring:
application:
name: hystrix
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
management:
endpoint:
endpoints:
web:
exposure:
include: 'hystrix.stream'
- 创建启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrixDashboard
@EnableCircuitBreaker
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class,args);
}
}
注解说朗
@EnablecircuitBreaker
:声明启用数据监控。
@EnableHystrixDashboard
:声明启用可视化数据监控。
- FeignProviderClient接口
@FeignClient(value = "provider")
public interface FeignProviderClient{
@GetMapping("/student/findAll")
public Collection<Student> findAll();
@GetMapping("/student/index")
public String index();
}
- HystrixHandler
@RestController
@RequestMapping("/hystrix")
public class HystrixHandler {
@Autowired
private FeignProviderClient feignProviderClient;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return feignProviderClient.findAll();
}
@GetMapping("/index")
public String index(){
return feignProviderClient.index();
}
}
-
启动成功之后,访问
http://localhost:9998/actuator/hystrix.stream
可以监控到请求数据 -
访问
http://localhost:9998/hystrix
可以看到可视化的监控界面,输入要监控的地址节点即可看到该节点的可视化数据监控。
Spring Cloud配置中心
Spring Cloud Config,通过服务端可以为多个客户端提供配置服务。Spring Cloud Config 可以将配置文件存储在本地,也可以将配置文件存储在远程Git仓库,创建Config Server,通过它管理所有的配置文件。
本地文件系统
- 子模块 nativeconfigserver,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
- 子模块创建配置文件 application.yml
server:
port: 8787
spring:
application:
name: nativeconfigserver
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:/shared
属性说明
profiles.active
:配置文件的获取方式
cloud.config.server.native.search-locations
:本地配置文件存放的路径
- resources路径下创建shared 文件夹,并在此路径下创建 configclient-dev.yml
server:
port: 8070
foo: foo version 1
- 创建启动类
@SpringBootApplication
@EnableConfigServer
public class NativeConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(NativeConfigServerApplication.class,args);
}
}
注解说朗
@EnableConfigServer
:声明配置中心。
创建客户端读取本地配置中心的配置文件
- 子模块 nativeconfigclient,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
- bootstrap.yml,配置读取本地配置中心的相关信息
server:
port: 8998
spring:
application:
name: nativeconfigclient
profiles:
active: dev
cloud:
config:
uri: http://localhost:8888
fail-fast: true
属性说明
cloud.config.uri
:本地Config Server的访问路径。
cloud.config.fail-fase
:设置客户端优先判断Config Server获取是否正常。
通过spring.application.name
结合spring.profiles.active
拼接目标配置文件名,configclient-dev.yml,去Config Server中查找该文件。
- 创建启动类
@SpringBootApplication
public class NativeConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(NativeConfigClientApplication.class,args);
}
}
- NativeConfigHandler
@RestController
@RequestMapping("/native")
public class NativeConfigHandler {
@Value("${foo}")
private String foo;
@Value("${server.port}")
private String port;
@GetMapping("/index")
public String index(){
return this.port +"-"+ this.poo;
}
}
远程配置中心
- 创建配置文件,上传至GitHub
server:
port: 9889
eureka:
client:
serviceUrl:
defaultzone: http://localhost:8888/
spring:
application:
name: configclient
●创建Config Server,新建Maven工程,pom.xml
- 子模块 configserver,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
- application.yml,配置读取远程配置中心的相关信息
server:
port: 9989
spring:
application:
name: configserver
cloud:
config:
server:
git:
uri: https://github.com/linerjialin/springcloudconfig.git
searchPaths: config
username: root
password: 123456
label: master
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/
- 创建启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
创建Config Client
- 子模块 configclient,pom.xml
<dependencies>
<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>
</dependencies>
- bootstrap.yml,配置读取远程配置中心的相关信息
spring:
cloud:
config:
name: configclient
label: master
discovery:
enabled: true
service-id: configserver
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/
属性说明
spring.cloud.config.name
:当前服务注册在Eureka Server上的名称,与远程仓库的配置文件名对应。
spring.cloud.config. label
:Git Repository的分支。
spring.cloud.config.discovery.enabled
:是否开启Config 服务发现支持。
spring.cloud.config.discovery.service-id
:配置中心在Eureka Server上注册的名称。
- 创建启动类
@SpringBootApplication
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
}
- Handler
@RestController
@RequestMapping("/hello")
public class HelloHandler {
@Value("${server.port}")
private String port;
@GetMapping("/index")
public String index(){
return this.port;
}
}
服务跟踪
Spring Cloud Zipkin
Zipkin 是一个可以采集并且跟踪分布式系统中请求数据的组件,让开发者可以更加直观的监控到请求在各个微服务所耗费的时间等,Zipkin: Zipkin Server、Zipkin Client。
创建Zipkin Server
- 子模块 zipkin,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.12.9</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.12.9</version>
</dependency>
</dependencies>
- application.yml
server:
port: 9090
- 创建启动类
@SpringBootApplication
@EnableZipkinServer
public class ZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinApplication.class,args);
}
}
注解说明
@EnableZipkinServer
:声明启动 Zipkin Server 。
创建Zipkin Client
- 子模块 zipkinclient,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8090
spring:
application:
name: zipkinclient
sleuth:
web:
client:
enabled: true
sampler:
probability: 1.0
zipkin:
base-url: http:/l localhost:9090/
eureka:
client:
service-url:
defaultZone: http://localhost:8888/
属性说明
spring.sleuth.web.client.enabled
:设置开启请求跟踪
spring.sleuth.sampler.probability
:设置采样比例,默认是1.0
srping.zipkin.base-url
:Zipkin Server地址
- 创建启动类
@SpringBootApplication
public class ZipkinClientApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinClientApplication.class,args);
}
}
- Handler
@RestController
@RequestMapping("/zipkin")
public class ZipkinHandler {
@Value("${server.port}")
private String port;
@GetMapping("/index")
public String index(){
return this.port;
}
}
SpringCloudAlibaba
Spring Cloud Alibaba工程结构:Spring Boot --> Spring Cloud --> Spring Cloud Alibaba
如何上手
- 创建父工程,pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
服务治理
服务注册+服务发现
Nacos
Nacos 服务注册
解压,启动服务。
Nacos 搭建成功,接下来注册服务。
- 在父工程路径下创建子工程,让子工程继承父工程的环境依赖,pom.xml 中添加 nacos 发现组件。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- application.yml 中配置
spring:
cloud:
nacos:
discovery:
# 指定nacos server地址
server-addr: localhost:8848
application:
name: my-nacos
Nacos 服务发现与调用
- pom.xml 添加 discovery,完成服务发现。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 通过 discoveryClient 发现注册到 nacos 中的 provider 服务。
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/instances")
public List<ServiceInstance> instances(){
List<ServiceInstance> provider = discoveryClient.getInstances("provider");
return provider;
}
}
@Configuration
public class ConsumerConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/index")
public String index(){
List<ServiceInstance> provider = discoveryClient.getInstances("provider");
int index = ThreadLocalRandom.current().nextInt(provider.size());
String url = provider.get(index).getUri()+"/index";
return "consumer随机远程调用provier:"+this.restTemplate.getForObject(url, String.class);
}
}
Ribbon 负载均衡
Ribbon 不是Spring Cloud Alibaba的组件,是Netflix提供的,默认使用轮询算法(依次调用)
轮询
@Configuration
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/index")
public String index(){
return "consumer远程调用provier:"+this.restTemplate.getForObject("http://provider/index", String.class);
}
}
随机
server:
port: 8180
provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Nacos 权重
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//读取配置文件
}
@Override
public Server choose(Object o) {
ILoadBalancer loadBalancer = this.getLoadBalancer();
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) loadBalancer;
//获取要请求的微服务名称
String name = baseLoadBalancer.getName();
//获取服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("选择的实例是port={},instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
}
server:
port: 8180
provider:
ribbon:
NFLoadBalancerRuleClassName: com.liner.configuration.NacosWeightedRule
Sentinel 服务限流降级
雪崩效应:
-
A调用B,但是B此时由于某种原因宕机了,停止运行,A调用B的线程处于阻塞状态,且不会释放,一直等待
-
A不知道B宕机仍然继续调用,B不进行处理,导致请求的大量阻塞最终导致A内存不足
解决方案
- 设置线程超时:超过一定时间还未调用将自动释放掉
- 设置限流:某个微服务达到上限时,其他请求暂时不去调用该服务
- 熔断器 Sentinel、Hystrix:
熔断、降级、限流的区别
-
降级:应对系统自身的故障,系统将某些不重要的接口的功能进行降低,即限制一些功能的使用,只提供部分功能,如超级省电模式下只提供接打电话功能,不能联网刷抖音
-
限流:只接受系统能承载的访问量,超出此访问量直接抛出异常,如手机每日流量设置,控制流量,超出则断掉流量
-
熔断:应对外部系统的故障,对方出现故障,为不伤及自身,采取熔断,不再调用,直接返回错误信息,如森林大火,砍了防护林,阻断火势蔓延
-
pom.xml 引入依赖
<!--sentinel对流量进行控制-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--actuator对流量进行收集-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- application 配置
management:
endpoints:
web:
exposure:
include: '*'
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
- 下载 Sentinel 控制台,解压,启动。
流控规则
直接限流(默认)
对当前资源直接进行限流(直接、Controller层)
关联限流
对当前资源所关联的资源进行控制,结果导致当前资源限流(间接、Controller层)
链路限流
提供更加细粒度的限流,对service层的保护,入口是Contrioller (service层) 一个资源有多个入口,链路即是对入口的限流保护
注意:1.6.3以上的 Sentinel 版本,默认收敛所有url入口的,需要手动关闭收敛,才能进行链路限流
1、pom.xml 添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>1.7.1</version>
</dependency>
2、application.yml
spring:
cloud:
sentinel:
filter:
enabled: false
3、写配置类
@Configuration
public class FilterConfiguration {
@Bean
public FilterRegistrationBean registrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CommonFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false");
registrationBean.setName("sentinelFilter");
return registrationBean;
}
}
4、Service
@Service
public class HelloService {
@SentinelResource("test")
public void test(){
System.out.println("test");
}
}
5、Controller
@GetMapping("/test1")
public String test1(){
this.helloService.test();
return "test1";
}
@GetMapping("/test2")
public String test2(){
this.helloService.test();
return "test2";
}
流控效果
快速失败
直接抛出异常
Warm UP
给系统一个预热的时间,预热时间段内单机阈值较低,预热时间过后单机阈值增加,预热时间内当前的单机阈值是设置的阈值的三分之一,预热时间过后单机阈值恢复设置的值。秒为单位
排队等待
当请求调用失败之后,不会立即抛出异常,等待下一次调用,时间范围是超时时间,在时间范围内如果能请求成功则不抛出异常,如果请求则抛出异常。毫秒为单位
降级规则
RT
单个请求的响应时间超过阈值,则进入准降级状态,接下来 1 S 内连续 5 个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。
异常比例
每秒异常数量占通过量的比例大于阈值,就进行降级处理,持续时间为时间窗口的值。
异常数
1 分钟内的异常数超过阈值就进行降级处理,时间窗口的值要大于 60S,否则刚结束熔断又进入下一次熔断了。
热点规则
热点规则是流控规则的更细粒度操作,可以具体到**对某个热点参数的限流**,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。
高级选项:参数例外项 参数类型,参数值,限流阈值
必须要添加 @SentinelResource,即对资源进行流控。
@GetMapping("/hot")
@SentinelResource("hot")
public String hot(
@RequestParam(value = "num1",required = false) Integer num1,
@RequestParam(value = "num2",required = false) Integer num2){
return num1+"-"+num2;
}
授权规则
给指定的资源设置流控应用(追加参数),可以对流控应用进行访问权限的设置,具体就是添加白名单和黑名单。
如何给请求指定流控应用,通过实现 RequestOriginParser 接口来完成,代码如下所示。
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String name = httpServletRequest.getParameter("name");
if(StringUtils.isEmpty(name)){
throw new RuntimeException("name is null");
}
return name;
}
}
要让 RequestOriginParserDefinition 生效,需要在配置类中进行配置。
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init(){
WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition());
}
}
自定义规则异常返回
创建异常处理类
public class ExceptionHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
httpServletResponse.setContentType("text/html;charset=utf-8");
String msg = null;
if(e instanceof FlowException){
msg = "限流";
}else if(e instanceof DegradeException){
msg = "降级";
}
httpServletResponse.getWriter().write(msg);
}
}
进行配置。
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init(){
WebCallbackManager.setUrlBlockHandler(new ExceptionHandler());
}
}
整合 RocketMQ
安装 RocketMQ
1、传入 Linux 服务器
2、解压缩
unzip rocketmq-all-4.7.1-bin-release.zip
3、启动 NameServer
nohup ./bin/mqnamesrv &
4、检查是否启动成功
netstat -an | grep 9876
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m0vWTMZr-1654699996150)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20220608205028295.png)]
5、启动 Broker
启动之前需要编辑配置文件,修改 JVM 内存设置,默认给的内存 4 GB,超过我们的 JVM 了。
cd bin
vim runserver.sh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlEowFb4-1654699996152)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20220608205108941.png)]
vim runbroker.sh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bV6Z1IXI-1654699996152)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20220608205139378.png)]
启动 Broker
nohup ./mqbroker -n localhost:9876 &
可以查看日志
tail -f ~/logs/rocketmqlogs/broker.log
启动成功
6、测试 RocketMQ
消息发送
cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Producer
消息接收
cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Consumer
7、关闭 RocketMQ
cd bin
./mqshutdown broker
./mqshutdown namesrv
安装 RocketMQ 控制台
1、解压缩,修改配置,打包
mvn clean package -Dmaven.test.skip=true
2、进入 target 启动 jar
java -jar rocketmq-console-ng-1.0.0.jar
打开浏览器访问 localhost:9877,如果报错是因为RocketMQ 安装在 Linux 中,控制台在 windows,Linux 需要开放端口才能访问,开放 10909 和 9876 端口
firewall-cmd --zone=public --add-port=10909/tcp --permanent
firewall-cmd --zone=public --add-port=9876/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
重新启动控制台项目
Java 实现消息发送
1、pom.xml 中引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2、生产消息
public class Test {
public static void main(String[] args) throws Exception {
//创建消息生产者
DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");
//设置NameServer
producer.setNamesrvAddr("192.168.200.128:9876");
//启动生产者
producer.start();
//构建消息对象
Message message = new Message("myTopic","myTag",("Test MQ").getBytes());
//发送消息
SendResult result = producer.send(message, 1000);
System.out.println(result);
//关闭生产者
producer.shutdown();
}
}
3、直接运行,如果报错 sendDefaultImpl call timeout,可以开放 10911 端口
firewall-cmd --zone=public --add-port=10911/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
打开 RocketMQ 控制台,可查看消息。
Java 实现消息消费
@Slf4j
public class ConsumerTest {
public static void main(String[] args) throws MQClientException {
//创建消息消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group");
//设置NameServer
consumer.setNamesrvAddr("192.168.200.128:9876");
//指定订阅的主题和标签
consumer.subscribe("myTopic","*");
//回调函数
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
log.info("Message=>{}",list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
}
}
读多写少用缓存,写多读少用mq
Spring Boot 整合 RocketMQ
provider
1、pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
2、application.yml
rocketmq:
name-server: 192.168.200.128:9876
producer:
group: myprovider
3、Order
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String buyerName;
private String buyerTel;
private String address;
private Date createDate;
}
4、Controller
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/create")
public Order create(){
Order order = new Order(
1,
"张三",
"123123",
"软件园",
new Date()
);
this.rocketMQTemplate.convertAndSend("myTopic",order);
return order;
}
consumer
1、pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
2、application.yml
rocketmq:
name-server: 192.168.200.128:9876
3、Service
@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "myConsumer",topic = "myTopic")
public class SmsService implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
log.info("新订单{},发短信",order);
}
}
服务网关
Zuul
Spring Cloud Gateway 是基于 Netty,跟 Servlet 不兼容,所以你的工程中不能出现 Servlet 的组件 。
1、pom.xml
注意,一定不能出现 spring web 的依赖,因为 Gateway 与 Servlet 不兼容。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
上面这种做法其实没有用到 nacos ,现在我们让 gateway 直接去 nacos 中发现服务,配置更加简单了。
1、pom.xml 引入 nacos
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
Gateway 限流
基于路由限流
1、pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
2、配置类
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置初始化的限流参数
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
new GatewayFlowRule("provider_route") //和配置文件中的routes:-id 必须一致,基于此路由限流 QPS每秒访问量
.setCount(1)//数量
.setIntervalSec(1)//秒
//上述含义 1秒通过1次
);
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",0);
map.put("msg","被限流了"); //自定义异常处理返回信息
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
3、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
基于 API 分组限流
1、修改配置类,添加基于 API 分组限流的方法,修改初始化的限流参数
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置初始化的限流参数
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",0);
map.put("msg","被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//自定义API分组
@PostConstruct
private void initCustomizedApis(){
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("provider_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/provider/api1/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/provider/api2/demo1"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
2、Controller 添加方法
@GetMapping("/api1/demo1")
public String demo1(){
return "demo";
}
@GetMapping("/api1/demo2")
public String demo2(){
return "demo";
}
@GetMapping("/api2/demo1")
public String demo3(){
return "demo";
}
@GetMapping("/api2/demo2")
public String demo4(){
return "demo";
}
也可以基于 Nacos 服务发现组件进行限流
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
API 分组代码修改,改为 discovery 中的服务名。
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/p1/api2/demo1"));
}});
分布式事务
模拟分布式事务异常
1、创建两个工程 order、pay,pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、建两个数据库 order、pay,两个微服务分别访问。
3、分别写两个服务的 application.yml
server:
port: 8010
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/order
server:
port: 8020
spring:
application:
name: pay
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/pay
4、分别写两个 Service
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(){
this.jdbcTemplate.update("insert into orders(username) values ('张三')");
}
}
@Service
public class PayService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(){
this.jdbcTemplate.update("insert into pay(username) values ('张三')");
}
}
5、控制器 Order 通过 RestTemplate 调用 Pay 的服务
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save")
public String save(){
//订单
this.orderService.save();
int i = 10/0;
//支付
this.restTemplate.getForObject("http://localhost:8020/save",String.class);
return "success";
}
}
@RestController
public class PayController {
@Autowired
private PayService payService;
@GetMapping("/save")
public String save(){
this.payService.save();
return "success";
}
}
6、启动类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@SpringBootApplication
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
}
分布式异常模拟结束,Order 存储完成之后,出现异常,会导致 Pay 无法存储,但是 Order 数据库不会进行回滚。
Seata 解决
1、下载
2、解压,修改两个文件
regisry.conf
registry {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
nacos-config.txt
3、启动 Nacos,运行 nacos-config.sh 将 Seata 配置导入 Nacos
进入 conf,右键 Git Bash Here
cd conf
sh nacos-config.sh 127.0.0.1
执行成功,刷新 Nacos,配置加入
nacos-config.txt 配置已生效
4、启动 Seata Server, JDK 8 以上环境无法启动
cd bin
seata-server.bat -p 8090 -m file
启动成功,Nacos 注册成功。
Seata 服务环境搭建完毕,接下来去应用中添加。
1、初始化数据库,在两个数据库中添加事务日志记录表,SQL Seata 已经提供。
2、直接在两个数据库运行脚本。
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3、两个工程的 pom.xml 添加 Seata 组件和 Nacos Config 组件。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
4、给 JDBCTemplate 添加代理数据源
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(new DataSourceProxy(dataSource));
}
}
@SpringBootApplication
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(new DataSourceProxy(dataSource));
}
}
5、将 registry.conf 复制到两个工程的 resources 下。
6、给两个工程添加 bootstrap.yml 读取 Nacos 配置。
spring:
application:
name: order
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
alibaba:
seata:
tx-service-group: ${spring.application.name}
spring:
application:
name: pay
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
alibaba:
seata:
tx-service-group: ${spring.application.name}
tx-service-group 需要和 Nacos 配置中的名称一致。
7、在 Order 调用 Pay 处添加注解 @GlobalTransactional
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save")
@GlobalTransactional
public String save(){
//订单
this.orderService.save();
int i = 10/0;
//支付
this.restTemplate.getForObject("http://localhost:8020/save",String.class);
return "success";
}
}