参考链接
官网-SpringCloud
Gitee-SpringCloud
语雀资料库:代码-脑图和资料
1 SpringCloud初使用
环境配置
SpringBoot和SpringCloud版本选择
自动热部署
2 服务注册与发现
通用
- 服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,其可以管理服务与服务之间依赖关系,同时实现服务调用、负载均衡、容错等,实现服务发现与注册。
- 服务注册与发现
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。
另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC。
调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)。
- 包含两个组件:Eureka Server和Eureka Client
- Eureka Server提供服务注册服务:各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。类似于物业中心。
- EurekaClient通过注册中心进行访问:是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。类似于业主、装修公司、来小区做核酸检测的医疗团队的,其都需要将自己注入到Server,然后节点要RPC前需要通过Server拿到地址,然后再自己使用服务调用工具对目标主机发起RPC访问。
- 自我保护
- 典型注册中心产品的对比
3 服务调用
负载均衡LB
- 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方
- 进程式LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon(消费端)
是基于Netflix Ribbon实现的一套客户端负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。小总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例。
Feign与OpenFeign(消费端)
简介
Feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service,它的使用方法是定义一个服务接口然后在上面添加注解。
- Feign远程调用
核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。@FeignClient+@RequestMapping构成远程调用的坐标,其他类中看似只是调用了调用者的方法,而实际上该方法跑去注册中心(如nacos)里和rpc里调用了并拿到东西返回。
- Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
- Feign集成了Ribbon
Feign利用Ribbon维护了服务提供方主机的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简
单的实现了服务调用。OpenFeign也默认支持Ribbon。
- Feign旨在使编写Java Http客户端变得更容易
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会
被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的
接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
- Feign的超时控制
默认Fetgn客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
- Feign和OpenFeign的区别

案例应用
- 场景
会员服务想要远程调用优惠券服务
- 启动多个微服务
在nacos上可查看到启动了多个微服务
被调用项目(优惠券服务)的代码
@RestController@RequestMapping("coupon/smscoupon")public class SmsCouponController {@Autowiredprivate SmsCouponService smsCouponService;/** 提供的服务功能:为请求者返回其具有的优惠券* */@RequestMapping("/member/list")public R membercoupons(){//应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给他返回SmsCouponEntity couponEntity = new SmsCouponEntity();couponEntity.setCouponName("满100-10");//优惠券的名字return R.ok().put("coupons",Arrays.asList(couponEntity));}}
在调用项目(会员服务) ```java 想要远程调用的步骤: 1 引入openfeign 2 编写一个接口,接口告诉springcloud这个接口需要调用远程服务 2.1 在接口里声明@FeignClient(“gulimall-coupon”)他是一个远程调用客户端且要调用coupon服务 2.2 要调用coupon服务的/coupon/coupon/member/list方法 @FeignClient+@RequestMapping构成远程调用的坐标,其他类中看似只是调用了CouponFeignService.membercoupons()。而实际上该方法跑去nacos注册中心里和rpc框架里调用了才拿到东西返回
3 在项目上开启远程调用功能 @EnableFeignClients,要指定远程调用功能放的基础包
pom.xml文件引入依赖<br />在会员服务项目引入openfeign依赖,其就具有了远程调用的其他服务的能力```java<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
在调用项目(会员服务)中配置一个单独的远程调用FeignService类
package com.efly.gulimall.member.feignservice;
import com.efly.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/*
1、接口上要打上被调用的项目名
2、方法上要打上被调用的远程完整访问名
*/
@FeignClient("gulimall-coupon-micservere")
public interface CouponFeignService {
@RequestMapping("/coupon/smscoupon/member/list")
public R getCouponsByMember();
}
在项目入口打上@EnableFeignClients标签,让SpringBoot知道这些包下面的是FeignClient
@EnableDiscoveryClient
@EnableFeignClients("com.efly.gulimall.member.feignservice")
@MapperScan("com.efly.gulimall.member.dao")
@SpringBootApplication
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
- 测试访问
在调用处实例化一个方法
@RestController
@RequestMapping("member/member")
public class MemberController {
@Autowired
private CouponFeignService service;
/**
* 列表
*/
@RequestMapping("/test")
public R test() {
return service.getCouponsByMember();
}
}
访问:http://localhost:8001/member/member/test
测试:如果被调用者的服务是在线的,则访问成功{"msg":"success","code":0,"coupons":[{"id":null,"couponType":null,"couponImg":null,"couponName":"满100-10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}
测试:如果被访问服务不在线,则访问失败
{"msg":"运行时异常:com.netflix.client.ClientException: Load balancer does not have available server for client: gulimall-coupon-micservere","code":500}
升级:远程调用丢失用户信息
问题
Feign在远程调用容易出现的问题:转发请求时,转发的请求头中没有含有SessionID的cookie,所以也就不能得到服务端的session数据,也就没有用户数据。
解决思路:向容器中导入定制的RequestInterceptor,并该RequestInterceptor->RequestTemplate加上cookie
一个点:RequestContextHolder为SpingMVC中共享request数据的上下文,底层由ThreadLocal实现,也就是说该请求只对当前访问线程有效。如果new了新线程就找不到原来request了。
####解决思路:向容器中导入定制的RequestInterceptor为请求加上cookie ####
package com.efly.gulimall.product.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Configuration
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
/*
1、RequestContextHolder为SpingMVC中共享request数据的上下文,底层由ThreadLocal实现,也就是说该请求只对当前访问线程有效。
如果new了新线程就找不到原来request了
*/
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2. 将老请求得到cookie信息放到feign请求上
String cookie = request.getHeader("Cookie");
template.header("Cookie", cookie);
}
}
}
};
}
}
4 服务降级
Hystrix(主要用于消费端)
基础概念:易记
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。Hystrix更多的是用在消费端(即主动调用方,即舔狗方),当然,服务端也可以用。
- 服务降级:FallBack(发信息不回,则慢慢降级,用兜底的处理方法)
- 服务熔断:break(发短信6次不回或8次超过2个小时候回—>则冷处理,先降级,再熔断拉黑继而删除,如果已经是熔断的,则后续访问直接执行兜底方法。。。当然服务会慢慢恢复。。一开口就是老舔狗了)
- 服务限流:FlowLimit

服务降级
降级服务:调用方不再等待,自己去处理兜底逻辑(使用@HystrixCommand注解)
需要服务降级的情况:
- 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理。
- 程序出现异常
- 其他服务不可用的情况
总之,服务不可用时有一个兜底处理方法,此为服务降级(即服务fallback)。
@Service
public class PaymentServiceImpl implements PaymentService {
//*************1.0 服务的熔断---//
//在调用方(即消费端)的FeignClient接口做服务降级:调用方法目标要求3秒,如果方法超过了5秒,那么到第3秒的时就不再等待,直接执行兜底的错误方法。
//@HystrixCommand:启用hystrix
@HystrixCommand(fallbackMethod = "getPaymentInfo_ErrorHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String getPaymentInfo_Error(Integer id) {
//return Thread.currentThread().getName()+"----ERROR----"+id;
System.out.println(Thread.currentThread().getName()+"-----------error----------"+id);
Integer timeout = 5;
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getName()+"-----getPaymentInfo_Error----ERROR----"+id;
}
//*************2.0 服务的熔断---//
@HystrixCommand(
fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id<0) {
throw new RuntimeException("******id不能为负数");
}
String simpleUUID = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t" + "成功调用,流水号是:" + simpleUUID;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id不能为负数,请稍后再试............"+id;
}
}
服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
断路器 本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
5 服务路由网关
Gateway(挡在前面)
简介(挡在前面,负载均衡、路由转发)
网关是请求流量的入口,常用功能包括反向代理、鉴权控制、权限校验,限流控制,最核心的功能是路由转发、负载均衡等。
动态上下线:发送请求需要知道商品服务的地址,如果商品服务器有123服务器,1号掉线后,还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上线还是下线。
一句话总结:
- SpringCloud Gateway使用的是Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架,是SpringCloud团队开发的。
- 请求先通过网关,再由网关路由到具体的服务提供者。
参考文档:cnblog:SpringCloud Gateway


访问均发到API-Gateway网关。由Gateway再发起后续的分发请求。
当然前面也可以套用一层,请求先到CDN->Nginx集群,再由Nginx集群分发到网关服务。
使用:创建一个独立的Gateway项目
- 基本信息选择

- 进一步选择Gateway组件

- pom里面加入common依赖
同时,由于gateway网关项目不需要连接数据库,所以在pom里面可以排除掉common项目里面对于数据库的引用,或者在启动类上标注@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
<dependency>
<groupId>com.efly.gulimall</groupId>
<artifactId>guliamll-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
- 将当前网关GateWay项目加入到nacos注册中心
spring: # 配置nacos.discovery cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # 在nacos发现中心里面显示的名字 application: name: gulimall-gateway-micservere
这样,nacos就可以发现这个网关项目了@EnableDiscoveryClient @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class GulimallGatewayApplication { public static void main(String[] args) { SpringApplication.run(GulimallGatewayApplication.class, args); } }
网关配置
三大特性
- Route路由:网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
- Filter过滤器。和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。
应用:全局日志,应用鉴权
- Predicate断言:这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。
处理流程:客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
简易配置 //各字段含义如下 id:我们自定义的路由 ID,保持唯一 uri:目标服务地址 predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
//下面这段配置的意思是:配置了一个 id 为 url-proxy-1的URI代理规则。
//路由的规则为:当访问地址http://localhost:8080/csdn/1.jsp时(因为Path=/csdn),会路由到上游地址https://blog.csdn.net/1.jsp
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
-id: url-proxy-1
uri: https://blog.csdn.net
predicates:
-Path=/csdn
和注册中心相结合的配置 详见:SpringCloud Gateway文档
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
## 精准匹配的要放前面,使其优先匹配 ##
- id: ware_route
uri: lb://gulimall-ware-micservere
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
## 兜底匹配放后面 ##
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
说明:
a、当访问链接是http://gulimall.com/api/ware/category/list/tree,此时匹配到第一个路由配置(根据断言predicates进行匹配),匹配上后根据filter过滤器进行路径替换:把api去掉,然后根据uri进行转发:此时负载均衡访问nacos上的gulimall-ware-micservere服务(lb:是负载均衡)。最终实际访问链接是:http://localhost:10000/ware/category/list/tree
即:http://gulimall.com/api/ware/category/list/tree ====> http://localhost:10000/ware/category/list/tree
b、当访问链接是http://localhost:88/api/captcha.jpg,此时匹配到第二个兜底路由配置(根据断言predicates进行匹配),匹配上后根据filter过滤器进行路径替换:把api替换为renren-fast,然后根据uri进行转发。
即:http://gulimall.com/api/captcha.jpg ====> http://localhost:8080/renren-fast/captcha.jpg
6 服务配置
Config分布式配置(独立的服务端&集成在其他消费端)
是什么:SpringCloud conig为微服务架构中的微服务提供集中化的外部配置支持,可以为各个不同的微服务应用提供一个中心化的配置。
- 集中管理配置文件
- 当需要更改配置时,不需要重启即可感知
怎么玩:Springcloud Config分为服务端和客户端两部分。服务端也称分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密、解密信息等访问接口。
7 服务消息通知
消息总线Spring Cloud Bus(独立的服务端和集成在其他消费端)
- 什么是SpringCloud Bus
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。其可以配合Spring Cloud Config使用,可以实现配置的动态刷新。Spring Clud Bus目前支持RabbitMQ和Kafka。
关于RabbitMQ的使用:RabbitMQ
- 什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个公用的消息主题。并让系统中的所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称为消息战线。在总线的各个实例上,都可以方便的广播一些需要让其他连接在该主题行的实例者都知道的消息。
- 基本原理与使用
ConfgClient实例都监听MQ的同一个topic(默认是SpingCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样其他监听同一个topic的服务就能得到通知,然后更新自身的配置。一次修改,广播通知,处处生效。
- 在Github上更改。
- 手动刷新Config Server。
- 由Config Server内部通过RabbitMQ消息队列机制将消息同步到各个客户端。
- 可以定制化给部分监听微服务
例子:curl -X POST "http://localhost:3344actuator/bus-refresh/config-clent:3355"//3344是ConfigServer,3355是单独想要通知的ConfigClient。
消息驱动Spring Cloud Stream(集成在其他消费/服务端)
作用:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。目前仅支持RabbitMQ、Kafaka。
Stream为什么能够统一底层差异?
在没有绑定器这个概念的情况下,SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性。通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。实现了应用程序与消息中间件细节之间的隔离。

一些注意点
- Stream中的消息通讯方式遵循了发布-订阅模式
- 生产端和消费端需要同时绑定Stream
重复消费
- 通过分组可以解决重复消费的问题
微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。这个组可以理解为是RabbitMQ的队列,交换机的数据会转发给所有与之绑定的队列,所有监听了队列的消费端是处于竞争关系的,即1个交换机绑定了10个队列,100个人绑定了某个队列。那么数据先到交换机,交换机会先转发数据到10个队列,每个队列只有1个消费者能够消费。这个队列就类似于Stream的分组概念。
持久化
8 服务链路跟踪
Sleuth+Zipkin(集成在各个端)
是什么?
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin。
- Sleuth:负责收集和整理数据。
- zipkin:负责展现上面收集到的数据。
使用:集成在各端,各端在访问的时候将会被Sleuth采样,然后使用zipkin的地址即可查看


