跳至主要內容

SpringCloudAlibaba

HeChuangJun约 2397 字大约 8 分钟

利用Spring官方向导构建Spring Cloud Alibaba

  • idea->new Project->选择springboot版本->Web->Spring Web->create
  • 1.pom.xml加入版本参数和依赖管理器和nacos
<properties>
    <java.version>17</java.version>
    <!--1.加入下面2个-->
    <spring-cloud.version>2022.0.1</spring-cloud.version>
    <spring-cloud-alibaba.version>2022.0.0.0-RC1</spring-cloud-alibaba.version>
</properties>

<dependencies>
  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--3.引入nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <!--2.加入2个依赖管理器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 2.application.properties

server.port=80
# nacos参数
spring.application.name=first-microservice
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=nacos:8848
spring.cloud.nacos.discovery.namespace=public
newproject.png
newproject.png

nacos心跳机制

  • 健康检查机制:客户端主动上报或者服务端主动向客户端探测
  • 主流的注册中心,包括nacos的健康检查机制主要都采用了 TTL(Time To Live)机制,即客户端在⼀定时间没有向注册中心发送心跳,那么注册中心会认为此服务不健康,进而触发后续的剔除逻辑
  • Nacos 提供了两种服务类型供用户注册实例时选择,实例分为临时实例和永久实例。
    • 临时实例只是临时存在于注册中心中,会在服务下线或不可用时被注册中心剔除,
      • OpenAPI 进行服务注册。实际是用户根据自身需求调用 Http 接口对服务进行注册,然后通过 Http 接口发送心跳到注册中心。在注册服务的同时会注册⼀个全局的客户端心跳检测的任务。在服务⼀段时间没有收到来自客户端的心跳后,该任务会将其标记为不健康,如果在间隔的时间内还未收到心跳,那么该任务会将其剔除。
      • SDK 的注册方式实际是通过 RPC 与注册中心保持连接。客户端会定时的通过 RPC 连接向 Nacos 注册中心发送心跳,保持连接的存活。如果客户端和注册中心的连接断开,那么注册中心会主动剔除该 client 所注册的服务,达到下线的效果。
    • 永久实例健康检查机制:永久实例永不提除但是会心跳失败。Nacos 中使用 SDK 对于永久实例的注册实际也是使用 OpenAPI 的方式进行注册,这样可以保证即使是客户端下线后也不会影响永久实例的健康检查。Nacos 采用的是注册中心探测机制,注册中心会在永久服务初始化时根据客户端选择的协议类型注册探活的定时任务。Nacos 现在内置提供了三种探测的协议,即Http、TCP 以及 MySQL 。⼀般而言 Http 和 TCP 已经可以涵盖绝大多数的健康检查场景。
  • 对于集群下的服务,Nacos ⼀个服务只会被 Nacos 集群中的⼀个注册中心所负责,其余节点的服务信息只是集群副本,用于订阅者在查询服务列表时,始终可以获取到全部的服务列表。临时实例只会对其被负责的注册中心节点发送心跳信息,注册中心服务节点会对其负责的永久实例进行健康探测,在获取到健康状态后由当前负责的注册中心节点将健康信息同步到集群中的其他的注册中心。
  • 在 Nacos 中,服务的注册我们从注册方式维度实际可以分为两大类。
    • SDK RPC 连接进行注册,客户端会和注册中心保持链接。只需要和注册中心集群中的任意⼀台节点建立联系,那么由这个节点负责这个客户端就可以了。注册中心会在启动时注册⼀个全局的同步任务,用于将其当前负责的所有节点信息同步到集群中的其他节点,其他非负责的节点也会创建该客户端的信息,在非负责的节点上,连接类型的客户端,会有⼀个续约时间的概念,在收到其他节点的同步信息时,更新续约时间为当前时间,如果在集群中的其他节点在⼀段时间内没有收到不是自己的负责的节点的同步信息,那么认为此节点已经不健康,从而达到对不是自己负责的节点健康状态检查。
    • OpenAPI 进行 IP 和端口注册。OpenAPI 注册的临时实例也是通过同步自身负责的节点到其他节点来更新其他节点的对应的临时实例的心跳时间,保证其他节点不会删除或者修改此实例的健康状态。前面我们特别指明了是临时实例而没有说所有实例,你应该也可能会想到这种方式对于持久化节点会显得多余,永久实例会在被主动删除前⼀直存在于注册中心,那么我们健康检查并不会去删除实例,所以我们只需要在负责的节点永久实例健康状态变更的时候通知到其余的节点即可。
  • 最后。如果不是给集群中raft算法节点的leader发送请求,则该节点会转发到leader去,leader判断完再同步到当前节点

临时节点与永久节点配置

# 默认临时节点
# 开启查看心跳包日志
logging.level.root=debug
# 临时节点基于心跳包形式向Nacos发送,每5秒发送一次

logging.level.root=debug
# 开启永久节点
spring.cloud.nacos.discovery.ephemeral=false
# 一定要配置这个ip段,否则nacos服务端对客户端探测再多网卡情况下不知道对走哪个网卡
spring.cloud.inetutils.preferred-networks=192.168.31
# 永久节点每秒会接收到来自Nacos的探查

Raft算法与Distro算法

Raft算法用于服务数据持久化,保证一致性,首先低频,需要关注所有节点数据一致性
Distro算法用于服务注册,关注性能而不太关注数据库,而且Raft算法如果3个节点有2个挂了就挂了,显然不合理

客户端负载均衡器与Spring Cloud Loadbalancer

  • Spring Cloud Loadbalancer不用写ip只用写服务名
  • Spring Cloud Loadbalancer初始化时去注册中心查询可用服务列表并保存到内存,服务上下线时注册中心会推送消息Spring Cloud Loadbalancer并更新可用服务列表

OpenFeign

  • OpenFeign是Spring Cloud项目中的一个模块,它是基于Feign开发的,可以看作是对Feign的进一步封装和增强。Feign是一个声明式、模板化的HTTP客户端,它的目标是简化HTTP API开发。OpenFeign在Feign的基础上增加了对Spring MVC注解、Spring Boot自动配置等特性的支持,并且集成了Spring Cloud的服务发现和负载均衡功能。相比于Feign,OpenFeign更适合在Spring Cloud微服务架构中使用。
  • 1.依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 2.config类增加@EnableFeignClients注解
  • 3.一个基于Spring Cloud的Feign客户端接口。其中,@FeignClient(name = "provider-service")标注表示该接口是对名为"provider-service"的服务进行调用,这个服务名称通常是在配置文件中指定的,用于实现服务发现。
    接口中定义了三个方法,它们对应着服务提供者中的三个API接口。
package com.itlaoqi.consumerserviceopenfeign.feignclient;

import com.itlaoqi.consumerserviceopenfeign.dto.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.Map;

@FeignClient(name = "provider-service")
public interface ProviderServiceFeignClient {

    @GetMapping("/hello")
    Map<String, Object> sendMessage();

    @PostMapping("/user/{uid}")
    User createUser(@PathVariable("uid") String uid, @RequestBody User user);

    @GetMapping("/list")
    List<User> query(@RequestParam("page") int page, @RequestParam("rows") int rows);
}

  • 4.使用
package com.itlaoqi.consumerserviceopenfeign.controller;

import com.itlaoqi.consumerserviceopenfeign.dto.User;
import com.itlaoqi.consumerserviceopenfeign.feignclient.ProviderServiceFeignClient;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ConsumerController {
    @Resource
    private ProviderServiceFeignClient providerServiceFeignClient;
    @GetMapping("/do1")
    public List doSth1(){
        List<User> userList = providerServiceFeignClient.query(1, 10);
        return userList;
    }

    @GetMapping("/do2")
    public User doSth2(){
        String uid="10";
        User user = providerServiceFeignClient.createUser(uid, new User(uid, "testUser", "testPassword", "testNickname"));
        return user;
    }

    @GetMapping("/do3")
    public String doSth3(){
        String result = providerServiceFeignClient.hello();
        return result;
    }
}

gateway

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

  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: nacos:8848
    gateway:#自动根据服务名匹配
      discovery:
        locator:
          enabled: true
server:
  port: 80


gateway自定义谓词

# TokenHeader为谓词前缀,Springcloud会自动将RoutePredicateFactory后缀注册为谓词
package com.itlaoqi.gateway.predicate;

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;

@Component
public class TokenHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenHeaderRoutePredicateFactory.Config> {

    public TokenHeaderRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            Set<Map.Entry<String, List<String>>> headers = exchange.getRequest().getHeaders().entrySet();
            for(Map.Entry me : headers){
                if(me.getKey().equals(config.getHeaderName())){
                    return true;
                }
            }
            return false;
        };
    }

    public static class Config {
        private String headerName;

        public String getHeaderName() {
            return headerName;
        }

        public void setHeaderName(String headerName) {
            this.headerName = headerName;
        }
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("headerName");
    }
}

spring:
    ...
    gateway:
      discovery:
        locator:
          enabled: false
      routes:
        - id: route_user_service 
          uri: lb://gateway-user-service
          predicates:
            - Path=/user/** 
            - TokenHeader=Token # 谓词前缀与谓词名称
          filters:
            - StripPrefix=1

nacos config

grpc长连接监听

<!-- Nacos配置中心starter -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

tax-service-dev.yaml

spring:
  application:
    name: tax-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        file-extension: yml
        server-addr: 192.168.31.231:8848
        username: nacos
        password: nacos
logging:
  level:
    root: info

@RefreshScope

package com.itlaoqi.nacosconfig.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

@Configuration
@RefreshScope
@Data
public class Setting {
    @Value("${setting.upload-addr}")
    private String uploadAddr;
    @Value("${setting.path}")
    private String path;
}