AlibabaCloud入坑笔记

Nacos注册中心

服务端安装

官网下载地址:https://nacos.io/zh-cn/docs/quick-start.html

解压安装包
进入bin目录
单机模式启动 sh startup.sh -m standalone
访问 localhost:8848/nacos
默认账号密码 nacos/nacos

客户端注册与发现

1. 添加nacos的依赖
        <!--添加nacos客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>


2. 在配置文件指定注册中心地址
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848


3. 在启动类用注解开启服务注册
@EnableDiscoveryClient

统一配置中心

1. 添加代码配置

1. 加入依赖
    <!--统一配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>


2. 配置bootstrap.yml  (它的优先级比application.yml高)
spring:
  application:
    name: order-service #在noas的名称
  cloud:
    nacos:
      config:
        server-addr: 192.168.200.100:8848 #Nacos配置中心地址
        file-extension: yaml #文件拓展格式
        group: risk #分组,一般用来区分不同的服务
        namespace: test #命名空间,用来区分不同环境(默认是public)
  #profiles:
  #  active: dev  

2. 先新建命名空间,命名空间id和namespace保持一致;配置文件名称和服务名称一致;grop也保持一致。

3. 通过url查看配置是否生效   http://192.168.200.100:8848/nacos/v1/cs/configs?dataId=order-service.yaml&group=risk&tenant=test

4. 代码里面通过   @RefreshScope   动态刷新Nacos配置

 

Feign远程调用

1. 引入依赖包
        <!--引入feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>


2. 在主函数开启feign
@EnableFeignClients


3. 编写远程调用接口(指定好其他服务的注册名称和路径)
@FeignClient(value = "video-service",path = "/video")
public interface VideoFeign {
    @RequestMapping("find_by_id")
    JsonResult findById(@RequestParam("videoId") int videoId);
}


4. 不设置也可以(feign默认1s超时,我们这里设置为5s)
ribbon:
  ReadTimeout: 5000 #超时时间
  ConnectTimeout: 5000 #连接时间
  MaxAutoRetries: 0 #同一台实例最大重试次数,不包括首次调用
  MaxAutoRetriesNextServer: 0 #重试负载均衡其他的实例最大重试次数,不包括首次调用
  OkToRetryOnAllOperations: false  #是否所有操作都重试

熔断降级

1. 导入依赖
        <!--引入sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>


2. 开启Feign对Sentinel的支持
feign:
  sentinel:
    enabled: true


3. 配置feign容错类
@FeignClient(value = "video-service",path = "/video", fallback = VideoServiceFallback.class)
public interface VideoFeign {


4. 创建容错类, 实现容错逻辑, 记得加注解 @Service
@Service
public class VideoServiceFallback implements VideoFeign {
    @Override
    public JsonResult findById(int videoId) {
        return JsonResult.error("feign的url错误,走了fallback");
    }
}

Sentinel流量哨兵

中文文档    https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

-Dserver.port=8080 控制台端口,sentinel控制台是一个spring boot程序,指定启动端口。
-Dcsp.sentinel.dashboard.server=localhost:8080 指把控制台后当作一个客户端,然后自动向该地址发送心跳包。
-Dproject.name=sentinel-dashboard 指定Sentinel控制台程序的名称

启动

nohup java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar &

访问 localhost:8080

默认账号密码 sentinel/sentinel

客户端接入

1. 导入依赖
        <!--引入sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>


2. 添加注册地址
spring:
  cloud:       
    sentinel:
      transport:        
        port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可,不同的服务用不同的端口     
        dashboard: localhost:8080 # 指定控制台服务的地址

默认是懒加载的,启动后,我们调用先服务接口,然后刷新
sentinel 面板就能看到了

流控规则

在簇点链路里面根据url配置,我们这是事根据qps来限流,然后快速刷新浏览器就会出现失败提示了。

熔断降级

慢调用比例(响应时间): 选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
  比例阈值:修改后不生效(这是一个bug,期待官方后续修复)
  熔断时长:超过时间后会尝试恢复
  最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
  比例阈值
  熔断时长:超过时间后会尝试恢复
  最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断
异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断
  异常数:
  熔断时长:超过时间后会尝试恢复
  最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断

自定义异常降级

现在失败后,默认都是返回     Blocked by Sentinel (flow limiting)     字符串,比较不容易排查。我们可以自定义项目数据交互格式。AlibabCloud版本升级,自从v2.1.0到v2.2.0后就出现了不兼容问题。

  • 【旧版】实现UrlBlockHandler并且重写blocked方法
@Component
public class BaseUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
       //降级业务处理
    }
}
  • 【新版】实现BlockExceptionHandler并且重写handle方法
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class BaseBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {

        Map<String,Object> info = new HashMap<>();
        if(e instanceof FlowException){
            info.put("code",-1);
            info.put("msg","限流异常");
        }
        else if(e instanceof DegradeException){
            info.put("code",-2);
            info.put("msg","降级异常");
        }
        else if(e instanceof ParamFlowException){
            info.put("code",-3);
            info.put("msg","热点参数异常");
        }
        else if(e instanceof SystemBlockException){
            info.put("code",-4);
            info.put("msg","系统异常");
        }
        else if(e instanceof AuthorityException){
            info.put("code",-5);
            info.put("msg","授权异常");
        }
        //设置json返回
        httpServletResponse.setStatus(200);
        httpServletResponse.setHeader("content-type","application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(info));
    }
}

 

gateway网关

动态路由

1. 添加依赖
        <!--添加gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>



2. 配置路由规则
server:
  port: 7100

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  #开启网关拉取nacos的服务
      routes: #数组形式
        - id: order-service  #路由唯一标识
          uri: lb://order-service  #从nocas进行转发到指定服务
          #order: 1 #优先级,数字越小优先级越高
          predicates: #断言 配置路由规则
            - Path=/order/**
          filters: #过滤器,请求在传递过程中通过过滤器修改
            - StripPrefix=1  #转发到具体服务时候,自动去掉第一层前缀(predicates的第一层地址)
        - id: video-service
          uri: lb://video-service
          predicates:
            - Path=/video/**
          filters:
            - StripPrefix=1
    nacos:
      discovery:
        server-addr: 192.168.200.100:8848 # nacos的地址
  zipkin:
    base-url: http://192.168.200.100:7200/ #zipkin地址
    discovery-client-enabled: false  #不用开启服务发现
    sleuth:
      sampler:
        probability: 1.0 #采样百分比

 

过滤器

import org.apache.commons.lang3.StringUtils;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * 网关不要加太多业务逻辑,否则会影响性能
 */
@Component
public class TestFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("TestFilter。。。。。。");
        //写业务逻辑
        String token = exchange.getRequest().getHeaders().getFirst("token");

        if(StringUtils.isBlank(token)){
            //停止请求
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //继续往下执行
        return chain.filter(exchange);
    }

    //数字越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

 

链路追踪

Sleuth链路追踪

1. 各个微服务添加依赖

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


参数解析:

[order-service,6f3487a81c89682e,7db08a9ad1ea24d6,false]
第一个值,spring.application.name的值
第二个值,6f3487a81c89682e,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
第三个值,7db08a9ad1ea24d6、spanid 基本的工作单元,获取元数据,如发送一个http
第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。

然后进行链路调用的时候,就会出现如下日志了:

zipkin仪表盘

安装与持久化

1. 官网以及服务端下载    https://zipkin.io/pages/quickstart.html

2. 启动服务端    默认端口是9411  可以通过    http://127.0.0.1:9411/zipkin/    进行访问

  java -jar zipkin-server-2.12.9-exec.jar

3. 持久化

  日志数据默认是存在内存中的,zipkin重启后数据就没了。我们可以持久化到mysql、es 中。

--STORAGE_TYPE    指定外部存储源 mysql/es
--MYSQL_HOST      mysql地址
--MYSQL_TCP_PORT  数据库端口
--MYSQL_DB        数据库名称
--MYSQL_USER      账号
--MYSQL_PASS      密码

nohup java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=root --MYSQL_PASS=root &
CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `remote_service_name` VARCHAR(255),
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
  PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
 
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
 
CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
 
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
 
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT,
  `error_count` BIGINT,
  PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
zipkin_log

启动客户端

1. 加入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>


2.
配置地址和采样百分比配置
spring:
  application:
    name: api-gateway
  zipkin:
    base-url: http://127.0.0.1:9411/ #zipkin地址
    discovery-client-enabled: false  #不用开启服务发现
  sleuth:
    sampler:
      probability: 1.0 #采样百分比 

默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。

 请求过后,直接可以在面板就能看到  链路详细信息 了。

 

Seata事务踩坑

版本一定要对应,不然会出现各种奇怪的错误

服务端版本:    
seata-server-1.1.0.tar.gz


客户端版本:
        <dependencies>
            <!--SpringBoot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--SpringCloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--AlibabaCloud-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--seata-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <version>2.2.1.RELEASE</version>
            </dependency>
        </dependencies>

服务端

1. 解压seata
  tar -zxvf seata-server-1.1.0.tar.gz
2. 创建日志文件夹
  cd seata && mkdir logs
3. 务必后台启动seata,否则关掉启动日志后,客户端会连接失败,出现 “can not register RM,err:register error,role:RMROLE,err:cost 30001 ms”
  nohup ./bin/seata-server.sh >log.out 2>1 &

 客户端

1. 全局异常处理(务必加入异常判断,否则在方法加上本地事务注解时,接口报错会返回数据库异常信息)

import com.wulei.common.pojo.Result;
import com.wulei.common.pojo.StatusCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Arrays;
@RestControllerAdvice
public class BaseExceptionHandler {
    public final Logger log = LoggerFactory.getLogger(this.getClass());
    /***
     * 异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result error(Exception e) {
        log.error("func [exceptionHandle] Exception [{} - {}] stackTrace[{}]",e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace()));
        // 务必加上这一截异常判断
        if(e instanceof TransactionSystemException) {
            return new Result(false, StatusCode.REMOTEERROR, "调用异常");
        }
        return new Result(false, StatusCode.ERROR, e.getMessage());
    }

    /**
     * 自定义异常(处理业务逻辑异常)
     * @param e
     * @return
     */
    @ExceptionHandler(RiskException.class)
    @ResponseBody
    public Result riskException(RiskException e) {
        log.error("func [RiskException] Exception [{} - {}] stackTrace[{}]",e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace()));
        return new Result(false, StatusCode.ERROR, e.getMessage());
    }
}

2. 配置切面拦截

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class SeataTransactionalAspect {

    private final static Logger logger= LoggerFactory.getLogger(SeataTransactionalAspect.class);

    @Before("execution(* com.wulei.*.service.*.*(..))")
    public void before(JoinPoint joinPoint) throws TransactionException {
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        if(method.isAnnotationPresent(GlobalTransactional.class)) {
            logger.info("拦截到需要分布式事务的方法," + method.getName());
            // 此处可用redis或者定时任务来获取一个key判断是否需要关闭分布式事务
            GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
            tx.begin();
            logger.info("创建分布式事务完毕" + tx.getXid());
        }
    }

    @AfterThrowing(throwing = "e", pointcut = "execution(* com.wulei.*.service.*.*(..))")
    public void doRecoveryActions(Throwable e) throws TransactionException {
        logger.info("方法执行异常:{}", e.getMessage());
        if (!StringUtils.isBlank(RootContext.getXID())) {
            GlobalTransactionContext.reload(RootContext.getXID()).rollback();
        }
    }
}

3. 配置注册信息

# seata
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 192.168.200.100:8091

 

 

posted @ 2020-12-21 14:57  吴磊的  阅读(3043)  评论(0编辑  收藏  举报
//生成目录索引列表