SpringCloud
一、服务注册与发现
Eureka(AP)
- 服务注册相对要快,因为不需要等注册信息replicate到其他节点,也不保证注册信息是否replicate成功;
- 当数据出现不一致时,虽然A,B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这回出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。
Nacos
/**
* 1-如何使用Nacos作为配置中心统一管理配置
* 1.依赖
* <dependency> cloud</grouId>
* <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
* </dependency>
* 2.创建一个bootstrap.properties:
* spring.application.name=gulimall-coupon
* spring.cloud.nacos.config.server-addr=127.0.0.1:8848
* 3.需要给配置中心默认添加一个(Data Id/数据集)gulimall-coupon.properties(应用名.properties)
* 4.动态获取配置
* @RefreshScope:动态获取并刷新配置
* @Value("${配置项的名}"):获取到配置
* 如果nacos配置中心和本地的配置文件中都配置了相同的项--优先使用nacos配置中心的配置
* 2-细节
* 1.命名空间:配置隔离:
* 默认:public(保留空间):默认新增的所有配置都在public空间;
* 1.开发、测试、生产:利用命名空间实现环境隔离
* 注意:在bootstrap.properties配置上 需要在哪个命名空间下的配置
* spring.cloud.nacos.config.namespace=XXXXXXXXXXXXXXXXXXXXXXXXXXX(配置管理ID)
* 2.每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
* 2.配置集:所有的配置的集合
* 3.配置集ID:
* Data ID--类似文件名
* 4.配置分组:
* 默认所有的配置集都属于:DEFAULT_GROUP;----1111,618,1212
* 配置配置分组:spring.cloud.nacos.config.group=1111---配置分组
*
* 每个微服务创建自己的命名空间,使用配置分组区分环境-dev\test\prod
* 3-同时加载多个配置集
* 1.微服务任何配置信息,在任何配置文件都可以放在配置中心中
* 2.只需要在bootstrap.properties说明加载配置中心中哪些配置文件即可
* 3.@Value、@ConfigurationProperties
* 以前SpringBoot任何方法从配置文件中获取值,都能使用
* 配置中心有的优先使用配置中心的配置
*/
Conul(CP)
- 服务注册相比Eureka会稍慢一些,因为Consul的reft协议要求必须过半数的节点都写入成功才认为注册成功;
- Leader过掉时,重新选举期间整个Consul不可用。保证了强一致性但牺牲可可用性。
##以开发者模式启动consul/localhost:8500
consul agent -dev -client=0.0.0.0
<!--springcloud提供的集于consul的服务发现-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul</artifactId>
</dependency>
<!--actuator的健康检查-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-actuator</artifactId>
</dependency>
#consul服务注册配置
cloud:
consul:
host: 127.0.0.1 #consul的主机地址
post: 8500 #服务器的端口号
discovery:
#是否需要注册
register: true
#注册的实例ID(唯一标志)
instance-id: ${spring.application.name}-1
#服务的名称
service-name: ${spring.application.name}
#服务的请求端口
port: ${server.port}
#指定开启ip地址注册
prefer-ip-address: true
#当前服务的请求ip
ip-address: ${spring.cloud.client.ip-addrss}
Zookeeper
二、远程调用
RestTemplate
Feign
- Feign是Netfix开发的声明式,模板化的HTTP客户端
- Feign自带Ribbon负载均衡(可以Ribbon的方式修改负载均衡策略)
1.导入依赖
<!--SpringCloud整合的openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.配置调用接口
/**
* Created with IntelliJ IDEA
* User: GHYANG
* Date: 2020-06-13
* Description:声明需要调用的微服务名称
*/
@FeignClient(name="service-product")
public interface ProductFeignClient{
/**
*配置需要调用的微服务接口(直接复制controller头部信息)
*/
@RequestMapping(value="/product/{id}",method=RequestMethod.GET)
public Product findById(@PathVariable("id") Long id);
}
3.在启动类上激活Feign
@EnableFeignClients
4.通过自动的接口调用远程微服务
//注入feign实例
@Autowired
ProductFeignClient productFeignClient;
//在方法体里调用
productFeignClient.findById();
5.其它配置
#配置fegin日志的输出
#NONE: 不输出日志
#BASIC: 适用于生产环境追踪问题
#HEADERS: 在BASIC的基础上,记录请求和相应头的信息
#FULL: 记录所有
feign:
client:
config:
service-product: #需要调用的服务名称
loggerLevel: FULL
三、缓存机制
Redis+Reidsson
-
缓存穿透: 空结果缓存
-
缓存雪崩:设置随机过期时间
-
缓存击穿:添加锁
//只要是同一把锁,就能锁住需要这个锁的所有线程
//1-synchroized(this) :SpringBoot所有的组件在容器中都是单例的
// 1–得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
// 2–本地锁:synchaonized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布锁,本地锁每个服务都会执行一次业务;
//使用map模拟本地缓存
private Map<String,Object> cache = new HashMap<>();
//模拟方式
Map<String, List<Catelog2Vo>> catalogJson = (Map<String, List<Catelog2Vo>>) cache.get("catalogJson");
if(cache.get("catalogJson") == null){
//缓存为空
//调用业务,并存入缓存
cache.put("catalogJson",parent_cid);
}
return catalogJson;
//使用Redisson
@ResponseBody
@GetMapping("/hello")
public String hello(){
//1-获取一把锁 只要锁的名字相同 就是同一把锁 从而实现分布式
RLock my_lock = redisson.getLock("my_lock");
//2-加锁
my_lock.lock();//阻塞式等待,默认加的锁都是30s时间
//2--锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,无需担心业务时间长,锁自动过期被删掉
//3--加锁的业务只要运行完成,就不会给当前锁续期,及时不手动解锁,锁默认在30s以后自动删除
// my_lock.lock(10, TimeUnit.SECONDS); //10秒自动解锁,自动解锁时间需大于业务的执行时间
//问题:my_lock.lock(10, TimeUnit.SECONDS:在锁时间到了以后,不会自动续期
/**
* 1-如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
* 2-如果我们未指定锁的超时时间,就使用30 * 1000{lockWatchdogTimeout看门狗的默认时间}
* 只要占锁成功,就会启动一个定时任务{重新给锁设置过期时间,新的过期时间就是看门狗的默认时间}
* internalLockLeaseTime{看门狗时间}/3--->10s 没隔10s自动再次续期,续成30s
*
* 最佳实战:
* 使用my_lock.lock(10, TimeUnit.SECONDS);省掉了整个续期操作,手动解锁,不使用看门狗机制
*/
try {
System.out.println("加锁成功-执行业务"+Thread.currentThread().getId());
Thread.sleep(20000);
}catch (Exception e){
}finally {
//3-解锁 假设解锁代码没有运行 redisson会不会出现死锁
System.out.println("释放锁"+Thread.currentThread().getId());
my_lock.unlock();
}
return "hello";
}
-
redis读写锁:
-
读写锁–保证一定读到最新数据,修改期间,写锁是一个排他锁(互斥锁、独享锁),读锁是一个共享锁
- 写锁没释放就必须等待
- 读锁 + 读锁:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁,他们都会同时加锁成功
- 写锁 + 读锁:等待写锁释放
- 写锁 + 写锁:阻塞方式
- 读锁 + 写锁:有读锁。写也必须等待
- 只要有写的存在,都必须等待
四、负载均衡
Ribbon
1.服务调用
- eureka内部集成了ribbon(SpirngCloud同样为Consul内部集成了ribbon 用法相同)
- 在创建RestTemplate的时候,声明@LoadBalanced
- 使用restTemplate调用远程服务:不需要拼接微服务的URL,以待请求的服务名替换IP地址
2.负载均衡
负载均衡策略:轮询(默认)、随机、重试、权重
3.重试机制
引入spring的重试依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artfactId>spring-retry</artfactId>
</dependency>
对ribbon进行重试配置
service-product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
Nginx F5
#轮询配置
upstream product{
service 127.0.0.1:8002; #服务器一
service 127.0.0.1:8081; #服务器二
}
#扩展:以下为静态资源配置
server{
listen 80; #监听的端口号
server_name gulimall.com #监听的域名
location /static/{ #匹配的url
root /usr/share/nginx/html; #匹配成功访问的实际资源路径
}
}
#扩展:以下为反向代理
server{
listen 80; #监听8080端口
server_name gulimall.com #监听的域名
location /{ #拦截监听到的请求
proxy_pass http://gulimall; #请求转发到系统网关 其中gulimall与下方upstream中的server相同
}
}
#upstream xxx:表示负载均衡服务器 即为上游服务器
upstream gulimall{
server 192.168.252.1:88 #实际请求的路径
}
#权重配置
#upstream xxx:表示负载均衡服务器 即为上游服务器
upstream gulimall{
server 192.168.252.1:88 weight=2; #实际网关及权重值
server 192.168.252.1:89 weight=3; #实际网关2及权重值
}
#ip绑定配置
#upstream xxx:表示负载均衡服务器 即为上游服务器
#匹配原理:根据获取客户端的ip地址,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号,采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,其每次都会映射到用一台后端服务器进行访问。
upstream gulimall{
server 192.168.252.1:88; #实际网关
server 192.168.252.1:89; #实际网关2
ip_hash;
}
五、服务熔断
-
雪崩效应:服务与服务之间依赖性强,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果。
-
服务隔离:指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖性。
-
熔断降级:当下游服务因访问压力过大而相应变慢或失败,上游服务为了保护系统的可用性,可用暂时切断下游服务的调用。
-
服务限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量以达到保护系统的目的。如:推迟解决、拒绝解决、部分拒绝解决。
Hystrix
1.简介
其是由Netfix开源的一个推迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
2.实现延迟和容错
包裹请求、跳闸机制、资源隔离、监控、回退机制、自我修复
3.引入Hystrix依赖(feign中已经继承了Hystrix)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netfix-hystrix
</artifactId>
</dependency>
4.配置开启Hystrix
feign:
hystrix:
enabled: true
5.自定义一个接口降级逻辑
/**
* Created with IntelliJ IDEA
* User: GHYANG
* Date: 2020-06-13
* Description:
*/
@Component
public class ProductFeignClientCallBack implements PeoductFeignClient{
/**
* 熔断降级方法
* @param id
* @return
*/
@Override
public Product findById(Long id) {
System.out.println("feign调用触发熔断降级方法");
return null;
}
}
6.在接口添加降级方法支持
@FeignClient(name = "service-product",fallback = ProductFeignClientCallBack.class)
7.配置超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInmilliseconds: 3000
#默认的连接超时时间1秒,若1秒没有返回数据,自动触发降级逻辑
8.使用监控功能
<!--引入hystrix的监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netfix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netfix-hystrix-dashboard</artifactId>
</dependency>
#1--暴露所有actuator监控的端点
management:
endpoints:
web:
exposure:
include: '*'
#2--在页面上访问localhost:端口/actuator/hystrix.stream
#3--在启动类上激活web监控平台@EnableHystrixDashboard
Alibaba Sentinel
- 丰富的应用场景、完备的实时监控、广泛的开源生态、完善的SPI扩展点
1.管理控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar xxxxxx.jar
#localhost:8080 sentiel--sentiel
2.将服务交给控制台管理
<!--父工程引入Alibaba实现的SpringCloud-->
<dependency>
<grupId>com.alibaba.cloud</grupId>
<artifactId>spring-cloud-alibaba-dependecies</artifactId>
<version>2.1.0.RELEASE</ version>
<type>pom</type>
<scope>pom</scope>
</dependency>
<!--子工程引入sentinel-->
<dependency>
<grupId>com.alibaba.cloud</grupId>
<artifactId>spring-cloud-starter-sentinel</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
#sentinel控制台的请求地址
3.定义降级逻辑
/**
* blockHandler:声明熔断时调用的降级方法
* fallback:声明异常执行时执行的降级方法
* value:自定义的资源名称(默认为当前全类名.方法名)
* 具体熔断配置需要在sentunel控制台上进行实时配置
*/
@SentinelResource(blockHandler = "orderBlockHandler",fallback = "orderFallback")
4.加载本地配置
spring:
cloud:
sentinel:
datasource:
ds1:
file:
file: classpath: XXXX.json #本地的xxx.json文件
data-type: json
rule-type: flow
//本地json文件配置
{
{
"resource":"orderFindById",
"controlBahavior":0,
"count":1,
"grade":1,
"limitApp":"default",
"strategy":0
}
}
六、服务网关
Zuul 1.0 网关
1.介绍
2.搭建Zuul网关服务器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netfix-eureka-client</artifactId>
</dependency>
3.路由
##方法一:路由配置--直接配置
zuul:
routes:
##配置商品服务映射
product-service: XXX #路由id,随便写
path: /product-service/** #映射路径
url: http://127.0.0.1:9001 #映射路径对应的实际微服务url地址
##方法二:路由配置--根据服务进行配置
#该方法需要配合eureka注册中心配置
#其中serviceId是注册到注册中心的服务名称
zuul:
routes:
##配置商品服务映射
product-service: XXX #路由id,随便写
path: /product-service/** #映射路径
serviceId: service-product
##方法三:路由配置--若路由id和对应的微服务serviceId一致(该方法配合Eureka使用)
zuul:
routes:
service-product: /product-service/**
##方法四:路由默认配置--若当前的微服务名称service-product 可直接使用默认路径 /service-product/** (该方法配合Eureka使用)
4.过滤器
//自定义过滤器--以身份认证为例
@Component
public class LoginFilter entends ZuulFilter{
/**
* 定义过滤器类型
* pre: 转发到微服务之前执行的过滤器 -- 多用于身份认证
* routing: 在路由请求时执行的过滤器
* post: 执行微服务获取返回值之后执行的过滤器
* error: 在整个阶段抛出异常时执行的过滤器
*/
public String filterType(){
return "pre";
}
/**
* 指定过滤器的执行顺序
* 返回值越小 执行顺序越高
*/
public int filterOrder(){
return 1;
}
/**
* 当前过滤器是否生效*/
public boolean showFilter(){
return true;
}
//执行过滤器中的业务逻辑
/**
* 案例身份认证:
* 1-所有的请求需要携带一个参数:access-token
* 2-获取request请求
* 3-通过request获取参数access-token
* 4-判断token是否为空
*/
public Object run() throws ZuulException{
System.out.println("Run Filter");
//1-获取zuul提供的上下文对象RequestContext
RequestContext ctx = RequestContext.getCurrentContext();
//2-从RequestContext中获取request
HttpServletRequest request = ctx.getRequest();
//3-获取请求参数access-token
String token = request.getParameter("access-token");
//4-判断
if(token == null){
//拦截请求返回认证失败
ctx.setSendZuulResponse(false);//拦截请求
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
//token不为空 则return null 继续操作
return null;
}
}
SpringCloud Gateway
1.路由配置
<!--springcloud Gateway内部是通过netty + webflux实现
webflux实现和springmvc存在冲突-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
#方法一:直接配置
spring:
cloud:
gateway:
routes:
#配置路由:路由id/url/断言
- id: product-service
url: http://127.0.0.1:9001 #目标微服务请求地址
predicates:
-Path=/product/**
#方法一:根据微服务配置
spring:
cloud:
gateway:
routes:
#配置路由:路由id/url/断言
- id: product-service
url: lb://service-product #lb://根据微服务名称从注册中心拉取请求路径
predicates:
-Path=/product/**
spring:
cloud:
gateway:
routes:
#配置路由:路由id/url/断言
- id: product-service
url: lb://service-product #lb://根据微服务名称从注册中心拉取请求路径
predicates:
-Path=/product/**
filters:
- RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器,在yaml中$字符需要转义
discovery:
locator:
enabled: true #开启根据服务名称自动转发
lower-case-service-id: true #微服务名称以小写形式呈现
2.过滤器
/**
* Created with IntelliJ IDEA
* User: GHYANG
* Date: 2020-06-14
* Description:自定义一个全局过滤器 实现GlobalFilter ordered接口
*/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
/**
* 指定过滤器的执行顺序,返回值越小,执行优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
/**
* 执行过滤器中的业务逻辑
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
//过滤器链执行下一个
return chain.filter(exchange);
}
}
3.统一鉴权
/**
* 执行过滤器中的业务逻辑
* 对请求参数的access-token进行判断
* 如果存在此参数:代表已经认证成功
* 如果不存在此参数:认证失败
* ServerWebExchange:相当于请求和相应的上下文(类似zuul中的RequestContext)
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
//获取请求参数
String token = exchange.getRequest().getQueryParams().getFirst("access-token");
//判断
if(token == null){
System.out.println("Fail");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); //拦截请求
}
//过滤器链执行下一个
return chain.filter(exchange);
}
4.网关限流
<!--监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
redis:
host: localhost
pool: 6379
database: 0
cloud:
gateway:
routes:
#配置路由:路由id/url/断言
- id: product-service
url: lb://service-product #lb://根据微服务名称从注册中心拉取请求路径
predicates:
-Path=/product-service/**
filters:
- name: RequestRateLimiter
args:
#使用SpEL从容器中获取对象
key-resolver: '#{@pathKeyResolver}'
#令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
#令牌桶的上限
redis-rate-limiter.bursCamacity: 3
- RewritePath=/product-service/(?<segment>.*),/$\{segment}
#RequestRateLimiter:使用限流过滤器 其是springcloud gateway提供的
#参数 replenishRate:向令牌桶中填充的速率
#参数 burstCapacity:令牌中的容量
@Configuration
public class KeyResolverConfiguration {
/**
* 编写基于请求路径的限流规则
* 方法名和配置文件中的key-resolver: '#{@pathKeyResolver}'相同
*/
@Bean
public KeyResolver pathKeyResolver() {
//自定义的KeyResolver
return new KeyResolver() {
/**
* ServerWebExchange :
* 上下文参数
*/
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just( exchange.getRequest().getPath().toString());
}
};
}
/**
* 基于请求参数的限流
* 请求 abc ? userId=1
* 方法名和配置文件中的key-resolver: '#{@pathKeyResolver}'相同
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getQueryParams().getFirst("userId")
//exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
);
}
}
<!--sentinel限流-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gatewat-adapter</artifactId>
</dependency>
/**
* sentinel限流的配置
*/
//@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
* 用于指定资源的限流规则.
* 1.资源名称 (路由id)--product-service
* 2.配置统计时间--setIntervalSec
* 3.配置限流阈值--setCount
*/
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// rules.add(new GatewayFlowRule("product-service")
// .setCount(1)
// .setIntervalSec(1)
// );
rules.add(new GatewayFlowRule("product_api")
.setCount(1).setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
/**
* 自定义API限流分组
* 1.定义分组
* 2.对小组配置限流规则
*/
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/product-service/product/**"). //以/product-service/product/开都的所有url
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/order-service/order")); //完全匹配/order-service/order 的url
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
/**
* 自定义限流处理器
* 触发限流时执行的处理器
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",001);
map.put("message","不好意思,限流啦");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockHandler);
}
}
spring:
cloud:
gateway:
routes:
#配置路由:路由id/url/断言
- id: product-service
url: lb://service-product #lb://根据微服务名称从注册中心拉取请求路径
predicates:
-Path=/product/**
filters:
- RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器,在yaml中$字符需要转义
discovery:
locator:
enabled: true #开启根据服务名称自动转发
lower-case-service-id: true #微服务名称以小写形式呈现
5.网关的高可用
Nginx模拟网关
<!--在nginx.conf配置反向代理-->
<!--路由到订单服务-->
location /api-product{
proxy_pass http://127.0.0.1:9002;
}
<!--路由到商品服务-->
location /api-order{
proxy_pass http://127.0.0.1:9002;
}
七、链路追踪
Sleuth
<!--sleuth链路追踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
logging:
level:
root: INFO
org.springframework.web.servlet.DispatcherServlet: DEBUG
org.springframework.cloud.sleuth: DEBUG
Zipkin
-
Zipkin是一个开源项目,其致力于收集服务的定时数据,以解决微服务架构的延迟问题,包括数据的收集、存储、查找和展现。
-
使用方式:
-
-
下载Zipkin.jar包并打开
-
localhost:9411/Zipkin/
-
<dependency> <groupId>org.srpingframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
-
spring: zipkin: base-url: http://127.0.0.1:9411/ #server的请求地址 sender: type: web #数据的传输方式,已http的形式向server端发送数据 sleuth: sampler: probability: 1 #采样比(1代表全采样)
-
-
数据持久化
-
-
官方提供的Zipkin数据库表
-
#数据库挂载 java -jar xxxx.jar --STORAGE_TYPE:mysql --MYSQL_HOST:127.0.0.1 --MYSQL_TOP_PORT:3306 --MYSQL_USER:root --MYSQL_PASS=xxx --MYSQL_DB=zipkin
-
-
使用消息中间件优化
-
使用rabbitMQ进行数据异步收集
-
- 准备rabbitMQ服务器
- 修改zipkin客户端,将消息以rabbit的形式发送到mq服务器
- 修改zipkin服务端,从rabbit中拉取消息
八、SpringCloud Stream
-
在实际的企业开发中,消息中间件是至关重要的组件之一。消息中间件主要解决应用解耦,异步消息、流量削锋等问题,实现高性能,高可用,高伸缩和最终一致性架构,不同的中间件其实现方式、内部结构不相同,当项目前期的消息中间件与后期的中间件发生改变时,SpringCloud Stream可以提供一种解耦合的方式。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-bindeer-rabbit</artifactId>
</dependency>
#消息生产者配置
spring:
cloud:
stream:
bindings:
output:
destination: itcast-default #指定消息发送的目的地
binders: #绑定器配置
defaultRabbit:
type: rabbit #rabbit中间件
#消息消费者配置
spring:
cloud:
stream:
bindings:
input: #内置的获取消息的通道,从itcast-default中获取消息
destination: itcast-default
binders:
defaultRabbit:
type: rabbit
九、配置中心
SpringCloud config
-
SpringCloud Config项目是一个解决分布式系统的配置管理方案,其包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。
-
文件命名规则:
-
- {application}-{profile}.yml
- {application}-{profile}.properties
- {application}为应用名称 profile指开发环境 (用于区分开发环境、测试环境、生产环境等)
<!--配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--子需拉取配置的服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 10000 #服务端口
spring:
application:
name: config-server #指定服务名
cloud:
config:
server:
git:
uri: https://xxxxxxxx.git #git地址
#需要创建一个配置中心 在需要拉取配置的服务创建一个xxx.yaml文件
spring:
cloud:
config:
name: product #应用名称 需要对应git中配置文件名称的前半部分
profile: dev #开发环境
label: master #git中的分支
uri: http://localhost:10000 #config-server的请求地址
#开启动态刷新的请求路径端点
managment:
endpoints:
web:
exposure:
include: refresh
-
当微服务数量多时,远程配置发生改变每次都需要到postman发送更新访问,这样会增加工作负担,SpringCloud中也有对应的解决方案,SpringCloud Bus将分布式的节点用轻量的消息代理连接起来,可以很容易搭建消息总线,配合Spring CloudConfig实现微服务应用配置信息的动态更新。
Apollo
-
Apollp是一个开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。
-
搭建路线:
- 下载apollo相关jar包及sql文件
- 在linux环境解压下载的zip包,并更改demo.sh里的数据库连接信息
<!--Apollo客户端依赖-->
<dependency>
<groupId>com.ctrip.framework.apollp</groupId>
<artifactId>apollo-client</artifactId>
</dependency>
apollo:
bootstrap: #开启apllo
enbaled: true
meta: http://192.168.74.101:8080
app:
id: test01 #指定apollo配置中心的AppId
十、消息总线
配置中心整合消息总线
<!--消息总线的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
#配置消息中间件rabbit
rabbit:
host: 127.0.0.1
port: 5672
username: guest
passwort: guest
十一、补充
bootstrap.yml:用来在程序引导时执行,应用于更加早期配置信息读取,如可以使用bootstrap.yml配置application.yml中使用到的参数
application.yml:应用程序特有的配置信息,可以用来配置后续各个模块中需要使用的公共参数等
两者加载顺序:bootstrap.yml 先于 application.yml
以上为个人学习笔记 可能有些地方不严谨或者出错 本人纯属小白 欢迎指出