Spring Cloud Alibaba 入门实践

    科技2023-09-17  97

    前言

    SpringCloudAlibaba实际上对我们的SpringCloud2.x和1.x实现拓展组件功能,相当于对SpringCloud 一代中的一些组件做了一些替代和补充。

    SpringCloud一代SpringCloudAlibaba注册中心Eurekanacos消息中间件默认三方rabbitmqRocketMq分布式事务解决方案第三方替代方案:2pcSeata分布式配置中心SpringCloudConfignacos熔断降级HystrixSentinel网关ZuulGateway

    这篇文章主要针对 注册中心、配置中心 nacos ,熔断降级 Sentinel 和网关Gateway进行实践。

    准备工作

    与一代Spring Cloud 不同,nacos 和 熔断降级展示面板都需要通过部署方式,不再由我们搭建模块来使用。我这里直接使用docker 来对nacos 和sentinel-dashboard 进行部署搭建。

    一、安装Docker

    wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo yum -y install docker-ce-18.06.1.ce-3.el7 systemctl enable docker && systemctl start docker docker --version

    二、搭建nacos

    2、1 拉取镜像文件

    docker pull nacos/nacos-server

    2、2 创建本地的映射文件,custom.properties

    mkdir -p /root/nacos/init.d /root/nacos/logs touch /root/nacos/init.d/custom.properties

    2、3 在custom.properties文件中写入以下配置

    management.endpoints.web.exposure.include=*

    2、4创建容器:使用standalone模式并开放8848端口,并映射配置文件和日志目录,数据库默认使用 Derby(这里直接使用最为方便一种方式)

    docker run -d -p 8848:8848 -e MODE=standalone -e PREFER_HOST_MODE=hostname -v /root/nacos/init.d/custom.properties:/home/nacos/init.d/custom.properties -v /root/nacos/logs:/home/nacos/logs --restart always --name nacos nacos/nacos-server

    2、5在页面上输入 http://ip:8848/nacos/ 用户名默认是 nacos 密码 nacos

    三、搭建sentinel-dashboard

    3、1拉取镜像

    docker pull bladex/sentinel-dashboard

    3、2 通过镜像运行容器 (通过这种方式页面展示的端口为 8858 API端口为:8719)

    docker run --name sentinel -d -p 8858:8858 -d ec702979af42

    3、3 页面上输入http://IP:8858 用户名默认是 sentinel 密码 sentinel

    代码实践

    一、搭建maven 父目录

    1、搭建父文件pom.xml固定版本号

    <groupId>com.yin</groupId> <artifactId>springcloud-alibaba</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>nacos</module> <module>nacos_consumer</module> <module>gateway</module> </modules> <!-- 统一管理jar包版本 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> <springboot.version>2.2.2.RELEASE</springboot.version> </properties> <dependencyManagement> <dependencies> <!-- spring boot 2.2.2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud Hoxton.SR1 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud 阿里巴巴 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> </dependencies> </dependencyManagement>

    二、搭建服务提供者

    2、1 服务提供者pom.xml 文件

    <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 服务注册发现功能 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.9.0.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>

    2、2 服务提供者application.yml 配置文件

    spring: application: name: nacos-discovery-provider profiles: active: dev cloud: nacos: discovery: server-addr: ip:8848 # server server: port: 9090

    2、3 启动方法添加@EnableDiscoveryClient注解

    @SpringBootApplication @EnableDiscoveryClient public class NacosApplication { public static void main(String[] args) { SpringApplication.run(NacosApplication.class, args); } }

    2、4 提供controller 供consumer 测试 2、5 服务启动(可以看到服务提供者已经注册上去了)

    三、搭建服务消费者

    (使用nacos服务注册发现功能、nacos配置中心 ,fegin 的调用、Sentinel 的 熔断降级 )

    3、1 服务提供者pom.xml 文件

    <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 服务注册发现功能 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.9.0.RELEASE</version> </dependency> <!-- nacos配置中心 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>0.9.0.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--Sentinel 的 熔断降级--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>0.9.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--fegin 的调用--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>

    3、2、配置文件application.yml

    spring: application: name: nacos-discovery-consumer profiles: active: dev cloud: nacos: discovery: server-addr: 120.76.142.68:8848 config: #配置中心的地址 server-addr: 120.76.142.68:8848 #分组 group: DEFAULT_GROUP #类型 file-extension: properties sentinel: transport: # 默认为8719,如果被占用会自动+1,直到找到为止 port: 8719 dashboard: 120.76.142.68:8858 eager: true # server server: port: 9091

    构建bootstrap.yml 配置文件

    bootstrap与application的区别: bootstrap.yml 用于应用程序上下文的引导阶段。application.yml 由父Spring ApplicationContext加载。

    可以实现动态实现@RefreshScope;可以对配置内容进行监听,察觉到内容被编辑之后会立刻刷新,而不用重启服务器。

    spring: application: name: nnacos-discovery-consumer cloud: nacos: discovery: #nacos 注册中心地址 server-addr: 120.76.142.68:8848 #是否动态加载 enabled: true config: #配置中心的地址 server-addr: 120.76.142.68:8848 #分组 group: DEFAULT_GROUP #类型 file-extension: properties

    3、3 启动方法添加@EnableDiscoveryClient、@EnableFeignClients注解

    @SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class NacosConsumerApplication { public static void main(String[] args) { SpringApplication.run(NacosConsumerApplication.class, args); } }

    3、4 使用openFegin 调用provider 代码

    @Component //写provider application name @FeignClient(name = "nacos-discovery-provider") public interface HelloService { @RequestMapping(value = "/test", method = RequestMethod.GET) public String test(@RequestParam("id") String id); } @RestController //配置自动刷新使用nacos 的配置配置代码 @RefreshScope @NacosConfigurationProperties(dataId = "nacos-discovery-consumer", autoRefreshed = true) public class ConsumerController { //application.yml 并未配置,通过nacos 进行配置 @Value("${nacos.config:1}") private String nacosConfig; @Resource private HelloService helloService; @GetMapping("/getProviderData") public String getProviderData(@RequestParam("id") String id) { return helloService.test(id) + nacosConfig; } }

    3、5 在nacos 配置中心配置配置文件

    在 Nacos Spring Cloud 中,dataId 的完整格式如下:

    ${prefix}-${spring.profile.active}.${file-extension}

    prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。 spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 p r e f i x . {prefix}. prefix.{file-extension} file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。

    3、6 测试

    由上图可以看到,springApplication 获取到nacos配置中心的内容,也调用provider 的内容。fegin 其实也集成负载均衡的策略。这个是一代springCould 内容,就不再详细展开了。

    3、7 Sentinel 的 熔断降级的集成

    a、项目启动成功之后,加载限流规则。

    @Component @Slf4j public class SentinelApplicationRunner implements ApplicationRunner { //url 连接 private static final String GETORDER_KEY = "getOrder"; @Override public void run(ApplicationArguments args) throws Exception { List<FlowRule> rules = new ArrayList<FlowRule>(); FlowRule rule1 = new FlowRule(); rule1.setResource(GETORDER_KEY); // QPS控制在1以内 rule1.setCount(1); // QPS限流 rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); log.info(">>>限流服务接口配置加载成功>>>"); } }

    b、注解形式配置管理Api限流

    @SentinelResource value参数:流量规则资源名称、 blockHandler 限流/熔断出现异常执行的方法 Fallback 服务的降级执行的方法

    @SentinelResource(value = “getOrderAnnotation”, blockHandler = "getOrderQpsException") @RequestMapping("/getOrderAnnotation") public String getOrderAnnotation() { return "getOrder接口"; } /** * 被限流后返回的提示 */ public String getOrderQpsException(BlockException e) { return "该接口已经被限流啦!"; }

    c、控制台形式管理限流接口

    四、搭建gateway 网关

    这个有使用gateway 的redis 限流,故引入spring-boot-starter-data-redis-reactive, 如果没有这个需求可以不引入。另外gateway 本省带有mvc组件,不再需要web依赖。

    4、1 gateway 的pom.xml文件

    <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.2.2.RELEASE</version> </dependency> <!-- 服务注册发现功能 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.9.0.RELEASE</version> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>

    4、2 application.yml 配置文件

    server: port: 80 spring: application: #spring application name name: my-gateway cloud: nacos: discovery: server-addr: ip:8848 gateway: globalcors: corsConfigurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE ###路由策略 routes: ###路由id - id: special_port ####转发http://localhost:9090/ uri: http://localhost:9090/ ###匹配规则 predicates: - Path=/special_port/** filters: - StripPrefix=1 - id: all_prot uri: lb://nacos-discovery-consumer predicates: - Path=/all_port/** filters: - StripPrefix=1 - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory args: key-resolver: "#{@ipKeyResolver}" #允许用户每秒处理多少个请求 redis-rate-limiter.replenishRate: 1 #令牌桶的容量,允许在一秒钟内完成的最大请求数 redis-rate-limiter.burstCapacity: 1 #不使用限流可以去掉redis redis: port: 6379 host: 127.0.0.1 #自定义不需要过滤url custom: gateway: token-filter: noAuthenticationRoutes: - /getUser - /logIn

    4、3 启动main函数

    @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } /** *使用ip限流的方式 * @return KeyResolver */ @Bean(name="ipKeyResolver") public KeyResolver keyResolver(){ return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { //1.获取请求request对象 ServerHttpRequest request = exchange.getRequest(); //2.从request中获取ip地址 //Ip地址 String hostString = request.getRemoteAddress().getHostString(); //3.返回 return Mono.just(hostString); } }; } }

    附:(这个与用户限流,使用一种) 用户限流 使用这种方式限流,请求路径中必须携带userId参数。

    @Bean KeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId")); }

    接口限流**(这个与用户限流,使用一种)** 获取请求地址的uri作为限流key。

    @Bean KeyResolver apiKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value()); }

    4、4 配置不需要过滤url

    @Component @ConfigurationProperties("custom.gateway.token-filter") public class TokenConfigurationBean { public List<String> getNoAuthenticationRoutes() { return noAuthenticationRoutes; } public void setNoAuthenticationRoutes(List<String> noAuthenticationRoutes) { this.noAuthenticationRoutes = noAuthenticationRoutes; } private List<String> noAuthenticationRoutes; }

    4、5 校验过滤器

    @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "Authorization"; private static final String loginURL = "http://localhost:9090/logIn"; @Resource private TokenConfigurationBean tokenConfigurationBean; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1.获取请求对象 ServerHttpRequest request = exchange.getRequest(); //2.获取响应对象 ServerHttpResponse response = exchange.getResponse(); //3.判断 是否为登录的URL 如果是 放行(判读是否需要过滤) if(tokenConfigurationBean.getNoAuthenticationRoutes().contains(request.getURI().toString())){ return chain.filter(exchange); } //4.判断 是否为登录的URL 如果不是 权限校验 //4.1 从头header中获取令牌数据 String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN); if(StringUtils.isEmpty(token)){ //4.2 从cookie中中获取令牌数据 HttpCookie first = request.getCookies().getFirst(AUTHORIZE_TOKEN); if(first!=null){ token=first.getValue();//就是令牌的数据 } } if(StringUtils.isEmpty(token)){ //4.3 从请求参数中获取令牌数据 token= request.getQueryParams().getFirst(AUTHORIZE_TOKEN); } if(StringUtils.isEmpty(token)){ //4.4. 如果没有数据 没有登录,要重定向到登录到页面 response.setStatusCode(HttpStatus.SEE_OTHER);//303 302 //location 指定的就是路径 response.getHeaders().set("Location",loginURL+"?From="+request.getURI().toString()); return response.setComplete(); } //5 解析令牌数据 ( 判断解析是否正确,正确 就放行 ,否则 结束) try { //Claims claims = JwtUtil.parseJWT(token); if (!"123" .equals(token)) { throw new RuntimeException("校验失败"); } } catch (Exception e) { e.printStackTrace(); //解析失败 response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } //添加头信息 传递给 各个微服务() request.mutate().header(AUTHORIZE_TOKEN,"Bearer "+ token); return chain.filter(exchange); } @Override public int getOrder() { return 0; } }

    输入http://localhost:80/all_port/getProviderData?id=1,会被转发其他对应路径

    携带了校验参数就可以正常获取到值

    Processed: 0.032, SQL: 8