SpringCloud面试题

SpringCloud常见组件有哪些

注册中心组件:Eureka、Nacos
负载均衡组件:Ribbon
远程调用组件:OpenFeign
网关组件:Zuul、Gateway
服务保护组件:Hystrix、Sentinel
服务配置管理组件:SpringCloudConfig、Nacos

Nacos的服务注册表结构是怎样的?

Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。
在这里插入图片描述
namespace命名空间
在这里插入图片描述

服务、分组
在这里插入图片描述
集群、实例
在这里插入图片描述

Nacos如何支撑阿里内部数十万服务注册压力?

Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。

Nacos如何避免并发读写冲突问题?

Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的拷贝的实例列表来覆盖旧的实例列表。
这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了(读旧的实例列表)。
以服务service为锁对象,同一个服务service中多个instance实例,只能串行来完成注册。多个服务service之间互不影响。

Nacos与Eureka的区别有哪些?

NacosEureka
接口方式Nacos对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能
实例类型Nacos的实例有永久和临时实例之分Eureka只支持临时实例
健康检测Nacos对临时实例采用心跳模式检测,对永久实例采用主动请求来检测Eureka只支持心跳模式
服务发现Nacos支持定时拉取和订阅推送(当服务发生变更时,通过订阅推送主动通知各个实例)两种模式Eureka只支持定时拉取模式(30s)

eureka服务注册、服务发现

eureka-server服务项目
1,创建eureka-server服务项目
2,启动类加@EnableEurekaServer
3,application.yml中将eurekaserver注册到eureka服务中

user-service项目
1,user-service pom文件中引入eureka-client
2,application.yml中将userservice服务注册到eureka服务中
3,RestTemplate远程调用,比如orderservice去调用userservice

nacos服务注册、服务发现

1,user-service pom文件中引入alibaba-nacos-discovery
2,application.yml中将userservice服务注册到nacos中
3,RestTemplate远程调用,比如orderservice去调用userservice

nacos配置集群

nocos注册中心,相应服务下创建集群,集群下创建对应实例,部分实例根据机器性能配置权重

server:
  port: 8080
spring:
  application:
    name: orderservice # order服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
      # 1,给order-service配置集群信息
      discovery:
         cluster-name: xa # 集群名称,自定义XA表示西安集群 BJ代表北京集群

userservice: # 配置负载均衡规则
  ribbon:
  	# 2,修改负载均衡规则
    NFLoadBalancerRuleClassName:
    	com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: # 指定饥饿加载的服务名称,这里是个集合,可以指定多个
      - userservice

Nacos配置管理

bootstrap.yaml文件,会在application.yml之前被读取。经常修改、热更新的配置放到nacos中管理。基本不会变更的一些配置保存在微服务本地
userservice.yaml不包含环境,可以被多个环境dev、prod等共享。

1,nacos配置列表中创建比如userservice-dev.yaml

pattern:
    dateformat: yyyy-MM-dd HH:mm:ss

2,引入nacos-config依赖
3,添加bootstrap.yaml,设置服务名userservice、开发环境dev、文件后缀名yaml
4,nacos配置热更新
方式1:@Value注入的变量,所在类上添加注解@RefreshScope

@RestController
@RequestMapping("/user")
public class UserController {
    @Value("${pattern.dateformat}")
    private String dateformat;

    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }
}    

方式2:使用@ConfigurationProperties

@Data
@Component
//@ConfigurationPropertie将application.yml配置的值注入到bean上,必须将对象注入IOC容器中才有配置绑定的功能
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}

Nacos配置共享的优先级

nacos配置 > 本地配置
nacos各环境配置 > nacos不包含环境配置

userservice-dev.yaml > userservice.yaml > 本地application.yaml

微服务雪崩解决方式

流量控制是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。
超时处理、舱壁模式、熔断降级是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。

1,超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
2,舱壁模式:舱壁模式来源于船舱的设计,船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。
于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
3,熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。断路器会统计访问某个服务的请求数量,异常比例
当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断
4,流量控制:限制业务访问的QPS(每秒处理的请求数量),避免服务因流量的突增而故障。

Sentinel的线程隔离与Hystix的线程隔离有什么差别?

HystixSentinel
Hystix默认是基于线程池实现的线程隔离Sentinel是基于信号量(计数器)实现的线程隔离
每一个被隔离的业务都要创建一个独立的线程池不用创建线程池
支持主动超时,支持异步调用不支持主动超时,不支持异步调用
线程的额外开销比较大,性能一般,但是隔离性更强轻量级,无额外开销,性能较好,但是隔离性一般

在这里插入图片描述

Sentinel的限流与Gateway的限流有什么差别?

限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。

Gateway则采用了基于Redis实现的令牌桶算法。

Sentinel限流算法

对比项滑动时间窗口令牌桶漏桶
能否保证流量曲线平滑不能,但窗口内区间越小,流量控制越平滑基本能,在请求量持续高于令牌生成速度时,流量平滑。在请求量在令牌生成速率上下波动时,无法保证曲线平滑能,所有请求进入桶内,以恒定速率放行,绝对平滑
能否应对突增流量不能,突增流量,只要高出限流阈值都会被拒绝能,桶内积累的令牌可以应对突增流量能,请求可以增存在桶内
流量控制精准度低,窗口区间越小,精度越高
默认限流模式是基于滑动时间窗口算法

滑动时间窗限流算法解决了固定时间窗限流算法的问题。其没有划分固定的时间窗起点与终点,而是将每一次请求的到来时间点作为统计时间窗的终点,起点则是终点向前推时间窗长度的时间点。这种时间窗称为“滑动时间窗”
比如1分钟阈值是100个,将1分钟分为4格,第一格假如10个请求,第二、三、四格15个请求,那么第二分钟的前15秒,就可以最多处理40个请求。
在这里插入图片描述

排队等待的限流模式则基于漏桶算法

在这里插入图片描述
在这里插入图片描述
将每个请求视作"水滴"放入"漏桶"进行存储;
"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水”;
如果"漏桶"满了则多余的"水滴"会被直接丢弃。
可以理解成请求在桶内排队等待,可以处理突发请求,请求处理曲线平滑
比如单机阈值=10,也就是100ms放行一个请求

热点参数限流则是基于令牌桶算法

在这里插入图片描述

以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
如果令牌桶中没有令牌,则请求等待或丢弃
在这里插入图片描述

Ribbon负载均衡注入RestTemplate

@LoadBalanced //加入负载均衡,order去调取user或者user2
@Bean //创建RestTemplate对象并注入spring容器
public RestTemplate restTemplate(){return new RestTemplate();}

修改Ribbon负载均衡策略

方式1:
代码方式(作用于order-service中调用别的任何微服务)
@Bean
public IRule randomRule(){return new RandomRule();}
方式2:
配置文件方式(只作用于userservice)
server:
  port: 8080
spring:
  application:
    name: orderservice # order服务名称

eureka:
  client:
    service-url:  # eureka的地址信息 eureka会将自己也注册到eureka服务中
      defaultZone: http://127.0.0.1:10086/eureka
      
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

Ribbon负载均衡基本流程

拦截我们的RestTemplate请求http://userservice/user/1
- RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
- DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
- eureka返回列表,localhost:8081、localhost:8082
- IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
- RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

Ribbon负载均衡,首次请求慢

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

server:
  port: 8080
spring:
  application:
    name: orderservice # order服务名称

eureka:
  client:
    service-url:  # eureka的地址信息,eureka会将自己也注册到eureka服务中
      defaultZone: http://127.0.0.1:10086/eureka
      
userservice: # 给某个微服务配置负载均衡规则,orderservice会远程调用userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
    
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: # 指定饥饿加载的服务名称,这里是个集合,可以指定多个
      - userservice

Ribbon的属性配置和类配置优先级

SpringCloud Alibaba—Ribbon的属性配置和类配置优先级

远程调用

RestTemplate远程调用

1,创建RestTemplate对象并注入spring容器

//创建RestTemplate对象并注入spring容器
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}

2,Service中注入RestTemplate,比如get方法,getForObject()去远程调用http请求

实现了跨服务的远程调用,其实就是发送了一次http请求
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        
        //2利用RestTemplate发起http请求,查询用户
        String url = "http://localhost:8081/user/"+order.getUserId();
        //get请求,设置返回值json封装成User对象
        User user = restTemplate.getForObject(url, User.class);
    
        //3封装user到order
        order.setUser(user);
        // 4.返回
        return order;
    }
}

Feign远程调用

1,引入openfeign依赖
2,启动类添加@EnableFeignClients注解
3,编写Feign的客户端

//在order-service中新建一个接口
@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

4,Service中使用FeignClient中定义的方法远程调用

//实现了跨服务的远程调用,其实就是发送了一次http请求
@Service
public class OrderService {
    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        
        //2使用feign远程调用
        User user = userClient.findById(order.getUserId());
        
        //3封装user到order
        order.setUser(user);
        // 4.返回
        return order;
    }
}

Feign远程调用,修改自定义配置

Feign可以支持很多的自定义配置,feign.Logger.Level修改日志级别、feign.codec.Decoder响应结果的解析器(例如解析json字符串为java对象)、feign.codec.Encoder请求参数编码、feign.Contract支持的注解格式、feign.Retryer失败重试机制

方式1:配置文件方式

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别 
feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别

方式2:Java代码方式
1,FeignConfiguration类设置自定义配置

//设置Feign自定义配置
public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}

2,如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中

@SpringBootApplication
//自动装配开关,全局有效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

如果是局部生效,则把它放到对应的@FeignClient这个注解中:

//局部有效
@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

Feign远程调用,日志级别

NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign远程调用,性能优化

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

底层客户端连接池优化手段
URLConnection默认实现,不支持连接池
Apache HttpClient支持连接池提高Feign的性能主要手段就是使用连接池代替默认的URLConnection
OKHttp支持连接池提高Feign的性能主要手段就是使用连接池代替默认的URLConnection

1,pom文件引入Apache HttpClient依赖
2,application.yml中配置连接池

feign:
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

Feign远程调用,最佳实践

1,创建module,feign-api
1.1,抽取 首先创建一个module,命名为feign-api
1.2,pom文件中引入openfeign依赖
1.3,UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

@FeignClient(value = "userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel(){
        return Logger.Level.BASIC;
    }
}

@Data
public class User {
    private Long id;
    private String username;
    private String address;
}

2,在order-service中使用feign-api
2.1,在order-service的pom文件中引入feign-api的依赖
2.2,解决扫描包问题
因为@SpringBootApplication只会扫描到本项目下的包,com.api.clients.UserClient是引入的第三方jar,需要再指定扫描
方式1:指定Feign应该扫描的包:

@SpringBootApplication
//自动装配开关
//basePackages指定feign client所在包
@EnableFeignClients(basePackages = "com.api.clients",defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

方式2:指定需要加载的Client接口:推荐方式二

@SpringBootApplication
//自动装配开关
//clients = UserClient.class指定加载哪个客户端
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

FeignClient整合Sentinel

1,OrderService的application.yml文件,开启Feign的Sentinel功能

feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数
  sentinel:
    enabled: true # 开启feign对sentinel的支持

2,编写失败降级逻辑

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
            	//失败降级逻辑
                log.error("查询用户异常", throwable);
                return new User();
            }
        };
    }
}

3,在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean

public class DefaultFeignConfiguration {
	@Bean
	public UserClientFallbackFactory userClientFallbackFactory(){
	    return new UserClientFallbackFactory();
	}
}

4,在feing-api项目中的UserClient接口中使用UserClientFallbackFactory

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

sentinel服务熔断过程

熔断状态
closed关闭状态断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
open打开状态服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
half-open半开状态放行一次请求,根据执行结果来判断接下来的操作。请求成功:则切换到closed状态;请求失败:则切换到open状态

gateway网关

1,创建module,pom文件中引入gateway依赖
2,创建application.yml,配置相关路由规则

server:
  port: 10010

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

3,重启网关,访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到http://userservice/user/1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值