文章目录
前言
微服务是一种架构方式,最终是通过技术架构去实现的,当前最火的莫过于SpringCloud.
- SpringCloud作为Spring家族中的一员,有整个Spring全家桶靠山,背景十分强大
- Spring作为Java领域的前辈,功力深厚,有强力的技术团队支撑(技术强)
- Spring框架可以说是当下程序员伴随成长的框架.(群众基础好),SpringCloud与Spring的各个框架无缝整合.
- SpringBoot给我们开发带来了便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建
一、SpringCloud是什么?
SpringCloud是Spring旗下的项目之一,Spring最擅长的是集成,将世界上最好的框架集成到自己的项目中,而SpringCloud也是一样,将现在市面上流行的一些技术整合到一起,实现了诸如:配置管理、服务发现、智能路由、负载均衡、熔断器、控制总线、集群状态等等功能,其主要设计的软件如下:
-
Eureka:注册中心
-
Zuul:服务网关
-
Ribbon:负载均衡
-
Feign:服务调用
-
Hystrix:熔断器
-
config: 配置中心
-
Bus : 消息总线
二、Eureka入门
1.介绍
Eureka主要解决了服务的管理问题,在未使用Eureka的时候,生产者对外提供服务的时候,需要暴露自己的地址,而消费者需要记录服务提供者的地址。如果在将来提供者的地址发生变更,此时消费者需要及时更新。如果服务较少还可以,随着项目的增大,一个项目可能会拆分出十几甚至几十个微服务,如果此时还要人为的管理地址,在测试和发布的时候也会非常麻烦。
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
Eureka就好比是滴滴,负责管理、记录服务提供者(生产者)的信息。服务调用者(消费者)无需自己寻找服务,而是将自己的需求告诉Eureka,然后Eureka将符合需求的服务告诉消费者。同时服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自然将其从服务中剔除,实现了服务的自动注册、发现和监控。
Eureka-Server:就是服务注册中心(可以是一个集群),对外暴露自己的地址。
提供者:启动后向Eureka注册自己信息(地址,服务名称等),并且定期进行服务续约
消费者:服务调用方,会定期去Eureka拉取服务列表,然后使用负载均衡算法选出一个服务进行调用。
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
Eureka和zookeeper的区别:
- zookeeper是一个软件,Eureka是一个项目,这个项目需要自己开发
- zookeeper是长连接,Eureka是短连接,默认会每隔30秒重新连接Eureka
- zookeeper的集群的事务是强一致性的,Eureka的事务是柔性事务
- zookeeper负载均衡策略默认是随机的,而Eureka的负载均衡策略默认是轮询
2.入门案例
项目结构:
父工程依赖:
<?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>cn.james.demo</groupId>
<artifactId>cloud_demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>user_service</module>
<module>user_consumer</module>
<module>eureka-server</module>
<module>zuul-demo</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--spring-cloud规定时间还是用格林威治版本-->
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<mapper.starter.version>2.1.4</mapper.starter.version>
<mysql.version>5.1.47</mysql.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.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>
编写EurekaServer
---->添加依赖
<?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>cloud_demo</artifactId>
<groupId>cn.james.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
---->启动类
package cn.james.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //声明这个应用是一个RurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class,args);
}
}
---->配置类(application.yml)
server:
port: 10086
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId),注意不要使用下划线,Eureka不识别
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: http://127.0.0.1:10086/eureka
register-with-eureka: true # fasle,不注册自己 true,表示注册自己
fetch-registry: true #false,不拉取服务 true表示拉取自己
---->启动服务,访问:http://localhost.10086
服务注册(user-service)
---->添加依赖
在user-service中添加对Eureka客户端依赖
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
---->编写启动类
通过添加@EnableDiscoveryClient来开启Eureka客户端功能
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现功能
public class UserServiceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceDemoApplication.class, args);
}
}
---->编写配置类(application.yml)
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/heima
username: root
password: 123
application:
name: user-service # 应用名称
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
---->controller类.UserMapper和实体类
controller:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User queryById(@PathVariable("id") Long id) { return userService.findById(id); } }
UserMapper:
public interface UserMapper extends BaseMapper<User> { }
User实体类:略
重启服务注册启动类,然后访问Eureka监控页面,查看:http://localhost:10086发现如下界面:
服务发现(user-consumer)
作用: 尝试从Eureka-Server中获取服务
---->添加依赖
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
---->客户端启动类
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端 可以省略
public class ConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
---->配置类(application.yml)
server:
port: 8080
spring:
application:
name: consumer # 应用名称
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
---->controller
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id){
// 根据服务id(spring.application.name),获取服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 取出一个服务实例
ServiceInstance instance = instances.get(0);
// 查询
User user = restTemplate.getForObject(instance.getUri()+"/user/" + id, User.class);
return user;
}
}
开启启动类,访问一下地址:http://localhost/consumer/id值,通过debug调试运行,生成一下的url值.
最后获取访问结果.
2.Eureka详解
2.1 基础架构
- 服务注册中心:Eureka的服务端应用,提供服务注册和发现功能,即刚刚建立的Eureka-server
- 服务提供者: 提供服务的应用,可以是SpringBoot应用,也可以是其他任意技术实现,只要对外提供的是Rest风格服务即可,即我们刚刚建立的user-service
- 服务消费者:消费应用从注册中心货物服务列表,从而得知每个服务方的信息,只从去哪里调用服务方,即我们刚建立的user-consumer.
2.2 高可用的EurekaServer
什么是高可用?(优质解释)
高可用是分布式系统架构设计中必须考虑的因素之一,通常指设计减少系统不能提供服务的时间,假设系统一直能够提供服务,那么系统的可用性是100%,如果系统每运行100个单位时间,会有一个时间单位无法提供服务,我们说系统的可用性是99%.很多公司的高可用目标是4个9,也就是99.99%.
EurekaServer在刚才的案例中只有一个,我们可以建立一个集群,形成高可用的Eureka中心.多个EurekaServer之间互相注册为服务,当服务提供者注册到EurekaServer集群中的某一个节点时,该节点会将服务的信息同步给集群中的每个节点,从而实现高可用的集群.因此,无论客户端访问到EurekaServer集群中任意一个节点,都可以获取到完整的服务列表信息.,作为消费者和生产者需要把信息注册到每个Eureka中.如果有多个Eureka,则每一个EurekaServer需要注册到其他几个Eureka服务中
例如:有两个分别为10086、10087,则:
10086要注册到10087上
10087要注册到10086上
此时我们需要修改原来的EurekaServer的application.yml的配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 应用名称,会在Eureka中显示
eureka:
client:
service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087
defaultZone: http://127.0.0.1:10087/eureka
所谓高可用注册中心,就是把EurekaServer自己也作为一个服务,注册到其他的EurekaServer上,多个EurekaServer之间互相发现对方,从而形成集群.此时我们需要配置一个和第一个一样(端口除外)的EurekaServer启动器.
步骤:
复制一个启动项
通过JVM参数覆盖配置文件配置:
-Dserver.port=10087 -Deureka.client.serviceUrl.defaultZone=http://127.0.0.1:10086/eureka启动即可。
3)启动测试:
4)生产者和消费者也需要注册服务到集群,因为EurekaServer不止一个,所以在注册服务的时候,需要修改配置文件的参数
eureka: client: service-url: # EurekaServer地址,多个地址以','隔开 defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
2.3 Eureka客户端
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true
参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
第一层Map的Key就是服务id,一般是配置中的
spring.application.name
属性第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:
locahost:user-service:8081
值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
user-service默认注册时使用的是主机名,如果我们想用ip进行注册,可以在user-service的application.yml添加配置:
eureka:
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更倾向于使用ip,而不是host名
instance-id: ${eureka.instance.ip-address}:${server.port} # 自定义实例的id
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renewal);
有两个重要参数可以修改服务续约的行为:
eureka:
instance:
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
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秒
会重新获取并更新数据。我们可以通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30
2.4 服务下线、失效剔除和自我保护
服务下线
当服务进行正常关闭操作的时候,会触发一个服务下线的Rest请求给EurekaServer,告诉注册中心,“我要下线了”,服务中心收到请求的时候,将该服务置位下线状态
失效剔除
当由于内存溢出或者网络故障等原因使得服务不能正常工作的时候,而此时注册中心未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务中心会创建一个定时任务,默认每个一段时间内将当前清单中超时的服务剔除,这个操作叫做失效剔除。
自我保护
当我们关停一个服务的时候,就会在Eureka面板收到一条警告:
这是触发了Eureka的自我保护机制,当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
以下配置可以关停自我保护:
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
关停之后,在此访问会显示如下界面:
1、正常下线的微服务会马上从eureka中消失,而非正常关闭的微服务不会马上从eureka中消失
2、 非正常关闭的微服务什么时候从eureka中消失呢?
eureka发现一个微服务联系不上了,会每隔30秒持续3次(90秒),如果还是没有联系上,那么就会把这个微服务放到一个剔除微服务的定时任务中,这个定时任务每隔60秒执行一次,所以最多150秒这个微服务就会从eureka中消失。
3、有的微服务已经过了150秒后仍然没有从eureka中消失,是因为eureka默认有自我保护机制。