SpringCloud复习

1.认识微服务

1.1单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署

优点:

  • 架构简单
  • 部署成本低

缺点:

  • 团队协作成本高
  • 系统发布效率低
  • 系统可用性差

总结:单体架构适合开发功能相对简单,规模较小的项目

1.2.微服务

微服务架构,是服务化思想指导下的一套最佳实践架构方案。服务化,就是把单体架构中的功能模块拆分为多个独立项目。

  • 粒度小
  • 团队自治
  • 服务自治

1.3.SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud.SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

SpringCloud基于SpringBoot实现了微服务组件的自动装配,从而提供了良好的开箱即用体验。但对于SpringBoot的版本也有要求:

2.微服务拆分

2.1微服务拆分原则

  • 创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。
  • 确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。

从拆分目标来说,要做到:

  • 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
  • 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。

从拆分方式来说,一般包含两种方式:

  • 纵向拆分:按照业务模块来拆分
  • 横向拆分:抽取公共服务,提高复用性

工程结构有两种:

  • 独立Project
  • Mvaen聚合

2.2远程调用

3.服务治理

服务远程调用存在的问题:

如果搭建那集群那么远程调用也就只能请求一个,如果请求的那个服务挂掉之后,相对应的整个个服务链路都将挂掉。

3.1注册中心原理

服务治理中的三个角色分别是什么?

  • 服务提供者:暴露服务接口,供其它服务调用
  • 服务消费者:调用其它服务提供的接口
  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息消费者

如何知道提供者的地址?

  • 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息

消费者如何得知服务状态变更?

  • 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者

当提供者有多个实例时,消费者该选择哪一个?

  • 消费者可以通过负载均衡算法,从多个实例中选择一个

3.2.Nacos注册中心

3.3.服务注册

引入Nacos依赖

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置Nacos地址

spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

3.4.服务发现和负载均衡

4.OpenFeign

4.1.快速入门

  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>

注:在以前负载均衡器用的都是Ribbon现在新版本用的是loadbalancer

4.2.连接池

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

feign:
  okhttp:
    enabled: true # 开启OKHttp功能

4.3.最佳实践

为了降低代码的耦合度我们推出了最佳实践的方法:

最佳实践一(代码耦合度增加了)

最佳实践二(比较清晰简单,但是代码存在耦合度)

既然有两种方案那我们应该用哪一个呢:

1.最早的时候前面说过微服务有两种项目结构,一种是创建一个文件夹,在文件夹中每一个微服务都是一个独立的project,既然每个微服务都是独立的project,那么在这个project下拆分为n个模块放client,业务代码和数据模型是比较合理的,所以可以采用方案一

2.第二种项目结构就是maven聚合的结构,本来就是就是聚合的那么再增加一个api模块也没有什么关系所以可以采用方案二

4.4.日志

5.网关

5.1.什么是网关

网关:就是网络的关口,负责请求的路由,转发,身份校验

在SpringCloud中网关的实现有两种:

6.网关路由

6.1.快速入门

6.2.路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标示
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由。
  • filters:路由过滤器,对请求或响应做特殊处理。

6.3.路由断言

6.4.路由过滤器

1.过滤器对单个路由生效

2.过滤器对所有路由生效

7.网关登录校验

7.1思路分析

7.2.网关请求处理流程

7.3.自定义GlobalFilter

网关过滤器有两种,分别是:

  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。

自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可:

7.4.自定义GateFilter

自定义GatewayFilter不是直接实现Gatewayfilter,而是实现AbstractGatewayfilterFactory,示例如下:

无参

有参

7.5.实现登录校验代码

package com.hmall.gateway.filters;

import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties; //自定义的
    private final JwtTool jwtTool; //自定义的
    private final AntPathMatcher antPathMatcher; //spring提供的
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取路径
        ServerHttpRequest request = exchange.getRequest();

        //判断是否存在需要放行的路径
        if (isExclude(request.getPath().toString())){
            //放行
           return chain.filter(exchange);
        }
        //获取token
        String token =null;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers!=null&&!headers.isEmpty()){
            token=headers.get(0);
        }
        //解析token
        Long userId=null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //TODO 传递用户信息
        System.out.println(userId);
        return chain.filter(exchange);
    }

    private boolean isExclude(String path) {
        List<String> excludePaths = authProperties.getExcludePaths();
        for (String pattern : excludePaths) {
            if (antPathMatcher.match(pattern,path)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

7.6.网关传递用户信息到微服务

将用户信息传到各个微服务中我们可以利用SpringMVC提供的拦截器来完成,但是这样的话我们就需要给每个微服务都设置一个拦截器,这样代码的重复性太高了,由于我们每个微服务都引用了一个通用的模块common,所以我们在common模块中用拦截器

1.由于我们是从网关中拿到的用户信息所以,首先通过网关来传递用户的信息

package com.hmall.gateway.filters;

import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties; //自定义的
    private final JwtTool jwtTool; //自定义的
    private final AntPathMatcher antPathMatcher; //spring提供的
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取路径
        ServerHttpRequest request = exchange.getRequest();

        //判断是否存在需要放行的路径
        if (isExclude(request.getPath().toString())){
            //放行
           return chain.filter(exchange);
        }
        //获取token
        String token =null;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers!=null&&!headers.isEmpty()){
            token=headers.get(0);
        }
        //解析token
        Long userId=null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
========================================================================================
        // 传递用户信息
        String userInfo = userId.toString();
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header("user-info", userInfo))
                .build();
        return chain.filter(swe);
    }
========================================================================================
    private boolean isExclude(String path) {
        List<String> excludePaths = authProperties.getExcludePaths();
        for (String pattern : excludePaths) {
            if (antPathMatcher.match(pattern,path)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

2.接着在通用的模块中使用拦截器通过请求头来获取网关传递过来的用户信息,并存入ThreadLocal

package com.hmall.common.interceptot;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        //判断是否有用户信息,如果有则放到ThreadLocal中
        if (StrUtil.isNotBlank(userInfo)){
            UserContext.setUser(Long.valueOf(userInfo));
        }
        //放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清理用户信息
        UserContext.removeUser();
    }
}

3.最后注册拦截器(重点:因为每个微服务的包都不同,所以主启动类根本扫不到注册拦截器的类。我们需要通过SpringBoot的核心功能“自动装配”把注册拦截器类的bean注册到容器中,由于注册拦截器中的类实现了SpringMVC提供的接口,我们的网关服务也引用的这个通用的模块,可是我们的网关服务没用引用SpringMVC的依赖,所以启动时会报错。我们可以利用conditon注解及其衍生注解,让注册拦截器的类不要在网关中生效)

package com.hmall.common.config;

import com.hmall.common.interceptot.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig,\
  com.hmall.common.config.JsonConfig

7.7.OpenFeign传递用户

微服务项目中的很多业务要多个微服务共同合作完成,而这个过程中也需要传递登录用户信息,例如:

想要从我们前面定义的UserContext(threadLocal)中取出用户信息,前提是要将用户信息set到UserContext里面,set进去的前提是得从请求头中取出来,如果购物车服务当前的请求是从网关发过来的,所以可以拿到请求头并存储到UserContext里面,但是当前购物车服务是被交易服务调用的,是交易服务发过来的,这显然是拿不到请求头的。要想解决这个问题请求在进入购物车服务时一定要在请求头中携带用户信息,这样才能存储到UserContext里面。

由于服务之间是由openFeign发起的远程调用,所以我们可以利用openFeign提供的工具

openFeian中提供拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求:

这个拦截器会在openFeign发起每一次请求时都生效

package com.hmall.api.config;

import com.hmall.common.utils.UserContext;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {

    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                Long userId = UserContext.getUser();
                if (userId!=null){
                    template.header("user-info",userId.toString());
                }
            }
        };
    }
}

为什么我们这里可以尝试从UserContext中取呢,因为请求进来以后先进交易服务,交易服务是从网关调用的,这个时候请求头中是带了用户信息的,所以交易服务是可以拿到用户信息并保存到UserContext中的,交易服务向购物车发请求的时候openFeign的拦截器就会生效,所以这里的openFeign的拦截器是可以取到交易服务当前登录用户的,然后保存到请求头,请求头再转发到购物车服务的时候自然就会带上user-info,这时购物车拦截器(这里的拦截器不是openFeign的拦截器)生效的时候就可以取出来了。

生效前提:要加在传递用户信息服务的启动类上,也就是我们的交易服务,原因可以看上述的4.4

package com.hmall.trade;

import com.hmall.api.config.DefaultFeignConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients(basePackages = "com.hmall.api.clients",defaultConfiguration = DefaultFeignConfig.class)
@MapperScan("com.hmall.trade.mapper")
@SpringBootApplication
public class TradeApplication {
    public static void main(String[] args) {
        SpringApplication.run(TradeApplication.class, args);
    }
}

7.8.微服务登录方案总结

8.配置管理

8.1什么是配置管理

8.2共享配置

8.2.1.例子

8.3.配置热更新

配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。
前提条件:

案例:购物车的限定数量目前是写死在业务中的,将其改为读取配置文件属性,并将配置交给Nacos管理,实现热更新。

解决:

package com.hmall.cart.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
    private Integer maxItems;
}
   private void checkCartsFull(Long userId) {
        int count = lambdaQuery().eq(Cart::getUserId, userId).count();
        if (count >= cartProperties.getMaxItems()) {
            throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));
        }
    }

8.4.动态路由

要实现动态路由首先要将路由配置保存到Nac0s,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。

我们需要完成两件事情:

  1. 监听Nacos配置变更的消息
  2. 当配置变更时,将最新的路由信息更新到网关路由表
8.4.1.监听Nacos配置

8.4.2.更新路由表

8.4.3.路由配置语法

8.4.4.例子
package com.hmall.gateway.config;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
    private final NacosConfigManager nacosConfigManager;
    private final String dataId="gateway-routes.json";
    private final String group="DEFAULT_GROUP";
    private final RouteDefinitionWriter writer;
    private final Set<String>routeIds=new HashSet<>();

    @PostConstruct //该注解让初始化完成后调用initRouteConfigListener这个方法
    public void initRouteConfigListener() throws NacosException {
        //项目启动时,先拉取一次配置,并添加配置监听器
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //监听器配置变更,需要更新路由表
                        updateConfigInfo(configInfo);
                    }
                });
        //第一次读取要配置也需要更新到路由表
        updateConfigInfo(configInfo);
    }
    public void updateConfigInfo(String configInfo){
        log.debug("监听到路由配置信息:{}",configInfo);
        //1.解析配置信息,转为RouteDefinition
        List<RouteDefinition>routeDefinitions= JSONUtil.toList(configInfo, RouteDefinition.class);
        //2.删除旧的路由表
        for (String routeId : routeIds) {
            writer.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();

        //更新路由表
        for (RouteDefinition routeDefinition : routeDefinitions) {
            //更新路由表
            writer.save(Mono.just(routeDefinition)).subscribe();
            //记录路由id,便于下一次更新删除
            routeIds.add(routeDefinition.getId());
        }
    }
}

[
    {
        "id": "item",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
        "id": "cart",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    },
    {
        "id": "user",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
        }],
        "filters": [],
        "uri": "lb://user-service"
    },
    {
        "id": "trade",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/orders/**"}
        }],
        "filters": [],
        "uri": "lb://trade-service"
    },
    {
        "id": "pay",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/pay-orders/**"}
        }],
        "filters": [],
        "uri": "lb://pay-service"
    }
]

9.微服务保护和分布式事务

9.1.雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩

雪崩问题产生的原因是什么?

  • 微服务相互调用,服务提供者出现故障或阻塞。
  • 服务调用者没有做好异常处理,导致自身故障,
  • 调用链中的所有服务级联失败,导致整个集群故障

解决问题的思路有哪些?

  • 尽量避免服务出现故障或阻塞。
  1.         保证代码的健壮性;
  2.         保证网络畅通;
  3.         能应对较高的并发请求
  • 服务调用者做好远程调用异常的后备方案,避免故障扩散
9.1.1.服务保护方案-请求限流

请求限流:限制访问微服务的请求的并发量,避免服务因流量激增出现故障。

9.1.2.服务保护方案-线程隔离

线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。

9.1.3.服务保护方案-服务熔断

服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。

熔断期间,所有请求快速失败,全都走fallback逻辑。

9.1.4服务保护技术
9.1.5.总结

解决雪崩问题的常见方案有哪些?

  • 请求限流:限制流量在服务可以处理的范围,避免因突发流量而故障
  • 线程隔离:控制业务可用的线程数量,将故障隔离在一定范围
  • 服务熔断:将异常比例过高的接口断开,拒绝所有请求,直接走fallback
  • 失败处理:定义fallback逻辑,让业务失败时不再抛出异常,而是返回默认数据或友好提示

9.2.Sentinel-快速入门

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https://sentinelquard.io/zh-cn/index.html

使用sentinel需要先下载sentinel的java包,然后在cmd窗口使用该命令:java '-Dserver.port=8090' '-Dcsp.sentinel.dashboard.server=localhost:8090' '-Dproject.name=sentinel-dashboard' '-jar' sentinel-dashboard.jar 

最后访问8090端口如果看到sentinel页面说明成功了

需要输入账号和密码,默认都是:sentinel

登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:

1)引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2)配置控制台

修改application.yaml文件,添加下面内容:

spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090
9.2.1.簇点链路

簇点链路,就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel会监控SpringMVC的每一个Endpoint(http接口)。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接口的请求路径:

为簇点资源名称:
Restful风格的API请求路径一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称

9.3.Sentinel-请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置:

通过压测得出

9.4.Sentinel-线程隔离

当商品服务出现阻塞或故障时,调用商品服务的购物车服务可能因此而被拖慢,甚至资源耗尽。所以必须限制购物车服务中查询商品这个有任务的可用线程数实现线程隔离


在sentinel控制台中,会出现Feign接口的簇点资源,点击后面的流控按钮,即可配置线程隔离:

9.5.Sentinel-Fallback

由于购物车在查询时要查询商品的最新信息,所以购物车服务需要去远程调用商品服务,但由于商品微服务响应很慢,所以在并发较高的情况下就会导致资源耗尽导致拖慢了购物车服务,因此对商品服务做了线程隔离,虽然对于购物车的增删功能没用影响,但是查询购物车的业务完全不可用了。所以我们利用降级逻辑fallback来完成

案例:给FeignClient编写fallback逻辑

假设我们有一个feignClient如下:

为其编写fallback逻辑

feign:
  sentinel:
    enabled: true

9.6.Sentinel-服务熔断

熔断是解决雪崩问题的重要手段。思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

我们可以在控制台通过点击簇点后的熔断按钮来配置熔断策略

在弹出的表格中这样填写:

这种是按照慢调用比例来做熔断,上述配置的含义是:

  • RT超过200毫秒的请求调用就是慢调用

  • 统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断

  • 熔断持续时长20s ,在这20s内所有远程调用都走fallback

9.7.分布式事务-什么是分布式事务

在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务

9.8.分布式事务-seata的架构和原理

Seata是 2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

分布式事务解决思路

解决分布式事务,各个子事务之间必须能感知到彼此的事务状态,才能保证状态一致。

Seata事务管理中有三个重要的角色:

  • TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
  • TM(Transaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
  • RM(Resource Manager)-资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态

9.9.分布式事务-部署TC服务

1.准备数据库表

Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。(可以自行搜索)

2.准备配置文件(可以自行搜索)

其中包含中文注释,大家可以自行阅读。

3.docker部署

需要注意,要确保nacos、mysql都在同一个网络中。如果某个容器不在同一个网络可以参考下面的命令将某容器加入指定网络:

docker network connect [网络名] [容器名]

在虚拟机的/root目录执行下面的命令(要记得提前下载好)

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=自己的虚拟机ip \
-v ./seata:/seata-server/resources \
--privileged=true \
--network 自己设置的网络 \
-d \
seataio/seata-server:1.5.2(不一定是1.5.2,填自己下载的seata镜像的版本)

9.10.分布式事务-微服务集成seata

首先要在项目中引入seata依赖

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

9.11.分布式事务-XA模式

XA规范是X/0pen 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范 提供了支持。Seata的XA模式如下:

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

9.12.分布式事务-AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事务,锁定资源:AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

实现AT模式

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值