美文网首页
什么都不会也能开发Spring Cloud!

什么都不会也能开发Spring Cloud!

作者: Lemtasev | 来源:发表于2019-09-26 11:24 被阅读0次

    教练,我想……


    1. 什么是Spring Cloud?
    2. Spring Cloud和Dubbo有什么不同?
    3. 怎么搭建Spring Cloud?

    1. 什么是Spring Cloud?

    “Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。”——度娘百科

    简单来讲,Spring Cloud就是一个基于Spring Boot架设的可以用来开发分布式微服务应用的框架。
    但是,它不仅仅可以用来开发微服务。


    2. Spring Cloud和Dubbo有什么不同?

    目前说到Spring Cloud就不得不提到Alibaba开源,最后贡献给Apache托管维护的分布式框架Dubbo,他们二者在目的上十分相近,但是实现原理以及开发方式又有许多不同。比如Dubbo是RPC,Spring Cloud是Http。
    如果从理论上说,Dubbo的数据传输方式为二进制码,而Spring Cloud则是序列化的Json字符串,所以效率后者肯定比前者低一些,但是曾有人测试这个差距在现代设备上已经微乎其微,所以我们可以忽略。
    从开发角度说,Dubbo的jar依赖过于繁杂,版本也是十分混乱,可能是由于之前断更了许多年,之后又交给了Apache管理,所以你在网上看到的关于Dubbo的教程,依赖一会儿是Alibaba的,一会儿是Apache的,出现冲突十分普遍。相反,Spring Cloud的依赖则十分统一,即使版本繁杂,只要你指定了一个版本,也基本上不会出现jar包依赖冲突的问题。
    撇开依赖问题,从开发角度说,两者各有优势,但总体上Dubbo难度略高一丢丢。


    3. 怎么搭建Spring Cloud?

    3.1 创建一个注册中心

    首先我用的IDE是idea,点File > new > Project...,弹出窗口后,左侧选择Spring Initializr(Spring初始化),右侧选择JDK,Next
    下一页填写项目相关信息,创建一个名为spring-cloud-demo的,空的Spring Boot项目,作为整个工程的父项目。你可以删掉src,因为父项目只是作为一个壳,留下一个pom.xml即可。
    为什么我不之间创建Maven项目呢?很简单,懒。因为我们的父项目要以Spring Boot为父项目,方便后续开发。
    另外提一下,我这里创建的Spring Boot版本号为2.1.6.RELEASE,你可以选用其他版本,但后续其他引用版本请自行更改。
    现在右键刚才创建的项目,new一个Module,还是选择Spring Initializr,名字叫scdemo-server,作为服务的注册中心。
    scdemo-server请修改pom.xml,将parent修改为刚才创建的spring-cloud-demo
    例如我的如下:

        <parent>
          <groupId>com.yq</groupId>
          <artifactId>scdemo-cloud-demo</artifactId>
          <version>0.0.1-SNAPSHOT</version>
        </parent>
    

    请确保你填写的groupId和其他信息都符合自己几分钟前命名的。
    请在dependencies标签下引入eureka的pom,例如:

        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    

    这时你的maven可能会报错,因为我们没有指定版本,这里之所以创建一个父项目,就是为了统一一个全局的版本号,避免因为各个项目引入不同版本的jar包引发麻烦。
    别着急,打开刚才父项目的pom.xml,添加一个dependencyManagement,这个标签属于project的直接子标签,请不要写错了位置。并且dependencyManagement中只是声明全局引用jar包的版本,并非真的把jar引入了项目。

      <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    

    在这里我们限定了spring-cloud全局版本号为Greenwich.RELEASE,即未来子项目中只要不特意声明<version>,都会默认遵循此版本。
    如此一来,应该可以成功导入spring-cloud-starter-netflix-eureka-server这个jar包了,并且从idea的MavenProjects窗口可以看到其版本号为2.1.0.RELEASE

    eureka-server版本
    此时,请在你的Spring Boot启动入口类上加上@EnableEurekaServer注解,以启动注册中心服务。
    打开application.yml(如果你没有这个文件,请在resources目录下新建这个文件,当然你也可以自行改成properties格式)文件,添加如下配置:
    server:
      port: 8761
    spring:
      application:
        name: spring-cloud-server
    eureka:
      instance:
        hostname: 127.0.0.1
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    

    run你的程序,然后浏览器访问http://127.0.0.1:8761/就可以看到注册中心的页面,这个页面显示了当前已注册服务和其他一些系统信息,当然,现在啥服务都没有。
    这就相当于Dubbo你启动了一个Zookeeper或者Nacos作为注册中心一样。

    3.2 创建一个服务提供者Provider

    为了统一消费者和提供者的接口定义,避免不必要麻烦的发生概率,这里我们写controller时会才用接口和实现的方式,将controller作为实现类,与接口分开打包,这样在需要调用的地方会方便很多。你现在看不懂这一段,很正常。
    再到父工程下new一个Module,名字就叫scdemo-api吧,同样请将父项目指向spring-cloud-demo
    pom.xml中添加如下依赖:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    

    其中spring-boot-starter-web是引入了web框架,因为我们的服务提供者对外提供了http服务。spring-cloud-starter-openfeign是一个访问http请求的客户端,原属于Netflix,后被整合到Spring Cloud集合下,衍生出openfeign,其实是一个东西。
    在src下创建一个名为ITestCloudService的接口,形如:

    @FeignClient(name = "spring-cloud-provider", path = "/api/v1/test")
    public interface ITestCloudService {
    
        @GetMapping("/t")
        Map<String, Object> testFnFeign();
    
    }
    

    其中@FeignClient声明这是一个Feign客户端可访问的接口,参数name指的是注册中心服务的名称,path指的是接口映射的前缀,类似于RequestMapping。
    然后删除这个项目的启动类,因为这个项目只作为接口依赖,并不会单独启动服务。同时请修改父项目的pom.xml,将这个接口声明依赖添加到全局版本声明中,至此你的父项目应该包含如下定义:

        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.yq</groupId>
                    <artifactId>scdemo-api</artifactId>
                    <version>0.0.1-SNAPSHOT</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    

    紧接着,再新建一个Module,这里就叫scdemo-provider吧。
    这个项目的pom.xml也请修改父项目,并且引入如下依赖:

            <dependency>
                <groupId>com.yq</groupId>
                <artifactId>scdemo-api</artifactId>
            </dependency>
            <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.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    

    请在src下新建一个TestCloudServiceImpl类,实现ITestCloudService接口,如果你无法importITestCloudService接口,请检查pom依赖是否正确。
    TestCloudServiceImpl类内容形如:

    @RestController
    @RequestMapping("/api/v1/test")
    public class TestCloudServiceImpl implements ITestCloudService {
        @Value("${server.port}")
        private String serverPort;
        @Value("${eureka.instance.appname}")
        private String eurekaInstanceAppName;
        @Override
        public Map<String, Object> testFnFeign() {
            Map<String, Object> map = new HashMap<>();
            map.put("serverPort", serverPort);
            map.put("eurekaInstanceAppName", eurekaInstanceAppName);
            map.put("me", "provider");
            return map;
        }
    }
    

    这里注入的2个Value是为了鉴别测试结果,接口的内容也很简单,主要说一下Spring的@RestController@RequestMapping这两个注解,并非只能写到controller上。联系上刚才写的Interface,这个‘Controller’的接口映射是@RequestMapping("/api/v1/test")+@GetMapping("/t"),也就是/api/v1/test/t
    接下来是provider的配置文件,application.yml

    server:
      port: 8762
    spring:
      application:
        name: spring-cloud-provider
    eureka:
      instance:
        appname: ${spring.application.name}
      client:
        service-url:
          defaultZone: http://127.0.0.1:8761/eureka/
    

    请启动Spring Boot的程序入口,然后访问http://127.0.0.1:8762/api/v1/test/t
    如果没问题,你将拿到一个返回结果:

    {"me":"provider","serverPort":"8762","eurekaInstanceAppName":"spring-cloud-provider"}
    

    至此服务提供者已经部署完成。

    3.3 创建一个消费者consumer

    重复内容就不多说了,修改pom.xml符项目,添加如下依赖:

            <dependency>
                <groupId>com.yq</groupId>
                <artifactId>scdemo-api</artifactId>
            </dependency>
            <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.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    

    可以看到,这里也引入了scdemo-api这个依赖,但并不是为了去实现它,而是调用它。
    消费者配置文件application.xml如下:

    server:
      port: 8763
    spring:
      application:
        name: spring-cloud-consumer
    
    eureka:
      instance:
        appname: ${spring.application.name}
      client:
        service-url:
          defaultZone: http://127.0.0.1:8761/eureka/
    

    请注意,消费者调用服务有两种可选方式。
    第一种RestTemplate,如需要使用,请在Spring Boot的配置类中注入Bean,可参考我的注入:

        @Bean
        @LoadBalanced // 开启负载均衡
        public RestOperations restTemplate(RestTemplateBuilder builder) {
            return builder.build();
        }
    

    第二种是使用Feign客户端,请在Spring Boot的配置类上加上@EnableFeignClients(basePackages = "com.yq.scdemo.api.*"),用来启用Feign客户端,其中basePackages指明扫描API接口的包名,即之前在那个scdemo-api项目下创建的interface的包名。
    因为Spring Boot默认扫描本项目启动类目录下的注解,如果API interface的包名在此之外,需要额外指定。
    本示例同时演示了2种客户端工具,实际情况请自行选择。
    最后来编写消费者的测试controller:

    @RestController
    public class TestConsumerController {
    
        @Autowired
        private RestOperations restTemplate;
    
        @Autowired
        private ITestCloudService testCloudService;
    
        @GetMapping("/rest")
        public Map testFnRest() {
            Map map = restTemplate.getForObject("http://spring-cloud-provider/api/v1/test/t", Map.class);
            map.put("me", "consumer");
            return map;
        }
    
        @GetMapping("/feign")
        public Map testFnFeign() {
            Map<String, Object> map = testCloudService.testFnFeign();
            map.put("me", "consumer");
            return map;
        }
    
    }
    

    /rest/feign分别演示了两种客户端的调用方式。
    其中rest的调用是写死的url,不方便维护,url中spring-cloud-provider为服务名,后面是接口地址。而feign的调用方式在我们提取出公共API Interface之后,极其类似Dubbo的开发模式,消费端无需关心接口地址和其他声明,因为这些都是服务端开发controller时顺便定义的。
    接下来启动消费者,如果没问题,
    访问http://127.0.0.1:8763/rest
    你将通过restTemplate方式调用云服务。
    访问http://127.0.0.1:8763/feign
    使用的则是feign客户端方式。
    当然,在这里因为调用的是同一个服务,所以返回结果都是:

    {"me":"consumer","serverPort":"8762","eurekaInstanceAppName":"spring-cloud-provider"}
    

    3.4 测试

    当这里,基本的Spring Cloud服务已经搭建完成,但是既然是微服务,怎么能不测试分布式呢?
    我们没有钱买许多电脑部署provider,但是我们可以模拟。
    接下来,请自行打开scdemo-provider项目的application.yml,修改

    server:
      port: 填写任何一个未被占用的端口号
    

    然后依据自己电脑的性能,开启多个provider服务。
    访问Eureka注册中心页面http://127.0.0.1:8761/,你可以看到自己已经启动的各项服务,请至少启动2个吧?

    已注册列表.jpg
    第一行是消费者,第二行是服务提供者,这里我只启动了2个provider,一个是8762端口,一个是8764端口。
    由于之前的配置中已经开启了负载均衡,所以此时再访问http://127.0.0.1:8763/rest或者http://127.0.0.1:8763/feign,你都将按照一定规则请求到2个不同的服务上,可以通过端口号来判断。至于负载均衡的规则,有兴趣的话可以自行研究并配置尝试。

    附录

    项目源码已提交至github,欢迎star
    https://github.com/lemtasev/spring-cloud-demo

    相关文章

      网友评论

          本文标题:什么都不会也能开发Spring Cloud!

          本文链接:https://www.haomeiwen.com/subject/rtrjuctx.html