鉴于代码的自行改造和版本升级,随意更换依赖版本可能导致项目报错,因此在正文之前附上版本对应表.
SpringBoot和SpringCloud的版本需要对应,不然会因为jar包版本不兼容导致一系列奇怪的问题。因为官方不会保证SpringBoot和SpringCloud不同版本的兼容性。
SpringCloud版本 | SpringBoot版本 |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
参考:https://spring.io/projects/spring-cloud
spring-cloud-dependencies 版本列表可查看:https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies
spring-boot-starter-parent 版本列表可查看:https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent
本文正文中的代码均基于以下版本:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
鉴于目前企业实际情况,自己重做了Spring Boot 2.1.6 + Spring Cloud Greenwich.SR3版本的代码,期间坑真的是很多,大部分都是版本升级导致的依赖名称变更等,感兴趣的朋友可以自己尝试重构,再拿我的代码做参考:
链接:https://pan.baidu.com/s/1sIDEqymSMFjdlIl5XAxWIQ
提取码:zfur
马丁福勒微服务论文: https://martinfowler.com/articles/microservices.html
补充:
要弄清楚什么是RESTful API,首先要弄清楚什么是REST。
REST – REpresentational State Transfer,英语的直译就是“表现层状态转移”。如果看这个概念,估计没几个人能明白是什么意思。那下面就让我来用一句人话解释一下什么是RESTful:
URL定位资源,用HTTP动词(GET,POST,PUT,DELETE等)描述操作。
Resource:资源,即数据。
Representational:某种表现形式,比如用JSON,XML,JPEG等;
State Transfer:状态变化。通过HTTP动词实现。
所以RESTful API就是REST风格的API。 那么在什么场景下使用RESTful API呢?在当今的互联网应用的前端展示媒介很丰富。有手机、有平板电脑还有PC以及其他的展示媒介。那么这些前端接收到的用户请求统一由一个后台来处理并返回给不同的前端肯定是最科学和最经济的方式,RESTful API就是一套协议来规范多种形式的前端和同一个后台的交互方式。
微服务技术栈:多种技术的集合体
我们讨论一个分布式的微服务架构时,需要考虑哪些维度/有哪些落地技术?
微服务条目 | 落地技术 |
---|---|
服务开发 | SpringBoot,Spring,SpringMVC |
服务配置与管理 | Netflix公司的Archaius、阿里的Diamond等 |
服务注册与发现 | Eureka、Consul、Zookeeper等 |
服务调用 | Rest、RPC、gRPC |
服务熔断器 | Hystrix、Envoy等 |
负载均衡 | Ribbon、Nginx等 |
服务接口调用(客户端调用服务的简化工具) | Feign等 |
消息队列 | Kafka、RabbitMQ、ActiveMQ等 |
服务配置中心管理 | Spring Cloud Config、Chef等 |
服务路由(API网关) | Zuul等 |
服务监控 | Zabbix、Nagios、Metrics、Specatator等 |
全链路追踪 | Zipkin、Brave、Dapper等 |
服务部署 | Docker、OpenStack、Kubernetes等 |
数据流操作开发包 | Spring Cloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息) |
事件消息总线 | Spring Cloud Bus |
功能点/服务框架 | Netflix/SpringCloud | Motan(新浪) | gRPC | Thrift(Facebook) | Dubbo(阿里)/DubboX(当当) |
---|---|---|---|---|---|
功能定位 | 完整的微服务架构 | RPC框架,但整合了ZK或Consul,实现集群环境的基本服务注册/发现 | RPC框架 | RPC框架 | 服务框架 |
支持REST | 是,Ribbon支持多种可插拔的序列化选择 | 否 | 否 | 否 | 否 |
支持RPC | 否 | 是 | 是 | 是 | 是 |
支持多语言 | 是(REST形式) | 否 | 是 | 是 | 否 |
服务注册/发现 | 是(Eureka) Eureka服务注册表,Karyon服务端框架支持服务自注册和健康检查 | 是(zookeeper/consul) | 否 | 否 | 是 |
负载均衡 | 是(服务端zuul+客户端Ribbon) zuul-服务,动态路由 云端负载均衡 Eureka(针对中间层服务器) | 是(客户端) | 否 | 否 | 是(客户端) |
配置服务 | Netflix Archaius SpringCloud Config Server集中配置 | 是(zookeeper提供) | 否 | 否 | 否 |
服务调用链监控 | 是(zuul) Zuul提供边缘服务,API网关 | 否 | 否 | 否 | 否 |
高可用/容错 | 是(服务端Hystrix+客户端Ribbon) | 是(客户端) | 否 | 否 | 是(客户端) |
典型应用案例 | Netflix | Sina | |||
社区活跃度 | 高 | 一般 | 高 | 一般 | 2017年8月才重启 |
学习难度 | 中等 | 一般 | 高 | 一般 | 低 |
文档丰富度 | 高 | 一般 | 一般 | 一般 | 高 |
其他 | Spring Cloud Bus为我们应用程序带来了更多管理端点 | 支持降级 | Netflix内部在开发集成gRPC | IDL定义 | 实践公司比较多 |
Dubbo和Dubbox
Dubbo源于阿里的淘宝网开源的分布式的服务架构,致力于提供高性能和透明化的RPC远程服务调用方案,是SOA(Service-Oriented Architecture,面向服务的架构)服务化治理方案的核心框架。淘宝网将其开源之后,得到了很多的拓展和支持(比较出名的有:当当网的扩展版本dubbox,京东的扩展版本jd-hydra等),2012年之后Dubbo不再维护,17年8月份阿里重启维护。正是这段时间让SpringCloud异军突起。 梁飞团队被打散之后,内部开始使用HSF。
Dubbox是一个开源的RPC(Remote ProcedureCall Protocol)远程调用框架,是由当当对阿里的Dubbo的升级,可以被视为Dubbo的增强版,基本架构没有变动,升级spring2.x到spring3.x,支持RESTful风格的调用调试方式,丰富了序列化的方式,提高了序列化的性能。Dubbox(即Dubbo eXtensions)是当当网Fork基于dubbo2.x的升级版本,兼容原有的dubbo。其中升级了zookeeper和spring版本,并且支持RESTfull风格的远程调用。【2018年3月当当被收购,对于后续社区维护不看好】
以下引自知乎:(如今梁飞已经掌管天猫)
阿里巴巴为什么主推HSF?比Dubbo有哪些优势?
“真实原因是 dubbo 是阿里 b2b 团队编写的框架,hsf 是淘宝团队编写的框架。淘宝在业务复杂度以及规模上当然完爆 b2b,所以内部 pk 必然是 hsf 获胜。”
RPC vs REST
服务提供方与调用方接口依赖方式太强:我们为每个微服务定义了各自的service抽象接口,并通过持续集成发布到私有仓库中,调用方应用对微服务提供的抽象接口存在强依赖关系,因此不论开发、测试、集成环境都需要严格的管理版本依赖,才不会出现服务方与调用方的不一致导致应用无法编译成功等一系列问题,以及这也会直接影响本地开发的环境要求,往往一个依赖很多服务的上层应用,每天都要更新很多代码并install之后才能进行后续的开发。若没有严格的版本管理制度或开发一些自动化工具,这样的依赖关系会成为开发团队的一大噩梦。而REST接口相比RPC更为轻量化,服务提供方和调用方的依赖只是依靠一纸契约,不存在代码级别的强依赖,当然REST接口也有痛点,因为接口定义过轻,很容易导致接口文档与实际实现不一致导致服务集成时的问题,但是该问题很好解决,只需要通过每个服务整合Swagger,让每个服务的代码与文档一体化,就能解决。所以在分布式环境下,REST方式的服务依赖要比RPC方式的依赖更为灵活。
服务对平台敏感,难以简单复用:通常我们在提供对外服务时,都会以REST的方式提供出去,这样可以实现跨平台的特点,任何一个语言的调用方都可以根据接口定义来实现。那么在Dubbo中我们要提供REST接口时,不得不实现一层代理,用来将RPC接口转换成REST接口进行对外发布。若我们每个服务本身就以REST接口方式存在,当要对外提供服务时,主要在API网关中配置映射关系和权限控制就可实现服务的复用了。
Spring Boot:专注于快速方便的开发单个个体微服务(关注微观)
Spring Cloud:关注全局的微服务协调治理框架,将Spring Boot开发的一个个单体微服务组合并管理起来(关注宏观)
Dubbo | Spring | |
---|---|---|
服务注册中心 | Zookeeper | Spring Cloud Netfilx Eureka |
服务调用方式 | RPC | REST API |
服务监控 | Dubbo-monitor | Spring Boot Admin |
断路器 | 不完善 | Spring Cloud Netflix Hystrix |
服务网关 | 无 | Spring Cloud Netflix Zuul |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
最大区别:
Spring Cloud的中文参考资料: https://springcloud.cc/spring-cloud-netflix.html
Spring Cloud的中文API: https://springcloud.cc/spring-cloud-dalston.html
Spring Cloud中国社区: http://springcloud.cn/
Spring Cloud中文网: https://springcloud.cc/
概述:Spring Cloud工程由一个父工程和若干个Module组成
应该遵循的条件:约定 > 配置 > 编码
RestTemplate是Spring提供的用于访问Rest服务的客户端模板工具集,提供了多种远程访问Http的方法
RestTemplate的API : https://docs.spring.io/spring/docs/5.2.2.RELEASE/javadoc-api/
在一些不涉及实现方法的模块中(消费者),只需要调用其他服务暴露出的接口即可满足的需求,使用RestTemplate类中的方法可以发出需要的HTTP请求并得到返回结果。(类似Ajax)
RestTemplate restTemplate = new RestTemplate();
//url:请求地址
//requestMap:请求参数
//type.class:HTTP响应转换成的对象类型
restTemplate.getForObject(url,type.class);
restTemplate.postForObject(url,requestMap,type.class);
com.chengsw</groupId>
microservicecloud</artifactId>
0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.lzlgroupId>
<artifactId>microservice-apiartifactId>
<version>${project.version}version>
dependency>
dependencies>
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.XXX.entity
mapper-locations:
- classpath:mybatis/mapper/**/*.xml
spring:
application:
name: microservicecloud-dept #为这个服务取名,非常重要!!!!!
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/cloudDB01
username: root
password: 123456
dbcp2:
min-idle: 5 #最小连接数
initial-size: 5 #初始化连接数
max-total: 10 #最大连接数
max-wait-millis: 200 #等待连接最长的超时时间
package com.XXX;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Provider8001_APP {
public static void main(String[] args) {
SpringApplication.run(Provider8001_APP.class,args);
}
}
Eureka就像一个物业管理公司,其他微服务就像小区的住户,每个住户入住时都要向物业管理公司注册,并定时向物业公司交管理费
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
dependency>
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #false表示自己就是注册中心,职责是维护实例,不参加检索
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置eureka server的交互地址,即对外暴露的地址
package com.XXX;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class Eureka7001_APP {
public static void main(String[] args) {
SpringApplication.run(Eureka7001_APP.class,args);
}
}
先启动EurekaServer主程序,访问该服务地址即可
在要注册的微服务的pom.xml文件中增加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
# 将微服务客户端注册进Eureka服务列表中
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka # 就是Eureka注册中心地址
@SpringBootApplication //本服务启动后,会自动注册进Eureka服务中
@EnableEurekaClient
public class Provider8001_APP {
public static void main(String[] args) {
SpringApplication.run(Provider8001_APP.class,args);
}
}
eureka:
instance:
instance-id: dept8001 #修改别名
prefer-ip-address: true #显示IP地址
在查看Eureka时点击进入某个微服务的info时,能给查看者一些必要的信息,可以帮助查看者快速的了解该微服务,开发中十分有意义。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<build>
<finalName>microservicecloudfinalName>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<delimiters>
<delimit>$delimit>
delimiters>
configuration>
plugin>
plugins>
build>
info:
author: XXX
build-version: $project.version$
Eureka的自我保护机制主要是为了网络异常时保持高可用设计的,当在Eureka中注册的微服务超过设定是时间内(默认90秒)没有向Eureka服务端发送心跳,该微服务会进入自我保护模式。在自我保护模式中,Eureka会保护服务注册表中的信息,不会注销任何服务实例,直至收到的心跳数恢复至阈值以上,该微服务退出自我保护模式。
好死不如赖活:Eureka的设计哲学是宁可保留错误的服务信息,也不盲目注销可能健康的服务。所以异常的服务不会被注销,而是进入了自我保护模式。
在Eureka Server模块下的yml文件中添加配置信息即可,true表示打开自我保护模式;false表示关闭自我保护模式(不推荐)
eureka:
server:
enable-self-preservation: false #禁用自我保护
系统中的微服务可以通过Eureka的服务发现去获得在Eureka中注册的服务的信息,这是一个对外暴露的接口。
@Autowired
private DiscoveryClient discoveryClient;
@ResponseBody
@GetMapping("/provider/discovery")
public Object discovery(){
List<String> list = discoveryClient.getServices();
System.out.println(list);
List<ServiceInstance> insList = discoveryClient.getInstances("MICROSERVICECLOUD-DEPT");
for (ServiceInstance si:insList) {
System.out.println(si.getHost() +"," + si.getServiceId() +"," +si.getPort() +"," +si.getUri() +"," +si.getMetadata());
}
return this.discoveryClient;
}
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class Provider8001_APP {
public static void main(String[] args) {
SpringApplication.run(Provider8001_APP.class,args);
}
}
在controller方法中使用restTemplate对象调用provider中暴露的URL 并获得返回对象即可
private static final String URL_PREFIX = "http://localhost:8001";
@GetMapping("/discovery")
public Object discovery() {
return restTemplate.getForObject(URL_PREFIX+"/provider/discovery",Object.class);
}
基本原理
上图是来自eureka的官方架构图,这是基于集群配置的eureka;
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。
服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。
集群就是在不同的机器上配置相同的服务来构建要一个大的运算整体
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #hostname为hosts文件中映射的地址
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #false表示自己就是注册中心,职责是维护实例,不参加检索
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置eureka server的交互地址
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #其他两个服务端的地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
http://eureka7001.com:7001
http://eureka7002.com:7002
http://eureka7003.com:7003
Eureka遵守AP,Zookeeper遵守CP(C:强一致性,A:高可用,P:分区容错性,三者只能选其二,高并发下P必选)
Zookeeper | Eureka |
---|---|
当网络出现故障时,剩余zk集群会发起投票选举新的leader,但是此过程会持续30~120s,此过程对于高并发来说十分漫长,会导致整个注册服务的瘫痪,这是不可容忍的 | 在15分钟内85%的节点都没有心跳,则注册中心 会认为客户端与之出现了网络故障,则会进入自动保护模式。1.Eureka不会移除没有收到心跳的服务;2.新的服务仍能在服务端注册,但是暂时不会被同步到其他节点上直到网络稳定 |
Eureka可以很好的应对网络故障导致部分节点失去连接的情况,而不会像zookeeper那样导致整个注册服务系统的瘫痪。
RDBMS(MySQL/Oracle/sqlServer) ——> ACID
NOSQL(Redis/mongoDB) ——> CAP
ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
CAP:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。**CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。**由于分区容错性P在是分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡。
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡工具。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
Ribbon源码: https://github.com/Netflix/Ribbon
英文名称:Load Balance,微服务或分布式集群中常用的一种应用
简单来说负载均衡就是将用户的请求ping平摊的分配到多个任务上,从而是系统达到HA(高可用)
两种负载均衡:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
register-with-eureka: false #作为消费者不提供服务,不应该注册自己
@SpringBootApplication
@EnableEurekaClient
public class Consumer80_APP {
public static void main(String[] args) {
SpringApplication.run(Consumer80_APP.class,args);
}
}
private static final String URL_PREFIX = "http://MICROSERVICECLOUD-DEPT/";
目标:构建provider集群后consumer通过负载均衡轮询调用在Eureka中注册的服务
<!--修改对应的端口号-->
<artifactId>microservicecloud-provider-dept-8002</artifactId>
spring:
application:
name: microservicecloud-dept #同一集群下必须使用同一服务名!!!!!
Ribbon其实就是一个软负载均衡的客户端组件,可以和其他需要请求的客户端结合使用。
IRule:根据特定算法从服务列表中选取一个需要访问的服务
IRule是一个接口,七大方法是其自带的落地实现类
只需在配置类中配置一个返回具体方法的bean即可
@Bean
public IRule MyRule(){
return new RandomRule();
}
RetryRule测试,在Eureka集群启动完成,provider集群启动完成后,启动consumer服务,ConfigBean已改为RetryRule()
先测试,发现是provider服务轮询机制,然后人为的停掉8002服务,再次测试,结果如下:
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
public class DeptConsumer80_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_App.class, args);
}
}
目标:每个服务调用3次后再随机
package com.atguigu.myrule;
import java.util.List;
import java.util.Random;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
public class MyRandomRule extends AbstractLoadBalancerRule {
private int total = 0; // 总共被调用的次数,目前要求每台被调用3次
private int currentIndex = 0; // 当前提供服务的机器号
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes only get more
* restrictive.
*/
return null;
}
// int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
// server = upList.get(index);
if (currentIndex < upList.size()) {
if (total < 2) {
server = upList.get(currentIndex);
total++;
} else {
server = upList.get(currentIndex);
currentIndex++;
total = 0;
}
} else {
Random random = new Random();
int index = random.nextInt(serverCount);
server = upList.get(index);
}
if (server == null) {
/*
* The only time this should happen is if the server list were somehow trimmed.
* This is a transient condition. Retry after yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
Feign是一个声明式WebService客户端,使用方法时定义一个接口并在上面添加注解即可。Feign支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持SpringMVC和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign 是在 Ribbon的基础上进行了一次改进。采用接口的方式, 只需要创建一个接口,面向接口;然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写客户端变得非常容易。类似于 mybatis 的 @Mapper注解 。
Feign源码 : https://github.com/OpenFeign/Feign
<dependencies>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>microservicecloud-apiartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>springloadedartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
// microservicecloud-api模块中
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
@PostMapping("/dept")
public boolean addDept(Dept dept);
@GetMapping("/dept")
public List<Dept> findAll();
@GetMapping("/dept/{id}")
public Dept findById(@PathVariable("id")Integer id);
}
@RestController
@RequestMapping("/consumer")
public class ConsumerDeptController {
@Autowired
private DeptClientService service;
@PostMapping("/dept")
public boolean addDept(Dept dept){
return service.addDept(dept);
}
@GetMapping("/dept")
public List<Dept> findAll(){
return service.findAll();
}
@GetMapping("/dept/{id}")
public Dept findById(@PathVariable("id")Integer id){
return service.findById(id);
}
}
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.XXX"})
@ComponentScan("com.XXX")
public class Consumer80Feign_APP {
public static void main(String[] args) {
SpringApplication.run(Consumer80Feign_APP.class,args);
}
}
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
多个微服务互相调用的时候,如果A调用B、C,而B、C又继续调用其他微服务,这就是扇出(像一把扇子一样慢慢打开)。
Hystrix源码: https://github.com/Netflix/Hystrix
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
@ResponseBody
@GetMapping("/dept/{id}")
@HystrixCommand(fallbackMethod = "nullDeptFallBack")
public Dept findById(@PathVariable("id")Integer id) {
Dept dept = deptService.findById(id);
if (null == dept){
throw new RuntimeException("返回值为空!");
}
return dept;
}
public Dept nullDeptFallBack(@PathVariable("id")Integer id) {
System.out.println(111);
return new Dept().setId(id).setDeptName("nullName").setDbSource("nullDB");
}
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableDiscoveryClient //服务发现
@EnableCircuitBreaker//对hystrixR熔断机制的支持
public class DeptProvider8001_Hystrix_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptProvider8001_Hystrix_App.class, args);
}
}
如果按照上面的熔断案例来做的话,Controller下的每个方法,都要给其编写一个FallBack方法,当方法慢慢变多,就会造成代码膨胀,一个是增加编写的工作量,另外一个也会增大维护的难度,代码的耦合度也会高,是十分不合理的,所以要将其解耦。
因为服务端的是通过实现接口访问服务端的,如果在父接口上实现了FallBack方法,通过这样一种方式去维护起来就能实现解耦,也顺便完成了降级的机制。
import com.XXX.entity.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class DeptClientServiceFallBackFactory implements FallbackFactory<DeptClientService> {
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
public boolean addDept(Dept dept) {
return false;
}
public List<Dept> findAll() {
return null;
}
public Dept findById(Integer id) {
return new Dept().setId(id).setDeptName("服务器跪了,").setDbSource("迟点来吧");
}
};
}
}
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory = DeptClientServiceFallBackFactory.class)
public interface DeptClientService {
@PostMapping("/dept")
public boolean addDept(Dept dept);
@GetMapping("/dept")
public List<Dept> findAll();
@GetMapping("/dept/{id}")
public Dept findById(@PathVariable("id")Integer id);
}
feign:
hystrix:
enabled: true
介绍:SpringCloud对Hystrix Dashboard进行了整合,可以对通过Hystrix发起的请求进行准实时统计,并以报表和图形的形式展示给用户(包括每秒执行多少次请求成功和失败等)。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
@SpringBootApplication
@EnableHystrixDashboard
public class DashBoard9001_APP {
public static void main(String[] args) {
SpringApplication.run(DashBoard9001_APP.class,args);
}
}
代理:Zuul提供外部的请求转发到具体的微服务实例中的服务
路由:Zuul可以对外部访问实现统一的入口
过滤:Zuul可以对外部访问进行干预,如请求校验、服务聚合等
Zuul需要配合Eureka使用,需要在Eureka中注册并获得其他微服务的信息。Zuul服务最终还是会注册进Eureka
理解:Zuul就像大楼的保安,可以请他找人(代理),找的人在外面叫什么名字(路由),准不准你进楼(过滤)。因为保安属于物业公司,所以保安要在物业公司注册,所获得的信息也来源于物业公司(与Eureka的关系)。
Zuul的源码: http://github.com/Netflix/zuul
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zuulartifactId>
dependency>
server:
port: 9527
spring:
application:
name: microservicecloud-zuul #为这个服务取名,非常重要!!!!!
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul.com
prefer-ip-address: true
127.0.0.0 zuul.com
@SpringBootApplication
@EnableZuulProxy
public class Zuul9527_APP {
public static void main(String[] args) {
SpringApplication.run(Zuul9527_APP.class,args);
}
}
启动测试,访问规则:步骤3中指定映射+端口号+微服务名称+访问路径。
例子:http://zuul.com:9527/microservicecloud-dept/dept
当不想暴露真实的服务名时,可以对服务名进行映射,只需在application.yml中配置即可,具体作用看注释
# 隐藏真实微服务名称
zuul:
# ignored-services: microservicecloud-dept 隐藏单个微服务名称
prefix: /chengsw # 统一访问路径前缀
ignored-services: "*" # 隐藏所有微服务
routes: # 映射:mydept.serviceId映射到mydept.path,不做隐藏,则访问两个地址都能成功,不安全
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**
注:因为Zuul是针对外部访问管理的,所以配置了隐藏的服务,在系统中其他模块进行服务名访问时依然可以正常运行的,这点可以通过打开consumer模块进行验证!
因为做了映射之后,新旧地址还是都可以访问,因此可以设置隐藏服务名,用于屏蔽原来的请求路径。
分布式系统面临的配置问题:微服务意味着将单体应用拆分成一个个子服务,这些服务都是要相应的配置信息才能运行,随着系统内微服务数量越来越多,配置信息也不断地增多,所以一套集中式的、动态的配置管理设施是必不可少的。
目标:将配置文件部署在github,Config服务端从github获取配置
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.eclipse.jgitgroupId>
<artifactId>org.eclipse.jgitartifactId>
<version>4.10.0.201712302008-rversion>
dependency>
spring:
profiles:
active:
- dev
---
spring:
profiles: dev
application:
name: microservicecloud-config-XXX-dev
---
spring:
profiles: test
application:
name: microservicecloud-config-XXX-test
server:
port: 3344
spring:
application:
#为这个服务取名,非常重要!!!!!
name: microservicecloud-config
cloud:
config:
server:
git:
# uri填github上仓库地址
uri: https://github.com/XXXX/SpringCloud_Configuration.git
@SpringBootApplication
@EnableConfigServer
public class ConfigServer3344_APP {
public static void main(String[] args) {
SpringApplication.run(ConfigServer3344_APP.class,args);
}
}
/{application}/{profile}[/{lable}]
/{application}-{profile}.yml
/{lable}/{application}-{profile}.yml
/{application}-{profile}.properties
/{lable}/{application}-{profile}.properties
可用例子(返回格式可能不大相同,但返回值相同):
不可用例子:
介绍:客户端主要是在加载时通过config server服务端获得github配置仓库的地址,进而通过目标配置文件的文件名获取相应的配置,最后将取得的配置对自身资源进行赋值并提供访问
1.创建远程配置yml文件并上传到github上。如下测试案例因为需要进行测试,所以配置了两个profiles方便切换并观察
spring:
profiles:
active:
- dev
---
server:
port: 8201
spring:
profiles: dev
application:
name: microservicecloud-config-client-dev
eureka:
client:
service-url:
defaultZone: http://eureka-dev.com:7001/eureka/
---
server:
port: 8202
spring:
profiles: test
application:
name: microservicecloud-config-client-test
eureka:
client:
service-url:
defaultZone: http://eureka-dev.com:7001/eureka/
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
spring:
cloud:
config:
name: application_config #需要从github上读取的资源名称,注意没有yml后缀名
profile: test #本次访问的配置项
label: master
uri: http://config3344.com:3344 #本微服务启动后先去找3344号服务,通过Spring Cloud Config获取GitHub的服务地址
spring:
application:
name: microservicecloud_config
@RestController
public class RestController {
@Value("${server.port}")
private String port;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaZone;
@Value("${spring.application.name}")
private String name;
@GetMapping("/config")
@Override
public String toString() {
return "RestController{" +
"port='" + port + '\'' +
", eurekaZone='" + eurekaZone + '\'' +
", name='" + name + '\'' +
'}';
}
}
介绍:其实前面client的配置案例都是帮助理解这个组件为主,并没有很大的实际意义。。。。。。这节的案例中是配置一个Provider,一个Eureka,他们的配置统一在github上获取,实现统一配置分布式管理和多环境变更,这个才比较有实战意义。
Eureka配置文件示例:
spring:
profiles:
active:
- dev
---
spring:
profiles: dev
application:
name: microservicecloud-eureka-client-dev
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #hostname为hosts文件中映射的地址
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #false表示自己就是注册中心,职责是维护实例,不参加检索
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #设置eureka server的交互地址
---
spring:
profiles: test
application:
name: microservicecloud-eureka-client-dev
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #hostname为hosts文件中映射的地址
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #false表示自己就是注册中心,职责是维护实例,不参加检索
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #设置eureka server的交互地址
Provider配置文件示例:
spring:
profiles:
active:
- dev
---
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.XXX.entity
mapper-locations:
- classpath:mybatis/mapper/**/*.xml
spring:
profiles: dev
application:
name: microservicecloud-dept #为这个服务取名,非常重要!!!!!
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://192.168.88.246:3306/cloudDB01
username: root
password: 123456
dbcp2:
min-idle: 5 #最小连接数
initial-size: 5 #初始化连接数
max-total: 10 #最大连接数
max-wait-millis: 200 #等待连接最长的超时时间
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
instance-id: dept8001
prefer-ip-address: true
---
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.XXX.entity
mapper-locations:
- classpath:mybatis/mapper/**/*.xml
spring:
profiles: test
application:
name: microservicecloud-dept #为这个服务取名,非常重要!!!!!
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://192.168.88.246:3306/cloudDB02
username: root
password: 123456
dbcp2:
min-idle: 5 #最小连接数
initial-size: 5 #初始化连接数
max-total: 10 #最大连接数
max-wait-millis: 200 #等待连接最长的超时时间
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
instance-id: dept8001
prefer-ip-address: true
org.springframework.cloud
spring-cloud-starter-config
spring:
cloud:
config:
name: application_config #需要从github上读取的资源名称,注意没有yml后缀名
profile: test #本次访问的配置项
label: master
uri: http://config3344.com:3344 #本微服务启动后先去找3344号服务,通过SpringCloudConfig获取GitHub的服务地址
spring:
application:
name: microservicecloud_config
微服务架构体系知识展望,包含但不限于