这是一个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知识
注意:
??下面的学习只会贴出部分代码,其余的代码将在github上存储,会根据组件的学习来逐步commit代码,所以可以根据代码的差异来比较每一版本代码的区别,了解增加新组件需要改动哪些代码,(从可以从历史记录中查看每一次commit的代码更新,来了解新增的组件修改了哪些代码),从而加深印象。当然,自己动手也很重要。
【PS:下面的代码,我后期才发现我写错了一个单词Service,有些地方写对,有些地方写错了,但并不影响代码运行。??主要是因为大写的时候没检查好】
下面的示例代码请参考:微服务项目基础搭建
父工程的创建方法在IDEA中和Eclipse中有区别,这里给出IDEA的,Eclipse的可以自查(搜索Eclipse创建父工程即可)
在父工程的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实体(与数据表对应)的共有依赖包。
创建一个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.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>
2.1新建模块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】
??这里提醒一个东西:上面都配了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);
}
}
?这是因为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的交互地址(注册地址),用于服务检索和服务注册
?为什么这里是配另外的集群节点的地址,不需要配自己的地址?
首先上面说了,其实自己配不配是不重要的,就算你不配,你的服务地址还是在的,消费者和生产者还是能够通过端口来访问eureka server。这里配置的是与其他集群节点的交互地址。
可以看到DS Replicas中有另外两个节点的列表,下图是7001的。
如果此时要注册服务或拉取服务,那么defaultZone要注意改成集群的:
参考代码spring-cloud-user-service-8002-eureka-cluster
7.1.然后你就会在三个eureka中都可以看到你注册的服务了:
当配置了集群服务,结果某个节点挂掉的时候,会报错,但并不影响服务。
Spring Cloud认知学习(一):Spring Cloud介绍与Eureka使用
原文:https://www.cnblogs.com/progor/p/12890967.html