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

2019-09-26  本文已影响0人  Lemtasev

教练,我想……


  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

上一篇下一篇

猜你喜欢

热点阅读