Spring Cloud认知学习(一):Spring Cloud介绍与Eureka使用


这是一个Spring Cloud系列文章,它并不会讲解所有的知识点,它只是基于微服务的场景来逐步介绍常见组件的作用和意义,以及场景组件的整合。对于每个组件的知识并不会讲解太多,只讲常见的,目的是尽可能快速的对Spring Cloud的常用组件有一个基础的认知,有了认知之后,你就可以基于你面对的场景来单独学习某个组件,逐步丰满自己Spring Cloud的知识。


Spring Cloud的介绍

  • Spring Cloud是一个微服务架构,他有多种组件来管理微服务的方方面面。Spring Cloud是用于构建微服务开发和治理的框架的集合。
  • Spring Cloud是最热门的Java技术毋庸置疑。
  • 官网

微服务的介绍

  • 微服务是什么这里就不细化介绍了吧,应用服务化已经成为了趋势,简单的说就是把以前ALL-IN-ONE的一体应用的内部功能进行拆分,比如把短信功能单独出来作为一个可以提供给外部调用的服务,这样既提供了短信服务的复用性(其他的应用也能够复用这个功能),也使得对某个功能进行单独的负载能力提升称为可能(All In One 的如果想提升抢购功能的负载能力的话,采用部署多个服务端来提升抢购功能的负载能力的时候也会顺带提升了用户注册等的负载能力,这就额外浪费了资源)。
  • 在微服务的理论中,为了解耦,每个微服务使用单独的数据库(当然了,可能有些人会觉得是同名服务使用同一个数据库,微服务这东西概念其实还挺多争论的。)。
  • 马丁.福勒谈微服务

Spring Cloud出现的原因:

  • 当你把原来的应用服务化了之后,那么就会遇到这些服务的管理问题了,比如说检测服务的可用性、查看现在有什么服务、多个同名(同功能)的服务怎么做到负载均衡之类的问题。
  • Spring Cloud,基于Spring Boot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件。这些组件也不全是Spring 自己开发的,有一些是开源的组件,Spring进行了封装了而已(Spring Cloud Netflix主要来自Netflix OSS的开源组件,Spring Cloud Alibaba由阿里提供)。Spring Cloud像Spirng Boot 的starter一样屏蔽了复杂的配置,让我们能够通过简单的配置来进行微服务开发

常见场景:

Spring Cloud可以解决以下的常见几个场景(暂时只列举几个常见场景,其实微服务的方方面面基本都有解决方案)

  • 服务的开发:使用Spring Boot开发服务方便快速(Spring Boot其实不算Spring Cloud的内部组件,只能算一家人吧)
  • 服务的注册与发现:主要是Eureka提供,用于把微服务注册到Eureka中和让服务消费者从Eureka中获取可用微服务列表。(当然现在也有很多采用别的组件来做服务的注册与发现)
  • 负载均衡:主要由Ribbon提供,用于在服务消费者端进行负载均衡,从而把请求均衡到各个同名服务上。
  • API网关:主要由Zuul提供,提供统一的服务调用入口,所有的服务调用都通过Zuul来调用,提供请求转发、请求过滤等功能。
  • 服务的容错的处理--断路器:主要有Hystrix提供,用于解决微服务调用时发生服务熔断的问题。
  • 分布式服务配置:主要由Spring Cloud Config提供,用于解决多个微服务的统一配置和分发配置问题。(一个服务的配置可以从Config配置中心中拉取)
  • 数据监控、消息总线。。。。。。。

微服务的优劣势:

优势:

  • 微服务化之后,代码也偏向简单模块化,会比较容易理解,就好比你搞一个正经的商城难,你搞一个注册功能还不轻松吗?😀而且代码模块化之后也方便管理,比如可以专门让某个部门负责某个服务的开发;而且因为代码模块化解耦之后,新的需求可以仅仅针对某个服务来开发。
  • 一个服务就是一个独立的服务端,可以独立部署,与其他服务的耦合度低。(All In One中你改个注册功能都要整个服务端重启)
  • 服务可以动态扩容,由于是一个服务就是一个独立的服务端,所以可以很自然的水平扩容,部署同名服务的多个服务端。
  • 。。。

劣势:

  • 运维的难度提升了,以前的ALL IN ONE是一个服务端,现在由于微服务化了,导致了N多个服务端的产生,这些服务端的部署、监控都是问题。(所以这又引起了自动部署和监控的需求。)
    • 以一个之前我听说过的事故来说一下:某个证券公司准备更新某个代码,结果漏更了一台机上的服务端,然后这个服务端因为与其他服务的配合问题,导致了不断得“自动收单”,可以理解成游戏中商人对于拍卖行中的商品来自动低价扫货。
  • 测试的难度提高了,如果你手动测试过自己的BUG的话,那么你应该知道假如你的某一个方法修改了,那么你应该检测一下调用了这个方法的地方是否可能会发生BUG。服务的调用也是这样的,如果某个服务修改了,安全起见的话你还是应该测试所有相关的服务的。(所以这又引起了自动化测试的需求)
    • 如果单单对一个端的测试来说,测试难度是降低了,但对于整体业务流程的测试难度就是加大了。
  • 当然了,因为分布式而导致的分布式事务问题也让人头疼。
  • (但如此热门的技术,相信大家都心里很清楚他的优点是远大于缺点的。)

Spring Cloud版本问题

版本介绍

  • Spring Cloud 的版本名称并不是像其他的项目那样使用1.0之类的数字的,他使用了伦敦的地铁名来作为版本的名称,据说这是考虑到了Spring Cloud与子项目的依赖关系,为了避免版本名称的冲突和误解的。
  • 在版本的后面跟上SR(Service Release)代表这是一个稳定的版本,后面会跟一个数字,代表第几次迭代,例如Hoxton.SR3

与Spring Boot版本对应关系

  • Spring Cloud的版本与Spring Boot要考虑版本的兼容性,以下是Spring Cloud与Spring Boot版本的对应关系。
  • 请注意:Dalston和Edgware是对应1.5版本的Spring Boot,他不能使用在2.0上面。
Release版本(地铁名) 对应的Spring Boot版本
Hoxton 2.2.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

目前最新版本是Hoxton.SR3,但国内主流的应该还是Finchley或者Greenwich,所以下面的示例都将以Finchley版本为例,注意此版下的组件基本都是2.0.0版本的。


---分割线---学习的前提---分割线---

下面将对于Spring Cloud的常用组件来学习。如果你继续向下学习,请确保你已经掌握了Spring Boot知识

注意:
💡下面的学习只会贴出部分代码,其余的代码将在github上存储,会根据组件的学习来逐步commit代码所以可以根据代码的差异来比较每一版本代码的区别,了解增加新组件需要改动哪些代码,(从可以从历史记录中查看每一次commit的代码更新,来了解新增的组件修改了哪些代码),从而加深印象。当然,自己动手也很重要。

【PS:下面的代码,我后期才发现我写错了一个单词Service,有些地方写对,有些地方写错了,但并不影响代码运行。😓主要是因为大写的时候没检查好】


基础项目搭建

  • Spring Cloud 主要侧重服务的治理,微服务主要由Spring Boot开发,所以我们首先基于Spring Boot构建一个简单的微服务项目,后面通过逐步增加功能来学习Spring Cloud。

下面的示例代码请参考:微服务项目基础搭建

1.创建一个Maven父工程:

父工程的创建方法在IDEA中和Eclipse中有区别,这里给出IDEA的,Eclipse的可以自查(搜索Eclipse创建父工程即可)

20200408145147


20200408145233


父工程的目录结构如下: ![20200408145332](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145332.png)

在父工程的POM.XML中增加如下代码,锁定后面的依赖版本:

    <!--使用dependencyManagement锁定依赖的版本 start-->
    <dependencyManagement>
        <dependencies>
            <!--由于此时没有了sping boot starter 作为parent工程,需要使用spring-boot-dependencies来达到相似效果-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.6.RELEASE</version>
                <!--但要注意此处版本可能与spring cloud冲突,由于我选择了Finchley,所以这里用了2.0.6-->
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.4</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.31</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>

        </dependencies>

    </dependencyManagement>
    <!--使用dependencyManagement锁定依赖的版本 end-->

注:当你在父工程下创建了新的module,那么此时父工程的POM.xml就会增加内容:
在IDEA中,父工程下添加module的时候,父工程自动变packaing为pom。
20200408145859

2.创建一个共有依赖包:

如果你学过maven的分模块开发,你应该知道,一些被多个模块依赖的东西会被抽离到一个单独模块中,然后其他模块依赖这个模块即可。下面创建的就是包含了User实体(与数据表对应)的共有依赖包。


在父工程上面右键`New`->`Module`来在父工程下新建模块`spring-cloud-common-data`,选择模块为Maven方式,命名模块后,一路next(也可以在最后一步重新定义模块的存储路径): ![20200408152755](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408152755.png)

创建一个User类:

package com.progor.study.entity;
// 请注意类放在哪个包里面。

public class User {
    private Integer id;
    private String username;
    private String fullName;

    public User() {
    }

    public User(Integer id, String username, String fullName) {
        this.id = id;
        this.username = username;
        this.fullName = fullName;
    }

    // 篇幅考虑,省略setter,getter代码
}

执行一段SQL,我们的后面的测试创建数据:

DROP DATABASE IF EXISTS cloud01;
CREATE DATABASE cloud01 CHARACTER SET UTF8;
USE cloud01;
CREATE TABLE user
(
  id int PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(255),
  fullName  VARCHAR(255)
);

INSERT INTO user(username,fullName) VALUES('zhangsan','张三');
INSERT INTO user(username,fullName) VALUES('lisi','李四');
INSERT INTO user(username,fullName) VALUES('wangwu','王五');
INSERT INTO user(username,fullName) VALUES('zhaoliu','赵六');
INSERT INTO user(username,fullName) VALUES('lidazhuang','李大壮');

SELECT * FROM user;

3.创建一个服务提供者:


3.1 在父工程上面右键`New`->`Module`来在父工程下新建模块`spring-cloud-user-service-8001`。 ![20200408145419](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145419.png)
![20200408145450](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145450.png)

20200408145545

20200408145627

3.2 引入web开发相关依赖包:


    <dependencies>
        <!--引入公共依赖包 start-->
        <dependency>
            <groupId>com.progor.study</groupId>
            <artifactId>spring-cloud-common-data</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--引入公共依赖包 end-->
        <!--引入web开发相关包 start-->
        <!--web 模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--使用jettey作为默认的服务器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--引入web开发相关包 end-->
    </dependencies>

3.3,基于spring boot创建两个接口(以及Service,Mapper之类的,前面说了需要Spring Boot基础,那么这些默认你都会了,就不解释了):
20200408204506
Controller的核心代码如下:

// 由于返回json数据,懒得加注解@ResponseBody了,加个RestController
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Integer id) {
        User user = userService.getUser(id);
        if (user == null) {
            throw new RuntimeException("该ID:" + id + "没有对应的用户信息");
        }
        return user;
    }

    @GetMapping("/user/list")
    public List<User> listUser() {
        List<User> users = userService.listUser();
        return users;
    }

}

Mapper代码:

@Mapper
public interface UserMapper {
    List<User> listUser();

    User getUser(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.progor.study.dao.UserMapper">
    <select id="listUser" resultType="com.progor.study.entity.User">
        SELECT * FROM user
    </select>
    <select id="getUser" parameterType="Integer" resultType="com.progor.study.entity.User">
        SELECT * FROM user WHERE id =#{id}
    </select>

</mapper>

application.yml:

server:
  port: 8001

spring:
  datasource:
    # 配置数据源
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud01
mybatis:
  # 全局配置文件位置:
  config-location: classpath:mybatis/mybatis-config.xml
  # 映射文件位置:
  mapper-locations: classpath:mybatis/mapper/*.xml

注意,在上面有执行SQL,这里的mybatis要查询的数据从cloud01数据库中获取。

访问http://localhost:8001/user/list,测试一下是否能调用到接口。

4.创建一个服务消费者

4.1:创建模块spring-cloud-user-consumer-80
20200408151908
4.2:导入依赖:

    <dependencies>
        <!--引入公共依赖包 start-->
        <dependency>
            <groupId>com.progor.study</groupId>
            <artifactId>spring-cloud-common-data</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--引入公共依赖包 end-->
        <!--引入web开发相关包 start-->
        <!--web 模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jettey作为默认的服务器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

4.3:创建代码:
20200408205519

application.yml代码:

server:
  port: 80

访问http://localhost:80/user/list,测试一下是否能调用到8001服务的接口(80是没有任何业务内容的,他调用的是8001的业务)。

小节总结

上面基础项目搭建应该成功实现了服务消费者通过Http请求来调用服务提供者的服务了。
下面将使用Spring Cloud对这个简单的微服务项目来增加功能,来讲解各种组件在微服务中的作用。
如果你不了解上面的例子,那你最好再学习一下,然后再看下面的内容。


Eureka服务注册与发现

介绍

  • Eureka,读音尤里卡。
  • Eureka用于服务的注册与发现。Eureka是Netflix开源的一个服务注册与发现的组件,也被Spring Cloud整合到Spring Cloud Netflix模块中。
  • 服务的注册与发现解决的问题:
    • 服务注册:服务注册使得多个同一服务的多个服务端使用同一个服务端名字注册在了服务注册中心中(比如短信功能有A1,A2,A3三个服务端,但他们在注册中心的名字都是短信功能,那么我需要短信功能的时候,我一查注册中心就发现有三个服务端我可以调用),这样使得可以在服务注册中心中统一管理服务,查看服务的各种状态。
    • 服务发现:服务的发现首先要基于服务的注册。在上面的简单的调用服务的例子中,你是需要指定服务提供者的URL路径的,这是非常耦合的行为,一个比较恰当的做法是让他能够变起来,而不是一个固定的值。怎么变呢?当有了服务注册之后,你可以从注册中心中拉取到某个服务的多个服务实例信息,然后获取其中一个服务实例解析出你需要的服务提供者的URL,而实际上我们每一次使用的服务实例可以是不同的,这样就让服务提供者的URL成为了可变的,也使得后面的对同名服务的负载均衡也成为可能。
  • 类似Eureka的服务注册与发现组件:consul,etcd,zookeeper。
  • 原理:Eureka由服务端Eureka Server和客户端Eureka Client,服务端Eureka Server用于维护服务的列表(注册中心),服务提供者通过客户端Eureka Client把自己的服务信息注册到服务端Eureka Server中;服务消费者通过客户端Eureka Client从服务端Eureka Server中拉取到服务端中注册的服务。获取到服务列表后,服务消费者就知道了服务的IP地址等信息,就可以通过http来调用服务了。

有人说,好多人都开始放弃eureka了,为什么这里还要讲?
虽然老旧,但作为曾经火过的,还是有一定的参考价值,而且你不知道你进的那家公司的技术是不是与时俱进的。或者说万一让你接手改造一个eureka的项目呢?
当然了,要不断学习新的技术,consul目前来看应该是不错的替代方案,我后面也会写这个的。

简单使用步骤

下面的代码可以参考:Eureka简单使用步骤


1.父工程导入依赖:

1.1 修改父工程的依赖:
现在开始spring cloud学习了,我们首先在父工程的pom.xml下面加入spring cloud的依赖锁定,来锁定我们组件的版本:
20200408223325

            <!--锁定spring cloud版本 start-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--锁定spring cloud版本 end-->

1.2修改spring-cloud-eureka-server-7001模块依赖:
然后再配置spring-cloud-eureka-server-7001模块的pom.xml,由于前面父工程导入了spring-cloud-dependencies,所以你这里的eureka虽然没指定版本,但继承了之前锁定的版本。

    <dependencies>
        <!--这里贴一下旧版本的eureka-server依赖包,注意新版本的eureka位置变了-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-eureka-server</artifactId>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>


2.新建spring-cloud-eureka-server-7001模块:

2.1新建模块spring-cloud-eureka-server-7001
上面说了,Eureka是有服务端和客户端的,客户端集成在服务消费者和服务提供者上,服务端需要单独创建,我们单独创建一个Eureka Server出来。
20200408172154

2.2:创建主启动类代码:

package com.progor.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;


@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer来把当前服务端作为一个Eureka服务端
public class EurekaServer7001Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001Application.class, args);
    }
}

2.3修改application.yml:

# 配置服务端口
server:
  port: 7001

# 配置eureka相关
eureka:
  instance:
    hostname: localhost # eureka实例的名字
  client:
    register-with-eureka: false # 这个选项是“是否把自己注册到eureka服务端”,由于它自己就是服务端,选false
    fetch-registry: false # 是否从注册中心拉取服务,由于它自己就是服务端,选false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 设置Eureka Server的交互地址(注册地址),用于服务检索和服务注册

2.4.测试访问Eureka Server:
运行主程序类之后,访问一下localhost:7001,如果有eureka界面的显示就说明eureka服务端配置成功了。



3.修改服务提供者

在服务提供者spring-cloud-user-service-8001中配置eureka,把服务注册到eureka中:
我们修改原来的spring-cloud-user-service-8001模块:

3.1修改pom.xml:

        <!--增加eureka 客户端依赖 start-->
            <!--旧版本的依赖:-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-eureka</artifactId>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--增加eureka 客户端依赖 end-->

3.2修改主程序类UserService8001Application:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient // 启用Eureka Client
public class UserService8001Application {
    public static void main(String[] args) {
        SpringApplication.run(UserService8001Application.class, args);
    }
}

3.3修改application.yml:

server:
  port: 8001

spring:
  datasource:
    # 配置数据源
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud01
  application:
    name: UserSerive # 多个同功能的服务使用应用名application.name来注册,这个应用名你可以在eureka 中看到,它变成了服务名
mybatis:
  # 全局配置文件位置:
  config-location: classpath:mybatis/mybatis-config.xml
  # 映射文件位置:
  mapper-locations: classpath:mybatis/mapper/*.xml

# eureka配置:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka # 指定eureka 服务端交互地址
  instance:
    instance-id: UserService8001 # 当前服务实例名称
    prefer-ip-address: true # 是否使用IP地址作为当前服务的标识,有些是会使用主机号,你可以尝试注释看看效果
    # 由于拉取服务和是否把自己注册到eureka的都是默认true的,所以不需要配置

3.4运行主程序类,查看http://localhost:7001,看是否有如下图的信息:
20200408231248



4.修改服务消费者

在服务消费者中配置eureka,使得能从eureka中获取注册的服务并且调用:
修改模块spring-cloud-user-consumer-80
4.1修改pom.xml:

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

4.2修改主程序类:

package com.progor.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

4.3 修改application.yml:

server:
  port: 80

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
    register-with-eureka: false # 由于它不是一个服务提供者,不注册到eureka

4.4修改AppConfig
修改Bean--RestTemplate,增加@LoadBalanced,让restTemplate能够把请求地址解析成服务名称:

package com.progor.study.config;

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 AppConfig {
    @Bean
    @LoadBalanced // eureka与这个配合,要使用LoadBalanced才会调用eureka中注册的服务
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

4.5修改controller:
20200408233111

package com.progor.study.Controller;

import com.progor.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class UserController {
    // 注意这个restTemplate需要自己生成Bean,参考com.progor.study.config.AppConfig
    @Autowired
    private RestTemplate restTemplate;
    // 指定远程访问的URL,也就是服务提供者的URL
//    private static final String REST_URL_PREFIX = "http://localhost:8001";
    // 1.注释直接使用URL来调用服务的代码,
    // 2.下面使用eureka来调用,下面的"http://USERSERIVE"的USERSERIVE是服务的名字,Eureka页面中你看过的
    // 3.这样就从eureka中拉取到名为USERSERIVE的服务的列表,并从中选择一个服务实例调用
    private static final String REST_URL_PREFIX = "http://USERSERIVE";

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Integer id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/user/" + id, User.class);
    }

    @GetMapping("/user/list")
    public List<User> listUser() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/user/list", List.class);
    }

}

4.6运行主程序类,访问接口http://localhost/user/list,查看是否能访问。
🟠如果你代码正确了,那么应该是整个正常访问的,那么注意了,我们上面并没有写固定的服务消费者的URL,那么他是怎么访问的呢?他通过拉取eureka中的服务列表来解析出的。【由于有时候可能存在拉取的数据延迟问题,如果不相等的话,最好按顺序启动7001,8001,80】



💡这里提醒一个东西:上面都配了defaultZone,其实在单Eureka Server情况下,Eureka Server的defaultZone是可以不配的,因为没有意义,(但消费者和生产者需要配),对于服务消费者和生产者来说,只要运行了起来,都可以根据IP来获取(上面的就算不配,也可以通过http://localhost:7001/eureka来访问),消费者和生产者并不关心Eureka Server的名字,他只关心地址。但在集群中,defaultZone有独特的意义。下面讲。



Eureka集群

Eureka里面可能会注册了很多服务,而服务消费者都从Eureka Server上拉取服务列表,这个负载压力对于Eureka可能是很大的,而且由于服务列表都从Eureka Server中拉取,所以Eureka Server也是非常重要的。为了保证Eureka Server的健壮性,我们通常都会搭建Eureka集群。



搭建步骤

下面的代码可以参考:Eureka简单集群实验

1.新建三个eureka-server模块,

spring-cloud-eureka-cluster-server-7002
spring-cloud-eureka-cluster-server-7003
spring-cloud-eureka-cluster-server-7004

2.都导入依赖包:

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

3.都对应修改主启动类:

// spring-cloud-eureka-cluster-server-7002:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer来把当前服务端作为一个Eureka服务端
public class EurekaClusterServer7002Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7002Application.class, args);
    }
}
// spring-cloud-eureka-cluster-server-7003:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer来把当前服务端作为一个Eureka服务端
public class EurekaClusterServer7003Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7003Application.class, args);
    }
}
// spring-cloud-eureka-cluster-server-7004:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer来把当前服务端作为一个Eureka服务端
public class EurekaClusterServer7004Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7004Application.class, args);
    }
}


4.修改host

❓这是因为eureka默认使用eureka.instance.name作为在eureka集群中的标识名字。那么不修改host的时候,会有一个问题:

  • 如果你使用host作为三个server的eureka.instance.name,那么此时eureka怎么区分这三个server呢?对于eureka是访问不了的
  • 而且在配置defaultZone的时候也不可以配置多个同名的。你可以尝试一下在defaultZone中写下多个localhost但端口不一样的URL。
  • 所以,在本地搭建集群的时候,需要配置host。
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
127.0.0.1 eureka7004.com

5.修改application.yml:

7002:

# 配置服务端口
server:
  port: 7002

# 配置eureka相关
eureka:
  instance:
    hostname: eureka7002 # eureka实例的名字
  client:
    register-with-eureka: false # 这个选项是是否把自己注册到eureka服务端,由于它自己就是服务端,选false
    fetch-registry: false # 是否从注册中心拉取服务,由于它自己就是服务端,选false
    service-url:
      defaultZone: http://eureka7003.com:7003/eureka/,http://eureka7004.com:7004/eureka/ # 设置Eureka Server的交互地址(注册地址),用于服务检索和服务注册

7003:

# 配置服务端口
server:
  port: 7003

# 配置eureka相关
eureka:
  instance:
    hostname: eureka7003 # eureka实例的名字
  client:
    register-with-eureka: false # 这个选项是是否把自己注册到eureka服务端,由于它自己就是服务端,选false
    fetch-registry: false # 是否从注册中心拉取服务,由于它自己就是服务端,选false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7004.com:7004/eureka/ # 设置Eureka Server的交互地址(注册地址),用于服务检索和服务注册

7004:

# 配置服务端口
server:
  port: 7004

# 配置eureka相关
eureka:
  instance:
    hostname: eureka7004 # eureka实例的名字
  client:
    register-with-eureka: false # 这个选项是是否把自己注册到eureka服务端,由于它自己就是服务端,选false
    fetch-registry: false # 是否从注册中心拉取服务,由于它自己就是服务端,选false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 设置Eureka Server的交互地址(注册地址),用于服务检索和服务注册

❓为什么这里是配另外的集群节点的地址,不需要配自己的地址?
首先上面说了,其实自己配不配是不重要的,就算你不配,你的服务地址还是在的,消费者和生产者还是能够通过端口来访问eureka server。这里配置的是与其他集群节点的交互地址。



6.启动三个server,查看效果:

可以看到DS Replicas中有另外两个节点的列表,下图是7001的。
20200409230629


7.对消费者和生产者的处理

如果此时要注册服务或拉取服务,那么defaultZone要注意改成集群的:
20200409230800
参考代码spring-cloud-user-service-8002-eureka-cluster

7.1.然后你就会在三个eureka中都可以看到你注册的服务了:
20200409230839

当配置了集群服务,结果某个节点挂掉的时候,会报错,但并不影响服务。


知识补充:

  • 服务续约:每隔30s,eureka就会检测服务的可用性。
  • 自我保护:你可以看到,如果当你把服务注册到eureka之后,如果你停止这个服务,这个服务很长时间都不会把这个服务从eureka中移除,这是eureka的自我保护机制,乐观的认为这个服务不久之后就会重新可用。
  • 服务剔除:如果Eureka Client(而且要是个服务提供者) 90s没有向Eureka Server发送心跳,那么Eureka Server就会认为这个服务实例已经不可用了,把它从服务列表中删除。【但基于自我保护后并不会删除。】

补充:

  • Eureka可以开启基于Spring Security的安全认证,这是为了防止任何人都可以检索服务。这里不讲这个知识点。我会单独写出来(但暂时咕咕咕),有兴趣自查。

posted @ 2020-05-14 19:50  随风行云  阅读(2128)  评论(1编辑  收藏  举报