网安开发:杭州某科技银行面经和答案
《网安面试指南》http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484339&idx=1&sn=356300f169de74e7a778b04bfbbbd0ab&chksm=c0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene=21#wechat_redirect
《Java代码审计》http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484219&idx=1&sn=73564e316a4c9794019f15dd6b3ba9f6&chksm=c0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene=21#wechat_redirect
《Web安全》http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484238&idx=1&sn=ca66551c31e37b8d726f151265fc9211&chksm=c0e47a12f793f3049fefde6e9ebe9ec4e2c7626b8594511bd314783719c216bd9929962a71e6&scene=21#wechat_redirect
本文是来自于一位杭州朋友的面经,下面挑了9个问题:
-
Nacos
如何实现自动刷新配置以及原理? -
什么是顺序
IO
-
Spring
中循环依赖解决的原理是什么? -
你知道哪些
SQL
优化手段? -
AQS原理是什么?
-
如何自定义
Starter
? -
说说
Spring Boot
自动配置原理 -
说说你对
Nacos
的理解 -
如何实现一个分布式服务注册中心?
建议自己先尝试性回答一遍,然后再继续看下文。
Nacos 如何实现自动刷新配置以及原理
在使用Nacos作为分布式配置中心时,其自动刷新配置的能力主要依赖于两方面的机制:Nacos客户端的监听机制和Spring Cloud的环境变化事件处理。
1. Nacos客户端的监听机制
Nacos客户端持续监听配置变化的核心是长轮询机制。具体来说,客户端会周期性地(默认是每30秒)向Nacos服务器发送请求,询问自上一次查询之后,被监听的配置是否有更新。如果服务器响应表示配置发生了变化,客户端则会立即向服务器发出查询配置的请求,获取最新的配置数据。
2. Spring Cloud 环境变化的处理
当Nacos客户端检测到配置更新并拉取新的配置后,如何使这些变化应用到Spring应用上,主要依赖于Spring Cloud的机制。
-
@RefreshScope: 对于那些标记为
@RefreshScope
的Bean,Spring Cloud提供了一种机制,能够在运行时动态地重新加载这些Bean的配置。当Nacos客户端拉取到新的配置后,通过发布一个EnvironmentChangeEvent
,可以触发Spring Context重新刷新这些@RefreshScope
标注的beans。 -
ContextRefresher & RefreshEventListener:
ContextRefresher
是Spring Cloud提供的一个工具,它可以用于重新加载应用上下文的配置环境。而RefreshEventListener
监听EnvironmentChangeEvent
,当收到此事件时,会调用ContextRefresher
的refresh()
方法来触发上下文的重新加载。
工作流程简述:
-
配置变更: 在Nacos配置中心修改配置。
-
配置监听与更新通知: Nacos客户端通过长轮询发现配置变更,并拉取最新配置信息。
-
处理配置更新: Nacos客户端接收到最新配置后,将新配置应用到Spring应用的Environment中。
-
触发刷新事件: Nacos客户端发布
EnvironmentChangeEvent
。 -
刷新@RefreshScope Beans: 侦听
EnvironmentChangeEvent
事件的组件激活上下文刷新,为标记有@RefreshScope
的Beans重新注入配置。
这种方式使得应用在运行时能够无缝地对配置进行热更新,而无需重启服务,极大提高了微服务架构下应用的灵活性和可维护性。
什么是顺序IO
顺序IO(Sequential Input/Output),是一种数据访问模式,指的是按照数据存储的顺序,连续地进行读写操作。在顺序IO中,数据被顺序地写入或读出,与它们在存储介质上的物理或逻辑存储顺序相匹配。这与随机IO(Random Input/Output)形成对比,随机IO是指访问存储介质上任意位置数据的过程,不按特定顺序进行。
顺序IO的特点:
-
高效性:顺序IO通常比随机IO更高效,尤其是在处理大量数据时。对于硬盘等机械式存储设备来说,顺序IO可以显著减少磁头移动的次数,从而减少了访问延迟。对于固态存储设备(如SSD)而言,虽然随机访问性能已大幅提升,但顺序IO在某些情况下仍然能够提供更好的吞吐量。
-
应用场景:适用于需要处理或传输大量连续数据的应用场景,例如视频播放、大文件传输、数据库备份等。
-
实现方式:在编程和系统设计中,利用缓冲区(buffer)可以提高顺序IO的效率。通过预读(read-ahead)和写延迟(write-behind)技术,系统可以减少IO操作次数,并优化IO性能。
与随机IO的比较:
-
随机IO:访问模式不连续,对数据位置没有严格的先后顺序要求,适用于数据库查询、小文件读写等场景。随机IO的性能受存储介质的影响较大,尤其是在机械硬盘上,随机读写性能通常远低于顺序读写。
-
顺序IO:通常在连续处理大量数据时表现更优,因为它减少了寻址时间,提高了数据处理速率。
总结
顺序IO是一种按照存储顺序进行数据读写的访问模式,它在处理大量连续数据时具有高效的性能优势。理解顺序IO与随机IO的不同及其适用场景,对于优化存储系统的性能、设计高效的数据处理程序具有重要意义。
Spring中循环依赖解决的原理是什么
Spring框架解决循环依赖的原理主要基于三级缓存机制,并且这种解决方案仅限于单例作用域(Singleton scope)的Bean。理解Spring解决循环依赖问题,首先需要了解Spring Bean生命周期中的关键步骤以及三级缓存的角色和功能。
1. Spring Bean生命周期关键步骤:
-
实例化(Instantiation):Spring首先通过构造器创建Bean的实例,此时Bean还未被填充属性。
-
属性填充(Populate Properties):Spring注入所有的Bean属性。
-
初始化(Initialization):如果Bean实现了InitializingBean接口,Spring会调用其
afterPropertiesSet
方法,或者根据Bean定义中的init-method
指定的方法进行初始化。
2. 三级缓存机制:
为了解决循环依赖,Spring引入了三级缓存:
-
一级缓存(Singleton Objects Cache):这是一个用于存放完全初始化完成的Bean的缓存。当一个Bean经过创建、属性填充和初始化后,会被放入这个缓存中,后续对该Bean的请求都通过这个缓存来满足。
-
二级缓存(Early Singleton Objects Cache):这个缓存存放早期的Bean实例,即已经实例化但尚未填充属性和初始化的Bean。这一层缓存的存在主要是为了处理循环依赖中属性注入的问题。
-
三级缓存(Singleton Factories Cache):这个缓存存放的是ObjectFactory,它用于创建早期Bean的引用。当一个Bean被实例化后,Spring会创建一个ObjectFactory,将其放入三级缓存中。如果在完成当前Bean的创建前就需要引用到这个Bean(即出现了循环依赖的情形),Spring会使用这个ObjectFactory来提前暴露一个Bean的引用。
解决循环依赖的过程:
假设有两个Bean,A和B,它们互相依赖。
-
当Spring尝试创建Bean A时,它发现A需要B,于是它暂停A的创建过程,开始创建B。
-
在创建B的过程中,Spring发现B需要A。由于A已经在创建中了(尽管还没完成),Spring通过三级缓存提供的ObjectFactory来获取A的早期引用,解决了B对A的依赖。
-
有了A的早期引用,Spring可以完成B的创建,并将B放入一级缓存。
-
随后,Spring回到A的创建过程,此时B已经准备好,所以可以完成A的创建并放入一级缓存。
通过这种方式,Spring利用三级缓存机制成功解决了循环依赖的问题。这个机制确保了在Bean的创建过程中,即使出现循环依赖,也能获取到所依赖Bean的引用,从而完成Bean的创建和装配过程。需要注意的是,这种机制仅适用于单例Bean,并且不支持通过构造器注入的方式解决循环依赖。
你知道哪些SQL优化手段
MySQL中的SQL优化是一个广泛且复杂的领域,涉及到很多层面的考量。以下是一些常见的SQL优化手段:
1. 使用合理的索引
-
创建合适的索引:选择合适的列添加索引,通常是在经常需要搜索、排序、分组的列上创建索引。
-
避免过多索引:索引虽好,但是过多的索引会减慢写操作(INSERT、UPDATE、DELETE)的性能,并且占用更多的磁盘空间。
-
使用前缀索引:对于长文本字段,可以使用前缀索引来减少索引大小和提高查找效率。
-
使用覆盖索引:尽可能使用覆盖索引,这样可以避免访问表数据,直接从索引中获取数据。
2. 优化查询语句
-
减少不必要的列和行的访问:只查询需要的列(避免使用SELECT *)并尽量减少返回的行数(使用LIMIT)。
-
优化WHERE子句:确保WHERE子句中的条件能够利用索引。
-
使用连接(JOIN)代替子查询(Subqueries):在某些情况下,连接操作比子查询效率更高。
-
使用合理的数据类型:小的数据类型通常更快,因为它们占用更少的空间并且处理速度更快。
3. 使用EXPLAIN分析查询
-
使用EXPLAIN关键字:了解MySQL如何执行SQL语句,包括使用的索引、数据扫描的行数等。
4. 优化数据库表结构
-
归一化与反归一化:归一化可以避免数据冗余,减少数据一致性的问题,但某些情况下适当的反归一化可以提高查询性能。
-
分区:对大表进行分区,可以提高查询性能。
-
适时使用视图和存储过程。
5. 调整MySQL配置
-
优化缓存和缓冲区大小:合理设置query_cache_size、innodb_buffer_pool_size等。
-
调整连接和线程设置:根据系统负载调整max_connections、thread_cache_size等。
6. 其他优化策略
-
避免锁竞争:通过优化事务大小和查询语句,减少锁竞争。
-
定期优化表和索引:使用OPTIMIZE TABLE定期对表进行优化。
-
使用批量操作:批量INSERT或UPDATE可以减少数据库的I/O操作。
以上只是一部分SQL优化的策略,实际应用时需要根据具体情况进行分析和调整。
AQS原理是什么
AbstractQueuedSynchronizer
(AQS)是 Java 并发包中的一个核心类,为构建锁或其他同步组件提供了一个可靠的基础框架。AQS使用了一个int类型的状态(state)来表示同步状态,以及通过内置的FIFO队列来管理那些获取同步状态失败的线程。
AQS的核心思想
AQS定义了一套多线程访问共享资源的同步器框架,核心思想是利用volatile变量state作为资源的共享状态标志,并通过CAS(Compare And Swap)操作来保证状态的安全修改,同时使用一个FIFO队列来管理线程的排队工作。
关键属性
-
state:一个volatile int类型的变量,用来表示同步状态。
-
队列:一个FIFO队列,用于存储获取同步状态失败的线程。
关键方法
AQS定义了一系列模板方法,具体如下:
-
acquire(int arg): 独占式地获取同步状态。
-
release(int arg): 独占式地释放同步状态。
-
acquireShared(int arg): 共享式地获取同步状态。
-
releaseShared(int arg): 共享式地释放同步状态。
其中acquire
和release
方法分别对应于独占模式的同步状态获取和释放,acquireShared
和releaseShared
方法对应于共享模式的同步状态获取和释放。
具体实现同步器时,需要根据资源的共享方式来重写这些方法。例如,ReentrantLock
是一个基于AQS实现的独占锁,它重写了acquire
和release
方法;而Semaphore
是一个基于AQS实现的共享锁,它则重写了acquireShared
和releaseShared
方法。
同步队列
AQS内部使用Node类作为同步队列的数据结构,每个参与同步状态竞争的线程都会被封装成一个Node节点加入到同步队列中。如果一个线程获取同步状态失败,它会成为一个节点加入到队列末尾,并在该节点上自旋;反之,它就会从队列中移除。这种机制确保了同步状态获取的公平性和效率。
独占式与共享式
AQS支持两种资源共享方式:
-
独占式:每次只能有一个线程持有同步状态。例如
ReentrantLock
。 -
共享式:允许多个线程同时持有同步状态。例如
CountDownLatch
、Semaphore
、CyclicBarrier
。
总结
AQS是构建锁和同步器的强大框架,它抽象了同步状态管理和线程排队等待机制。通过继承并实现AQS定义的方法,可以非常方便地实现各种同步需求。AQS的设计充分体现了封装细节、简化实现的设计哲学,是Java并发包的精髓之一。
如何自定义Starter?
要创建自己的starter,你需要遵循以下步骤:
-
创建项目:新建一个Maven项目作为你的starter项目。
-
添加依赖:在项目的
pom.xml
中添加你希望starter包含的依赖。同时,添加spring-boot-autoconfigure
和spring-boot-starter
用于自动配置的支持。 -
编写自动配置:创建一个带有
@Configuration
注解的类来定义所需要自动配置的beans。利用条件注解(如@ConditionalOnClass
,@ConditionalOnMissingBean
等)来控制配置类的条件加载。 -
创建
spring.factories
文件:在resources/META-INF
目录下创建一个spring.factories
文件。然后,列出你的自动配置类全名,以org.springframework.boot.autoconfigure.EnableAutoConfiguration
为key。org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.YourAutoConfiguration
-
打包和使用:将你的starter项目打包,并将其作为依赖添加到其他项目中去,这样就可以使用你的自定义starter了。
创建自定义starter时,你需要定义自动配置类,并通过条件注解精确控制这些配置类的加载。加上正确的
spring.factories
配置,你的starter就可以被Spring Boot应用自动检测和配置了。这种机制让Spring Boot的"约定大于配置"原则得以实施,极大简化了Spring应用的配置工作。
说说 Spring Boot自动配置原理
Spring Boot自动配置的魔法主要来自于两个核心注解@EnableAutoConfiguration
或@SpringBootApplication
(它包含了@EnableAutoConfiguration
)以及spring.factories
文件的配合。
-
@EnableAutoConfiguration
注解:这个注解告诉Spring Boot基于添加的jar依赖自动配置应用程序。Spring Boot会在classpath中查找META-INF/spring.factories
文件,加载其中配置的自动配置类。 -
条件注解:自动配置类通常会配合各种条件注解来决定某个配置是否应该生效,例如
@ConditionalOnClass
(某个class位于classspath上时才生效)、@ConditionalOnMissingBean
(容器中不存在指定Bean时配置生效)等。 -
spring.factories
文件:位于META-INF
目录下的此文件,是自动配置的核心。它通过键值对的形式,列出了所有需要被自动配置的类。Spring Boot启动时会读取这个文件,根据文件中指定的配置类,完成相应的自动配置。
说说你对Nacos的理解
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它支持几乎所有类型的服务发现和配置管理场景。Nacos的核心功能包括服务发现、动态配置管理、服务元数据及流量管理。为了满足服务发现和动态配置管理,Nacos采用了一系列设计原则和实现机制。
服务发现的实现原理
在服务发现方面,Nacos客户端注册服务时,会将服务实例的信息(如服务名、IP、端口等)发送到Nacos服务器,Nacos服务器将这些信息存储在内部数据结构中。客户端查询服务时,Nacos服务器会从存储结构中查找并返回服务实例列表。
Nacos支持DNS-based和RPC-based服务发现。对于DNS-based服务发现,Nacos作为DNS服务器,当客户端查询服务域名时,Nacos直接返回服务实例IP列表。对于RPC-based服务发现,Nacos客户端通过HTTP长轮询等方式从Nacos服务器获取服务实例列表,并缓存于本地,当服务实例变更时,Nacos服务器会推送变更给客户端。
配置管理的实现原理
在配置管理方面,Nacos客户端向Nacos服务器获取或提交配置信息,Nacos服务器存储这些配置信息。客户端获取配置信息时,会本地缓存一份,通过HTTP长轮询等方式监听配置变更。一旦配置变更,Nacos服务器会通知所有监听该配置的客户端。
数据一致性保证
Nacos采用了一致性协议(如Raft)来保证集群模式下的数据一致性。在Raft协议的帮助下,Nacos可以确保每一个配置更新操作都会被准确地同步到集群的所有节点上。
实现细节
-
服务健康检查:Nacos提供多种健康检查方式,包括客户端心跳、服务端主动探测等,确保服务列表的准确性和实时性。
-
负载均衡和流量控制:Nacos可以将客户端的负载均衡逻辑和服务端的流量控制规则进行协同,支持服务级别的流量控制和服务降级等策略。
-
配置版本和历史管理:Nacos支持配置的版本管理,可以轻松回滚到任何一个历史版本,同时支持配置的变更历史查询,方便用户跟踪配置的变更记录。
-
服务分组和命名空间:为了更好地支持多环境和多租户,Nacos提供了服务分组和命名空间的概念,允许用户将服务和配置划分到逻辑隔离的空间中。
Nacos的实现原理结合了现代云原生应用对服务发现和配置管理的需求,通过简单而强大的设计实现了一套易用、高效、可靠的服务基础设施。
如何实现一个分布式服务注册中心?
实现一个分布式服务注册中心涉及到服务定位、注册、发现、健康检查和一致性维护等多个关键方面。以下是构建一个简单的分布式服务注册中心的基本步骤和考虑因素:
1. 定义服务注册与发现的协议
首先,需要定义服务注册与发现的通信协议,比如 REST API、gRPC 等。这个协议必须包含以下基本操作:
-
服务注册:服务启动时向注册中心注册自己的信息(服务名、IP、端口、健康检查信息等)。
-
服务注销:服务关闭时从注册中心注销自己。
-
服务发现:客户端通过服务名查询服务实例列表。
-
服务续约:服务定期向注册中心发送心跳,表示自己还“活着”。
2. 实现注册中心服务器
注册中心是服务注册和发现过程的中心节点。其核心组件可以包括:
-
服务存储:用于存储注册服务的信息。可以是内存数据结构,也可以是持久化存储如数据库或分布式键值存储系统。
-
API接口层:实现定义的服务注册、注销、查询等API接口。
-
健康检查机制:定期检查服务的健康状态,移除不健康的服务实例。
-
数据一致性:在分布式环境下,需要保证注册的服务信息在多个注册中心节点间一致。可以采用Raft、Paxos等分布式一致性算法。
3. 实现服务健康检查
服务注册中心需要定期检查每个服务实例的健康状况。这通常通过以下方式实现:
-
客户端心跳:服务实例定期向注册中心发送“心跳”来更新其状态。
-
服务端检查:注册中心定期调用服务实例的健康检查接口来验证其是否健康。
4. 保证注册信息一致性
在分布式环境下,服务注册中心自身可能也是分布式部署的。这时要确保各个节点间服务注册信息的一致性,可以依赖于:
-
一致性协议:使用Raft、Paxos等分布式一致性算法来保证跨节点数据的同步和一致性。
5. 实现服务发现客户端
服务发现客户端通常作为SDK提供,供其他服务导入并使用,主要实现以下功能:
-
查询注册中心:根据服务名查询服务实例的地址。
-
负载均衡:实现简单的负载均衡算法,如轮询、随机选择服务实例。
-
服务缓存:缓存服务实例信息,减少对注册中心的查询频率。
-
监听服务变更:支持监听服务实例列表的变化,动态更新本地缓存。
示例架构简述
-
开启多个注册中心节点,彼此之间通过一致性协议如Raft维护信息一致性。
-
每个微服务启动时,向注册中心的某个节点注册自己的信息,并定期发送心跳续约。
-
微服务通过客户端类库查询服务,并使用提供的负载均衡策略调用其他服务。
-
注册中心通过健康检查机制定期检查每个服务实例的状态,移除不健康的实例。
构建分布式服务注册中心是一个复杂且挑战性的任务,需要深入理解分布式系统的原理,包括网络通信、数据一致性、容错机制等。