学习总结,方便后续开发时快速回忆技术点。有兴趣看B站黑马技术视频,老师风趣幽默,技术点讲解通俗易懂,推荐!
微服务治理的技术栈,最早是阿里的dubbo,后国外的SpringCloud,阿里集成的SpringCloudAlibaba等技术栈,实现微服务协调治理,形成一个完整的技术栈生态群。
SpringCloudAlibaba集成Dubbo和SpringCloud技术栈,整合出即适用于Dubbo又可用于SpringCloud的技术群,在国内也是非常火热的。
那么实际开发中,可能会遇见不同的企业需求,不同的技术选型
SpringCloud集成了各种微服务的功能组件,基于Springboot实现这些组件的自动装配,提供了很好的开箱即用的体验:
客服端服务每隔一定时间都会向Eureka服务器发送心跳,确保客服端服务是存活的。
spring-cloud-starter-netflix-eureka-server
,可能不同版本中使用的组件不同@EnableEurekaServer
spring-cloud-starter-netflix-eureka-client
,可能不同版本中使用的组件不同若要启动两个相同的实例,端口不同,那么直接在idea dashboard控制台中,拷贝一份,然后设置启动参数即可。
-Dserver.port=8082
Ribbon的作用就是负载均衡
,当远程调用时Ribbon会拦截请求,然后到注册中心找对应的服务们(相同实例的服务),通过负载均衡算法(如轮询等),找出具体的某一个实例地址,进行接口访问。
@Bean
@LoadBalanced // 标记@LoadBalanced后,表示此对象发起的请求,会被ribbon拦截,并负载均衡处理
public RestTemplate restTemplate() {
return new RestTemplate();
}
是通过LoadBalancerInterceptor
(实现自ClientHttpRequestInterceptor)实现的。ClientHttpRequestInterceptor是一个拦截器,会拦截所有客户端发起的Http类型的请求。后面项目业务中如果有用到,可以实现此接口的方法。(如进行http请求时,所有请求要走代理,可以这么玩)
Ribbon默认采用的懒加载模式
,即第一次启动的时候才回去创建LoadBalanceClient,请求时间会很长。而饥饿加载
,会在项目启动时就创建,降低第一次访问的耗时,可通过配置开启:
ribbon:
eage-load:
enabled: true # 开启饥饿模式
clients: # 指定对某个服务访问时的饥饿加载
- userservice
- xxxservice
阿里巴巴产品。功能丰富,包含服务注册发现、远程配置中心
注意:nacos所在目录不要有中文!!
是作为一个外部服务使用的,需要进行下载安装,启动。
引入相关的依赖,建议包管理中直接引入spring-cloud-alibaba-dependencies
,然后方便管理其他依赖的版本信息
添加spring-cloud-starter-alibaba-nacos-discovery
配置文件中设置nacos服务器地址信息
spring:
cloud:
nacos:
server-addr: localhost:8848
启动类加上注解@EnableDiscoveryClient(启用 服务注册发现 客户端)
启动项目即可
配置文件中配置集群
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: BJ # 配置集群名称
服务分级模型(三级):
Ribbon负载均衡不会去考虑集群情况,只会轮询所有的服务实例,那么涉及到不同地点的集群时,肯定要优先选择本地集群的服务实例,那么可以使用nacos的负载均衡
。
假如A、B服务,A远程调用B,那么负载时需要先找本地集群
A、B服务都要配置集群属性,也就是上面的
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: BJ # 配置集群名称
在A配置中设置调用B服务时使用的负载算法
Bservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载规则
配置后,再次A调用B时会先找本地集群中的B服务,如果没找到,那么就去其他外地集群找服务,且在日志中会打印警告信息,告知是跨集群访问
。
在nacos控制台中,可手动设置实例的权重值(默认都是1的权重),如设置为0.1
设置后,此实例的访问频率会大大降低(注意:这里说的是集群模式下
),如果权重设置为0,那么此实例不会被访问,通常情况下可用作平滑升级时使用
。
通过namespace
,进行环境隔离
在nacos中可以设置不同的命名空间进行环境的隔离。
默认情况下是public
,nacos自带的命名空间,若要放入其他命名空间,需要在代码中配置命名空间。
spring:
cloud:
nacos:
discovery:
namespace: xxxxxx-xxxx-xxx-xxxx # 命名空间的ID
cluster-name: BJ # 集群
在nacos中,所有的实例默认都是临时实例
,每隔一段时间,临时实例都会给nacos发送健康心跳,确保存活。当临时实例死亡时,nacos会把临时实例剔除服务列表中。而非临时实例,是nacos主动询问服务是否存活,若服务挂了,那么nacos不会剔除它,而是给个不健康的状态,然后等它上线。
通过配置设置为非临时实例:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
nacos管理页面,创建配置,配置的DataId就是文件名,一般命名:服务名-profile.yml
即可。如business-dev.yml
,group默认即可,配置格式一般yaml即可。
项目启动时先读取nacos配置文件
---->本地application.yml文件—>…,在springcloud中,bootstrap文件的优先级是高于application.yml的,所以cloud中就使用bootstrap文件即可。
引入配置中心的相关依赖spring-cloud-starter-alibaba-nacos-config
在代码中做配置即可:bootstrap.yml
spring:
application:
name: business
profiles:
active: dev # 开发环境
cloud:
nacos:
server-addr: localhost:8848
Nacos中的配置变更后,微服务不需重启可以感知。需要下面两种配置实现:
方法一:在@Value注入的变量所在类上使用注解@RefreshScope
实现属性刷新(单量注入属性)
@Configuration
@RefreshScope
public class AConfig {
@Value("${time.a}")
private String a;
}
方法二:使用@ConfigurationProperties
注解(批量注入属性)而且不管是nacos还是consul,他们的配置都是会自动刷新的
@Component
@Data
@ConfigurationProperties(prefix = "time")
public class AConfig {
private String a;
}
服务启动时会从nacos中读取多个配置文件,首先是读取服务名-profile.yml
的文件,其次还会读取服务名.yml
的文件,那么服务名.yml
的文件可以作为共享的主配置文件来使用。
使用到时之间创建一个文件即可,按照上例的话就是business.yml
的文件,然后做配置就行了。而且有一点:带环境区分的文件的优先级是比主配置文件高的,若有相同的配置,那么毫无疑问是环境区分的文件business-dev.yml
的优先。
初始化mysql数据(mysql可以是集群也可以是单实例),sql文件可到网上找
配置nacos
进入config/cluster.conf文件(将cluster.conf.example文件重命名即可),添加内容
127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847
在config/application.properties文件中添加
mysql数据库相关连接
端口号属性
启动nacos集群
通过nginx做反向代理,负载均衡
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
当做好Nacos集群时,java客户端可以直接配置nginx的地址和端口访问即可
spring:
cloud:
nacos:
server-addr: localhost:80
声明式远程调用,用来替代Ribbon的复杂远程调用。Ribbon远程调用:
实现方式
引入相关依赖spring-cloud-starter-openfeign
启动类加上注解@EnableFeignClients
声明一个Feign客户端,比如要调用B中的服务接口,那么在A服务中创建此客户端
@FeignClient("BService")
public interface BServiceClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
直接使用即可,且Feign集成了Ribbon,那么负载均衡也是Feign同样具备的功能。
一般设置个日志级别完全够用了。Feign集成Ribbon,同样具有失败重试的功能了。
全局配置
feign:
client:
config:
default: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
loggerLevel: BASIC
局部配置
feign:
client:
config:
bservice: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
loggerLevel: BASIC
声明一个Bean
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.BASIC;
}
}
全局配置
在启动类的注解上
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
局部配置
在相应的Feign客户端上
@FeignClient(value = "BService", configuration = FeignClientConfiguration.class)
Feign底层的客户端实现:
优化点:
开始改造
引入HttpClient的支持依赖feign-httpclient
配置连接池,同样可配置一些超时时间等属性
feign:
client:
config:
default: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
loggerLevel: BASIC
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 每个路径的最大连接数
(紧耦合,不推荐)法一:继承。可以给Feign客户端和controller服务接口定义统一的父接口
参数映射不会被继承,注解需要再写一遍。。
法二:抽取。将Feign客户端抽取为独立模块,并且将接口有关的POJO、默认的Feign配置都放入到这个模块中,作为基础服务提供给其他所有消费者使用。
如果将feign单独作为一个模块使用时,可能会涉及到定义的Feign客户端不在引用者的Feign包扫描范围内
,而无法实现注入。两种解决方式:
法一:指定FeignClient的扫描包
@EnableFeignClients(basePackages = "com.wlh.feign")
法二:指定具体的Client
@EnableFeignClients(clients = {UserClient.class})
当然,也可能会涉及到无法扫描到,其他的包下的一些POJO类等组件,那么我们可以在@SpringBootApplication中去指定,扫描多个包。
示例:
@SpringBootApplication(scanBasePackages = {
"com.wlh.oss.config","com.wlh.oss.controller"
})
所有的请求必须经由网关处理进行路由转发,不可直接访问某一个微服务,且要做好上游token,下游鉴权(cloud模板有相关代码实现)。
网关功能:
创建新的网关module,引入网关依赖spring-cloud-starter-gateway
,和服务注册的依赖,如spring-cloud-starter-alibaba-nacos-discovery
做配置bootstrap.yml
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service # 路由id,自定义,唯一
uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
# uri: http://localhost:8080 # 也可跳转至某个具体url
predicates: # 路由断言,判断路由是否符合规则
- Path=/user/** # 以/user/ 开头就符合此路由
当访问localhost:10010/user/1
时,会跳转到lb://userservice/user/1
的接口上面。
断言工厂中有很多断言的条件,其中Path只是其中的一种,是由PathRoutePredicateFactory类进行处理的。
如果设置了多个断言规则,如果访问路径符合全部的断言规则,那么该请求就是符合断言规则的,可以进行路由转发。
快捷链接:SpringCloudGateway断言工厂之Path
GatewayFilter是网关的过滤器,可以对进入网关的请求和微服务返回的响应做处理。
Gateway提供了30多种过滤器,可看官方文档:过滤器工厂
这些过滤器可直接在配置文件中进行配置bootstrap.yml
或其他远程配置中心都可以
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service # 路由id,自定义,唯一
uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
predicates: # 路由断言,判断路由是否符合规则
- Path=/user/** # 以/user/ 开头就符合此路由
filters: # 过滤器
- AddRequestHeader=token,aabbcc
以上是给所有经过网关的且进行路由的请求,增加一个名为token
的请求头参数,且值是aabbcc
,这是局部设置过滤器。
如果要给所有的请求,路由转发时都设置一个过滤器,那么可以这样写:
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service # 路由id,自定义,唯一
uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
predicates: # 路由断言,判断路由是否符合规则
- Path=/user/** # 以/user/ 开头就符合此路由
default-filters:
- AddRequestHeader=token,aabbcc
全局过滤器:处理一切进入网关的请求和微服务响应,上面的GatewayFilter是通过配置定义的,无法处理详细的业务逻辑,那么就需要使用这种代码形式的过滤器。
使用方法:实现GlobalFilter接口,实现方法
// @Order(-1) // 表示设置优先级
@Component
public class GlobalGWFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 获取请求参数
MultiValueMap<String, String> queryParams = request.getQueryParams();
String token = queryParams.getFirst("token");
if ("aabbcc".equals(token)) {
// 链放行,交给后面的过滤器继续进行处理
return chain.filter(exchange);
}
// 终止,返回响应
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
@Override
public int getOrder() { // 设置优先级,越小优先级越高
return -1;
}
}
设置优先级,可以使用注解,也可以实现Ordered接口,实现getOrder方法即可。
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter(所有路由)、GlobalFilter全局过滤器
请求转发路由前
,会将当前过滤器、DefaultFilter和GlobalFilter合并到一个过滤器链中(集合中),排序后依次进行经过执行每个过滤器。底层是通过适配器模式
将GlobalFilter适配为GatewayFilter类型。
defaultFilter
>路由过滤器
>GlobalFilter
顺序执行。跨域:域名不一致就是跨域:
跨域问题的存在是:浏览器禁止请求的发起者与服务端发生跨域ajax请求
,请求被浏览器拦截的问题。
解决方案:CORS
处理,在配置文件中配置:
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true # 解决options请求拦截问题
cors-configurations:
'[/**]':
allowedOrigins: # 允许哪些网站跨域
- "*" # 表示允许所有跨域
allowedMethods: # 允许跨域的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 允许携带cookie
maxAge: 360000 # 跨域监测的有效期,网站一次跨域检测后,360000时间内不检查此网站的跨域