持续更新中。。。。。
springboot
问题:介绍以下 SpringBootApplication 注解?以及自动配置机制?
答:SpringBootApplication 是一个组合注解,包括3个注解。标注它之后就会触发自动配置(@EnableAutoConfiguration)和组件扫描(@ComponentScan)。
-
@ComponentScan默认扫描当前配置类所在包及子包下的所有组件, exclude 属性会将主启动类、自动配置类屏蔽掉 -
@Configuration可标注配置类,@SpringBootConfiguration并没有对其做实质性扩展。 -
@EnableAutoConfiguration自动配置最关键的,这个注解主要有下面两个组成@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration-
@AutoConfigurationPackage// 表示包含该注解的类所在的包应该在 AutoConfigurationPackages 中注册。 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们。就是它起的作用。
-
@Import(AutoConfigurationImportSelector.class)
会导入AutoConfigurationImportSelector类。会执行selectImports方法,这个方法最终会调用SpringFactoriesLoader.loadFactoryNames方法,就是类似 spi 机制从META-INF/spring.factories中加载spring.factories文件,这个文件内容是 Properties 形式的。key 都是org.springframework.boot.autoconfigure.EnableAutoConfiguration指定的所有自动配置类(是一个很大的字符串,里面都是自动配置类的全限定类名),装配到IOC容器中,之后自动配置类就会通过ImportSelector和@Import的机制被创建出来。
-
spring cloud Netflix
请你说说eureka都有哪些常见的配置项?
答:Eureka 提供的配置项可以分成三大类:
- 一类用于控制 Eureka 服务器端行为,以 eureka.server 开头;
- 一类则是从客户端角度出发考虑配置需求,以 eureka.client 开头;
- 而最后一类则关注于注册到 Eureka 的服务实例本身,以 eureka.instance 开头。
请注意,Eureka 除了充当服务器端组件之外,实际上也可以作为客户端注册到 Eureka 本身,这时候它使用的就是客户端配置项 单节点常见配置如下:
server:
port: 8761 //端口
eureka:
client:
registerWithEureka: false //注册到其他eureka服务器,单节点不需要
fetchRegistry: false // 从其他服务器拉取注册信息,单节点不需要
serviceUrl:
defaultZone: http://localhost:8761 // 配置项用于服务地址,构建 Eureka 服务器集群是很有用
集群常见配置:集群是一种 Peer to Peer 模式。
application-eureka1.yml
server:
port: 8761
eureka:
instance:
hostname: eureka1 //Eureka 服务的主机名称
client
serviceUrl
defaultZone: http:// eureka2:8762/eureka/ //用于指向集群中的其他 Eureka 服务器
application-eureka2.yml
server:
port: 8762
eureka:
instance:
hostname: eureka2 //Eureka 服务的主机名称
client
serviceUrl
defaultZone: http://eureka1:8761/eureka/ //用于指向集群中的其他 Eureka 服务器
能说一下或者画图简述一下eureka架构概念吗?
答:Eureka 有以下几个概念与服务治理直接相关,分别是服务注册、服务续约、服务取消和服务剔除等操作。
-
服务注册
服务注册(Register)是服务治理的最基本概念,内嵌了Eureka客户端的各个微服务通过向Eureka服务器提供 IP 地址、端点等各项与服务发现相关的基本信息完成服务注册操作。 -
服务续约
因为Eureka客户端与服务器端通过短连接完成交互,所以,Eureka客户端需要每隔一定时间主动上报自己的运行时状态,从而进行服务续约。 -
服务取消
服务取消(Cancel)就是Eureka客户端主动告知Eureka服务器自己不想再注册到Eureka中
Eureka 把注册信息保存在哪里?用什么数据结构保存的?
答:注册信息保存在内存里,InstanceRegistry 接口的实现类 AbstractInstanceRegistry 中发现了 Eureka 用于保存注册信息的数据结构,是一个双层的ConcurrentHashMap:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
其中第一层的 ConcurrentHashMap 的 Key 为 spring.application.name,也就是服务名,Value 为一个 ConcurrentHashMap;而第二层的 ConcurrentHashMap 的 Key 为 instanceId,也就是服务的唯一实例 ID,Value 为 Lease 对象。Eureka 采用 Lease(租约)这个词来表示对服务注册信息的抽象,Lease 对象保存了服务实例信息以及一些实例服务注册相关的时间,如注册时间 registrationTimestamp、最新的续约时间 lastUpdateTimestamp 等。
怎么理解 Eureka 高可用?
答: Eureka 的高可用部署方式被称为 Peer Awareness 模式,可以从PeerAwareInstanceRegistryImpl 类中看出 Eureka 服务注册时,会将注册的节点调用replicateToPeers方法,也就是同步到其他Eureka节点。
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
// 注意这行 该方法作就是用来实现服务器节点之间的状态同步
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
什么是 AP ?对比常用的服务注册中心?
答:AP是指的当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。
-
Zookeeper保证CP
zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。 -
Eureka保证AP
Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此,
Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
Eureka保证高可用(A)和最终一致性:- 服务注册相对要快,因为不需要等注册信息replicate到其他节点,也不保证注册信息是否replicate成功。
- 当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。
-
Consul保证CP
Consul使用GO语言编写,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案。使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为Consul的raft协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul服务不可用。
所以,Consul强一致性(C)带来的是:- 服务注册相比Eureka会稍慢一些。因为Consul的raft协议要求必须过半数的节点都写入成功才认为注册成功。
- Leader挂掉时,重新选举期间整个consul不可用。保证了强一致性但牺牲了可用性。
-
Nacos保证CP + APNacos除了服务的注册发现之外,还支持动态配置服务。动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
一句话概括就是Nacos = Spring Cloud注册中心 + Spring Cloud配置中心。
Ribbon 中的有哪些负载均衡策略?默认的是哪种?
答:负载均衡算法可以分成两大类,即静态负载均衡算法和动态负载均衡算法。
- 静态负载均衡算法比较容易理解和实现,典型的包括随机(Random)、轮询(Round Robin)和加权轮询(Weighted Round Robin)算法等。
注意:所有涉及权重的静态算法都可以转变为动态算法,因为权重可以在运行过程中动态更新。例如动态轮询算法中权重值基于对各个服务器的持续监控并不断更新。
- 动态算法包括源 IP 哈希算法、最少连接数算法、服务调用时延算法等。 默认的负载均衡策略是轮询策略。
问题:为什么在 RestTemplate 上添加一个 @LoadBalanced 注解之后就自动具备负载均衡功能呢?
问题:请简述一下Zuul?
答:Zuul 是一个API 网关,它主要的功能是请求监控、安全管理、路由规则、日志记录、访问控制、服务适配等功能。网关能够起到客户端与微服务之间的隔离作用。随着业务需求的变化和时间的演进,网关背后的各个微服务的划分和实现可能需要做相应的调整和升级。这种调整和升级应该实现对客户端透明。
使用时需要加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动器上需要增加EnableZuulProxy 注解。
问题:如何使用 Zuul 实现服务路由?
答:服务路由就是通过 Zuul 访问的请求会路由并转发到对应的后端服务中。Zuul 进行服务访问的 URL 通用格式:http://zuulservice:5555/service,其中 zuulservice 代表 Zuul 服务器的地址。而这里的 service 所对应的后端服务的确定就需要依赖位于 Zuul 中的服务路由信息。
有以下三种方式:
-
基于服务发现映射服务路由
因为 Zuul 启动的过程中,会从 Eureka 中获取当前所有已注册的服务信息,然后自动生成服务名称与目标服务之间的映射关系。比如:后端配置的服务如下spring: application: name: userservice我们就可以通过
http://zuulservice:5555/userservice访问到该服务,注意该 URL 中的目标服务就是userservice,与服务定义中的名称保持一致。可以通过访问Zuul提供的服务路由端点http://localhost:5555/actuator/routes来获取服务路由映射信息。 -
基于动态配置映射服务路由 上面那种基于服务发现机制的系统自动映射使用简单,但是有局限性,比如不使用默认的服务名称来命名目标服务,或者在各个请求路径之前加一个统一的前缀(Prefix)等。上面那种就做不到,所以就需要基于动态配置映射服务路由实现。这种方式就是需要在
application.yml文件配置zuul: prefix: /springhealth //添加一个前缀用来标识模块和子系统,请求时需要带上前缀,区分某一个模块或者子系统 routes: ignored-services: 'userservice' //忽略系统自动映射的路由被外部使用 userservice: /user/**这里我们使用
/user来为user-service指定请求根地址。现在我们访问http://zuulservice:5555/springhealth/user/时,就相当于将请求发送给了Eureka中的userservice实例。 -
基于静态配置映射服务路由 上面两种方式都离不开eureka服务注册中心,比如有这样业务我们需要访问第三方服务,该服务无法注册到我们的 Eureka 注册中心,但是它会暴露了一个 HTTP 端点给我们调用。这个时候就可以使用静态映射服务路由了。
zuul: routes: thirdpartyservice: path: /thirdpartyservice/** url: http://thirdparty.com/thirdpartyservice这样访问
/thirdpartyservice/**时,Zuul 会将该请求转发到外部的第三方服务http://thirdparty.com/thirdpartyservice中。 如果我们使用静态配置映射服务路由时也需要负载均衡,我们就可以通过配置ribbon实现:zuul: routes: thirdpartyservice: path: /thirdpartyservice/** serviceId: thirdpartyservice ribbon: eureka: enabled: false //关闭 Ribbon 与 Eureka 之间的关联 因为静态服务没有注册eureka上 thirdpartyservice: ribbon: // 手工指定 Ribbon 的服务列表 listOfServers: http://thirdpartyservice1:8080,http://thirdpartyservice2:8080
问题:说说 Zuul 的实现原理?
答:Zuul 响应 HTTP 请求的过程是一种典型的过滤器结构,内部提供了 ZuulFilter 组件来实现这一机制。IZuulFilter 是个接口,ZuulFilter 是 IZuulFilter 的直接子类。
public interface IZuulFilter {
// 决定是否需要执行该过滤器,一般情况该方法都会返回 true
boolean shouldFilter();
// 该 Filter 具体需要实现的业务逻辑。
Object run() throws ZuulException;
}
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
....
// 过滤器类型 内置过滤器分为 PRE、ROUTING、POST 和 ERROR 四种
public abstract String filterType();
// 过滤器顺序
public abstract int filterOrder();
.....
}
有了过滤器,系统中就应该存在一个管理过滤器的组件,称为过滤器注册表 FilterRegistry
public class FilterRegistry {
// 经典的单例模式来创建 FilterRegistry 实例。
private static final FilterRegistry INSTANCE = new FilterRegistry();
public static final FilterRegistry instance() {
return INSTANCE;
}
// 在 FilterRegistry 内部,可以看到就是直接使用了线程安全的 ConcurrentHashMap 来缓存 ZuulFilter。
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
private FilterRegistry() {
}
public ZuulFilter remove(String key) {
return this.filters.remove(key);
}
public ZuulFilter get(String key) {
return this.filters.get(key);
}
public void put(String key, ZuulFilter filter) {
this.filters.putIfAbsent(key, filter);
}
public int size() {
return this.filters.size();
}
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
问题:生产中你们的 Feign 会做什么样的配置?
答:一般会做这样的配置:
feign:
client:
config:
default:
connectTimeout: 5000 // 相当于Request.Options建立链接的超时时长
readTimeout: 120000 // 相当于Request.Options读取超时时长
httpclient:
enabled: false // 关闭 httpclient
okhttp:
enabled: true // 开启okhttp,主要还要引入 okhttp 包
compression:
request:
enabled: true
#支持压缩的mime types
mime-types: text/xml,application/xml,application/json
#min-request-size: 2048
response:
enabled: true
hystrix:
enabled: true // 开启 熔断
ribbon:
eureka:
enabled: true //开启 ribbon
ReadTimeout: 200000
ConnectTimeout: 100000
MaxAutoRetries: 0 // 同一实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1 // 重试其他实例的最大重试次数,不包括首次所选的server
OkToRetryOnAllOperations: false //是否所有操作都进行重试 关闭重试
hystrix:
threadpool:
default:
coreSize: 1000 ##并发执行的最大线程数,默认10
maxQueueSize: 1000 ##BlockingQueue的最大队列数
queueSizeRejectionThreshold: 500 ##即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 110000
所以,一般会配置
- 配置请求的连接超时时间和读取超时时间。
- 关闭
httpclient,开启okhttp,feign.okhttp.enabled=true ,feign.httpclient.enabled=false - 配置开启 ribbon
- 配置 hystrix ,Hystrix主要被用于实现实现微服务之间网络调用故障的熔断、过载保护及资源隔离等功能,所以配置 hystrix 线程池线程数,配置BlockingQueue的最大队列数。