Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
个人理解就是对Springboot遇到的各种问题的解决,通过组件解决问题,有的组件可要可不要的,常见所需组件选择:
服务注册中心,优先选择Nacos和Zookeeper,Erueka已过时
服务负载均衡,考虑Ribben和LoadBalancer
服务熔断降级,优选Sentinel,次选Hystrix
服务调用,Open Feign 次选Feign
服务网关,GateWay, 次选Zuul
服务配置,Nacos,次选Config
服务总线,Nacos,次选Bus
Spring Cloud Alibaba,致力于提供微服务开发的一站式解决方案,只需添加一些注解和少量配置即可解决大量SB的问题,相比原生Spring Cloud简便搭建,学习曲线低,性能强悍,设计合理,故以SCA 为主 SC为辅
使用IDEA创建SC步骤:
1.创建父项目(Maven)
2.项目设置,设置中将全局编码,工程编码,默认编码设置为UTF-8,编译器设置为8,将package改为pom意为父工程,聚合管理其他模块,同时配置各个依赖的版本如lombok,druid,springboot等防止出现版本兼容问题,如有需要可再配,同时使用pom+impot解决maven单继承机制,在引入Spring Cloud(注意对应springboot版本)
3.创建子模块(Modul不是project)测试各个服务
引入相关依赖,无需引入版本(版本制裁,引入的是父项目版本),但注意boot-starter和boot是不同的依赖,父项目没配置的子项目要自己配置
举例:微服务之会员中心微服务:处理与会员相关的功能,例如会员注册、登陆、信息修改、积分管理、订单管理、优惠券管理等。
后端基本环境:
首先创建application.yml, 配置端口,mybatis,druid数据池及数据库相关配置
再创建SpringBoot主程序@SpringBootApplication
接着创建相关数据库信息,对应的在idea创建entity映射
创建返回结果成功与否的类Result,同时能包装json数据
再创建Dao接口定义相关的方法,再在Mapper.xml文件中实现方法,同样先写Service接口再加以实现
完成Controller类,添加方法/接口,通过Postman测试
完成基本框架
浏览器 =》 服务消费模块 =》 基本框架
添加消费服务模块:
创建新Moduel,引入相关maven依赖,创建配置文件application.yml及主启动类,创建对应entity类以及Result类
注入ResTemplate,Spring提供的用于访问Rest服务的模板类,,这个类提供了许多便捷访问远程HTTP服务的方法,且支持REST风格就像浏览器发出http请求调用对应API。 配置RestTempLate
@Configuration
public class CustomizationBean{
//注入RestTemplate对象/bean
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate;
}
}
创建Controller类
@RestController
public class MemberConsumerController{
//定义一个基础url地址,打到服务端
public static final Stirng member_service_url ="...10000";
//装配RestTemplate对象
@Resource
private RestTemplate restTemplate;
@PostMapping("/member/consumer/save")
public Result<Member> save(Member member){
//打到服务端接口,相当于http://localhost.../member/save
return restTemplate.postForObject(member_service_url+
"/member/save",member,Result.class);
//传的参数是member,返回对象类型为Result
//注意此时发送到服务端数据已经是json格式,故要在服务端用@RequetBody注解
//同时因为数据以流形式传输故需两emtity类实现Serilazibale接口
}
}
Run Dashboard:当springcloud的服务有多个时,可以用其方便管理,无需切换启动
要在 /.idea/workspace.xml 加入RunDashboard
公用模块,抽取公共模块使用maven打成jar包,其他模块引入即可,如Member,Result类
//完成公共模块配置
pom.xml:
lombok
<optional>true</optional> //表示其他模块引入该模块时不会引入此依赖,默认false
建立公共包包含 Member类和Result类
使用Maven右侧菜单栏 上方不build-生命周期-clean和install 即可生成jar文件夹,即可在其他模块引入
相关信息在maven-archiver下的pom.properties
其他模块引入:
<dependency>
相关信息在pom.properties中有(上面有讲)
</dependency>
Eureka(虽然过时了但有些老项目可能还要用,方便Nacos会学的更快)
假如服务提供方有多个,而服务消费方只有一个,它如何找到对应的提供方?
1.provider服务端可注册服务到Eureka Service
2.consumer消费端亦可注册服务或者从Eureka Service得到服务,即远程调用
3.Eureka包含两个组件:Eureka Server和Eureka Client
4.各个子模块(Consumer和Provider)通过配置启动后,会在Eureka Server注册,Provider会周期性(默认30s)发送心跳(心跳)给Eureka Server,若过了几个周期(默认90s)没有收到则会把服务节点移除
服务治理:传统管理各个服务直接依赖关系复杂,可以用服务治理进行优化实现 服务调用,负载均衡,容错,服务发现与注册等,Eureka就是服务治理
RPC:远程调用,即实现不同计算机之间通讯,因为各个模块可能在不同计算机上,上面的RESTTemplate就可以远程调用
单机版Eureka(后面集群)
消费服务(80),提供服务(10000),Eureka Service(9001)
创建Eureka Service模块,加入相关依赖(不要从其他模块直接复制粘贴)
引入Eureka场景启动器
<dependency>
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
</dependency>
application.yml配置Eureka-server
eureka:
instance:
hostname:localhost #服务实例名
client:
rgister-wiht-eureka: false #配置不向注册中心注册自己且多个eureka集群之间可能亦需要交流
ftech-registry:false #表示自己就是注册中心,作用维护服务实例,不需检索服务
service-url:
defaultZone:http://localhost:9001/euraka/ #交互模块,查询服务和注册服务依赖这个地址
创建主启动类EurekaApplication
@EnableEurekaServer #表示该程序作为EurekaServer
@SpringBootApplication
...
将提供服务(10000)注册到Eureka,成为服务提供者即(Eureka Server)
引入Eureka-client场景启动器
<dependency>
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
</dependency>
在application.yml配置eureka-client
eureka:
client:
register-with-eureka:true #将自己注册到Eureka-Server
fetch-registry:true #如果是单节点可不要,若是集群必须true才能配合Ribbon使用负载均衡
service-url:
defaultZone:http://localhost:9001/eureka #将自己注册到那个eureka-server
修改启动类
@EnableEurekaClient #将该模块标识为eureka-client
@SpringBootApplication
将消费服务(80)注册到Eureka,可以获得Eureka Service(9001)的注册信息
加入依赖,eureka client场景启动器
<dependency>
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
</dependency>
application.yml得配置和eureka类似
eureka:
client:
rgister-wiht-eureka: true
ftech-registry: true
service-url:
defaultZone:http://localhost:9001/euraka/
同样修改主启动类
@EnableEurekaClient
@...
大致流程:
1.启动Eureka Server和服务器提供者
2.提供者将自己信息注册到Eureka Server,通过心跳机制维护状态
3.消费者需要调用接口时,去注册中心获取实际RPC远程调用地址(即提供者地址)
4.消费者获取的地址会储存到JVM内存,默认每隔30s去更新一次服务调用地址
Eureka自我保证机制
EurekaClient定时向EurekaServer发送心跳包,若server端一段时间没收到client心跳包则会剔除改服务。如果开启自我保证机制,则不会剔除该服务(因为可能由于网路延迟原因造成的)
此机制为CAP理论之一
CAP理论:一致性:所有节点在同一时间数据完全一致
可用性:服务在正常时间内一直可用
区分容错性:系统遇到某节点或网络故障时仍能对外满足可用性或一致性服务
一个分布式系统只能同时满足三个中的两个
PS:自我保证机制可用关闭,但一般不关闭
Eureka集群
如果Eureka只有一个,那么它宕掉整个程序就会垮掉,为安全得引入集群
集群中Eureka之间可相互注册
创建第二个Eureka(9002)
pom.xml配置类似9001
application.yml(同时注意修改9001的yml文件)
Server:
port:9002
.
.hostname:eureka9002.com
.
client:
...
service-url:
defaultZone:http://eureka9001.com:9001/eureka/ 注册到eureka9001.com的9001端口
主启动类类似
为让浏览器直接访问Eureka,要修改host文件(C/Windows/System32/divers/etc)
#配置eureka主机和ip映射
127.0.0.1 eureka9001.com //这名字是在yml配置的
127.0.0.1 eureka9002.com
即可在浏览器直接访问如:eureka9002.com:9002
将服务消费(80)和提供服务(10000)注册到Eureka集群(略)
例如80:
yml文件:
...
defaultZone:http://eureka9001.com:9001/eureka,
http://eureka9002.com:9002/eureka
服务提供方也通常有集群(因为可能有多个请求打到服务提供方)
如添加个provider(10002),各个配置与10000一样(可拷贝java和source文件,不建议直接拷贝模块)
改进:provider-10000与provider-10002是一个集群提供服务,需将spring.application.name统一 服务别名这样才能方便进行负载均衡
修改10000与10002的yml文件
Spring:
application:
name:member-service-provider
修改服务消费方配置
Controller中
public static final String SERVICE_URL = "http://MEMBER-SERVICE-PROVIDER"
//MEMBER-SERVICE-PROVIDER就是服务提供方(集群),名字就是注册到注册中心的名字,目前两个(10000和100002)
Bean中
@Bean
@LoadBalanced //此注解表示赋予RestTemplate负载均衡能力即(默认)轮流使用100002和10000
public RestTemplate getRestTemplate(){
return new RestTemplate;
}
服务消费方(Eureka Client)得到服务方注册信息
消费方
Controller类修改
@Resource
private DiscoverClient discoverryClient;
//先注入DiscoverClient
@GetMapping("/member/consumer/discovery")
public Object discovery(){
List<String> services = discoveryClient.getServices(); //得到服务方集群
for(String service:services){
//所有服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for(ServiceInstance instance:instances){ //得到单个实例
getServiceId()....
}
}
}
同时bean类加入注解
@EnableDiscoveryClient
Ribbon
一套客户端负载均衡工具进程内LB,主要向客户端提供负载均衡算法和调用,基于某种规则(如之前的轮询,随机连接等,可以自己定制算法)连接指定服务
负载均衡LB(Load Balance),分为集成LB和进程内LB
切换均衡算法
例如更换成随机访问,一般情况下轮询就行了
在消费服务中
添加RibbonRule类
@Configuration
public class RibbonRule{
//配置自己的均衡算法
@Bean
public IRule myRibbonRule(){
return new RoundomRule(); //切换成随机访问
}
}
修改主程序类
@RibbonClient(name = "MEMBER_SERVICE_PROVIDER_URL",configuration="RobonnRule")
Open Feign
一个声明式WebService客户端,可以让编写Web Service客户端(远程调用)更简单,使用方法只需在一个服务接口上添加注解即可,SC对其进行了封装,配合Eureka和Ribbon组合支持负载均衡 PS:因为Feign不支持SpringMvc注解故被淘汰,使用Open Feign
现在获得服务可选择Ribbon+RestTemplate 或 OpenFeign
使用OpenFeign
创建新消费服务consumer-openfeign-80来测试
基本配置与上一个一样
在pox.xml引入
<dependency>
org.springframework.cloud
spring-cloud--starter-openfeign
</dependency>
//引入场景启动器
配置yml文件
port:80
name:e-commerce-consumer-openfeign-80
eureka:... //类似之前的
主启动器
@SpringBootApplication
@EnableEureaClient
@EnableFeignClient //启用Open Feign
...
不同的是,定义远程调用接口
@Compoent
@FeignClient(value = "MEMBER-SERVICE-PROVIDER") //这定义url
public interface MemberFeignService{
//结合上面的注解就是调用远程url http://MEMBER-SERVICE-PROVIDER/member/get/{id}
@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id") Long id); //定义方法
}
还要来个Controller
@RestContoller
public class MemberConsumerFeignController{
接口代理对象装配进去
@Resource
private MemberFeignService memberFeignService;
@GetMapping(value = "/member/.../{id}")
public Result getMemberById(@PathVariable("id" Long id){
memberFeignService.getMemberById(id);
}
}
OpenFeign 好处是支持MVC注解+接口解耦
Feign提供日志打印功能,可调控日志级别对Feign接口调用情况监控
级别:
NONE:默认的,不显示日志
BASIC:只记录请求方法,url,响应头信息
HEADERS:包含上面信息,还要请求和响应的头信息
FULL:包括以上全部还有请求和响应的正文
一般不用
网关服务SpringCloud Gateway
可对提供统一的调用接口,根据不同请求url,转发到对应的后端服务(配置即可),还具备限流,熔断,鉴权,负载均衡,反向代理等能力
Gateway是在Spring生态系统上构建的API网关服务,旨在对API进行路由,以及提供上面所述功能,比Zuul强太多故不用Zuul
Gateway核心组件:
路由(Routing):构建网关的基本模块,由ID,目标URI,一系列断言和过滤器组成,断言为true则匹配该路由
断言(Predicate):对HTTP请求中所有内容(请求头和请求参数)进行匹配,若请求与断言匹配则路由
过滤(Filter):在请求被路由前或之后对请求进行处理,如对Http请求断言匹配成功后,可以通过网关过滤机制对http请求处理(如修改请求带上参数)
大致工作机制
客户端发送请求 -> Spring Cloud Gateway -> 在Gateway Handler Mapping找到与请求相匹配的路由,将
其发送给Gateway Web Handler -> Handler通过过滤器链执行逻辑
构建GateWay项目架构(本身也是微服务模块)
本身是服务消费方的升级,故构架可参考消费方
e-commerce-gateway-20000:
pom.xml
//引入gateway
<dependency>
org.springframework.cloud
spring-cloud-starter-gateway
</dependency>
PS:得注销actuator和 spring-boot-starter,否则会出错启动不起来
他是服务网关,不需要web
yml文件(配置路由也可以写个配置类但通常就在yml文件):
port:20000
name:e-commerce-gateway
cloud:
gateway:
routes: //配置路由,可多个,内部是List
- id: member_route01 //路由id唯一
uri: http://localhost:10000 //服务端的uri,gate最终访问:url=uri+path
predicates: //此为断言,自己规定规则
- Path=/member/get/**
Euera:...9001 //配置Eureka,只注册一个
接上例如 客户端/浏览器 请求http://localhost:20000/member/get/1
只有前(蓝色)后(红色)都匹配成功才访问到http://localhost:10000/member/get/1,匹配失败则404
PS:此处uri太单调,后期会调灵活
主启动器
@SpringBootApplication
@EnableEurekaClient
PS:主程序启动顺序:Eureka注册中心 9001 => 提供服务端10000 => 网关20000
现在将路由改为动态路由
如 将服务器接口切换成10000或10002
前提得将10000和100002注册到Eureka
uri:lb//member-service-provider
#lb协议名,member-service-provider是注册到Eureka的服务名(必须小写),
#默认情况下负载均衡算法轮询
predicates:
- Path=/member/get/**
这样每次服务器/客户端发送http请求,Eureka会切换不同的接口给它(负载均衡),负载均衡算法可以自己定义,前面说过
Predicate/断言(细节)
就是一组匹配规则,当匹配成功就执行对应Route,失败则放弃处理/转发,Spring Cloud Gateway,包含许多内置Route Predicate工厂,这些Predicate都与http请求不同属性匹配,组合使用
比如时间:After,Before
http请求:请求头,带参数
组合:After Route Predicate,Before Route Predicate,Between Route Predicate...
例一:只有2023-11-18 12:35:50之后的请求才能匹配,否则不处理
...
predicates:
- Path=/member/get/**
- After=2023-11-18T12:35:00.000+08:00[Asain/Shanghai]
//8:00为毫秒
PS:必须满足两个条件才能匹配
例二:请求带有cookie键:user值:liwei 才能匹配成功
predictes:
- Cookie=user, liwei //注意逗号后面有空格
例三:请求头有Id 值为hello
predicates:
- Header=Id, hello
例四:请求参数有email并满足电子邮件基本格式,才能成功(如localhost:20000/.../email=...)
predicates:
- Query=eamil, (正则表达式)
例五:需客户端请求ip为127.0.0.1
predicates:
- RemoteAddr=127.0.0.1
补充path可定义多个路径中间用逗号隔开即可
...
predicates:
- Path=/member/get/**,/member/save
Filter(过滤器)
在请求被路由前或之后对请求进行处理,如对Http请求断言匹配成功后,可以通过网关过滤机制对http请求处理(如修改请求带上参数)
大致分为GatewayFilter和GlobalFilter,即 官方自带(使用较少) 和 用户自定义(常用)
例:GatewayFilter在进行断言后再添加参数
filter和uri,predicates是一个层级的
uri:lb//member-service-provider
predicates:...
filters:
- AddRequestParameter=color,blue
传入请求http://localhost:10000后经过过滤器后变为http:localhost:10000?color=blue
例:自定义GlobalFilter,如果请求参数user=sb,pwd=123456则放行,否则不能通过
自定义Filter类:
@Compoent
public class GatewayFilter implements GlobalFilter,Ordered{
@Override
filter方法(exchange,chain){
//先获取对应参数值,如 http://.../get/1?user=sb&pwd=123456
String user =
exchange.getRequest().getQueryParams().getFirst("user");
String pwd =
exchange.getRequest().getQueryParams().getFirst("pwd");
if(!("sb".equals(user) && "123456".equals(pwd))){ #如果不满足
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain filter(exchange); //满足则带过
}
@Override
getOrder方法(...){ //表示过滤器执行顺序,数字越小优先级越高
}
}