目录
这是一个Spring Cloud系列文章,它并不会讲解所有的知识点,它只是基于微服务的场景来 逐步介绍 常见组件的作用和意义,以及场景组件的整合。对于每个组件的知识并不会讲解太多,只讲常见的,目的是尽可能快速的对Spring Cloud的常用组件有一个基础的认知,有了认知之后,你就可以基于你面对的场景来单独学习某个组件,逐步丰满自己Spring Cloud的知识。
Spring Cloud可以解决以下的常见几个场景(暂时只列举几个常见场景,其实微服务的方方面面基本都有解决方案)
Hoxton.SR3
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知识
注意:
:bulb: 下面的学习只会贴出部分代码,其余的代码将在github上存储,会根据组件的学习来逐步commit代码 , 所以可以根据代码的差异来比较每一版本代码的区别,了解增加新组件需要改动哪些代码 ,(从可以从历史记录中查看每一次commit的代码更新,来了解新增的组件修改了哪些代码),从而加深印象。当然,自己动手也很重要。
【PS:下面的代码,我后期才发现我写错了一个单词Service,有些地方写对,有些地方写错了,但并不影响代码运行。:sweat:主要是因为大写的时候没检查好】
下面的示例代码请参考: 微服务项目基础搭建
父工程的创建方法在IDEA中和Eclipse中有区别,这里给出IDEA的,Eclipse的可以自查(搜索Eclipse创建父工程即可)
父工程的目录结构如下: ![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。
如果你学过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.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)
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基础,那么这些默认你都会了,就不解释了):
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.1:创建模块 spring-cloud-user-consumer-80
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:创建代码:
application.yml代码:
server: port: 80
访问 http://localhost:80/user/list
,测试一下是否能调用到8001服务的接口(80是没有任何业务内容的,他调用的是8001的业务)。
上面基础项目搭建应该成功实现了服务消费者通过Http请求来调用服务提供者的服务了。
下面将使用Spring Cloud对这个简单的微服务项目来增加功能,来讲解各种组件在微服务中的作用。
如果你不了解上面的例子,那你最好再学习一下,然后再看下面的内容。
服务的注册与发现
解决的问题:
有人说,好多人都开始放弃eureka了,为什么这里还要讲?
虽然老旧,但作为曾经火过的,还是有一定的参考价值,而且你不知道你进的那家公司的技术是不是与时俱进的。或者说万一让你接手改造一个eureka的项目呢?
当然了,要不断学习新的技术,consul目前来看应该是不错的替代方案,我后面也会写这个的。
下面的代码可以参考: Eureka简单使用步骤
1.1 修改父工程的依赖:
现在开始spring cloud学习了,我们首先在父工程的pom.xml下面加入spring cloud的依赖锁定,来锁定我们组件的版本:
<!--锁定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>
spring-cloud-eureka-server-7001
上面说了,Eureka是有服务端和客户端的,客户端集成在服务消费者和服务提供者上,服务端需要单独创建,我们单独创建一个Eureka Server出来。
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服务端配置成功了。
在服务提供者 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
,看是否有如下图的信息:
在服务消费者中配置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:
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】
:bulb:这里提醒一个东西:上面都配了defaultZone,其实 在单Eureka Server情况下 ,Eureka Server的defaultZone是可以不配的,因为没有意义,(但消费者和生产者需要配),对于服务消费者和生产者来说,只要运行了起来,都可以根据IP来获取(上面的就算不配,也可以通过http://localhost:7001/eureka来访问),消费者和生产者并不关心Eureka Server的名字,他只关心地址。但在集群中,defaultZone有独特的意义。下面讲。
Eureka里面可能会注册了很多服务,而服务消费者都从Eureka Server上拉取服务列表,这个负载压力对于Eureka可能是很大的,而且由于服务列表都从Eureka Server中拉取,所以Eureka Server也是非常重要的。为了保证Eureka Server的健壮性,我们通常都会搭建Eureka集群。
下面的代码可以参考: Eureka简单集群实验
spring-cloud-eureka-cluster-server-7002
spring-cloud-eureka-cluster-server-7003
spring-cloud-eureka-cluster-server-7004
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
// 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); } }
:question:这是因为eureka默认使用eureka.instance.name作为在eureka集群中的标识名字。那么不修改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
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的交互地址(注册地址),用于服务检索和服务注册
:question:为什么这里是配另外的集群节点的地址,不需要配自己的地址?
首先上面说了,其实自己配不配是不重要的,就算你不配,你的服务地址还是在的,消费者和生产者还是能够通过端口来访问eureka server。这里配置的是与其他集群节点的交互地址。
可以看到DS Replicas中有另外两个节点的列表,下图是7001的。
如果此时要注册服务或拉取服务,那么defaultZone要注意改成集群的:
参考代码 spring-cloud-user-service-8002-eureka-cluster
7.1.然后你就会在三个eureka中都可以看到你注册的服务了:
当配置了集群服务,结果 某个节点挂掉的时候 ,会报错,但并不影响服务。