【学习笔记】微服务学习笔记(库存)

164 阅读14分钟

Spring Cloud

eureka

首先需要引入一个服务端模块,然后服务端模块需要使用@enableeurekaserver来开启服务端

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

然后是配置

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    register-with-eureka: false  #不注册自己
    fetch-registry: false  #表示自己是注册中心,不检索服务
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/
  #server: #关闭自我保护
#    enable-self-preservation: false
#    eviction-interval-timer-in-ms: 2000

建立一个客户端使用@enableeurekaclient来开启客户端

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  profiles:
    active: dev

eureka:
  client:
    register-with-eureka: true #将自己注册进去
    fetch-registry: true #从注册中心抓取自己的注册信息
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #注册到的地址
  instance:
    instance-id: payment8001 #设置实例id
    prefer-ip-address: true #开启显示实例路径
    #lease-renewal-interval-in-seconds: 1 # 向服务端发送心跳的时间
   # lease-expiration-duration-in-seconds: 2 #剔除服务上限时间

实现对应的业务逻辑即可

建立另一个模块来调用这个模块

spring:
  profiles:
    active: dev
  application:
    name: cloud-order-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #注册到的地址
  instance:
    instance-id: order80
    prefer-ip-address: true

调用的时候我们是用restTemplate去调用

 public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";  //这里我们只需要访问服务名,如果里面有多个实例,那么久需要开启restTemplate的负载均衡能力

    @Autowired
    private RestTemplate restTemplate; //使用前一定要先注入

    @GetMapping("/consumer/payment")
    public CommonReturnType<Payment> create(Payment payment) {
        return restTemplate.postForObject(PAYMENT_URL + "/payment",payment,CommonReturnType.class);
    }

    @GetMapping("/consumer/payment/{id}")
    public CommonReturnType<Payment> getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL + "/payment/" +id, CommonReturnType.class);
    }
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced //服务template负载均衡能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

==这里有个小坑,在调用另一个模块的服务的时候,我们需要在另一个模块的接收参数中使用@responseBody来控制这个 参数否则接收不到(上面那这个不是)==

服务发现

使用@EnableDiscoveryClient注解来开启服务发现

@Resource
    private DiscoveryClient discoveryClient;注入发现客户端
        
 @GetMapping("payment/discovery")
    public Object discovery() {
        final List<String> services = discoveryClient.getServices();
        for (String item : services) {
           log.info("测试:" + item);
       }

        final List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

        for(ServiceInstance info: instances) {
            log.info(info.getInstanceId() + info.getServiceId()+info.getInstanceId() + info.getUri());
        }
        return this.discoveryClient;        


image-20210707233906838

三大服务注册中心的异同

eureka :ap架构, 保证服务的高可用

zookeeper :cp架构,保证数据的一致性

consul:cp架构,保证数据的一致性,当数据出错就直接拒绝请求

服务调用

ribbon

一款用于实现软负载均衡和服务调用的客户端组件(负载均衡 + RestTemplate)

ribbon和nginx的区别

ribbon是本地负载均衡(进程内负载均衡),调用微服务接口的时候,会从注册中心上获取注册信息服务列表之后缓存到jvm本地,从而在本地实现RPC远程服务调用

nginx是服务端负载均衡(集中式负载均衡),即负载均衡是由服务端实现

自定义负载均衡策略

==注意:这个定义不能出现在@componentscan下的所有包,这样会让所有ribbon客户端都共享这个策略,达不到定制化的效果(即跳出主类外)==

@Configuration
public class MyRule {

    @Bean
    public IRule mySelfRule() {
        return new RandomRule(); //自定义随机规则
    }
}

然后,使用注解开启

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
public class Order {
    public static void main(String[] args) {
        SpringApplication.run(Order.class, args);
    }
}

这样,就能开启定制化负载均衡策略

ribbon负载均衡算法

public interface LoadBalance {

    ServiceInstance instances(List<ServiceInstance> serviceInstances);


}


@Component
public class MyLB implements LoadBalance{

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current > 2147483647 ? 0 : current + 1;
        } while(!this.atomicInteger.compareAndSet(current, next));
        System.out.println("******next:" + next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}
 @GetMapping("/consumer/payment/lb")
    public String getPaymentLB() {
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if(instances == null || instances.size() == 0) {
            return null;
        }

        ServiceInstance serviceInstance = loadBalance.instances(instances);
        final URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri + "/payment/lb", String.class);

    }

OpenFeign

将ribbon和resttemplate进行封装,集成了ribbon

首先引入jar包

 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

然后在主启动类上开启使用feign

@SpringBootApplication
@EnableFeignClients //开启feign服务调用功能
public class FeignOrder80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignOrder80.class, args);
    }
}

然后编写Feign服务

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") //查找微服务名
public interface PaymentFeignService { 
    @GetMapping("/payment/{id}") //微服务路径
    CommonReturnType getPaymentById(@PathVariable("id") Long id); 
}

总结:就是将微服务中的接口或者服务进行封装成一个服务,可以供多个服务进行调用,这样就简化了操作,起到了和ribbon + restTemplate一样的调用这个微服务的功能。实际上比ribbon + restTemplate更加方便。

OpenFeign超时控制

在配置文件中配置设置openfeign连接超时时间

ribbon:
  ReadTimeout: 8000
  ConnectTimeout: 8000

openFeign 日志打印功能

logging:
  level:
    com/zero/springcloud/service/PaymentFeignService: debug #feign日志以什么级别监控哪个接口

在配置类中配置级别,使用显示最全的这个

@Configuration
public class configFeignConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

服务降级

hystrix

是一个用于处理分布式系统的延迟和容错的开源库

@enableHystrixDashboard:开启监控

@enableHystrix:开启降级

@EnableCircuitBreaker:开启熔断

服务降级

服务端异常fallback处理:==@EnableCircuitBreaker注解来开启==

@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") //3秒是超时上限
    })
    public String paymentInfo_error(Integer id) throws InterruptedException {
        int timeoutNumber = 2;
        TimeUnit.SECONDS.sleep(timeoutNumber);
        return "线程池    " + Thread.currentThread().getName() + "     paymentInfo_error   id" + id + "服务出错了" + timeoutNumber + "秒";
    }

    public String paymentInfo_TimeoutHandler(Integer id) {
        return "线程池: " + Thread.currentThread().getName() + "   paymentInfo_ERRhANDLER  " + id;
    }

客户端异常fallback处理:==@Enablehystrix注解开启hystrix==

配置yml

feign:
  hystrix:
    enabled: true

 @GetMapping("/consumer/hystrix/err/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimoutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
    })
    public String timout(@PathVariable("id") Integer id) {
        return paymentHystrixService.paymentInfo_error(id);
    }

    public String paymentTimoutFallbackMethod(@PathVariable("id") Integer id) {
        return "支付系统繁忙,请把钱全给我";
    }

统一fallback处理

类上定义全局默认配置==@defaultProperties(defaultFallback = "paymentTimeoutFallbackMethod")==

 public String paymentTimeoutFallbackMethod(@PathVariable("id") Integer id) {
        return "服务出错,请稍后再试";
    }

当使用@hystrixCommand注解的方法出错的时候就会使用默认的fallback,当有具体的配置的时候就会就近原则

优化方案:

直接在服务调用业务处实现服务接口,然后实现类就是对应的调用服务的fallback方法。解决了业务逻辑混乱的问题

服务熔断

//**********服务熔断***********
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),  //是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间窗口期(时间范围内)
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60") //失败率达到多少后熔断
    })
    public  String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if(id < 0) {
            throw  new RuntimeException("id不能为负数");
        }
        String s = IdUtil.simpleUUID();
        return Thread.currentThread().getName() + "流水号:" + s;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id不能不负数,请稍后再试  + id:" + id;
    }

熔断的过程===》 降级 =》 熔断 =》 慢慢恢复

当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠窗口,在这个时间窗内,降级逻辑是临时的主逻辑,当休眠时间窗到期,断路器将会进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器就会闭合,主逻辑就会恢复,如果这次请求仍然有问题,那么,断路器继续进入打开状态,休眠时间窗重新计时。

熔断类型:

1.熔断打开:不在调用当前服务,内部设置时钟一般为mttr(平均故障处理时间),当打开时长达到所设置时钟则进入半熔断状态

2.熔断关闭:熔断关闭不会对服务进行熔断

3.熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

服务限流

服务监控平台

1.需要actuor依赖,然后需要在需要监控的平台配置

 /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     * @return
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

然后,直接填入监控服务的端口+hystrix.stream路径就可

服务网关

Gateway

路由

      uri: http://localhost:8001     #匹配后提供服务的路由地址
      uri: lb://cloud-payment-service  #注册中心中的微服务名(得开启负载均衡)

断言 Predicate

-Path = 过滤路径【- Path=/payment/lb/**】

-After= 在指定条件之后【- After=2021-07-09T21:26:48.435+08:00[Asia/Shanghai]】表示在这个时间之后

-Before=

-Between=

-Cookie=username,zero【- Cookie=username,zero(携带username为zero的cookie)】

-Header=X-Request-Id, \d+ 【- Header=X-Request-Id,\d+ #请求头要又X-Request-Id属性并且值为整数的正则表达式】

-Host=*.zero-face.cn

-Method=get

-Query=username,\d+【参数条件,要又参数名叫username,并且值要是整数】

过滤

自定义过滤器
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("*****come in filter" + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname == null) {
            log.info("用户名为空,非法用户");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

依赖:==因为gateway底层是netty和webflux实现的,和springmvc出现冲突==

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-webmvc</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
gateway网关配置的两种方法

1.yml配置

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment-routh #路由的id,没有固定规则,但是要求唯一,建议配合服务名
#          uri: http://localhost:8001 #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/get/** #断言,路径相匹配的进行路由
        - id: payment-routh2
#          uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/lb/**

2.代码中注入

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_zero", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }

}

配置中心

引入依赖 ==@EnableConfigServer==激活配置中心

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
 </dependency>

服务端配置如下

spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/zero-c/springcloud-config.git
          search-paths:
            - springcloud-config
      label: master

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    register-with-eureka: true
    fetch-registry: true
  instance:
    instance-id: cloud-config-center

访问:http://localhsot:3344/{label}/fileName

客户端:引入依赖

 <!--配置中心客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

使用==@EnableEurekaClient==注解开启

配置文件:

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master #分支名称
      name: application #而配置文件名称
      profile: dev #读取后缀名称
      uri: http://localhost:3344 #配置中心地址
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
    instance-id: config-client
    prefer-ip-address: true

解决动态修改配置文件(手动修改所有)

消息总线-springcloud-Bus

全局通知

用于通知所有的配置客户端修改配置,设计思想是利用mq的消息广播功能,通知所有的微服务模块自动刷新配置

引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

添加mq的配置

 rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
#暴露rabbitmq相关配置,暴露bus刷新配置节点
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

发送post请求到配置中心服务端刷新

==curl -X post "http://localhost;3344/actuator/bus-refresh"==

定点刷新

更改刷新格式

curl -X post "http://localhost:3344/actuator/bus-refresh/微服务名:端口号"

消息驱动 cloud stream

需要一个消息提供方
<!--消息驱动-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

配置

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: #此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: #服务综合处理
        output: #通道名称
          destination: studyExchange #表示用exchange名称定义
          content-type: application/json #设置消息类型,本次为json,文本则设置为“text/plain”
          binder: ${spring.cloud.stream.binders.defaultRabbit} #设置要绑定的消息服务的具体设置
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        instance-id: stream-rabbitmq-sender

然后是发送消息逻辑

@EnableBinding(Source.class)  //指信道channel和exchange绑定在一起(定义消息的推送管道)
public class IMessageServiceImpl implements IMessageProvider{

    @Resource
    private MessageChannel messageChannel; //消息发送管道

    @Override
    public String send() throws UnsupportedEncodingException {
        String s = UUID.randomUUID().toString();
        messageChannel.send(MessageBuilder.withPayload(s).build());
        System.out.println("*******serial:" + s);
        return null;
    }
}

最后通过接口调用触发发送逻辑

需要一个接收者
spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: #此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: #服务综合处理
        input: #通道名称
          destination: studyExchange #表示用exchange名称定义
          content-type: application/json #设置消息类型,本次为json,文本则设置为“text/plain”
          binder: ${spring.cloud.stream.binders.defaultRabbit} #设置要绑定的消息服务的具体设置
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        instance-id: stream-rabbitmq-receiver
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT) //监听sink输入
    public void input(Message<String> message) {
        System.out.println("我是消费者1号,------>" + message.getPayload() + "   serverPort:" +serverPort);
    }
}

解决重复消费

同组只被消费一次,不同组可以重复消费

 bindings: #服务综合处理
        input: #通道名称
          destination: studyExchange #表示用exchange名称定义
          content-type: application/json #设置消息类型,本次为json,文本则设置为“text/plain”
          binder: ${spring.cloud.stream.binders.defaultRabbit} #设置要绑定的消息服务的具体设置
          group: zero1 #分组,解决重复消费

解决持久化

也是分组解决,带分组的服务可以拿到消息队列中没有被消费的消息,而未分组不行

springcloud Sleuth

提供了一套完整的分布式链路追踪的解决方案

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

配置

spring:
  zipkin:
    base-url: http://localhost:9411
  sleuth:
  	sampler:
  	probability: 1 #采样率,一般0.5即可

执行zipkin-server-exec的jar包才能开启图形化界面9411

springcloud Alibaba

Nacos服务注册和配置中心

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
server:
  port: 9001
spring:
  application:
    name: cloudalibaba-provider-payment9001
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
management:
  endpoints:
    web:
      exposure:
        include: *

配置中心

引入:

 <!--nacos配置-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

bootstrap

server:
  port: 3377
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #nacos作为服务注册中心
      config:
        server-addr: localhost:8848  #nacos作为配置中心地址
        file-extension: yaml #指定yaml的配置

application

spring:
  profiles:
    active: dev #表示拉取带有dev的配置文件

nacos配置规则:

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

==注意:在nacos写配置文件的时候需要将文件后缀名写全yaml==

分类配置:

nacos集群和持久化配置

nacos采用集中式存储的方式来支持集群化部署,目前只支持mysql

持久化配置:

更改conf目录下的application.properties

 spring.datasource.platform=mysql
 db.num=1
 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
 db.user.0=root
 db.password.0=123456

更改集群配置

添加集群机器ip和端口。vim /usr/local/nacos/conf/cluster.conf.example

修改启动脚本

sentinel实现熔断与限流

下载setinel控制台jar包并运行

将项目配到控制台

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
management:
  endpoints:
    web:
      exposure:
        include: '*'

流控规则:

​ 直接:达到阈值就限流

​ 关联:a达到阈值,b挂,支付到达阈值,订单挂

​ 链路:就是制定入口资源,当入口资源达到阈值就限流

流控效果:

​ 直接:快速失败

​ 预热:设置预热时间,在预热时间内,将阈值除以3当做真实阈值,预热时间过了以后,恢复真正阈值

​ 排队等待:严格控制请求通过的间隔时间

降级规则:

​ 降级策略:

​ RT:平均响应时间,超出阈值且在时间窗口内通过的请求书>=5,两个条件同时满足后触发降级,窗口期过后,关闭断路器

​ 异常比例数:qps大于等于5,超过阈值且每秒异常数达到一定比例饿,超过阈值就熔断,触发降级;时间窗口结束后,关闭降级。

​ 异常数:异常数(分钟统计)超过阈值,触发降级,时间窗口结束后,关闭降级。

​ ==sentinel没有半开状态,当资源降级后,在时间窗口期内,不能访问,如果过了时间窗口期,服务恢复,则关闭熔断,否则继续熔断==

热点key限流

@GetMapping("/hot")
@SentinelResource(value = "hot",blockHandler = "deal_testHotKey") //只违背控制台规则才触发
public String testHotKey(@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false)String p2) {
    return "testHotKey";
}

public String deal_testHotKey(String p1, String p2, BlockedException exception) {
    return "deal_testHotKey";
}
参数例外项

系统规则

​ load自适应:仅对Linux和Unix生效,

CPU usage:cpu 的使用率,

平均RT:

并发线程数:

入口qps:

sentinel规则持久化

引入依赖:

  <!--sentinel持久化-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

然后配置文件

spring:
	sentinel:
		datasource:
        		ds1:
         		 nacos:
           		 server-addr: localhost:8848 #保存到nacos地址
           		 dataId: ${spring.application.name} #配置名
            	 groupId: DEAULT_GROUP #默认分组
            	 data-type: json #文件类型
           		 rule-type: flow #规则类型

再新建配置

[
    {
        "resource": "/reteLimit/byUrl", #资源名称
        "limitApp": "default", #来源应用
        "grade": 1, #阈值类型,0---线程数,1---qps
        "count": 1, #单机阈值
        "strategy": 0, #流控模式,0--直接,1--关联,2--链路
        "controlBehavior": 0, #流控效果,0--快速失败,1--warm up,2--排队等待
        "clusterMode": false #是否集群
    }
]

分布式事务的问题:

一次业务性操作需要跨多个数据源或者需要跨多个系统进行远程调用,就会产生分布式事务问题

seata

一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务

全局唯一事务ID

3组件

tc:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或者回滚

tm:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或者全局回滚的决议

rm:控制分支事务,负责分支注册,状态汇报,并接受事务协调器的指令,驱动分支的事务的提交或者回滚

流程:

TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
XID在微服务调用链路的上下文传播;
RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
TM向TC发起针对XID的全局提交或者回滚决议;
TC调度XID下管辖的全部分支事务完成提交或者回滚请求。
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--和Seata下载版本相吻合-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.2.0</version>
        </dependency>

原理:

事务加载的两个阶段:

第一阶段 加载

Seata会拦截业务SQL,

1.解析SQL语义,找到业务SQL要更新的业务数据,在业务数据被更新前,将其保存为before image(前置快照)。

2.执行业务SQL,更新业务数据,在业务数据更新之后,

3.将其保存为after image ,最后生成

第二阶段 提交

业务SQL在一阶段已经提交到数据库,所以SeaTa框架只需要将一阶段保存的快照数据和行锁删掉,完成数据清理即可

第二阶段 回滚

提交异步化,非常快速的完成

回滚通过一阶段的回滚日志进行反向补偿

二阶段如果是回滚的话,就需要回滚一阶段已经执行的业务SQL,还原业务数据,回滚方式是用==before image还原业务数据==;但在还原前需要首先校验脏写,对比数据库当前业务数据和after image,如果两份数据完全一致说明没有脏写,可以还原业务数据, 如果不一致就说明有脏写,需要转人工处理。

集群高并发情况下,如何保证分布式全局唯一ID的生成

集群高并发环境下,唯一id需求:

​ 1.全局唯一

​ 2.趋势递增

​ 3.单调递增

​ 4.信息安全

​ 5.含时间戳

一般情况下,我们的解决方案有三种:

UUID:无需网络生成,jdk自带,能保证唯一性;但是它无序,写入数据库的性能差;如果用作主键占用空间较多

数据库自增主键:mysql并发性并不好,而且,mysql易崩,集群环境下,横向扩容比较难

基于redis生成全局id策略:redis集群,步长设置为机器台数,这样实现每台机器跨台数增长,就满足集群高并发唯一id需求

高级解决方案:

雪花算法snowflake:

结构:

​ 总共64位,1位符号位,41位时间戳,10位工作进程id,12位序列号位,其中只有41位时间戳和10bit工作进程可以变,然后,工作进程id分为站点以及机器id各5位(实际可以自己更改),然后可以生成。

  • 毫秒数在高位,自增序列在低位,整个id趋势都是递增

  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性高,生成的ID性能也很高long型

  • 可以根据自身业务分配bit位,很灵活

    • 依赖机器时钟,如果机器时钟回拨,会导致重复id生成(百度开源的分布式唯一id生成器UidGenerator和leaf--美团点评分布式ID生成系统彻底解决了这个问题)
    • 在单机上是递增的,但是由于设计到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不适全局递增的情况