赞
踩
Spring Cloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路 由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括
Netflix
以上只是其中一部分,架构图
Spring Cloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,他的命名是以A到Z为首字母的一些单词组成(其实是伦敦地铁站的名字):
Spring Clound 和Spring Boot版本对应关系
Release Train | Boot Version |
---|---|
Hoxton | 2.2x |
Greenwich | 2.1x |
Finchley | 2.0x |
Edgware | 1.5x |
Dalston | 1.5x |
本次采用Greenwich版本
模拟一个服务调用的场景。方便学习后面的课程
微服务中需要同时创建多个项目,为了方便课堂演示,先创建一个父工程,后续的工程都以这个工程为父,使用 Maven的聚合和继承。统一管理子工程的版本和配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R80HYYsE-1613811086756)(C:\Users\36610\AppData\Roaming\Typora\typora-user-images\image-20210213140311249.png)]
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lqh</groupId> <artifactId>spring-cloud</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>user-service</module> <module>consumer-demo</module> <module>eureka-service</module> <module>lqh-gateway</module> <module>config-server</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> </parent> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> <mapper.starter.version>2.1.5</mapper.starter.version> <mysql.version>8.0.23</mysql.version> </properties> <dependencyManagement> <dependencies> <!-- springCloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 通用Mapper启动器 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>${mapper.starter.version}</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
注意:spring cloud和spring boot的版本对应 greenwich版本cloud对应spring boot 2.1x
注意:注意聚合父工程
<packaging>pom</packaging>
这里已经毒地大部分要用到的依赖进行了管理,方便后续使用
我们新建一个项目,对外提供查询用户的服务。
选中lqh-springcloud,创建子工程
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-cloud</artifactId> <groupId>com.lqh</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>user-service</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 通用Mapper启动器 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--jdk11会遇到版本问题,添加下面依赖就可解决--> <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-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-bus</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> </project>
项目结构
server:
port: 9091
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud
username: root
password: 123456
application:
name: user-service
mybatis:
type-aliases-package: com.lqh.user.pojo
使用Mysql图形界面工具,导入springclound.sql脚本
启动类
@SpringBootApplication
@MapperScan("com.lqh.mapper ")
@EnableDiscoveryClient
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
实体类
@Data @Table(name = "tb_user") public class User{ // id @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 用户名 private String userName; // 密码 private String password; // 姓名 private String name; // 年龄 private Integer age; // 性别,1男性,2女性 private Integer sex; // 出生日期 private Date birthday; // 创建时间 private Date created; // 更新时间 private Date updated; // 备注 private String note; }
UserMapper
public interface UserMapper extends Mapper<User> {
}
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
controller
对外提供REST风格web 服务,根据id查询用户
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userService.queryById(id);
}
}
完成上述代码后的项目结构
启动项目,访问http://localhost:9091/user/7
问题分析
在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务 提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂 的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来 测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
网约车
这就好比是网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多 人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人 的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息 (联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到 你面前,为你服务,完美!
Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉 Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳”
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服 务列表中剔除
这就实现了服务的自动注册、发现、状态监控。
基本架构
工作原理图解析
5.3.1 编写EurekaServer
Eureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建web工程使用Eureka,可以使用Spring Boot方式搭建
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-cloud</artifactId> <groupId>com.lqh</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka-service</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project>
2.编写启动类:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3.编写配置:
server: port: 10086 spring: application: name: eureka-service eureka: client: service-url: #eureka服务器地址,如果做集群,需要指定其他eureka地址 defaultZone: ://127.0.0.1:10086/eureka #不注册自己 register-with-eureka: false #不拉取服务a fetch-registry: false
4.启动服务,并访问:http://127.0.0.1:10086/
在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.在启动类上开启Eureka客户端功能
通过添加@EnableDiscoveryClient
来开启Eureka客户端功能
@SpringBootApplication
@MapperScan("com.lqh.mapper ")
@EnableDiscoveryClient //开启Eureka发现功能
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
3.编写配置
server: port: 12123 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bill-manager username: root password: 123456 application: name: bill-service mybatis: type-aliases-package: com.lqh.bean mapper-locations: classpath:/mappers/*.xml eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
注意:
重启项目,访问Eureka监控页面查看
我们先user-service 服务已经注册了
在服务消费工程consumer-demo上添加Eureka客户端依赖;可以使用工具类DiscoveryClient根据服务名称获取对 应的服务地址列表。
1.添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.在启动类添加开启Eureka客户端发现的注解
@SpringBootApplication
@EnableDiscoveryClient
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
3.修改配置
spring:
application:
name: consumer-demo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
4.修改代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例:
@RestController @RequestMapping("/consumer") public class ConsumerController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/{id}") public User queryById(@PathVariable("id") Long id) { String url = "http://localhost:9091/user/" + id; List<ServiceInstance> serviceInstances = discoveryClient.getInstances("user-service"); ServiceInstance serviceInstance = serviceInstances.get(0); url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id; return restTemplate.getForObject(url, User.class); } }
5.Debug跟踪运行:
接下来我们详细讲解Eureka的原理及配置
Eureka架构中的三个核心角色:
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
服务提供者
提供服务的应用,可以是Spring Boot应用,也可以是其他任意技术实现,只要对外提供REST风格服务即可。本例中就是我们实现的user-service
服务消费者
消费应用从注册中心获取服务列表,从而的值每个服务方的信息,知道去哪里调用服务方。本例中就是我们的consumer-demo
EurekaServer即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
Eureka Server是一个web应用,可以启动多个实例(配置不同端口)保证Eureka Server的高可用
服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会吧服务的信息同步给集群中的每一个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
而作为客户端,需要把信息注册到每个Eureka中
如果 三个Eureka,则每一个EurekaServer都需要注册到其他几个Eureka服务中
例如:有三个分别为10086,10087,10088,则:
动手搭建高可用的EurekaServer
我们假设要搭建两条EurekaServer的集群,端口分别为:10086和10087
进行一系列的配置:
服务提供者要想EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-erueka=true 参数是否为true,事实上 默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息, EurekaServer会把这些信息保存到一个双层Map结构中 。
默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在 user-service 中添加配置如下:
eureka:
instance:
ip-address: 127.0.0.1 #ip地址
prefer-ip-address: true #更倾向于使用ip,而不是host名
修改完后先后重启 user-service 和 consumer-demo ;在调用服务的时候就已经变成ip地址;需要注意的是:不是 在eureka中的控制台服务实例状态显示。
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉 EurekaServer:“我还活着”。这个我们称为服务的续约(renew);
有两个重要参数可以修改服务续约的行为;可以在 user-service 中添加如下配置项:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心 跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可
获取服务列表
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从Eureka Server服 务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在 consumer-demo 项目 中通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30
生产环境中,我们不需要修改这个值。
但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。
如下的配置都是在Eureka Server服务端进行:
服务下线
当服务进行正常关闭操作时,它会出发一个服务下线的REST请求给EurekaServer,告诉服务中心:我要下线。服务中心接受到请求之后,将该服务置为下线状态
失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未受到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认60秒))将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。 可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。
自我保护
我们关停一个服务,就会在Eureka面板看到一条警告:
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服 务实例的比例是否超过了85%,当EurekaServer节点在短时间内丢失过多客户端(可能发生了网络分区故障)。在 生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因 为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多 数服务依然可用。
但是这给我们的开发带来了麻烦,因此开发阶段我们都会关闭自我保护
eureka:
server:
enable-self-preservation : false #关闭自我保护模式(缺省为打开)
eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
小结:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true
# ip地址
ip-address: 127.0.0.1
# 续约间隔,默认30秒
lease-renewal-interval-in-seconds: 5
# 服务失效时间,默认90秒
lease-expiration-duration-in-seconds: 5
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
# 获取服务地址列表间隔时间,默认30秒
registry-fetch-interval-seconds: 10
eureka:
server:
# 服务失效剔除时间间隔,默认60秒
eviction-interval-timer-in-ms: 60000
# 关闭自我保护模式(默认是打开的)
enable-self-preservation: false
很有效,保证了大多 数服务依然可用。
但是这给我们的开发带来了麻烦,因此开发阶段我们都会关闭自我保护
eureka:
server:
enable-self-preservation : false #关闭自我保护模式(缺省为打开)
eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
小结:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true
# ip地址
ip-address: 127.0.0.1
# 续约间隔,默认30秒
lease-renewal-interval-in-seconds: 5
# 服务失效时间,默认90秒
lease-expiration-duration-in-seconds: 5
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
# 获取服务地址列表间隔时间,默认30秒
registry-fetch-interval-seconds: 10
eureka:
server:
# 服务失效剔除时间间隔,默认60秒
eviction-interval-timer-in-ms: 60000
# 关闭自我保护模式(默认是打开的)
enable-self-preservation: false
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。