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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM