1.SpringCloud概述
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,领导选举,分布式会话,集群状态。(用来管理项目)
2.注册中心Eureka
Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,Spring Cloud Eureka提供服务端与客户端,服务端即是Eureka服务注册中心,客户端完成微服务向Eureka服务的注册与发现。服务端和客户端均采用Java语言编写。
服务器将自己的信息(IP、端口号、服务类) 提供给注册中心,然后每个服务器将所有存在注册中心的注册信息保存一份在自己服务器中,从而实现服务器之间的交流
3. 分布式项目搭建
在一个分布式项目中,通常会采用父项目和子项目的方式来进行模块化开发和管理。父项目用于管理整个项目的共享依赖和配置,而子项目则代表着项目的不同模块或子系统。
3.1 创建父项目
可以删除父项目中的src,父项目不需要写代码
在父项目pom.xml中指定打包方式为pom
<packaging>pom</packaging>
3.2 创建子项目
子项目pom.xml中指定父项目
<parent>
<artifactId>springcloud-teach</artifactId>
<groupId>com.luobei</groupId>
<version>1.0</version>
</parent>
父项目pom.xml中指定子项目
<modules>
<module>eureka-server</module>
</modules>
配置yml配置文件
XML
server:
port: 8081
spring:
application:
name: goods #微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #向这个接口注册
instance:
instance-id: goods-8081 #指定当前微服务在注册中心中的id
主启动类上开启 @EnableEurekaClient
java
@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
3.2.1 创建注册中心
注册中心其实就是一个特殊的子项目,除yml配置不同,其他与别的子项目一致
XML
server:
port: 8761
spring:
application:
name: eureka-server #微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #是否将当前服务器注册到注册中心
fetch-registry: false #是否将注册列表的信息拉取到本地
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}
#注册中心的接口,其他微服务器通过该接口进行注册
主启动类上开启@EnableEurekaServer
java
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
4.微服务调接口
在微服务架构中,服务之间的通信是通过调用接口来实现的。这些接口可以是 RESTful API、gRPC、消息队列等方式实现的。
4.1 RestTemplate(仅了解)
属于SpringBoot
缺点:put、delete请求得不到返回的结果,使用、维护不方便
4.2 openfeign
OpenFeign 是一个基于 Java 的声明式 HTTP 客户端,通常用于微服务架构中不同服务之间的接口调用。它允许开发者通过注解的方式定义接口,然后由 OpenFeign 自动生成具体的实现。这样可以简化服务之间的通信,让接口调用看起来更像是本地方法调用。
4.2.1 openfeign使用
4.2.1.1 导入依赖
父项目:
XML
<dependencyManagement>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
公共模块:
XML
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
子项目:
XML
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
4.2.1.2 编写接口
若接口将会被多个微服务调用则写在commons中
java
@FeignClient(name="GOODS",path="/goods")
public interface FeignGoodsService {
@PutMapping("/updateStock")
ResponseResult<List<Goods>> del(@RequestBody List<OrderGoodsVo> orderGoodsVoList);
}
@FeignClient(name = "GOODS",path = "/goods") 中name为被调用微服务的名字,path为路径前缀,将要调用的接口方法的声明、请求方式全部拷贝过来
4.2.1.3 使用接口
在需要使用接口的微服务的主启动类上扫描feign接口
@EnableFeignClients(basePackages = "com.luobei.commons.service")
然后在需要使用的地方注入就行了
java
@Resource
private FeignGoodsService feignGoodsService;
@Override
public OrderVo add(Integer uid, AddOrderVo orderVo) throws OutOffStockException {
ResponseResult<List<Goods>> del = feignGoodsService.del(Arrays.asList(orderGoodsVo));
return del;
}
4.2.2 openfeign通信日志
openfeign提供了日志打印功能,通过配置日志级别,对接口的调用情况进行监控和输出
日志级别 | 解释 |
---|---|
NONE | 默认的,不显示任何日志 |
BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
HEADERS | 除了 BASIC 中定义的信息之外,还有请求和响应的头信息 |
FULL | 除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据 |
在调用别的服务器的服务器配置配置类
java
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LogConfiguration {
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
因为feign日志的输出级别都是debug级别,还需要设置service包的日志级别
logging:
level:
com.commons.service: debug
5. Ribbon负载均衡
Ribbon是一个基于HTTP和TCP客户端的负载均衡器,默认使用轮询的方式
SpringCloud Ribbon是基于Netfix Ribbon实现的一套客户端负载均衡工具
5.1 Ribbon配置
5.1 .1 全局配置
在需要调取多个服务器的服务器中配置配置类
@Configuration
public class RibbonConfiguration {
@Bean
public IRule rule(){
return new RandomRule();
}
}
5.1.2 局部配置
注意:全局与局部同时存在时,ribbon优先使用全局配置
GOODS: #微服务名字
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
5.2 Ribbon超时管理
Ribbon默认请求超时时间为1000毫秒,有的时候不够用,因此需要重新配置
ribbon:
http:
client:
enabled: true #开启超时管理
ReadTimeout: 10000 #请求超时
ConnectTimeout: 10000 #连接超时
6.网关(Gataway)
网关是在分布式系统架构中的一种设计模式,用于集中处理和管理请求;微服务架构中常用网关技术有:Zuul、Spring Cloud Gataway等
6.1 网关的作用
1.路由和负载均衡:网关可以根据请求的路径或其他条件将请求路由到不同的后端微服务
2.鉴权和认证:网关可以进行用户身份验证、授权以及访问权限的校验
3.监控和日志:网关可以记录请求和响应的日志,从而监控系统的运行状况、性能和问题
6.2 Zuul
Zuul 是 Netflix 开源的一个微服务架构中的边缘服务(Edge Service),主要用于实现动态路由、负载均衡、鉴权、监控等功能。(性能不如Gataway)
6.2.1 zuul使用
6.2.1.1 创建zuul微服务
连接父项目。。
导入zuul依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
6.2.1.2 配置eureka、路由
XML
server:
port: 8500
spring:
application:
name: zuul #微服务名字,很重要,微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #向这个接口注册
instance:
instance-id: zuul-8500 #指定当前微服务在注册中心中的id
zuul:
routes:
goods: #路由名,用户自定义
service-id: GOODS #调用的微服务名
path: /goods/** #匹配的路径
order:
service-id: ORDER
path: /order/**
# GOODS: /goods/**
# ORDER: /order/**
注意:路由有两种配置方式,都可
6.2.1.3 zuul主启动类添加注解
java
@SpringBootApplication
@EnableEurekaClient //注册
@EnableZuulProxy //开启路由功能
@EnableHystrix //解决504报错问题
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
注意:使用zuul时请求地址应多一个请求的微服务名(http://localhost:8500/goods/goods/all)
6.2.2 zuul过滤器实现网关限流
zuul过滤器 | |
---|---|
PRE | 请求到达zuul之前执行,可以用来鉴权、判断登录、限流 |
ROUTING | 路由时(将请求转发给对应的微服务)执行 |
POST | 路由完毕时(微服务返回结果)执行 |
ERROR | 在pre、routing、post出现异常时执行 |
自定义过滤器使用令牌桶算法实现网关限流
java
@Slf4j
@Component
public class LimitFilter extends ZuulFilter {
//创建令牌桶,并设置令牌个数
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1);
//指定当前过滤器类型
@Override
public String filterType() {
return "pre";
}
//执行顺序,可以为负数,数字越小越先执行
@Override
public int filterOrder() {
return Integer.MIN_VALUE;
}
//是否要对当前请求进行过滤
@Override
public boolean shouldFilter() {
//得到请求的uri,判断是否需要登录,如果不需要返回false(放行),否则返回true(执行run()方法)
//得到上下文对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
log.info(request.getRequestURI());
return true;
}
//执行过滤业务的代码:例如校验refreshtoken、token
@Override
public Object run() throws ZuulException {
//获取令牌
if(!RATE_LIMITER.tryAcquire()){
//令牌用完,至少有100个请求正在处理
log.info("令牌不足");
//结束本次请求
RequestContext context = RequestContext.getCurrentContext();
context.setSendZuulResponse(false);
HttpServletResponse response = context.getResponse();
try {
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("系统正忙");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
6.2.2.1 漏桶算法
简单来说,把我们的分布式系统理解成一个上方注水下方漏水的桶,固定下方漏水的速率,这样就能限制请求的速度,当水超过桶流量则丢弃
6.2.2.2 令牌桶算法
每个请求向从桶里获取一个令牌,若令牌没有了则拒绝或等待请求处理完成的返回令牌;
6.3 Gataway
SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,因此Gataway的性能比较高效
注意:gataway基于webflux,因此与spring web 不兼容
6.3.1 使用Gataway
创建Gataway子项目,连接父项目
主启动类开启@EnableEurekaClient
6.3.1.1 配置配置类
XML
server:
port: 8600
spring:
application:
name: gataway #微服务名字,很重要,微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
cloud:
gateway:
routes:
- id: goods #路由名(任意)
uri: lb://GOODS #要调用的微服务 lb:负载均衡
predicates: #匹配的路径
- Path=/goods/**
- id: order
uri: lb://ORDER #要调用的微服务 lb:负载均衡
predicates: #匹配的路径
- Path=/order/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #向这个接口注册
instance:
instance-id: gataway-8600 #指定当前微服务在注册中心中的id
6.3.1.2 配置过滤器
java
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("gateway过滤器");
//获取uri
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
log.info(path.toString());
if(requireLogin(path)){
//需要登录
List<String> authorization = request.getHeaders().get("authorization");
if(authorization!=null){
String token = authorization.get(0);
log.info(token);
return chain.filter(exchange);
}else{
//没登陆
ServerHttpResponse response = exchange.getResponse();
ResponseResult<Boolean> responseResult = new ResponseResult<>(ResponseResult.NO_LOGIN,"请登录",false);
try {
//转换成json字符串
String json = new ObjectMapper().writeValueAsString(responseResult);
//转byte数组
byte[] data = json.getBytes(StandardCharsets.UTF_8);
//将数组封装到buffer
DataBuffer buffer = response.bufferFactory().wrap(data);
//设置响应头
response.getHeaders().add("Content-Type","application/json;charset=utf-8");
//返回数据,并终止
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
//放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
private boolean requireLogin(String uri){
//将不需要登录就能操作的uri罗列出来
String[] uris = {"/user/login","/user/register","/kill/info","/nginx/port"};
String[] staticResource = {"/goods/info/","/goods/find/","/images/"};
for (String s : uris) {
if(uri.equals(s) ){
return false;
}
}
for (String s : staticResource) {
if(uri.startsWith(s)){
return false;
}
}
return true;
}
}