SpringCould整合Mybatis-Plus+Druid+Swagger2(很详细很全)
Spring Cloud中文网: https://www.springcloud.cc/
consumer: 调用方
provider: 被调用方
一个接口一般都会充当两个角色(不是同时充当)
1、dubbo: zookeeper + dubbo + springmvc/springboot
官方地址:http://dubbo.apache.org/#!/?lang=zh-cn
配套
通信方式:rpc
注册中心:zookeper/redis
配置中心:diamond
2、springcloud: 全家桶+轻松嵌入第三方组件(Netflix 奈飞)
官网:http://projects.spring.io/spring-cloud/
配套
通信方式:http restful
注册中心:eruka/consul
配置中心:config
断路器:hystrix
网关:zuul
分布式追踪系统:sleuth+zipkin
学习资料:https://blog.csdn.net/zhangweiwei2020/article/details/78646252
Spring Cloud 为开发者提供了快速构建分布式系统中一些常用模式的工具(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线)。分布式系统的协调导致了样板模式,使用 Spring Cloud 开发人员可以快速建立实现这些模式的服务和应用程序。它们将适用于任何分布式环境,包括开发人员自己的笔记本电脑、裸机数据中心和托管平台(如 Cloud Foundry)
官网:https://docs.spring.io/spring-cloud
版本选择
Spring Cloud 专注于为典型用例和可扩展性机制提供良好的开箱即用体验以覆盖其他用例。
- 分布式/版本化配置
- 服务注册和发现
- 路由
- 服务到服务呼叫
- 负载均衡
- 断路器
- 分布式消息传递
推荐Springboot最合适的版本是2.4.6
详细的选择版本方法:
https://blog.csdn.net/weixin_55891090/article/details/114852531
https://start.spring.io/actuator/info
CAP定理
CAP理论知识
分布式系统CAP原理/注册中心选择
CAP定理
指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时) 可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间) 分区容错性(P):分区容忍性,就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好) CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
一、注册中心
主流的注册中心:
zookeeper、Eureka、consul、etcd 等
1.1、创建Eureka Server
Spring-Cloud Euraka是Spring Cloud集合中一个组件,它是对Euraka的集成,用于服务注册和发现。
Spring Boot 选择版本2.4.6
1.2、application.yml
server:
port: 8761
eureka:
instance:
#注册中心名称
hostname: localhost
client:
#声明自己是个服务端#表示是否将自己注册到Eureka Server,默认为true
registerWithEureka: false
#表示是否从Eureka Server获取注册信息,默认为true
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 报红码 自我保护模式禁止关闭,默认是开启状态true
#enable-self-preservation: false
#关闭服务监听 :控制台日志打印:Running the evict task with compensationTime 0ms
logging:
level:
com.netflix: warn
1.3、启动类添加注解
@EnableEurekaServer ///开启 Eureka Server
1.4、访问:http://localhost:8761
访问:注册服务
http://localhost:8761
问题:eureka管理后台出现一串红色字体:是警告,说明有服务上线率低
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
关闭检查方法:eureka服务端配置文件加入
server:
enable-self-preservation: false
注意:自我保护模式禁止关闭,默认是开启状态true
二、商品服务 product_server
三、订单服务 product_server
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sxpcwlkj</groupId>
<artifactId>product_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>product_server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.yml
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: order-service
创建数据库springcould
创建表 p_product 商品表
CREATE TABLE `p_product` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`product_title` varchar(200) DEFAULT NULL,
`product_pice` decimal(10,2) DEFAULT NULL,
`product_num` int(11) DEFAULT NULL,
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建Entity Mapper Controller Servicr
访问:产品服务
http://localhost:8771/api/v1/product/selectById?id=1
常用的服务间调用方式
RPC:
远程过程调用,像调用本地服务(方法)一样调用服务器的服务
支持同步、异步调用
客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接
RPC:
远程过程调用,像调用本地服务(方法)一样调用服务器的服务
支持同步、异步调用
客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接
PRC数据包小
protobuf
thrift
rpc:编解码,序列化,链接,丢包,协议
Rest(Http):
http请求,支持多种协议和功能
开发方便成本低
http数据包大
java开发:HttpClient,URLConnection
四、负载均衡器 Ribbon
目前主流的负载方案分为以下两种:
- 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
- 客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载。
Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。
Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的(https://github.com/Netflix/ribbon)。
4.1 启动类加代码
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
4.2 controller访问
package com.sxpcwlkj.order_server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("selectById")
public String selectById(int id){
String url = "http://product-service/api/v1/product/selectById?id=" + id;
String result = restTemplate.getForObject(url , String.class);
return result;
}
}
#自定义负载均衡策略
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
扩展:idea启动多个项目
项目启动是优先使用这里的配置
-Dserver.port=8771
负载均衡有好几种实现策略,常见的有:
策略类 | 命名 | 说明 |
RandomRule | 随机策略 | 随机选择 Server |
RoundRobinRule | 轮训策略 | 按顺序循环选择 Server |
RetryRule | 重试策略 | 在一个配置时问段内当选择 Server 不成功,则一直尝试选择一个可用的 Server |
BestAvailableRule | 最低并发策略 | 逐个考察 Server,如果 Server 断路器打开,则忽略,再选择其中并发连接最低的 Server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为 circuit tripped 的 Server,过滤掉那些高并发连接的 Server(active connections 超过配置的网值) |
ResponseTimeWeightedRule | 响应时间加权策略 | 根据 Server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断 Server 所在区域的性能和 Server 的可用性轮询选择 Server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server |
五、负载均衡Feign
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
Feign: 伪RPC客户端(本质还是用http)
官方文档: https://cloud.spring.io/spring-cloud-openfeign/
1、使用feign步骤讲解(新旧版本依赖名称不一样)
加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类增加@EnableFeignClients
增加一个接口 并@FeignClient(name="product-service")
2、编码实战
3、注意点:
1、路径
2、Http方法必须对应
3、使用requestBody,应该使用@PostMapping
4、多个参数的时候,通过@RequestParam("id") int id)方式调用
package com.sxpcwlkj.order_server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableFeignClients
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.sxpcwlkj.order_server.service;
import com.sxpcwlkj.order_server.utils.JsonResultObject;
public interface OrderService {
JsonResultObject selectById(int id);
}
package com.sxpcwlkj.order_server.service.impl;
import com.sxpcwlkj.order_server.entity.Product;
import com.sxpcwlkj.order_server.service.OrderService;
import com.sxpcwlkj.order_server.service.ProductClient;
import com.sxpcwlkj.order_server.utils.JsonResultObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ProductClient productClient;
@Override
public JsonResultObject selectById(int id) {
String s = productClient.selectById(id);
return JsonResultObject.getSuccessResult(s);
}
}
使用requestBody,应该使用@PostMapping
多个参数的时候,通过@RequestParam(”id”) int id)方式调用
package com.sxpcwlkj.order_server.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/api/v1/product/selectById")
String selectById(@RequestParam(value = "id") int id);
}
package com.sxpcwlkj.order_server.controller;
import com.sxpcwlkj.order_server.entity.Product;
import com.sxpcwlkj.order_server.service.OrderService;
import com.sxpcwlkj.order_server.utils.JsonResultObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
/**
* Ribbon 方式请求
* @param id
* @return
*/
@GetMapping("selectById")
public String selectById(int id){
String url = "http://product-service/api/v1/product/selectById?id=" + id;
String result = restTemplate.getForObject(url , String.class);
return result;
}
/**
*
* @param id
* @return Feign 方式请求
*/
@GetMapping("selectByIdTwo")
public JsonResultObject selectByIdTwi(int id){
return orderService.selectById(id);
}
}
Feign 方式请求
http://localhost:8781/api/v1/order/selectByIdTwo?id=1
商品服务,增加模拟请求超时
try {
//表示休眠n秒的时间单位
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
修改调用超时时间
#修改调用超时时间
feign:
client:
config:
default:
connectTimeout: 5000 #连接超时
readTimeout: 5000 #读取超时
loggerLevel: basic #日志等级
请求的相应时间大于设定时间时
抛出异常:java.net.SocketTimeoutException: Read timed out
六、Hystrix限流、熔断和降级
在分布式系统中,远程系统或服务不可避免的调用失败(超时或者异常)。假设客户端依赖多个服务,在一次请求中,某一个服务出现异常,则整个请求会处理失败;当某一服务等待时间过长,则所有的请求都会阻塞在这个服务的请求上。这样因为一个服务就导致了整个系统的可用性。Netflix的组件Hystrix可以将这些请求隔离,针对服务限流,当服务不可用时能够熔断并降级,防止级联故障。
理想状态下:
当某一个服务出现延迟时,所有的请求都阻塞在依赖的服务Dependency I
当我们使用了Hystrix时,Hystrix将所有的外部调用都封装成一个HystrixCommand
或者HystrixObservableCommand
对象,这些外部调用将会在一个独立的线程中运行。我们可以将出现问题的服务通过熔断、降级等手段隔离开来,这样不影响整个系统的主业务。
文档地址:
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki
1、什么是Hystrix?
1)hystrix对应的中文名字是“豪猪”
2)hystrix 英[hɪst'rɪks] 美[hɪst'rɪks]
2、为什么要用?
在一个分布式系统里,一个服务依赖多个服务,可能存在某个服务调用失败,
比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,
通过Hystrix就可以解决
http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_circuit_breaker_hystrix_clients
3、提供了熔断、隔离、Fallback、cache、监控等功能
4、熔断后怎么处理?
出现错误之后可以 fallback 错误的处理信息
兜底数据
6.1.断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
6.2.Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
6.3.资源隔离
在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.
6.4.增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
6.5.启动类里面增加注解
@EnableCircuitBreaker
下载之后 启动类上面注解 提示 过期,研究来 之后发现是 Springcould版本太高,netflix-hystrix 还没有适配到新的版本,所以没有找到合适的jar
解决方案:降级
降级springboot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
降级 Spring Could
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
下载依赖,注解 显示正常
订单服务:
熔断降级服务异常报警通知
1、加入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置redis链接信息
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000
3、使用
//监控报警
String saveOrderKye = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKye);
final String ip = request.getRemoteAddr();
new Thread( ()->{
if (StringUtils.isBlank(sendValue)) {
System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
//发送一个http请求,调用短信服务 TODO
redisTemplate.opsForValue().set(saveOrderKye, "save-order-fail", 20, TimeUnit.SECONDS);
}else{
System.out.println("已经发送过短信,20秒内不重复发送");
}
}).start();
七、断路器Dashboard监控仪表盘
7.1:添加依赖
<!--断路器Dashboard监控仪表盘-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--断路器Dashboard监控仪表盘-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
7.2:启动类增加注解
@EnableHystrixDashboard
7.3:配置文件增加endpoint
management:
endpoints:
web:
exposure:
include: "*"
控制台访问:http://localhost:8781/hystrix
Hystrix Dashboard输入: http://localhost:8781/actuator/hystrix.stream
模拟部分接口出现异常,断路器 开路
八、网关zuul
1)什么是网关
API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能
统一接入
智能路由
AB测试、灰度测试
负载均衡、容灾处理
日志埋点(类似Nignx日志)
流量监控
限流处理
服务降级
安全防护
鉴权处理
监控
机器网络隔离
2)主流的网关
zuul:是Netflix开源的微服务网关,和Eureka,Ribbon,Hystrix等组件配合使用,Zuul 2.0比1.0的性能提高很多
kong: 由Mashape公司开源的,基于Nginx的API gateway
nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求
创建
api-gateway网关服务 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sxpcwlkj</groupId>
<artifactId>api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 application.yml
server:
port: 9000
#服务的名称
spring:
application:
name: api-gateway
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
启动类添加注解
@EnableZuulProxy
//默认集成断路器 @EnableCircuitBreaker
package com.sxpcwlkj.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
默认访问规则
http://gateway:port/service-id/**
之前的接口:
http://localhost:8781/api/v1/order/selectByIdTwo?id=1
现在网关接口:
http://localhost:9000/order-service/api/v1/order/selectByIdTwo?id=1
自定义order-service转发
zuul:
routes:
order-service: /apigateway-1/**
product-service: /apigateway-2/**
A:http://localhost:9000/order-service/api/v1/order/selectByIdTwo?id=1
B:http://localhost:9000/apigateway-1/api/v1/order/selectByIdTwo?id=1
A:http://localhost:9000/product-service/api/v1/product/selectById?id=1
B: http://localhost:9000/apigateway-2/api/v1/product/selectById?id=1
设置禁止访问的url
#统一入口为上面的配置,其他入口忽略
ignored-patterns: /*-service/**
A:http://localhost:9000/order-service/api/v1/order/selectByIdTwo?id=1
B:http://localhost:9000/apigateway-1/api/v1/order/selectByIdTwo?id=1
A:http://localhost:9000/product-service/api/v1/product/selectById?id=1
B: http://localhost:9000/apigateway-2/api/v1/product/selectById?id=1
#忽略整个服务,对外提供接口
ignored-services: product-service
A:http://localhost:9000/order-service/api/v1/order/selectByIdTwo?id=1
B:http://localhost:9000/apigateway/api/v1/order/selectByIdTwo?id=1
A:http://localhost:9000/product-service/api/v1/product/selectById?id=1
B: http://localhost:9000/apigateway/api/v1/product/selectById?id=1
路由映射重复覆盖问题
zuul:
routes:
order-service: /apigateway/order/** #自定义服务转发规则
product-service: /apigateway/product/**
#统一入口为上面的配置,其他入口忽略
ignored-patterns: /*-service/**
A:http://localhost:9000/apigateway/order/api/v1/order/selectByIdTwo?id=1
A:http://localhost:9000/apigateway/product/api/v1/product/selectById?id=1
Http请求头过滤问题
String token=request.getHeader("token"); String cookie=request.getHeader("cookie"); System.out.println(token); System.out.println(cookie);
问题来了: cookie=null
查看源码 除了
Cookie", "Set-Cookie", "Authorization 其他的铭感请求头都被过滤了
解决
zuul:
routes:
order-service: /apigateway/order/** #自定义服务转发规则
product-service: /apigateway/product/**
#统一入口为上面的配置,其他入口忽略
ignored-patterns: /*-service/**
#忽略整个服务,对外提供接口
#ignored-services: product-service
#处理http请求头为空的问题
sensitive-headers:
zuul流程
过滤器执行顺序问题 ,过滤器的order值越小,越先执行
ZuulFilter.class 自定义Zuul过滤器实现登录鉴权
package com.sxpcwlkj.apigateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 登录过滤器
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 过滤器类型,前置过滤器
*
* @return
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 过滤器顺序,越小越先执行
*
* @return
*/
@Override
public int filterOrder() {
return 4;
}
/**
* 过滤器是否生效
*
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
System.out.println(request.getRequestURI());
//放行哪些 Url
if ("/apigateway/order/api/v1/order/selectByIdTwo".equalsIgnoreCase(request.getRequestURI())) {
return true;
}
// else if ("/apigateway/product/api/v1/product/selectById".equalsIgnoreCase(request.getRequestURI())){
// return true;
// }
return false;
}
/**
* 业务逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//JWT
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//token对象
String token = request.getHeader("token");
if (StringUtils.isBlank((token))) {
token = request.getParameter("token");
}
//登录校验逻辑 根据公司情况自定义 JWT
if (StringUtils.isBlank(token)) {
//拦截
//禁止访问
requestContext.setSendZuulResponse(false);
//响应状态码
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
//放行
return null;
}
}
九、限流 guava框架
参考:https://blog.csdn.net/liyantianmin/article/details/79086571
1、nginx层限流
2、网关层限流guava
每个API接口都是有访问上限的,当访问频率或者并发量超过其承受范围时候,我们就必须考虑限流来保证接口的可用性或者降级可用性.即接口也需要安装上保险丝,以防止非预期的请求对系统压力过大而引起的系统瘫痪.
通常的策略就是拒绝多余的访问,或者让多余的访问排队等待服务,或者引流.
如果要准确的控制QPS,简单的做法是维护一个单位时间内的Counter,如判断单位时间已经过去,则将Counter重置零.此做法被认为没有很好的处理单位时间的边界,比如在前一秒的最后一毫秒里和下一秒的第一毫秒都触发了最大的请求数,将目光移动一下,就看到在两毫秒内发生了两倍的QPS.
网关在创建一个拦截器 优先级 高于 登陆拦截器
package com.sxpcwlkj.apigateway.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
public class OrderRateLimiterFilter extends ZuulFilter{
//每秒产生1000个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return -4;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//只对订单接口限流
if ("/apigateway/order/api/v1/order/selectByIdTwo".equalsIgnoreCase(request.getRequestURI())){
return true;
}
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
//可以使用非阻塞的形式达到降级运行的目的,即使用非阻塞的tryAcquire()方法:
if(!RATE_LIMITER.tryAcquire()){ //未请求到limiter则立即返回false
//为请求到,说明超出了,需要缓解访问 限流了
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
return null;
}
}
十、网关集群
微服务网关Zull集群搭建,所有服务通过网关访问,如果网关服务挂了怎么办呢???
解决方案:nginx+lvs+keepalive
参考:https://www.cnblogs.com/liuyisai/p/5990645.html
十一、链路追踪组件Sleuth
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false]
1、第一个值,spring.application.name的值
2、第二个值,96f95a0dd81fe3ab ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
3、第三个值,852ef4cfcdecabf3、spanid 基本的工作单元,获取元数据,如发送一个http
4、第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。
需要用到的服务添加依赖
<!--链路追踪组件Sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
常见的问题首次访问有记录,第二次访问奖没有记录了?
解决方案:在每个接口方法内部添加一个日志打印即可解决
可视化链路追踪系统Zipkin部署
官网:https://zipkin.io/
大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
同类产品
鹰眼(EagleEye)
CAT
twitter开源zipkin,结合sleuth
Pinpoint,运用JavaAgent字节码增强技术
StackDriver Trace (Google)
zipkin组成:Collector、Storage、Restful API、Web UI组成
下载安装:
有多种安装方式 推荐 Docker 安装
我的是windows系统 用的是jar 启动
链接:https://pan.baidu.com/s/1fIQEfGrppzwdqKbg7HChig
提取码:l0mp
java -jar zipkin-server-2.10.1-exec.jar
访问:http://localhost:9411/
sleuth收集跟踪信息通过http请求发送给zipkin server,zipkinserver进行跟踪信息的存储以及提供Rest API即可,Zipkin UI调用其API接口进行数据展示
默认存储是内存,可也用mysql、或者elasticsearch等存储
整合zipkin 加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
里面包含 spring-cloud-starter-sleuth、spring-cloud-sleuth-zipkin
添加配置 (订单服务和产品服务)
spring:
#zipkin服务所在地址
zipkin:
base-url: http://localhost:9411/
#配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
sleuth:
sampler:
probability: 1
启动执行接口,查询
十二、配置中心
创建config-server服务
这里有个坑,就是配置文件没有把git的账号密码地址配置 只写了一下参数,启动报错,完整的配置文件
#服务名称
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/shanpengnian/new-spring-could-config.git
username: shanpengnian@163.com
password: s********
timeout: 5
default-label: master
#服务的端口号
server:
port: 9100
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
在仓库创建一个私有的项目,创建文件:product-service.yml
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
通过配置中心访问此文件
http://localhost:9100/product-service.yml
微服务里面客户端接入配置中心
product-server
第一步:加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
第二步:修改对应服务的配置文件,把application.yml 改为 bootstrap.yml
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#指定从哪个配置中心读取
cloud:
config:
discovery:
service-id: CONFIG-SERVER
enabled: true
profile: test
#建议用lable去区分环境,默认是lable是master分支
#label: test
备份:application.yml
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#zipkin服务所在地址
zipkin:
base-url: http://localhost:9411/
#配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
sleuth:
sampler:
probability: 1
http://localhost:9100/test/product-service-test.yml
访问方式(一定要注意语法,如果有问题,会出错)
多种访问路径,可以通过启动日志去查看
例子 http://localhost:9100/product-service.yml
/{name}-{profiles}.properties
/{name}-{profiles}.yml
/{name}-{profiles}.json
/{label}/{name}-{profiles}.yml
name 服务器名称
profile 环境名称,开发、测试、生产
lable 仓库分支、默认master分支
其他服务改版为配置执行拉取配置
<!--配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
第一步:在git 配置不同的分支/版本配置
例:product-service-test.yml
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#zipkin服务所在地址
zipkin:
base-url: http://localhost:9411/
#配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
sleuth:
sampler:
probability: 1
branch: test
env: test
修改对应服务的配置文件,把application.yml 改为 bootstrap.yml
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#指定从哪个配置中心读取
cloud:
config:
discovery:
service-id: CONFIG-SERVER
enabled: true
profile: test
#建议用lable去区分环境,默认是lable是master分支
label: test
其他服务 同理修改
注意点:
1.配置文件要用bootstrap.yml
2.默认读取文件名是 服务名称
十三、消息总线Bus
一个事件,需要广播或者单独传递给某个接口
配置更新了,但是其他系统不知道是否更新
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
1、消息队列介绍
参考:https://www.cnblogs.com/linjiqin/p/5720865.html
2、同类产品
ActiveMQ
RocketMQ
Kafka
等
3、SpringCloud默认推荐使用RabbitMQ
4、RabbitMQ介绍
官方文档:http://www.rabbitmq.com/getstarted.html
中文文档:http://rabbitmq.mr-ping.com/
rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统。它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rabbit MQ 是建立在Erlang OTP平台上。
1、安装Erlang
首选是要注意两个软件的兼容 https://www.rabbitmq.com/changelog.html
这里我下载的是:https://www.erlang.org/downloads
双击运行安装软件,注意安装路径
配置环境变量
打开cmd,输入erl
2.安装RabbitMQ
https://www.rabbitmq.com/install-windows.html
下载后 双击运行安装 ,注意安装路径
注意,我这里是直接在path路径 写的绝对路径
在菜单找到启动选项 双击启动
http://localhost:15672/#/
消息总线整合配置中心架构流程
1、config-client加入依赖
<!--配置中心结合消息队列-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2、在配置文件中增加关于RabbitMQ的连接(如果是本机,则可以直接启动,采用默认连接配置)
spring
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
3、需要刷新配置的地方,增加注解
@RefreshScope
4、访问验证 post方式:
http://localhost:8772/actuator/bus-refresh
看到上面暴露的开放接口,说明成功了
5、动态刷新配置: 在开发和测试环境使用,尽量少在生产环境使用
最后、项目启动顺序
哪个服务配置消息总线,就post访问哪个项目
1)注册中心
2)配置中心
3)对应的服务:商品服务、订单服务。。。
4)启动网关