栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springCloud 初探(在更)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springCloud 初探(在更)

分布式系统理论

分布式系统是若干个独立计算机的集合,这些计算机的集合,这些计算机对于用户来说就像单个相关系统。分布式系统是由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的,普通的机器完成单个计算机无法完成的计算,存储任务。其目的是:利用更多的机器,处理更多的数据。

RPC

定义: RPC是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范。它允许程序调用另一个地址空间的过程或函数,而不是程序显式编码这个远程调用的细节。即程序员无论是调用本地还是远程的函数,本质上编写的调用代码基本相同。

RPC原理:

 

RPC的核心:通讯, 序列化(方便数据传输)。

序列化:数据传输需要转换。

解决这些核心问题我们可以使用Doubb。

springCloud技术概况

 springCloud技术分布图:

springCloud升级:

springBoot和springCloud版本兼容查询

https://spring.io/projects/spring-cloud#learn

springCloud 对应依赖:


    org.springframework.cloud
    spring-cloud-dependencies
    2021.0.3
    pom
    import

Eureka

 作用: Eureka能够自动注册并发现微服务,然后对服务的状态,信息进行集中管理,这样我们需要获取其他服务的信息时,我们只需要向Eureka进行查询就可以了。

这样服务之间的强广联性就会被进一步减弱。

 对应依赖:


            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-server
            3.1.3

配置application.yml文件

eureka:
  client:
    #由于我们是作为服务端角色,所以不需要获取服务端,改为false, 默认为true
    fetch-registry: false
    #暂时不需要将自己注册到eureka
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:8888/eureka

且要在启动类中添加注解: @EnableEurekaServer 

效果图:

 接下来将各个服务作为客户端。

作为客户端的对应依赖:


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client
    3.1.3

配置各个服务的application.yml,让将服务地址指向eureka服务的地址,这样才能实现注册。 

客户端application.yml配置:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka

spring:
  application:
    name: ”对应名字“

效果图:

当我们的服务启动之后,每隔一段时间eureka会发送一次心跳包,这样eureka就能检测到我们的服务是否还在正常运行。

通过eureka来调用服务 

例子一:

旧代码

public UserBook queryByUid(Integer uid) {
        List dbBorrows = dbBorrowDao.queryByUid(uid);
        RestTemplate template = new RestTemplate();
        DbUser user = template.getForObject("http://localhost:8083/dbUser/" + uid, DbUser.class);
        UserBook userBook = new UserBook();
        userBook.setUser(user);
        LinkedList books = new LinkedList<>();
        for(int i = 0; i < dbBorrows.size(); i++){
            DbBook book = template.getForObject("http://localhost:8081/dbBook/" + dbBorrows.get(i).getBid(), DbBook.class);
            books.add(i, book);
        }
        userBook.setBook(books);
        return userBook;
    }

这里其实就是通过询问eureka对应的服务名来获取对应的ip地址。 

 负载均衡的实现

    同一个服务器实际上可以注册很多个的, 但它们的端口是不同的,比如我们创建多个用户查询服务,将原有的端口进行修改,由idea中设置启动参数来决定,这样就可以创建几个同端口的相同服务了。 

效果图中说明在用户服务处有多个相同的服务。 

 当我们要使用用户服务时,如果有第一个用户服务down掉的话,就会有另一个用户服务来执行对应的操作,防止整哥微服务不可用,大大提高了安全性。

当存在多个相同服务的时候就会通过对应的负载均衡的策略使每个服务都被调用起来,从而实现负载均衡。

新的实现代码

1.在condig包中创建一个BeanConfiguration.java。

@Configuration
public class BeanConfiguration {
    @Bean
    //负载均衡
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

2.在对应的service层中的使用@Autowired进行自动注入使用,将原来的ip地址改为在eureka中对应的服务名字。

@Resource
private RestTemplate template;

public UserBook queryByUid(Integer uid) {
        List dbBorrows = dbBorrowDao.queryByUid(uid);
//将原来的ip地址改为在eureka中对应的服务名字
        DbUser user = template.getForObject("http://user-service/dbUser/" + uid, DbUser.class);
        UserBook userBook = new UserBook();
        userBook.setUser(user);
        LinkedList books = new LinkedList<>();
        for(int i = 0; i < dbBorrows.size(); i++){
            DbBook book = template.getForObject("http://book-service/dbBook/" + dbBorrows.get(i).getBid(), DbBook.class);
            books.add(i, book);
        }
        userBook.setBook(books);
        return userBook;
    }

注册中心高可用

 为了防止eureka down掉,我们可以搭建eureka集群。

效果图:

 搭建eureka集群步骤:

1.修改两个eureka服务端的配置文件。

applicationn01.yml

server:
  port: 9999
eureka:
  instance:
    #由于不支持多个localhost的eureka的服务器,但是又只能在本地测试,所有就只能自定义主机名称了
    hostname: eureka01
  client:
    #不需要获取服务端
    fetch-registry: false
    #去掉register-with-eureka选项,让eureka服务器自己注册到其他的eureka服务器,这样才能相互启用
    service-url:
      #注意这里要填写其他的eureka服务器地址,不用写自己的
      defaultZone: http://eureka02:9999/ereka

application02.yml

server:
  port: 9999
eureka:
  instance:
    #由于不支持多个localhost的eureka的服务器,但是又只能在本地测试,所有就只能自定义主机名称了
    hostname: eureka02
  client:
    #不需要获取服务端
    fetch-registry: false
    #去掉register-with-eureka选项,让eureka服务器自己注册到其他的eureka服务器,这样才能相互启用
    service-url:
      #注意这里要填写其他的eureka服务器地址,不用写自己的
      defaultZone: http://eureka01:8888/ereka

2.启动eureka集群

在因为是本地测试所以我们要修改本地的hosts。

eureka01: 

eureka02: 

3.把所有的微服务在eureka集群中都注册一次

service-url:
      defaultZone: http://localhost:8888/eureka, http://localhost:9999/eureka

 在一个eureka down掉的时候,另一个eureka还会继续工作,这时我们就可以对应down 掉的eureka进行维修,这样就实现了高可用。

OpenFeign

    OpenFeign和RestTemplate一样,也是一种HTTP客户端请求工具,但它使用起来更加便捷。

对应依赖:


    org.springframework.cloud
    spring-cloud-starter-openfeign
    3.1.3

在对应的启动类上加上 @EnableFeignClients 

配对应的FeignClient服务接口 。(可以单独创建一个包来存放这些服务接口)

服务接口格式:

@FeignClient("在eureka中配置的服务名字")
public interface BookClient {
//该接口里面的方法为你要调用的Controller中的方法,这些方法的路径要写全
    @GetMapping("/dbBook/{id}")
    DbBook queryById(@PathVariable("id") Integer id);
}

 要使用此接口时可以通过@Resource进行注入。(类似Dao层的mybatis,其通过@FeignClient将接口注入到spring中)

 调用此接口的Service层中的代码就要修改为下:

public UserBook queryByUid(Integer uid) {
        List dbBorrows = dbBorrowDao.queryByUid(uid);
//        DbUser user = template.getForObject("http://user-service/dbUser/" + uid, DbUser.class);
        DbUser user = userClient.queryById(uid);
        UserBook userBook = new UserBook();
        userBook.setUser(user);
        LinkedList books = new LinkedList<>();
        for(int i = 0; i < dbBorrows.size(); i++){
//            DbBook book = template.getForObject("http://book-service/dbBook/" + dbBorrows.get(i).getBid(), DbBook.class);
            DbBook book = bookClient.queryById(dbBorrows.get(i).getBid());
            books.add(i, book);
        }
        userBook.setBook(books);
        return userBook;
    }

OpenFeign服务降级

为对应的client接口创建实现类。

@Component //注入到spring中,使得BookClient能调用到此实现类
public class BookFallBackClient implements BookClient{
    @Override
    public DbBook queryById(Integer id) {
        return new DbBook();
    }
}
@Component  注入到spring中,使得UserClient能调用到此实现类
public class UserFallBackClient implements UserClient{
    @Override
    public DbUser queryById(Integer id) {
        return new DbUser();
    }
}

 将此实现类添加到client的备选方案中。

 在application.xml配置熔断支持

feign:
  circuitbreaker:
    enabled: true

 效果图:

Hystrix  

  Hystrix服务熔断

    微服务之间是可以相互调用的。 

由于位于最底端的服务提供者发生了故障,那么此时会直接导致ABCD全线崩溃,就像雪崩一样。

这种情况实际上是不可避免的,由于太多的因素,比如网络故障,系统故障,硬件问题,会导致这种极端的情况发生,因此我们需要找到对应的方法来解决次问题。

为了解决分布式系统的雪崩问题,springCloud提供了Hystrix熔断组件,它就像我们家中的保险丝一样,当电流过载的时候直接熔断掉,防止危险的进一步发生,从而保障家庭用电的安全,可以想象一下,如果整条链路上的服务已经全线崩溃,这时还在不断地有大量的请求到达,想要各个服务进行处理,肯定是会使得情况越来越糟。

熔断机制是应对雪崩效应的一种微服务链路保护机制,当检测到链路的某个微服务不可用或者响应的时间太长时,会进行服务降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息,当检测到该节点微服务响应正常后恢复调用链路。

实际上,熔断就是在降级的基础上进一步形成的,也就是说,在一段时间内多次调用失败,那么就直接升级为熔断。

 当需要的服务正常启动后,熔断机制就会关闭了。

Hystrix服务降级

    服务降级并不会直接返回错误信息,而是可以提供一个补救的措施,正常响应给请求者,这样相当于服务依然可用,但是服务能力肯定是下降的。 

对应依赖:


            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
            2.2.10.RELEASE

在启动类上添加注解: @EnableHystrix。

 

 在对应的Controller层中编写编写备选方案。

//备选方案,返回一个空的对象
    public ResponseEntity onError(Integer uid) {
        UserBook userBook = new UserBook();
        userBook.setUser(null);
        userBook.setBook(Collections.emptyList());
        return ResponseEntity.ok(userBook);
    }

在对的方法上添加注解:@HystrixCommand(fallbackMethod = "备选方案名")

 效果图: 

Gateway路由网关 

可能并不是所有的微服务都需要直接暴露给外部调用,这时我们就可以使用路由机制,添加一层防护,让所有的请求全部通过路由来转发到各个微服务,并且转发给多个相同微服务实例也可以实现负载均衡。

​​​​​​​

 部署网关

对应依赖为下:

        
            org.springframework.cloud
            spring-cloud-starter-gateway
            3.1.2
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
            3.1.3
        

第一个是网关的依赖,第二个是跟其他微服务一样,需要注册到eureka中才能生效,不要添加web依赖,使用的是WebFlux框架。

编写配置文件

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka, http://localhost:9999/eureka
spring:
  application:
    name: gateway
server:
  port: 8500

 继续在配置文件中配置路由功能。

spring:
  cloud:
    gateway:
      routes:
      - id: borrowService #路由的名字
        uri: lb://borrow-service #路由的地址,lb表示使用负载均衡到微服务,也可以使用Http正常转发
        predicates: #路由规则 断言什么请求会被路由
        - Path=/dbBorrow/queryUserBook/** #只要访问这个路径,一律都被路由到上面指定的服务

 在输入路径后,如果路径符合断言,就会将uri和Path进行拼接。

路由过滤器

路由过滤器支持以某中方式修改传入的HTTP请求或传出的HTTP响应,路由过滤器的范围是某个过滤器,跟之前的断言一样

修改对应的配置文件。

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
      - id: borrowService #路由的名字
        uri: lb://borrow-service #路由的地址,lb表示使用负载均衡到微服务,也可以使用Http正常转发
        predicates: #路由规则 断言什么请求会被路由
        - Path=/dbBorrow/queryUserBook/** #只要访问这个路径,一律都被路由到上面指定的服务
      - id: bookService
        uri: lb://book-service
        predicates:
        - Path=/dbBook/**
        filters: #添加过滤器
        - AddRequestHeader=Test, HELLO WORLD!

 在对应的Controller层中测试是否过滤成功。

效果图:
 

设置全局过滤器

 例子:拦截没有携带指定参数的请求。

在gateway项目中创建一个实现类。

@Component
public class TestFilter implements GlobalFilter {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        exchange表示请求过来的信息
        //先获取ServerHttpRequest对象,注意不是HttpServletRequest
        ServerHttpRequest request = exchange.getRequest();
        //打印请求携带的所有参数
        System.out.println(request.getQueryParams());
        //判断是否包含test参数,且参数值是否为1
        List test = request.getQueryParams().get("test");
        if(test != null && test.contains("1")){
            //将ServerHttpExchange向过滤链的下一级传递,类似javaWeb中的过滤器
            return chain.filter(exchange);
        } else {
            //直接在这里不再向下传递,然后返回响应
            return exchange.getResponse().setComplete();
        }
    }
}

次过滤器会作用于整个网关。

效果图:

只要路径中没有携带test=1就会被拦截。

当然过滤器会存在很多个的,所以我们手动指定过滤器之间的顺序。可以通过实现Ordered接口,重写getOredr方法来设置执行的顺序。

@Component
public class TestFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        exchange表示请求过来的信息
        //先获取ServerHttpRequest对象,注意不是HttpServletRequest
        ServerHttpRequest request = exchange.getRequest();
        //打印请求携带的所有参数
        System.out.println(request.getQueryParams());
        //判断是否包含test参数,且参数值是否为1
        List test = request.getQueryParams().get("test");
        if(test != null && test.contains("1")){
            //将ServerHttpExchange向过滤链的下一级传递,类似javaWeb中的过滤器
            return chain.filter(exchange);
        } else {
            //直接在这里不再向下传递,然后返回响应
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder() {
       //返回的数字表示执行的顺序 
        return 0;
    }
}

注意Oreder的值越小执行的优先级就越高,并且无论是配置文件里编写的单个路由过滤器还是全局过滤器,都会受到Order的影响(单个路由过滤器的Order值按从上到下的顺序从1开始递增),最终是按照Order值决定哪个路由过滤器先执行,当Order值相同时,全局路由过滤器会优先于单个路由过滤器执行。 

微服务CAP原则

在一个分布式系统中存在 Consistency(一致性),Availabiity(可用性),Partition Tolerance(区分容错性)三者是不可同时保证的,最多同时保证其中的两个。

 一致性:在分布式系统中的所有数据备份,在同一时刻都是相同的值。(所有的节点无论何时访问都能拿到最新的值)

可用性:系统中非故障节点收到的每个都必须得到响应。(不如我们之前使用的服务降级和熔断,其实就是一种维持可用性的措施,虽然服务器返回的是无意义的数据,但不至于用户的请求会被服务器忽略)

区分容错性:一个分布式系统里面,节点之间组成的网络本应该是相互连通的,然而可能因为一些故障(比如网络丢包等,这是很难避免的),使得一些节点之间不能连通,整个网络分成了几块区域,数据就散落在这些不相连通的区域里面。(这样就可能出现某些被分区节点存放的数据访问失败,我们需要来容忍这些不可靠的情况)

总的来说,数据存放的节点越多,分区容忍性就越高,都是要复制更新的次数就会越来越多,同时为了保证一致性,更新所有节点数据所需要的时间就越长,那么可用性就会降低。

所以存在三种方案:

  

springCloud Alibaba的使用  

    springCloud的缺点:

1.springCloud部分组件停止维护和更新了,给开发带来了不便。

2.springCloud部分开发环境复杂,没有完善的可视化界面,我们需要大量的二次开发和定制。

3.springCloud配置复杂,难上手,部分配置差别难以群分和合理应用。

    springCloud Alibaba的优点:阿里使用过的组件经历了考验,性能强悍,设计合理,现在开源出来给大家使用成套的产品搭配完善的可视化界面给开发带来运维带来了极大的便利,搭建简单,学习曲线低。

spring-cloud-alibaba对应依赖:


    
        
            com.alibaba.cloud
            spring-cloud-alibaba-dependencies
            2.2.7.RELEASE
            pom
            import
        
    

 Nacos

Nacos: 一个易于使用的动态服务发现、配置和服务管理平台,用于构建云原生应用程序。

作用: 将其作为服务注册中心。

 Nacos作为服务注册中心对应依赖:


     com.alibaba.cloud
     spring-cloud-starter-alibaba-nacos-discovery
 

在github中下载nacos并将其复制到项目中。

在startup.sh中设置nacos让其在前台运行。 

配置服务。 

直接使用nacos。

后台启动nacos: bash nacos/bin/startup.sh。(以集群的方式) 

关闭nacos: bash nacos/bin/shutdown.sh。

后台启动naocs: bash nacos/bin/startup.sh -m standalone。(以单例的方式启动)

nacos 的地址为: localhost:8084/nacos。

 导入对应依赖。

        
            org.springframework.cloud
            spring-cloud-dependencies
            2021.0.1
            pom
            import
        
        
            com.alibaba.cloud
            spring-cloud-alibaba-dependencies
            2021.0.1.0
            pom
            import
        

在子项目中添加依赖。

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
            2021.1
        

设置配置文件。

spring:
  cloud:
#配置nacos注册中心地址
    nacos:
      discovery:
        server-addr: lcoalhost:8848

效果图:

 使用openFeign调用服务。

对应依赖为下:

        
            org.springframework.cloud
            spring-cloud-starter-openfeign
            3.1.1
        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
            3.1.1
        

和原生的调用方式一样。

临时实例和非临时实例的区别:
临时实例:和eureka一样,采用心跳机制向nacos发送请求保持在线的状态,一但心跳停止,就代表实例下线,不保留实例信息。

非临时实例:由nacos主动进行联系,如果连接失败,那么不会删除实例信息,而是将健康状态设置为false,相当于对某个实例状态持续进行监控。

 设置非临时实例

设置配置文件。

  cloud:
    nacos:
      discovery:
#        修改为false 表示其是个非临时文件
        ephemeral: false

 对应的实例下线时会颜色会变为红色。

 集群分区

对应依赖为下:

spring:
  cloud:
    nacos:
      discovery:
           #对应的集群名
        cluster-name: name

效果图

在默认情况下,集群间的调用方式采用的是轮番调用,使用为了实现就近原则,我们要对配置文件进行相应配置。

spring:
  cloud:
        #将loadbalancer的nacos支持开启,集成nacos负载均衡
    loadbalancer:
      nacos:
        enabled: true

 在同个集群中如果存在多个相同的服务时,就会根据权重来执行对应的服务,我们可以在nacos页面中进行设置。

也可以通过配置文件进行修改。

spring:
  cloud:
    nacos:
      discovery:
        #设置权重,默认为1
        weight: 2

 配置中心

我们可以通过配置来加载远程配置,这样我们可以远端集中管理配置文件。

我们可以通过在nacos中,点击新建配置项,进行配置。

点击发布。

在项目中添加依赖。

        
            org.springframework.cloud
            spring-cloud-starter-bootstrap
            3.1.2
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
            2021.1
        

编写bootstrap.yml文件。

spring:
  application:
    #去远程仓库中调用对应名字的配置
    name: user-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        file-extension: yml
        server-addr: localhost:8848

此时两个yml文件都会被使用。

 在默认情况下,在nacos中修改yml文件,对应的服务中的yml的信息并不会发生对应的改变,虽然对其做了监听。

为了能够保证在nacos中修改的yml文件立刻生效,我们需要添加注解@RefreshScope。

 效果图:

原配置文件 

 修改后的配置文件

 结果

命名空间 

 新建命名空间。

在配置文件中配置所属的命名空间。

spring:
  cloud:
    nacos:
      discovery:
        namespace: 对应命名空间的id

命名空间不相同的服务是不能相互调用的。

指定分组

 修改配置文件。

spring:
  cloud:
    nacos:
      discovery:
        group: 对应的组名

默认为 DEFAULT_GROUP。 

高可用

 在本地数据库导入nacos中的sql文件。

 配置conf/application.properties文件。

 将cluster.conf.example重命名为cluster.conf,并对其内容进行修改,输入多个的nanos地址,这里使用内网映射。

 因为要创建nacos集群,所以我们要创建多个nacos,通过复制修改好的nacos进行操作,这时我们只要再对每个nacos修改端口号就可以了。(要以集群的方式启动,可能会启动失败,多启动几次)

效果图: 

我们需要一个负载均衡器来管理这些nacos,这里我们使用Nginx。

在Mac上安装Nginx。

brew install nginx 

编辑Nginx。

nano /usr/local/etc/nginx/nginx.conf

编辑内容:

#    添加我们刚创建好的nacos服务器
upstream nacos-server {
    #被代理的服务群,nacos-server服务群的名字
    server localhost:8801;
    server localhost:8802;
}

server {
    #监听的端口
    listen    80;
    #服务名
    server_name localhost;
    
    #类似过滤器,当访问到符合此要求的路径时就会去访问对应的服务群
    location /nacos {
        proxy_pass http://nacos-server;
    }
}

重启Nginx。

brew services restart nginx 

将各个微服务的nacos的注册地址改为localhost:80/nacos。(其会实现负载均衡)

在云服务器上做反向代理的例子。

1.配置nacos的端口号和对应的数据库。

2.启动俩个nacos。

3.在Nginx中配置反向代理。

集群效果:

在每个微服务中将nacos的地址改为云服务器上Nginx反向代理的地址。

  

最终效果图:

 Sentinel流量防卫兵

 Sentinel 可以做到熔断和降级,可以取代Hystrix。

Sentinel具有以下功能:

  • 丰富的适用场景:哨兵在阿里巴巴被广泛使用,在过去10年中,几乎涵盖了Double-11(11.11)购物节的所有核心场景,例如需要限制突发流量以满足系统容量的“第二次杀戮”,消息峰值剪切和山谷填充,不可靠的下游服务的断路,集群流量控制等。
  • 实时监控:哨兵还提供实时监控功能。您可以实时查看一台机器的运行时信息,以及少于500个节点的集群的聚合运行时信息。
  • 广泛的开源生态系统:Sentinel提供与Spring Cloud、Dubbo和gRPC等常用框架和库的开箱即用集成。您只需将适配器依赖项添加到您的服务中,即可轻松使用Sentinel。
  • Polyglot支持:Sentinel为Java、Go和C++提供了原生支持。
  • 各种SPI扩展:Sentinel提供易于使用的SPI扩展接口,允许您快速自定义逻辑,例如自定义规则管理、调整数据源等。

下载对应的jar包。

sentinel V1.8.3

 将jar导入到项目,为其创建一个服务。

启动项目。 

 添加对应的依赖。

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-sentinel
            2021.0.1.0
        

对微服务中application.yml进行配置。(实际上Sentinel是本地在管理,但我们可以连接到监控页面,这样可以图形化操作了)

spring:
  cloud:
    sentinel:
#     添加监控页面地址
      transport:
        dashboard: localhost:8858

为了提高可读性和节省空间,Sentinel只监视被调用过的微服务。所以为了让sentinel监视我们的微服务,我们需要手动的调用一次微服务。

效果图:

流量控制

我们的机器不可能无限的接受和处理客户端的请求,如果不加以限制,当发生高并发时,就会使得系统的资源很快的被耗尽。为了避免这种情况,我们可以添加流量控制,当一段时间内的流量达到一定的阀值的时候,新的请求将不会再进行处理,这样不仅可以合理地应对高并发的情况,同时也可以在一定的程度上保护服务器不受到外界的攻击。

解决方案

 针对判断是否超过流量的阀值的四种算法

1.漏桶算法

 2.令牌桶算法(有点像游戏里的能量条机制)

 3.固定时间窗口算法

4.滑动时间窗口算法

通过Sentinel进行设置

 阀值类型:QPS就是每秒中的请求数量,并发线程数是按服务当时使用的线程数据进行统计的。

流控模式:当达到阀值时,流控的对象,这里暂时只用直接。

流控效果:就是我们上面所说的三种方案。

流控模式的区别:

1.直接:只针对当前接口。

2.关联:当其他接口超过阀值时,会导致当前接口被限流。(别的接口出现问题由当前接口承担责任)

3.链路:更细粒度的限流,能精确到具体的方法。

链路模式能够更加精准的进行流量控制,链路流控模式指的是,当从指定接口过来的资源请求达到限流条件时,开启限流,这里得先讲解一下@SentinelResource的使用。

我们可以对某个方法进行限流控制,无论是谁在何处调用了它,这里需要使用到@SentinelResource,一但方法被标注,那么就会进行监控。

在调用的方法上添加注解@SentinelResource。

 @SentinelResource("detail")
    @Override
    public UserBook queryByUid(Integer uid) {
        List dbBorrows = dbBorrowDao.queryByUid(uid);
        RestTemplate template = new RestTemplate();
//        DbUser user = template.getForObject("http://localhost:8083/dbUser/" + uid, DbUser.class);
        DbUser user = userClient.queryById(uid);
        UserBook userBook = new UserBook();
        userBook.setUser(user);
        LinkedList books = new LinkedList<>();
        for(int i = 0; i < dbBorrows.size(); i++){
//            DbBook book = template.getForObject("http://localhost:8081/dbBook/" + dbBorrows.get(i).getBid(), DbBook.class);
            DbBook book = bookClient.queryById(dbBorrows.get(i).getBid());
            books.add(i, book);
        }
        userBook.setBook(books);
        return userBook;
    }

修改配置文件。

spring:
  cloud:
    sentinel:
      #关闭Context收敛,这样被监控方法可以进行不同链路的单独监控
      web-context-unify: false

运行查看效果。

 对精确限流设置限流策略。

 设置阀值为1,然后进行连续访问测试,发现精确限流的位置抛出了异常。

 那么这个链路选项实际上就是决定只限流从哪个方向来的调用,比如我们只对borrow2这个接口对queryByUid方法的调用进行限流,那么我们就可以为其制定链路。

 入口资源的设置表示:进行精确限流的路径,如果设置了入口资源,那么其他路径调用被精确限流的方法时则不会被限流。

系统保护规则

 检测对应的设备来进行限流。

限流和异常处理 

我们看到被限流之后返回的Sentinel默认的数据,其实我们可以返回我们自己定义的数据。 ​​​​​​​

 这里我们先创建好被限流状态下需要返回的内容,自定义一个请求映射。

 在要返回自定义的微服务的Controller中添加自定义的错误页面。

 @RequestMapping("/blocked")
    public JSONObject blocked() {
        JSONObject json = new JSONObject();
        json.put("code", 403);
        json.put("success", false);
        json.put("message", "访问频率过快, 请稍后访问!");
        return json;
    }

在配置文件中配置限流页面返回信息。

spring:
  cloud:
    sentinel:
      block-page: /dbUser/blocked

效果图: 

 对于方法级别的限流,当某个方法被限流时,会直接在后台抛出异常,那么这种情况可以通过Sentinel添加一个替代方案,这样当我们发现异常时会直接执行我们的代替方案并返回。

在service层中添加代替方案。

    @SentinelResource(value = "detail", blockHandler = "blocked")
    //限流之后代替返回的其他方案,这样就不会使用默认的抛出异常的方式了
    @Override
    public UserBook queryByUid(Integer uid) {
        List dbBorrows = dbBorrowDao.queryByUid(uid);
        RestTemplate template = new RestTemplate();
//        DbUser user = template.getForObject("http://localhost:8083/dbUser/" + uid, DbUser.class);
        DbUser user = userClient.queryById(uid);
        UserBook userBook = new UserBook();
        userBook.setUser(user);
        LinkedList books = new LinkedList<>();
        for(int i = 0; i < dbBorrows.size(); i++){
//            DbBook book = template.getForObject("http://localhost:8081/dbBook/" + dbBorrows.get(i).getBid(), DbBook.class);
            DbBook book = bookClient.queryById(dbBorrows.get(i).getBid());
            books.add(i, book);
        }
        userBook.setBook(books);
        return userBook;
    }

    //代替方案
    public UserBook blocked(Integer uid, BlockException blockException) {
        UserBook userBook = new UserBook();
        userBook.setBook(Collections.emptyList());
        userBook.setUser(new DbUser());
        return userBook;
    }

blockHandler只会处理限流的异常,而不会处理方法体内的其他代码异常。

如果要处理限流以外的其他异常,我们可以通过其他参数进行处理。

 @RequestMapping("/test")
    @SentinelResource(value = "test",
                fallback = "except", //fallback指定出现异常是的替代方案
                exceptionsToIgnore = IOException.class) //忽略注定的异常,也就是出现这些指定的异常时不回调用的替代方案
    public String test() {
        throw new RuntimeException("抛出异常");
    }
    public String except(Throwable t) {
        return t.getMessage();
    }

效果图: 

 当在@SentinelResource中同时存在 fallback和blockHandler时,在抛出限流异常范围内的异常的时候就先调用blockHandler中的替代方案,其他的时候就会调用fallback。(注意在两个都打存在的时候,因为限流会在方法执行前调用,所以在限流代替方案执行完以后还会在执行出现其异常时的代替方案)

热点参数限流

我们可以对某一热点进行精准限流,比如在某一时刻,不同参数被携带访问的频率是不一样的:

http://localhost:8082/test?a=10 访问100次

http://localhost:8082/test?b=10 访问0次

http://localhost:8082/test?c=10 访问3次 

由于携带的参数a的请求比较多,我们就可以只对携带参数a的请求进行限流。 

创建一个新的测试请求映射。

 @RequestMapping("/test")
    @SentinelResource("test") //注意这里需要添加@SentinelResource才可以,用户资源名称就使用这里定义的资源名称
    public String findBorrow(@RequestParam(value = "a", required = false) String a,
                             @RequestParam(value = "b", required = false) String b,
                             @RequestParam(value = "c", required = false) String c) {
        return "请求成功!" + "a = " + a + " b = " + b + " c = " + c;
    }

在Senntinel中设置热点配置。(我们对a进行了限流)

 效果图:

 我们也可以对某个参数的特定值进行特定限流。(我们对a进行特定值限流) 

效果图:

Sentinel的服务熔断和降级

为了防止链路故障,我们能进行隔离,这里我们有两种隔离方案。

1.线程池隔离

线程池隔离实际上就是对每个服务的远程调用单独开放线程池,比如服务A要调用服务B,那么只基于固定数量的线程池,这样即使在短时间内出现大量请求,由于没有线程可以分配,所以就不会导致资源耗尽。 ​​​​​​​

 2.信号量隔离

信号量隔离是使用Semaphore类实现的,思想基本跟上面的相同,也是限定指定的线程数量能够同时进行服务调用,但它相对于线程池开销会更小一些,使用效果同样优秀,也支持超时等,Sentinel就是采用这个方案进行隔离的。 

说回我们的熔断与降级,当下游的服务因为某些原因变得不可用或响应过慢时,上游为了保证自己整体的高可用性,不再继续调用目标服务而是快速返回或执行自己的代替方案。 

 整个过程分为三个状态:

1.关闭:熔断器不工作,所有的请求全部该干嘛就干嘛。

2.开启:熔断器工作,所有的请求一律降级。

3.半开:尝试进行一下正常的流程,要是还是不行就继续保持开启的状态,否则关闭。

 在Sentinel设置熔断规则

熔断策略

1.慢调用比例:如果出现那种半天都处理不完的调用,有可能就是服务出现故障,这个选项是按照最大效应时间(RT)进行判断的,如果一次请求的处理时间超过了指定的RT,那么就会判断为慢调用,在一个统计时长内,如果请求数目大于最小请求数目,并且被判断定为慢调用的请求比例已经超过阀值,将触发熔断,经过熔断时长之后,将会进入到半开状态进行试探。(这里和Hystrix一致)

 测试代码

@RequestMapping("/borrow2/{uid}")
    public String test2(@PathVariable("uid") Integer uid) throws InterruptedException {
        Thread.sleep(1000);
        return "hello World!";
    }

在在对应的微服务中设置熔断设置。

 效果图:

 2.异常比例:与慢调用比例相似,不过这里判断的是出现异常的次数。

测试代码

@RequestMapping("/borrow3/{uid}")
    public String test3(@PathVariable("uid") Integer uid) throws Exception {
       throw new RuntimeException();
    }

在Sentinel中配置熔断配置。

效果图:

3.异常数:很异常比例好像,但有明确指出异常数量。

降级策略

我们需要在@SentinelResource中配置blockHandler参数。(这里跟之前方法限流的配置是一样的,因为如果添加了@SentinelResource注解,那么这里就会进行方法级别细粒度的限制,和之前方法级别限流一样,会在降级之后直接抛出异常,如果不添加则返回默认的限流页面,blockHandler的目的就是处理这种Sentinel机制的异常,所以这里其实和之前的限流配置是一个道理,因此下面熔断配置也应该对value自定义名称的资源进行配置,才能作用到此方法上)

测试代码:

//降级测试
    @RequestMapping("/borrow4/{uid}")
    @SentinelResource(value = "findBorrow", blockHandler = "test")
    public ResponseEntity findBorrow(@PathVariable("uid") Integer uid) throws Exception {
        throw new RuntimeException();
    }
    //代替方案
    public ResponseEntity test(Integer uid, BlockException e) {
        System.out.println(e.getClass());
        UserBook userBook = new UserBook();
        userBook.setUser(new DbUser());
        userBook.setBook(Collections.emptyList());
        return ResponseEntity.ok(userBook);
    }

在Sentinel中设置熔断规则。

 效果图:

 抛出降级异常。

openFeign支持Sentinel

 前面我们使用Hystrix的时候,就可以直接对openFeign的每个接口调用单独进行服务降级,而使用Sentinel,也可以的。

在配置文件中开启支持。

feign:
  sentinel:
    enabled: true

 和之前的openfign整合eureka的服务降级配置相似。

BookClient接口:

@FeignClient(value = "book-service",fallback = BookClientImpl.class)
public interface BookClient {
    @GetMapping("/dbBook/{id}")
    DbBook queryById(@PathVariable("id") Integer id);
}

BookClient替代方案:

@Component
public class BookClientImpl implements BookClient{
    @Override
    public DbBook queryById(Integer id) {
        return new DbBook();
    }
}

启用代替方案效果图: 

 Sentinel整合RestTemplate使用服务降级

在config进行配置。

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class,
            fallback = "对应的降级方案", fallbackClass = ExceptionUtil.class)
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
 Seata与分布式事务

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

事务特性

 分布式讲解方案

1.XA分布式事务协议 -2PC (两阶段提交实现) 

这里的PC实际上指的是Prepare和Commit,也就是说它分为两个阶段,一个是准备一个是提交,整个过程的参与者一共有两个角色,一个是事务的执行者,一个是事务的协调者,实际上整个事务的运作需要毅力啊协调者来维持。

 在准备和提交阶段,会进行:

准备阶段:

    一个分布式事务是由协调者来开启的,首先协调者会向所有的事务执行者发送事务内容,等待所有的事务执行者答复。

各个事务执行者开始执行事务操作,但不会进行提交,并将undo和redo信息记录到事务日志中。

如果事务执行者执行事务成功,那么就告诉协调者成功Yes,否则告诉协调者失败No,不能提交事务。

提交阶段:

    当前有的执行者都反馈完成之后,进入第二阶段。

协调者会检测各个执行者的反馈内容,如果所有的返回都是成功,那么就告诉所有的执行者可以提交事务了,最后再释放锁的资源。

如果有至少一个执行者返回失败或超时面,那么就让所有的执行者都会回滚,分布式事务执行失败。

虽然这种方式看起来比简单,但是存在以下几个问题:

1.事务协调者是非常核心的角色,一旦出现问题,将导致整个分布式不能正常运行。

2.如果提交阶段发生网络问题,导致某事务执行者没有收到协调者发来的提交命令,将导致某些执行者没提交,这样肯定是不行的。

2.XA分布式事务协议 -3PC(三阶段提交实现)

三阶段提交是在二阶段提交的基础上的改进播版本,主要是加了超时记机制,同时在协调者和执行者都引入了超时机制。

三个阶段分别进行:

CanCommit阶段:

    协调者向执行者发送CanCommit请求,询问是否可以执行事务提交操作,然后开始等待执行者的响应。

ProeCommit阶段:

    协调者根据执行者的反应情况来决定是否可以进入第二阶段事务的PreCommit操作。

如果所有的执行者都返回Yes,则协调者向所有的执行者发送PreCommit请求,并进入Prepared阶段,执行者接收到请求后,会执行事务操作,并将undo和redo信息记录到事务日志中,如果成功执行,则返回成功的响应。

如果所有的执行者至少有一个返回No,则协调者会向所有的执行者发送abort请求,所有的执行者在收到请求或超时一段时间没有收到任何请求时,会直接中断事务。

DoCommit阶段:

    该阶段进行真正的事务提交。

    协调者接收到所有执行者发送的成功响应,那么它就从PreCommit状态进入DOCommit状态,并向所有的执行者发送doCommit请求,执行者接收到doCommit请求之后,开始执行事务提交,并在完成事务提交之后释放所有的事务资源,并最后向协调者发送确认响应,协调者接收到所有执行者的确认响应之后,完成事务(如果因为网络问题导致执行者没有接收到doCommit请求,执行者会在超时之后直接提交事务,虽然执行者只是猜测协调者返回的是doCommit请求,但是因为前面的两个流程都正常执行,所以能够在一定程度上认为本次事务是成功的,因此会直接提交)

    协调者没有接收到至少一个执行者发送的成功响应(可能是响应超时),那么就会执行中断事务,协调者会向所有的执行者发送abort请求,执行者接收到abort请求之后,利用其在PreCommit阶段记录的undo信息来执行事务的回滚操作,并在完成回滚操作之后释放所有的事务资源,执行者完成事务回滚之后,向协调者发送确认信息,协调者接收到参与者反馈的确认信息之后,执行事务的中断。

第三阶段的特点:

1.3PC在2PC的第一阶段和第二阶段中插入一个准备阶段,保证在最后提交阶段之前各参与节点的状态是一致的。

2.一旦参与者无法及时收到来自协调者的信息之后,会默认执行Commit,这样就不会因为协调者单方面故障导致全局出现问题。

3.但是我们知道,实际上超时之后的Commit决策本质上就是一个赌注罢了,如果此时协调者发送的是abort请求但是超时未接收,那么就会直接导致数据一致性的问题。 

3.TCC (补偿事务)

补偿事务TCC就是Try,Comfirm,Cancel,它对业务有入侵性,一共分为三个阶段。

Try阶段:

    比如我们需要借书时,将书籍的库存-1,并将用户的借阅量-1,但是这个操作,除了直接对库存和借阅量进行修改之外,还需要将减去的值,单独存放到冻结表中,但是此时不会创建借阅信息,也就是说只是预先把关键的东西给处理了,预留业务资源出来。

Confirm阶段:

    如果Try执行成功无误,那么就进入Confirm阶段,接着之前,我们就该创建借阅信息了,只能使用Try阶段预留的业务资源,如果创建成功,那么就对Try阶段冻结的值进行解冻,整个流程就完成了,如果失败了,就会进入Cancel阶段。

Cancel阶段:

    将冻结的东西还给人家,进行回滚。

TCC特点:

跟XA协议相比,TCC就没有协调者这一角色的参与了,而是自主通过上一阶段的执行情况来确保正常,充分利用了集群的优势,性能也是有很大的提升,但是缺点也很明显,它与业务具有一定的关联性,需要开发者去编写更多的补偿代码,同时并不一定所有的业务流程都适用于这种形式。 

 ​​​​​​​Seata机制简介

 seata支持四种事务:

1.AT:标志上就是2PC的升级版,在AT模式下,用户只需要关心自己的"业务SQL"。

     一阶段:seata会拦截"业务SQL",首先解析SQL语义,找到"业务SQL"要更新的业务数据,在业务数据更新之前,将其保存成"before image",然后进行"业务SQL"更新业务数据,在业务数据更新后,再将其保存到"after image",最后生成行锁。以上操作只在一个数据库事务内完成,这样保证了第一阶段操作的原子性。

    二阶段如果确定提交的话,因为"业务SQL"在一阶段已经提交到数据库,所以Seata框架只需要将第一阶段保存的快照数据和行锁删掉,完成数据清除即可,当然如果需要回滚,那么就用"before image"还原业务数据,但在还原前首先要校验脏读,对比"数据库当前业务数据"和"after image",如果两份数据完全一致就说明没有脏读,可以还原业务数据,如果不一致就说明有脏读,出现脏读就需要转人工处理。  

2.TCC:和我们上面讲解的思路是一样的。

3.XA:同上,但是要求数据库本身支持这种模式才可以。

4.saga:用用处理长事务,每个执行者需要实现事务的正向操作和补偿操作。

 那么,以AT模式为例,我们的程序是如何才能做到不对业务进行侵入的情况下实现分布式事务能?实际上,Seata客户端,是通过对数据源进行代理实现的,使用的是DataSourceProxy类,所以在程序这边,我们只需要将对应的代理类注册到Bean即可。(0.9版本之后支持自动代理,并不需要我们手动导入)

 使用file进行部署(以AT为例)

下载seata-server。

seats-server

 在idea中配置seata服务。

 seata支持本地部署也支持服务注册与发现中心部署。(比如eureka,nacos)

seata存在着事务分组机制:

1.事务分组:seata资源逻辑,可以按微服务的需要,在应用程序(客户端)对自定义事务进行分组,每个组取一个名字。

2.集群:seata-server服务端一个或多个节点组成的集群cluster。应用程序(客户端)使用时需要指定事务逻辑分组与seata服务器集群(默认为default)的映射关系。

为啥要设计成通过事务分组再直接映射到集群?为什么不直接将事务指定到集群呢?

获取事务分组到映射集群的配置。这样设计后,事务分组可以作为资源的逻辑隔离单位,出现某集群故障时可以快速failover(故障切换),只切换对应的分组,可以把故障缩减到服务级别,但提前也是你有足够server集群。

将各个服务作为seata的客户端导入对于依赖:

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-seata
            2021.0.1.0
        

修改配置文件。

seata:
  service:
    vgroup-mapping:
      #这里需要对事务组进行映射,默认组名为应用名称-seata-service-group,将其映射到default集群
      #这个很关键,一定要配置,不然会找不到服务
      borrow-service-seata-service-group: default
    grouplist: localhost:8868

也可以设置自定义的服务分组。

seata:
  service:
    vgroup-mapping:
      #这里需要对事务组进行映射,默认组名为应用名称-seata-service-group,将其映射到default集群
      #这个很关键,一定要配置,不然会找不到服务
      xx服务名xx-seata-service-group: xxx
    grouplist: localhost:8868
  tx-service-group: xxx

现在我们接着来配置开启分布式事务,首先在启动类上添加注解,此注解会添加一个后置处理器将数据源封装为支持分布式事务的代理数据源(1.4.2版本还是要手动添加此注解才能生效) 

 接着我们需要在开启分布式事务的方法上添加注解@GlobalTransactional。

 因为Seata会分析修改数据的sql,同时生成对应的反向回滚sql,这个回滚记录会存放在undo_log表中,所以要求每个Client都有一个对应的undo_log表(也就是说服务连接的数据库都需要创建一个这样的表,因为我们的例子就一个数据库,所有只要创建一个表)

创建undo_log表的sql语句。

CREATE TABLE IF NOT EXISTS `undo_log`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAr(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAr(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    `ext`           VARCHAr(100) DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;

 启动服务,进行测试。

第一次借阅成功。 

第二次借阅失败。

 查看数据库是否进行回滚。(这里进行了回滚) 

我们可以打印XID,查看其对应的XID,在service层添加语句。

System.out.println(RootContext.getXID());

也可以在日志中查看。

使用nacos模式部署

我们先为Seata在nacos中配置一个命名空间。

修改seata的/conf/registry.conf文件,修改其注册和配置中的“type”,“namespace”,“password”,“username”。

 注册信息配置完成之后,接着我们需要将配置文件也放到Nacos中,让Nacos管理配置,这样我们就可以对配置进行热更新了,一旦环境需要改变,只需要直接到Nacos中修改即可。

  

 我们需要对配置导入到Nacos中,我们打开seata源码的 /script/config-center/nacos目录,这是官方提供上传脚本,我们直接运行即可。(去github下载seata源码)

​​​​​​​

 在nacos中查看seata的配置。

 把所有微服务的事务分组信息的配置放在nacos中,我们还需要将对应的事务组映射配置也添加上,DataId格式为service.vgroupMapping.'事务的名称'。  ​​​​​​​

 接下来我们要对服务端的配置进行修改,我们删除原本的seata配置,添加新的seata配置。

seata:
  #注册
  registry:
    type: nacos
    nacos:
      #使用seata命名空间,这样才能找到seata服务,由于组名我们设置的是SEATA_GROUP就是默认的名字,所以就不用配了
      namespace: 550e71d6-4604-4952-a24b-b0d3781d8223
      username: nacos
      password: nacos
  #配置
  config:
    type: nacos
    nacos:
      namespace: 550e71d6-4604-4952-a24b-b0d3781d8223
      username: nacos
      password: nacos

  启动seata服务,在nacos中对应的命名空间观察seata服务是否正常启动。

启动各个微服务,各个服务使用nacos配置成功。

测试事务效果图:

  

 我们还可以配置一下事务会话信息的存储方式,默认是file类型的,那么就会在运行目录下创建file_store目录,我们可以将其放到数据库中存储,只需要修改一下数据即可。

 默认情况的存储方式:

将会话信息存放到数据库中 

修改nacos中的seata配置store.mode,store.session.mode,将存储方式改为数据库方式。

 

 将数据库的配置信息进行修改。

1.数据库启动。

 2.数据库的URL。

3.数据库用户名和密码。

 

创建seata数据库。

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAr(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAr(32),
    `transaction_service_group` VARCHAr(128),
    `transaction_name`          VARCHAr(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAr(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAr(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAr(32),
    `resource_id`       VARCHAr(256),
    `branch_type`       VARCHAr(8),
    `status`            TINYINT,
    `client_id`         VARCHAr(64),
    `application_data`  VARCHAr(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAr(128) NOT NULL,
    `xid`            VARCHAr(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAr(256),
    `table_name`     VARCHAr(32),
    `pk`             VARCHAr(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAr(20) NOT NULL,
    `lock_value`     VARCHAr(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('HandleAllSession', ' ', 0);

数据库效果图:

运行事务效果图: 

分布式权限校验  

因为是分布式服务,每个微服务存储的sessionb是各不相同的,而我们需要的是保证所有的微服务都能同步这些session信息,这样我们才能实现某一个微服务登录时,其他微服务都能知道。

实现上述要求的方案

方案一:我们可以在每台服务器上都复制一份Session,但这样显然是很浪费时间的,并且用户验证数据占用的内存会成倍的增加。

方案二:将Session移出服务器,用统一访问Redis或是Mysql即可,这样就能保证服务都可以同步Seesion了。

 明显方案二是可行的。

每个微服务需要添加验证机制,导入对应依赖。


        
            org.springframework.session
            spring-session-data-redis
            2.7.0
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.7.0
        

导入springSecurity框架的依赖。

        
            org.springframework.boot
            spring-boot-starter-security
            2.7.0
        

对每个微服务的配置文件进行修改。

spring:
  session:
    store-type: redis
  redis:
    host: lcoalhost:6379

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/975320.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号