从0到1的Springcloud Alibaba项目,一篇入门!!!

1、新建项目

我们用maven管理项目

第一步:选择maven

从0到1的Springcloud Alibaba项目,一篇入门!!!_第1张图片

第二步:项目命名,项目路径

从0到1的Springcloud Alibaba项目,一篇入门!!!_第2张图片

第三步:进入项目,把src文件夹删掉(不删也没事,主要是用不到这个文件夹)

从0到1的Springcloud Alibaba项目,一篇入门!!!_第3张图片

2、引入项目依赖

在父项目pom文件加入下面依赖。

注:springboot项目版本与cloud版本有一定的对应关系,在官网可以看到,下面是对应的版本关系,选择合适的即可。

从0到1的Springcloud Alibaba项目,一篇入门!!!_第4张图片

本项目所用依赖版本:



    4.0.0

    org.example
    project_springcloud
    1.0-SNAPSHOT

    
        8
        8
        
        2.3.8.RELEASE
        
        Hoxton.SR10
        
        2.2.1.RELEASE
    

    
    
        
            
                org.springframework.boot
                spring-boot-dependencies
                ${spring.boot.version}
                pom
                import
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring.cloud.version}
                pom
                import
            
        
    

注意:

  1. 父工程dependencyManagement中管理的依赖在你们仓库中可能没有,所以要注释dependencyManagement先下载对应版本的依赖
  2. dependencyManagement只是声明依赖,并不实现引入,因此子项目需要显示声明需要用的依赖

3、Nacos服务注册和发现

3.1、Nacos下载与启动

模拟生产环境,采用Linux部署

第一步:官网下载nacos(Nacos | Nacos),下载不了的可以关注公众号【JavaCoding】回复nacos获取

从0到1的Springcloud Alibaba项目,一篇入门!!!_第5张图片

第二步:进入bin文件,修改startup.sh配置文件,下载的默认启动配置需要的内存太大,改小一点

从0到1的Springcloud Alibaba项目,一篇入门!!!_第6张图片

第三步:命令启动sh startup.sh -m standalone,注:这里standalone参数代表着单机模式运行,非集群模式

从0到1的Springcloud Alibaba项目,一篇入门!!!_第7张图片

控制台输出这样的内容代表启动成功,我们打开start.out日志查看内容:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第8张图片

3.2、服务注册

第一步:新建一个服务service1

要在父级目录下创建service1项目,具体步骤与上述创建父项目类似,过程省略,创建后的项目结构如下:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第9张图片

pom依赖:



    
        project_springcloud
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    service1

    
        8
        8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
            ${spring.boot.version}
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
            ${spring.cloud.alibaba.version}
            
                
                    nacos-client
                    com.alibaba.nacos
                
            
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
            ${spring.cloud.alibaba.version}
            
                
                    nacos-client
                    com.alibaba.nacos
                
            
        
        
        
            com.alibaba.nacos
            nacos-client
            1.4.2
        

    

第二步:登录我们启动的nacos地址,新建一个命名空间

从0到1的Springcloud Alibaba项目,一篇入门!!!_第10张图片

第三步:点击配置列表右侧加号,新建一个配置

从0到1的Springcloud Alibaba项目,一篇入门!!!_第11张图片

从0到1的Springcloud Alibaba项目,一篇入门!!!_第12张图片

第四步:yml中配置(注意这里项目中配置文件名一定要是bootstrap)

spring:
  application:
    name: service1
  cloud:
    nacos:
      discovery:
        # 你的服务器ip
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
      config:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
        prefix: service1
        group: DEFAULT_GROUP
        file-extension: yaml

最后启动项目:

我们可以看到,项目端口是用的我们nacos里配置里写好的端口

从0到1的Springcloud Alibaba项目,一篇入门!!!_第13张图片

点开nacos服务列表:

我们可以看到我们启动的服务已经注册进来了,这里的服务名就是我们项目里spring.application.name配置的名

从0到1的Springcloud Alibaba项目,一篇入门!!!_第14张图片

最后接口测试:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第15张图片

从0到1的Springcloud Alibaba项目,一篇入门!!!_第16张图片

注意:

我们这里的启动类并没有加@EnableDiscoveryClient注解,是因为从Spring Cloud Edgware开始,这个注解可以省略。但是maven依赖中必须要有spring-cloud-starter-alibaba-nacos-discovery服务注册的依赖。

但是值得注意的是,如果使用了Eureka作为注册中心,那么需要使用@EnableEurekaClient注解来启动Eureka客户端。因此,是否需要省略@EnableDiscoveryClient注解取决于你的具体需求和使用的服务注册中心类型。

从0到1的Springcloud Alibaba项目,一篇入门!!!_第17张图片

4、Gateway网关服务

4.1、项目引入

第一步:新建Gateway项目,过程省略,项目结构如下

从0到1的Springcloud Alibaba项目,一篇入门!!!_第18张图片

第二步:引入pom依赖



    
        project_springcloud
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    gateway

    
        8
        8
    

    
        
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
            ${spring.cloud.alibaba.version}
            
                
                    nacos-client
                    com.alibaba.nacos
                
            
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
            ${spring.cloud.alibaba.version}
            
                
                    nacos-client
                    com.alibaba.nacos
                
            
        
        
        
            com.alibaba.nacos
            nacos-client
            1.4.2
        

    

第三步:nacos新增配置文件,与service1一样,端口我们设置为8002

从0到1的Springcloud Alibaba项目,一篇入门!!!_第19张图片

从0到1的Springcloud Alibaba项目,一篇入门!!!_第20张图片

第四步:配置文件

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
      config:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
        prefix: gateway
        group: DEFAULT_GROUP
        file-extension: yaml

第五步:启动项目

从0到1的Springcloud Alibaba项目,一篇入门!!!_第21张图片

在nacos里可以看到我们的Gateway服务已经注册进来了

从0到1的Springcloud Alibaba项目,一篇入门!!!_第22张图片

4.2、路由配置

上面步骤我们只是把Gateway服务启动,并没有体现出Gateway的作用,我们想要看到的是通过访问不通的接口,Gateway可以帮我自动找到到相应的服务。

简单介绍Gateway配置项:

路由(Route):由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则转发到该路由。

(1)id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)

(2)uri:请求最终被转发到的目标地址

(3)order: 路由优先级,数字越小,优先级越高

(4)predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中

(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改

断言(Predicate):参照 Java8 的新特性Predicate,允许开发人员匹配 HTTP 请求中的任何内容,比如请求头或请求参数,最后根据匹配结果返回一个布尔值。

Predicate 来自于 Java8 的接口。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。

Predicate 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。Spring Cloud Gateway 内置了许多 Predict,这些 Predict 的源码在 org.springframework.cloud.gateway.handler.predicate 包中,有兴趣可以阅读一下。

过滤器(Filter):可以在返回请求之前或之后修改请求和响应的内容。

Gateway 过滤器的生命周期:

PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

Gateway 过滤器从作用范围可分为两种:

GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)

GlobalFilter:应用到所有的路由上(无需配置,全局生效)

有两种方式可以实现Gateway路由配置:

准备工作:我们先按照service1服务创建一个service2服务,端口用8003,创建后的项目架构:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第23张图片

从0到1的Springcloud Alibaba项目,一篇入门!!!_第24张图片

service1和service2接口方法如下:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第25张图片

第一种:yml配置

在Gateway项目的yml中加入以下配置:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
      config:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
        prefix: gateway
        group: DEFAULT_GROUP
        file-extension: yaml
    # gateway 配置
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
          # 路由唯一标识
        - id: gateway-service1
          # 要转发到哪个服务,我们这里不直接写ip:端口的形式,因为一旦服务的域名或IP地址发生修改,路由配置中的 uri 就必须修改
          # 使用了lb形式,从注册中心负载均衡的获取uri
          uri: lb://service1
          # 设置断言
          predicates:
              # 满足/service1的请求路径会路由到localhost:8001服务
            - Path=/service1/**
          # 过滤器
          filters:
            # 去除原始请求路径中的前1级路径,也就是/service1
            - StripPrefix=1

          # 路由唯一标识
        - id: gateway-service2
          # 要转发到哪个服务
          uri: lb://service2
          # 设置断言
          predicates:
            # 满足/service1的请求路径会路由到localhost:8001服务
            - Path=/service2/**
          # 过滤器
          filters:
            # 去除原始请求路径中的前1级路径,也就是/service1
            - StripPrefix=1

启动项目:

通过网关访问service1服务的接口:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第26张图片

通过网关访问service2服务的接口:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第27张图片

结论:虽然两个服务接口地址一样,但通过网关配置,我们可以根据不同的请求路径前缀来访问不通的服务

第二种:代码配置

通过代码我们可以更加灵活的对Gateway进行配置,面对的场景也更加复杂。

第一步:先把yml中Gateway配置注释

从0到1的Springcloud Alibaba项目,一篇入门!!!_第28张图片

第二步:添加自定义配置类

从0到1的Springcloud Alibaba项目,一篇入门!!!_第29张图片

package com.javacoding.config;

import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.*;

@Component
public class MyRouteDefinitionRepository implements RouteDefinitionRepository {
    @Override
    public Flux getRouteDefinitions() {
        List routeDefinitions = new ArrayList<>();
        // service1的route配置
        RouteDefinition service1 = setService1();
        // service2的route配置
        RouteDefinition service2 = setService2();
        routeDefinitions.add(service1);
        routeDefinitions.add(service2);
        return Flux.fromIterable(routeDefinitions);
    }

    private RouteDefinition setService2() {
        RouteDefinition definition = new RouteDefinition();
        // id
        definition.setId("service2");
        URI uri = UriComponentsBuilder.fromUriString("lb://service2").build().toUri();
        // uri
        definition.setUri(uri);

        PredicateDefinition predicate = new PredicateDefinition();
        predicate.setName("Path");

        Map predicateParams = new HashMap<>(8);
        predicateParams.put("pattern", "/service2/**");
        predicate.setArgs(predicateParams);

        //定义Filter
        FilterDefinition filter = new FilterDefinition();
        filter.setName("StripPrefix");
        Map filterParams = new HashMap<>(8);
        //该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类
        filterParams.put("_genkey_0", "1");
        filter.setArgs(filterParams);

        definition.setFilters(Collections.singletonList(filter));
        definition.setPredicates(Collections.singletonList(predicate));
        return definition;
    }

    private RouteDefinition setService1() {
        RouteDefinition definition = new RouteDefinition();
        // id
        definition.setId("service1");
        URI uri = UriComponentsBuilder.fromUriString("lb://service1").build().toUri();
        // uri
        definition.setUri(uri);

        PredicateDefinition predicate = new PredicateDefinition();
        predicate.setName("Path");

        Map predicateParams = new HashMap<>(8);
        predicateParams.put("pattern", "/service1/**");
        predicate.setArgs(predicateParams);

        //定义Filter
        FilterDefinition filter = new FilterDefinition();
        filter.setName("StripPrefix");
        Map filterParams = new HashMap<>(8);
        //该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类
        filterParams.put("_genkey_0", "1");
        filter.setArgs(filterParams);

        definition.setFilters(Collections.singletonList(filter));
        definition.setPredicates(Collections.singletonList(predicate));
        return definition;
    }

    @Override
    public Mono save(Mono route) {
        return null;
    }

    @Override
    public Mono delete(Mono routeId) {
        return null;
    }
}

第三步:启动项目测试

从0到1的Springcloud Alibaba项目,一篇入门!!!_第30张图片

从0到1的Springcloud Alibaba项目,一篇入门!!!_第31张图片

总结:这些只是基础的配置,实际生产中还会有自定义全局过滤器等。

5、Feign服务调用

5.1、服务调用

当我们在service1调用service2中的接口时,传统的项目我们直接用maven引入service2项目就可以直接调用。但在微服务中,各个项目都是独立存在的,我们如果用http去调用每次都非常繁琐,而且不方便拓展。因此Feign就是解决这个问题。

第一步:创建Feign项目,过程省略,项目结构如下

从0到1的Springcloud Alibaba项目,一篇入门!!!_第32张图片

第二步:pom依赖



    
        project_springcloud
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    feign

    
        8
        8
    

    
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
    

第三步:写feign客户端接口

package com.javacoding.service;

import com.javacoding.service.impl.Service2ClientBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "service2", fallback = Service2ClientBack.class)
public interface Service2Client {

    @RequestMapping("/test/msg")
    public String test(@RequestParam("msg") String msg);

}

说明:

1. @FeignClient注解标识当前接口是feign调用接口,value代表哪个服务调用接口,这里我们想调用service2服务,所以写service2(为什么写service2,因为service2服务里的配置文件spring.application.name,与他保持一致);fallback是如果服务调用失败,我该怎么处理,这个类就是处理这个问题的。

2. 接口请求路径,请求名和方法与service2要调用的接口保持一致。

package com.javacoding.service.impl;

import com.javacoding.service.Service2Client;
import org.springframework.stereotype.Component;

/**
 * 调用失败返回
 */
@Component
public class Service2ClientBack implements Service2Client {
    @Override
    public String test(String msg) {
        return "service2服务调用失败!!!";
    }
}

第四步:在service1服务中通过feign实现service2接口请求响应

1、在service1的pom中加入feign依赖



    org.example
    feign
    1.0-SNAPSHOT



    org.springframework.cloud
    spring-cloud-starter-openfeign

2、启动类添加@EnableFeignClients注解

package com.javacoding;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

3、controller添加调用的方法

package com.javacoding.controller;

import com.javacoding.service.Service2Client;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/test")
public class TestController {
    
    @Resource
    private Service2Client service2Client;

    @GetMapping("/msg")
    public String test(@RequestParam("msg") String msg) {
        return "service1:" + msg;
    }

    /**
     * 通过feign调用service2的接口
     * 
     * @param msg
     * @return
     */
    @GetMapping("/feign/msg")
    public String test2(@RequestParam("msg") String msg) {
        String result = service2Client.test(msg);
        return "通过feign调用请求返回:" + result;
    }

}

4、测试

从0到1的Springcloud Alibaba项目,一篇入门!!!_第33张图片

总结:我们并没有在service1中引入service2的服务,也实现了调用service2服务中的接口。之后我们想要在微服务中引入其他服务只需写一个某某服务的接口(xxxClient)利用@FeignClient注解指明哪个服务,通过这个接口就可以去调用我们的目标服务的接口。

5.2、服务降级

前面我在@FeignClient注解里加了fallback参数。Fallback是通过Hystrix实现的, 所以需要开启Hystrix,spring boot application.properties文件配置feign.hystrix.enabled=true,这样就开启了Fallback。

service1的nacos中的yml配置:

server:
    port: 8001

feign:
  hystrix:
      enabled: true

service2中服务抛出个异常,模拟服务不可用:

package com.javacoding.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/msg")
    public String test(@RequestParam("msg") String msg) {
        throw new RuntimeException();
//        return "service2:" + msg;
    }

}

测试:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第34张图片

5.3、负载均衡

如果我们service2服务是非常重要的,我们通过会部署好几个防止其中一个崩溃导致服务不可用,那么service1在通过feign调用的时候到底调用了哪一个service2呢?如何把请求进行分配呢?

第一步:先把service2的nacos配置的端口注释掉,改为写在项目bootstrap.yml里面。经过测试发现,如果不写在项目bootstrap.yml里面,后续我们启动两个service2服务的时候会一直用nacos配置里的端口。

从0到1的Springcloud Alibaba项目,一篇入门!!!_第35张图片

spring:
  application:
    name: service2
  cloud:
    nacos:
      discovery:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
      config:
        server-addr: 114.115.xxx.xx:8848
        namespace: 900c8c5e-b442-4b61-abf4-90b640e643ea
        prefix: service2
        group: DEFAULT_GROUP
        file-extension: yaml
server:
  port: 8003

第二步:改造一下service2中的方法,返回信息中加入端口信息,方便区分请求的是哪个服务

package com.javacoding.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/msg")
    public String test(@RequestParam("msg") String msg, HttpServletRequest request) {
//        throw new RuntimeException();
        int serverPort = request.getServerPort();
        return "service2:" + msg + ",端口:" + serverPort;
    }

}

第三步:启动网关、service1、service2服务,然后复制service2更改端口启动,这样service2服务就启动了两个实例。

从0到1的Springcloud Alibaba项目,一篇入门!!!_第36张图片

所有服务启动后nacos里的实例信息如下:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第37张图片

第四步:测试接口

从0到1的Springcloud Alibaba项目,一篇入门!!!_第38张图片

总结:可以看到我们的请求在8003和8004端口来回切换,这就是默认的轮训策略。

拓展:除了默认的轮训还有哪些策略?

通常我们都是自定义的方式来配置我们的负载均衡策略,这样我们会有更多的操作空间。

在service1中我们自定义配置:

package com.javacoding.config;

import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class FeignBalanceConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getResTemplate() {
        return new RestTemplate();
    }

//    @Bean
//    public RoundRobinRule getRule() {
//        // 轮训
//        return new RoundRobinRule();
//    }

    @Bean
    public RandomRule getRule() {
        //随机策略
        return new RandomRule();
    }
}

测试随机策略:

从0到1的Springcloud Alibaba项目,一篇入门!!!_第39张图片

还有其他一些策略,不在一一演示了,配置如下:

@Bean
public AvailabilityFilteringRule getRule() {
    //首先会过滤掉故障机或者并发链接数超过阈值的服务器.剩余的机器轮询配置
     return new AvailabilityFilteringRule();
}


@Bean
public WeightedResponseTimeRule getRule() {
    //服务器影响时间越快,则权重越高
     return new WeightedResponseTimeRule();
}


@Bean
public BestAvailableRule getRule() {
    //最大可用策略,即先过滤出故障服务器后,选择一个当前并发请求数最小的
    return new BestAvailableRule();
}

ok,以上内容如果都学会并了解那代表你已经可以用springcloud微服务应对90%开发需求了。当然还有Hystrix、Ribbon组件,虽然没讲如何用,但这两个组件Feign都已经很好了集成了,如果你面对的是非常复杂的开发场景,那也可以单独去配置这两个组件(原理都是一样的)。

希望这篇微服务项目可以带你很好的入门~~~

你可能感兴趣的:(学习记录,spring,cloud,spring,后端)