SpringCloud学习笔记
本文主要介绍 SpringCloud 的入门
一、SpringCloud 的五大组件
(需要牢牢记住他们,现在混个眼熟,下面会详细介绍。)
自学参考文档:
- SpringCloud 官方文档(汉化版):https://springcloud.cc/spring-cloud-dalston.html
- SpringCloud中国社区:http://springcloud.cn/
- SpringCloud中文网:https://springcloud.cc
自学参考文章:
- 一文详解微服务架构:https://www.cnblogs.com/skabyy/p/11396571.html
自学参考知乎各个大神们的回答:
- 微服务架构是什么?:https://www.zhihu.com/question/65502802
自学参考视频:
- kuangstudy:https://www.kuangstudy.com/course/play/1321005531116863490
二、什么是微服务
Spring官网:https://spring.io/
微服务(Microservice Architecture) 是近几年流行的一种架构思想,关于它的概念很难一言以蔽之。究竟什么是微服务呢?我们在此引用ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话:
原文:https://martinfowler.com/articles/microservices.html
中文:https://www.cnblogs.com/liuning8023/p/4493156.html
就目前而言,对于微服务,业界没有一个统一的标准定义。通常而言,微服务架构是一种架构模式,或者说是一种架构风格,它提倡将单一的应用程序划分为一组小的服务,每个服务运行在其独特的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中。另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择适合的语言,工具对其进行构建,可以有一个非常轻量级的集中管理来协调这些服务,可以用不同的语言来编写服务,也可以使用不同的数据库。 (牢牢记住上面这段概念,几乎每句话都有微服务的关键点)
三、传统开发模式 VS 微服务
- 传统的web开发方式
一般被称为Monolithic(单体式开发)。所有的功能打包在一个 WAR包里,基本没有外部依赖(除了容器),部署在一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI等所有逻辑。
优点:
集中式管理,开发简单,功能都在本地,没有分布式的管理和调用。
缺点:
1.效率低,开发都在同一个项目改代码,互相等待,冲突不断;
2.稳定性差,一个微小的问题,可能导致整个程序挂掉;
3.维护性难,代码高耦合,内部关系难以摸清楚;
4.扩展性差,无法满足高并发下的业务需求;
- 随着业务的发展,移动端兴起
这一阶段,架构设计存在着很多不合理的地方:
1.网站和移动端存在着很多相同业务逻辑的重复代码;
2.数据库被多个应用依赖,无法重构和优化;
3.数据有时候通过数据库共享,有时候通过接口调用传输,接口调用关系杂乱;
4.单个应用为了给其他应用提供接口,设计得越来越复杂,包含本不属于它得逻辑;
5.应用之间界限模糊,功能归属混乱,出现问题后各部门职责很难划分,出现分歧或争端;
6.所有的应用都在一个数据库上操作,数据库出现性能瓶颈;
7.管理后台保障级别比较低,添加新的功能可能影响到其他应用;
8.开发、部署、维护、升级愈发困难;
- 下一阶段,除去大量的冗余代码
在这一阶段,服务已经被拆分开了,但是数据库依然是共用的。会出现一些问题:
1.数据库性能瓶颈,而且存在一定的风险;
2.数据库表结构可能被多个服务依赖,维护困难;
- 提高系统的实时性,再次升级架构(微服务)
此时,拆分后的各个服务可以采用异构的技术。比如,数据分析服务可以使用数据仓库作为持久化层,以便于高效地做一些统计计算;促销服务访问比较频繁,因此可以加入缓存机制。
微服务,它是具体解决某一个问题/提供落地对应服务的一个服务应用,狭义的看,可以看作是IDEA中的一个个微服务工程,或者Moudel。IDEA 工具里面使用Maven开发的一个个独立的小Moudel,它具体是使用SpringBoot开发的一个小模块,专业的事情交给专业的模块来做,一个模块就做着一件事情。强调的是一个个的个体,每个个体完成一个具体的任务或者功能。
微服务的优点:
1.单一职责原则;
2.每个服务足够内聚,足够小,代码容易理解;
3.开发效率高,一个服务可能就是专一的只干一件事;
4.微服务能够被小团队单独开发,这个团队只需2-5个开发人员组成;
5.松耦合,无论是在开发阶段或部署阶段都是独立的;
6.可以使用不同的语言开发;
7.易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,Hudson,bamboo;
8.每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库;
微服务的缺点:
1.开发人员要处理分布式系统的复杂性;
2.多服务运维难度,随着服务的增加,运维的压力也在增大;
3.各个服务间的通信成本问题;
4.整个应用分散成多个服务,定位故障相对困难;
5.一个服务故障可能产生雪崩效用,导致整个系统故障;
整体解决思路如下:
四、SpringCloud入门
官网:http://projects.spring.io/spring-cloud/
SpringCloud没有采用数字编号的方式命名版本号,而是采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序,比如最早的Realse版本:Angel,第二个Realse版本:Brixton,然后是Camden、Dalston、Edgware,目前最新的是Hoxton SR4 CURRENT GA通用稳定版。
- 微服务技术栈有那些?
微服务技术条目 | 落地技术 |
---|---|
服务开发 | SpringBoot、Spring、SpringMVC等 |
服务配置和管理 | Netfix公司的Archaius、阿里的Diamond等 |
服务注册与发现 | Eureka、Consul、Zookeeper等 |
服务调用 | Rest、PRC、gRPC |
服务熔断器 | Hystrix、Envoy等 |
负载均衡 | Ribbon、Nginx等 |
服务接口调用(客户端调用服务的简化工具 | Fegin等 |
消息队列 | Kafka、RabbitMQ、ActiveMQ等 |
服务配置中心管理 | SpringCloudConfig、Chef等 |
服务路由(API网关) | Zuul等 |
服务监控 | Zabbix、Nagios、Metrics、Specatator等 |
全链路追踪 | Zipkin、Brave、Dapper等 |
数据流操作开发包 | SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息) |
时间消息总站 | SpringCloud Bus |
服务部署 | Docker、OpenStack、Kubernetes等 |
- 各微服务框架对比
功能点/服务框架 | Netflix/SpringCloud | Motan | gRPC | Thrit | Dubbo/DubboX |
---|---|---|---|---|---|
功能定位 | 完整的微服务框架 | RPC框架,但整合了ZK或Consul,实现集群环境的基本服务注册发现 | RPC框架 | RPC框架 | 服务框架 |
支持Rest | 是,Ribbon支持多种可拔插的序列号选择 | 否 | 否 | 否 | 否 |
支持RPC | 否 | 是(Hession2) | 是 | 是 | 是 |
支持多语言 | 是(Rest形式) | 否 | 是 | 是 | 否 |
负载均衡 | 是(服务端zuul+客户端Ribbon),zuul-服务,动态路由,云端负载均衡Eureka(针对中间层服务器) | 是(客户端) | 否 | 否 | 是(客户端) |
配置服务 | Netfix Archaius,Spring Cloud Config Server 集中配置 | 是(Zookeeper提供) | 否 | 否 | 否 |
服务调用链监控 | 是(zuul),zuul提供边缘服务,API网关 | 否 | 否 | 否 | 否 |
高可用/容错 | 是(服务端Hystrix+客户端Ribbon) | 是(客户端) | 否 | 否 | 是(客户端) |
典型应用案例 | Netflix | Sina | |||
社区活跃程度 | 高 | 一般 | 高 | 一般 | 2017年后重新开始维护,之前中断了5年 |
学习难度 | 中等 | 低 | 高 | 高 | 低 |
文档丰富程度 | 高 | 一般 | 一般 | 一般 | 高 |
其他 | Spring Cloud Bus为我们的应用程序带来了更多管理端点 | 支持降级 | Netflix内部在开发集成gRPC | IDL定义 | 实践的公司比较多 |
- Rest 搭建学习环境
1.创建父工程 springcloud (使用 pom 打包方式)
<?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.zhou</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-consumer-dept-80</module>
<module>springcloud-provider-dept-8001</module>
<module>springcloud-api</module>
</modules>
<!--打包方式 pom -->
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloud的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>runtime</scope>
</dependency>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</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.2.5</version>
</dependency>
<!--SpringBoot 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--日志测试-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!--Maven 资源过滤问题-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
注意:父工程为 springcloud,其下有多个子 module
2.创建子模块 springcloud-api,它只负责接管 pojo
pom 依赖
<?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>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<dependencies>
<!--当前的module自己需要的依赖,如果父类中已经配置了版本,这里就不需要写了-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
3.在SQLyog中创建数据库 db01
idea连接此数据库并创建一些字段和数据
insert into dept (dname, db_source) VALUES ("开发部",DATABASE());
insert into dept (dname, db_source) VALUES ("设计部",DATABASE());
insert into dept (dname, db_source) VALUES ("人事部",DATABASE());
insert into dept (dname, db_source) VALUES ("运营部",DATABASE());
insert into dept (dname, db_source) VALUES ("企划部",DATABASE());
insert into dept (dname, db_source) VALUES ("编辑部",DATABASE());
4.创建实体类
package com.zhou.springcloud.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@Accessors(chain = true) //链式编程,默认 boolean chain() default false;
public class Dept implements Serializable { //Dept 实体类
private Long deptno;//主键
private String dname;
//这个数据存在那个数据库的字段,微服务,一个服务对应一个数据库,同一个信息可能存在不同的数据库
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
}
5.创建子模块 springcloud-provider-dept-8001 (服务的提供者)
子模块 springcloud-provider-dept-8001 的整体项目结构如下:
pom 依赖
<?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>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<dependencies>
<!--我们需要拿到实体类,所以要配置 api module-->
<dependency>
<groupId>com.zhou</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
application.yaml
server:
port: 8001
#mybatis 配置
mybatis:
type-aliases-package: com.zhou.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#speing 配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8
username: root
password: root
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
DeptMapper 接口
package com.zhou.springcloud.mapper;
import com.zhou.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface DeptMapper {
boolean addDept(Dept dept);
Dept queryById(@Param("id") Long id);
List<Dept> queryAll();
}
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhou.springcloud.mapper.DeptMapper">
<insert id="addDept" parameterType="Dept">
insert into dept (dname, db_source)
values (#{dname},DATABASE());
</insert>
<select id="queryById" parameterType="Long" resultType="Dept">
select * from dept where deptno=#{id};
</select>
<select id="queryAll" resultType="Dept">
select * from dept
</select>
</mapper>
DeptService 接口
package com.zhou.springcloud.service;
import com.zhou.springcloud.pojo.Dept;
import java.util.List;
public interface DeptService {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
DeptServiceImpl.java
package com.zhou.springcloud.service;
import com.zhou.springcloud.mapper.DeptMapper;
import com.zhou.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService{
@Autowired
private DeptMapper deptMapper;
@Override
public boolean addDept(Dept dept) {
return deptMapper.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptMapper.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptMapper.queryAll();
}
}
DeptController.java
package com.zhou.springcloud.controller;
import com.zhou.springcloud.pojo.Dept;
import com.zhou.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
//提供Restful服务
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/add")//方便此时的测试,这里用了Get,没有用Post
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept getDept(@PathVariable("id") Long id){
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll(){
return deptService.queryAll();
}
}
6.启动类
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//启动类
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
7.Run 测试
访问:http://localhost:8001/dept/list (测试成功!)
访问:http://localhost:8001/dept/get/1 (测试成功!)
访问:http://localhost:8001/dept/add?dname=地狱部 (测试成功!)
查询数据库
1.创建子模块 springcloud-consumer-dept-80 (服务的消费者)
pom 依赖
<?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>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<dependencies>
<!--不需要连接数据库,需要实体类+web-->
<dependency>
<groupId>com.zhou</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 80
ConfigBean.java
package com.zhou.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration //@Configuration 相当于 spring中 applicationContext.xml
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
RestTemplate 部分源码:
DeptConsumerController.java
package com.zhou.springcloud.controller;
import com.zhou.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Controller
public class DeptConsumerController {
//理解:消费者,不应该有 service 层
//RestTemplate.... 供我们直接调用就可以了,注册到Spring中
@Autowired
private RestTemplate restTemplate;
//http://localhost:8081/dept/add?dname=地狱部
private static final String REST_URL_PREFIX="http://localhost:8081";
@RequestMapping("/consumer/dept/add")
@ResponseBody
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept,Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
@ResponseBody
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
@ResponseBody
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
DeptConsumer_80.java 启动类
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
2.修改:springcloud-provider-dept-8001 它的 DeptController.java类的addDept()方法
(否则,在下面测试中,使用浏览器url传参的方式来插入数据时,数据库中显示dname的值为null)
@PostMapping("dept/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
3.启动
首先,启动服务的提供者 springcloud-provider-dept-8001 的启动类 DeptProvider_8001
其次,启动服务的消费者 springcloud-consumer-dept-80 的启动类 DeptConsumer_80
4.Run 测试
访问:http://localhost/consumer/dept/list
访问:http://localhost/consumer/dept/get/5
访问:http://localhost/consumer/dept/add?dname=架构师3
查看数据库
五、Eureka 服务注册中心
- Eureka 定义
Eureka 是Netflix的一个子模块,也是核心模块之一。Eureka是基于REST的服务,用于定位服务,以实现云端中间件层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper。
在云中,应用程序不能总是知道其他服务的确切位置。一个服务注册中心,比如Netflix Eureka,或者一个sidecar解决方案,比如HashiCorp Consul,都会有所帮助。springcloud为流行的注册中心提供DiscoveryClient实现,比如Eureka、Consul、Zookeeper,甚至Kubernetes的内置系统。还有一个springcloud负载均衡器可以帮助您在服务实例之间小心地分配负载。
官方介绍:https://spring.io/projects/spring-cloud-netflix
springcloudnetflix通过自动配置并绑定到Spring环境和其他Spring编程模型习惯用法,为Spring启动应用程序提供Netflix操作系统集成。通过一些简单的注释,您可以快速启用和配置应用程序中的常见模式,并使用经过测试的Netflix组件构建大型分布式系统。提供的模式包括服务发现(Eureka)、断路器(Hystrix)、智能路由(Zuul)和客户端负载平衡(Ribbon)
- Dubbo 和 SpringCloud对比
最大区别:Spring Cloud 抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式。
二者解决的问题域不一样:Dubbo的定位是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案。
Dubbo | SpringCloud | |
---|---|---|
服务注册中心 | Zookeeper | Spring Cloud Netfilx Eureka |
服务调用方式 | RPC | REST API |
服务监控 | Dubbo-monitor | Spring Boot Admin |
断路器 | 不完善 | Spring Cloud Netfilx Hystrix |
服务网关 | 无 | Spring Cloud Netfilx Zuul |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总栈 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
严格来说,这两种方式各有优劣。虽然从一定程度来说,SpringCloud牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这个优点在当下强调快速演化的微服务环境下,显得更加合适。
- Eureka基本的架构
1.Springcloud 封装了Netflix公司开发的Eureka模块来实现服务注册与发现 (对比Zookeeper).
2.Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心.
3.系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接。(方便监控系统中各个微服务是否正常运行)
- 与Dubbo架构对比
- 构建 Eureka 代码示例
1.创建子模块 springcloud-eureka-7001
pom 依赖
<artifactId>springcloud-eureka-7001</artifactId>
<!--导入依赖-->
<dependencies>
<!--spring-cloud-starter-netflix-eureka-server 依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
application.yaml
server:
port: 7001
# Eureka 部署
eureka:
instance:
# Eureka服务端的实例名字
hostname: localhost
client:
# 表示是否向 Eureka 注册中心注册自己(这个模块本事是服务器,所以不需要)
register-with-eureka: false
# fetch-registry 如果为 false,则表示自己为注册中心,客户端的为 true
fetch-registry: false
# Eureka 监控页面
service-url:
#public static final String DEFAULT_URL = "http://localhost:8761/eureka/";
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类 EurekaServer_7001.java
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Auther: zhouzhou
* @Description: 启动之后,访问 http://127.0.0.1:7001/
*/
@SpringBootApplication
@EnableEurekaServer //服务端的启动类,可以接受别人注册进来
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
Run 测试
报错信息:Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationPropertiesBean
观察报错信息,得出有可能是版本冲突问题,解决办法是将 spring-cloud-starter-netflix-eureka-server 依赖的版本降为2.1.4.RELEASE
【注意】:报错原因是版本问题,可以选择到官网查看版本是否一致,比如 SPRINGCLOUD的版本,我的父依赖用的是GREENWICH.SR1
你如果用了HOXTON.SR10
甚至更新的,请自行查找对应的版本依赖。
继续测试,Run 成功!
访问:http://localhost:7001/
- Eureka 服务注册 信息配置以及自我保护机制
配置 Eureka-client
1.在上面创建的子模块 springlouc-provider-dept-8001 的 pom 中添加依赖
<!--Eureka:spring-cloud-starter-netflix-eureka-client 依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
2.application.yaml 中添加设置
# Eureka配置:配置注册中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
3.启动类中使用 @EnableEurekaClient
注解
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//启动类
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
4.先启动7001服务端,再启动8001客户端进行测试,访问监控页:http://localhost:7001/ 产看结果如图,成功
5.修改 Eureka 上的默认描述信息
# Eureka配置:配置注册中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept8001 # 修改eureka上的默认描述信息
prefer-ip-address: true
查看:
6.配置关于服务加载的监控信息(springcloud-provider-dept-8001)
没配置之前,访问:springcloud-provider-dept8001
跳出的页面如下:
配置服务加载的监控信息步骤如下:
pom.xml中添加依赖
<!--actuator完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yaml 中添加配置
# info配置
info:
# 项目名称
app.name: zhouzhou-springcloud
# 公司名称
company.name: blog.zhouzhou.com
7.Run 再刷新页面,继续访问:springcloud-provider-dept8001,跳出页面如下
- EureKa自我保护机制
默认情况下,如果Eureka Server在90秒内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往面临很多问题,比如微服务状态正常,网络分区故障,导致此实例被注销。
固定时间内大量实例被注销,可能会严重威胁某个微服务架构的可用性,为了解决这个问题,Eureka开发了自我保护机制。
Eureka Server在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server即进入自我保护机制。
Eureka Server触发自我保护机制后,页面会出现提示:
Eureka Server进入自我保护机制,会出现以下几种情况:
(1)Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务;
(2)Eureka仍然能够接受新服务的注册和查询,但是不会被同步到其它节点上(即保证当前节点依然可用);
(3)当网络稳定后,当前实例新的注册信息会被同步到其它节点上;
Eureka自我保护机制是为了防止误杀服务而提供的一种机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除客户端;当Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka会自动退出自我保护机制。
如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一无效的服务实例,则会调用失败。对于这个问题需要服务消费者要有一些容错机制,比如重试,断路器等。
- 注册进来的微服务,获取其中的一些信息(团队开发)
1.查看 EurekaDiscoveryClient 源码:
2.观察 DiscoveryClient 源码:
3.在 springcloud-provider-dept-8001 的 DeptController.java中添加 discovery() 方法
//DiscoveryClient 可以用来获取一些配置的信息,得到具体的微服务
@Autowired
private DiscoveryClient discoveryClient;
/**
* 获取一些注册进来的微服务的信息
* @return
*/
@GetMapping("dept/discovery")
public Object discovery(){
//获取微服务列表清单
System.out.println("getServices()=>"+discoveryClient.getServices());
System.out.println("description()=>"+discoveryClient.description());
List<ServiceInstance> instances = discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost()+" "+ //主机名称
instance.getPort()+" "+ //端口号
instance.getUri()+" "+ //uri
instance.getInstanceId() //服务id
);
}
return this.discoveryClient;
}
4.上面 discoveryClient.getInstances()的参数 —> SPRINGCLOUD-PROVIDER-DEPT
5.主启动类中加入 @EnableDiscoveryClient 注解
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//启动类
@SpringBootApplication
//@EnableEurekaClient 开启Eureka客户端注解,在服务启动后自动向注册中心注册服务
@EnableEurekaClient
//@EnableDiscoveryClient 开启服务发现客户端的注解,可以用来获取一些配置的信息,得到具体的微服务
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
6.Run 测试
先启动 springcloud-eureka-7001 中的住启动类 EurekaServer_7001,
再启动 springcloud-provider-dept-8001 中的主启动类 DeptProvider_8001,
访问:http://localhost:7001/ 一切正常
继续访问:http://localhost:8001/dept/discovery
springcloud-provider-dept-8001 控制台输出:
- Eureka:集群环境配置
整体结构如下:
1.新建 子模块springcloud-eureka-7002 和 springcloud-eureka-7003
2.添加 pom 依赖 (与springcloud-eureka-7001相同)
3.application.yml配置 (与springcloud-eureka-7001相同)
(端口号用各自的 7001、7002和7003)
4.主启动类 (与springcloud-eureka-7001相同)
5.集群成员相互关联
配置一些自定义本机名字,在C:WindowsSystem32driversetc
找到本机hosts
文件,在hosts
文件最后加上,要访问的本机名称(默认是localhost)
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
【注意】:修改hosts
文件后一定要保存,如果遇到 修改hosts文件无权限的问题,参考下图配置:
6.修改 各自的 application.yml 的配置
(1)设置各自的 服务端的实例名字(hostname)
(2)设置各自的 集群(关联)
springcloud-eureka-7001 的 application.yaml
server:
port: 7001
# Eureka 部署
eureka:
instance:
# Eureka服务端的实例名字
hostname: eureka7001.com
client:
# 表示是否向 Eureka 注册中心注册自己(这个模块本事是服务器,所以不需要)
register-with-eureka: false
# fetch-registry 如果为 false,则表示自己为注册中心,客户端的为 true
fetch-registry: false
# Eureka 监控页面
service-url:
# public static final String DEFAULT_URL = "http://localhost:8761/eureka/";
# 单机:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群(关联):7001关联 7002、7003
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
springcloud-eureka-7002 的 application.yaml
server:
port: 7002
eureka:
instance:
# Eureka服务端的实例名字
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
# 集群(关联):7002关联 7001、7003
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
springcloud-eureka-7003 的 application.yaml
server:
port: 7003
eureka:
instance:
# Eureka服务端的实例名字
hostname: eureka7003.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
# 集群(关联):7003关联 7001、7002
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
7.通过 springcloud-provider-dept-8001 的yaml配置文件,修改 Eureka 的配置:配置服务注册中心地址
# Eureka配置:配置注册中心地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-provider-dept8001 # 修改eureka上的默认描述信息
prefer-ip-address: true
8.模拟集群搭建完毕。(可以把一个项目挂载到三个服务器上了)
测试,访问:http://localhost:7001/
测试,访问:http://localhost:7002/
测试,访问:http://localhost:7003/
- Eureka与Zookeeper 的对比
1.CAP原则
- RDBMS (MySQLOraclesqlServer) ===> ACID
- NoSQL (RedisMongoDB) ===> CAP
2.ACID是什么
- A (Atomicity) 原子性
- C (Consistency) 一致性
- I (Isolation) 隔离性
- D (Durability) 持久性
3.CAP是什么
- C (Consistency) 一致性
- A (Availability) 可用性
- P (Partition tolerance) 分区容错性
CAP的三进二:CA、AP、CP
4.CAP理论的核心
一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类
- CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
- CP:满足一致性,分区容错的系统,通常性能不是特别高
- AP:满足可用性,分区容错的系统,通常可能对一致性要求低一些
5.作为分布式服务注册中心,Eureka比Zookeeper好在哪里?
著名的CAP理论指出,一个分布式系统不可能同时满足C (一致性) 、A (可用性) 、P (容错性),由于分区容错性P再分布式系统中是必须要保证的,因此我们只能再A和C之间进行权衡。
Zookeeper 保证的是 CP —> 满足一致性,分区容错的系统,通常性能不是特别高
Eureka 保证的是 AP —> 满足可用性,分区容错的系统,通常可能对一致性要求低一些
- Zookeeper保证的是CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30-120s,且选举期间整个zookeeper集群是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zookeeper集群失去master节点是较大概率发生的事件,虽然服务最终能够恢复,但是,漫长的选举时间导致注册长期不可用,是不可容忍的。
- Eureka保证的是AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有之中自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
(1)Eureka不在从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2)Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上 (即保证当前节点依然可用)
(3)当网络稳定时,当前实例新的注册信息会被同步到其他节点中
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。