知其然不知其所以然,大厂常问面试技术如何复习?
1、热门面试题及答案大全
面试前做足功夫,让你面试成功率提升一截,这里一份热门350道一线互联网常问面试题及答案助你拿offer
2、多线程、高并发、缓存入门到实战项目pdf书籍
3、文中提到面试题答案整理
4、Java核心知识面试宝典
覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入
-
单元测试:
src/test/java
-
组件测试:
src/componentTest/java
-
API测试:
src/apiTest/java
需要注意的是,这里的API测试更多强调的是对业务功能的测试,有些项目中可能还会存在契约测试和安全测试等,虽然从技术上讲都是对API的访问,但是这些测试都是单独的关注点,因此建议分开对待。
值得一提的是,由于组件测试和API测试需要启动程序,也即需要准备好本地数据库,我们采用了Gradle的docker-compose
插件(或者jib插件),该插件会在运行测试之前自动运行Docker容器(比如MySQL):
apply plugin: ‘docker-compose’
dockerCompose {
useComposeFiles = [‘docker/mysql/docker-compose.yml’]
}
bootRun.dependsOn composeUp
componentTest.dependsOn composeUp
apiTest.dependsOn composeUp
更多的测试分类配置细节,比如JaCoCo测试覆盖率配置等,请参考本文的示例项目代码。对Gradle不熟悉的读者可以参考笔者的Gradle学习系列文章。
在日志处理中,除了完成基本配置外,还有2个需要考虑的点:
在日志中加入请求标识,便于链路追踪。在处理一个请求的过程中有时会输出多条日志,如果每条日志都共享统一的请求ID,那么在日志追踪时会更加方便。此时,可以使用Logback原生提供的MDC(Mapped Diagnostic Context)功能,创建一个RequestIdMdcFilter:
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
//request id in header may come from Gateway, eg. Nginx
String headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);
MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);
try {
filterChain.doFilter(request, response);
} finally {
clearMdc();
}
}
集中式日志管理,在多节点部署的场景下,各个节点的日志是分散的,为此可以引入诸如ELK之类的工具将日志统一输出到ElasticSearch中。本文的示例项目使用了RedisAppender将日志输出到Logstash:
ecommerce-order-backend-${ACTIVE_PROFILE}
elk.yourdomain.com
6379
whatever
ecommerce-ordder-log
true
redis
当然,统一日志的方案还有很多,比如Splunk和Graylog等。
在设计异常处理的框架时,需要考虑以下几点:
-
向客户端提供格式统一的异常返回
-
异常信息中应该包含足够多的上下文信息,最好是结构化的数据以便于客户端解析
-
不同类型的异常应该包含唯一标识,以便客户端精确识别
异常处理通常有两种形式,一种是层级式的,即每种具体的异常都对应了一个异常类,这些类最终继承自某个父异常;另一种是单一式的,即整个程序中只有一个异常类,再以一个字段来区分不同的异常场景。层级式异常的好处是能够显式化异常含义,但是如果层级设计不好可能导致整个程序中充斥着大量的异常类;单一式的好处是简单,而其缺点在于表意性不够。
本文的示例项目使用了层级式异常,所有异常都继承自一个AppException:
public abstract class AppException extends RuntimeException {
private final ErrorCode code;
private final Map<String, Object> data = newHashMap();
}
这里,ErrorCode
枚举中包含了异常的唯一标识、HTTP状态码以及错误信息;而data
字段表示各个异常的上下文信息。
在示例系统中,在没有找到订单时抛出异常:
public class OrderNotFoundException extends AppException {
public OrderNotFoundException(OrderId orderId) {
super(ErrorCode.ORDER_NOT_FOUND, ImmutableMap.of(“orderId”, orderId.toString()));
}
}
在返回异常给客户端时,通过一个ErrorDetail类来统一异常格式:
public final class ErrorDetail {
private final ErrorCode code;
private final int status;
private final String message;
private final String path;
private final Instant timestamp;
private final Map<String, Object> data = newHashMap();
}
最终返回客户端的数据为:
{
requestId: “d008ef46bb4f4cf19c9081ad50df33bd”,
error: {
code: “ORDER_NOT_FOUND”,
status: 404,
message: “没有找到订单”,
path: “/order”,
timestamp: 1555031270087,
data: {
orderId: “123456789”
}
}
}
可以看到,ORDER_NOT_FOUND
与data
中的数据结构是一一对应的,也即对于客户端来讲,如果发现了ORDER_NOT_FOUND
,那么便可确定data
中一定存在orderId
字段,进而完成精确的结构化解析。
除了即时完成客户端的请求外,系统中通常会有一些定时性的例行任务,比如定期地向用户发送邮件或者运行数据报表等;另外,有时从设计上我们会对请求进行异步化处理。此时,我们需要搭建后台任务相关基础设施。Spring原生提供了任务处理(TaskExecutor)和任务计划(TaskSchedulor)机制;而在分布式场景下,还需要引入分布式锁来解决并发冲突,为此我们引入一个轻量级的分布式锁框架ShedLock。
启用Spring任务配置如下:
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(newScheduledThreadPool(10));
}
@Bean(destroyMethod = “shutdown”)
@Primary
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setTaskDecorator(new LogbackMdcTaskDecorator());
executor.initialize();
return executor;
}
}
然后配置Shedlock:
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = “PT30S”)
public class DistributedLockConfiguration {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Bean
public DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {
return new DistributedLockExecutor(lockProvider);
}
}
实现后台任务处理:
@Scheduled(cron = “0 0/1 * * * ?”)
@SchedulerLock(name = “scheduledTask”, lockAtMostFor =
THIRTY_MIN, lockAtLeastFor = ONE_MIN)
public void run() {
logger.info(“Run scheduled task.”);
}
为了支持代码直接调用分布式锁,基于Shedlock的LockProvider创建DistributedLockExecutor:
public class DistributedLockExecutor {
private final LockProvider lockProvider;
public DistributedLockExecutor(LockProvider lockProvider) {
this.lockProvider = lockProvider;
}
public T executeWithLock(Supplier supplier, LockConfiguration configuration) {
Optional lock = lockProvider.lock(configuration);
if (!lock.isPresent()) {
throw new LockAlreadyOccupiedException(configuration.getName());
}
try {
return supplier.get();
} finally {
lock.get().unlock();
}
}
}
使用时在代码中直接调用:
public String doBusiness() {
return distributedLockExecutor.executeWithLock(() -> “Hello World.”,
new LockConfiguration(“key”, Instant.now().plusSeconds(60)));
}
本文的示例项目使用了基于JDBC的分布式锁,事实上任何提供原子操作的机制都可用于分布式锁,Shedlock还提供基于Redis、ZooKeeper和Hazelcast等的分布式锁实现机制。
除了Checkstyle统一代码格式之外,项目中有些通用的公共的编码实践方式也需要在整个开发团队中进行统一,包括但不限于以下方面:
-
客户端的请求数据类统一使用相同后缀,比如Command
-
返回给客户端的数据统一使用相同后缀,比如Represetation
-
统一对请求处理的流程框架,比如采用传统的3层架构或者DDD战术模式
-
提供一致的异常返回(请参考“异常处理”小节)
-
提供统一的分页结构类
-
明确测试分类以及统一的测试基础类(请参考“自动化测试分类”小节)
静态代码检查主要包含以下Gradle插件,具体配置请参考本文示例代码:
-
Checkstyle:用于检查代码格式,规范编码风格
-
Spotbugs:Findbugs的继承者
-
Dependency check:OWASP提供的Java类库安全性检查
-
Sonar:用于代码持续改进的跟踪
健康检查主要用于以下场景:
-
我们希望初步检查程序是否运行正常
-
有些负载均衡软件会通过一个健康检查URL判断节点的可达性
此时,可以实现一个简单的API接口,该接口不受权限管控,可以公开访问。如果该接口返回HTTP的200状态码,便可初步认为程序运行正常。此外,我们还可以在该API中加入一些额外的信息,比如提交版本号、构建时间、部署时间等。
启动本文的示例项目:
./run.sh
然后访问健康检查API:http://localhost:8080/about,结果如下:
{
requestId: “698c8d29add54e24a3d435e2c749ea00”,
buildNumber: “unknown”,
buildTime: “unknown”,
deployTime: “2019-04-11T13:05:46.901+08:00[Asia/Shanghai]”,
gitRevision: “unknown”,
gitBranch: “unknown”,
environment: “[local]”
}
以上接口在示例项目中用了一个简单的Controller实现,事实上Spring Boot的Acuator框架也能够提供相似的功能。
软件文档的难点不在于写,而在于维护。多少次,当我对照着项目文档一步一步往下走时,总得不到正确的结果,问了同事之后得到回复“哦,那个已经过时了”。本文示例项目所采用的Swagger在一定程度上降低了API维护的成本,因为Swagger能自动识别代码中的方法参数、返回对象和URL等信息,然后自动地实时地创建出API文档。
配置Swagger如下:
@Configuration
@EnableSwagger2
@Profile(value = {“local”, “dev”})
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(SWAGGER_2)
.select()
.apis(basePackage(“com.ecommerce.order”))
.paths(any())
.build();
}
}
启动本地项目,访问http://localhost:8080/swagger-ui.html:
在传统的开发模式中,数据库由专门的运维团队或者DBA来维护,要对数据库进行修改需要向DBA申请,告之迁移内容,最后由DBA负责数据库变更实施。在持续交付和DevOps运动中,这些工作逐步提前到开发过程,当然并不是说不需要DBA了,而是这些工作可以由开发者和运维人员一同完成。另外,在微服务场景下,数据库被包含在单个服务的边界之内,因此基于内聚性原则(咦,这好像是本文第三次提到内聚原则了,可见其在软件开发中的重要性),数据库的变更最好也与项目代码一道维护在代码库中。
本文的示例项目采用了Flyway作为数据库迁移工具,加入了Flyway依赖后,在src/main/sources/db/migration
目录下创建迁移脚本文件即可:
resources/
├── db
│ └── migration
│ ├── V1__init.sql
│ └── V2__create_product_table.sql
迁移脚本的命名需要遵循一定的规则以保证脚本执行顺序,另外迁移文件生效之后不要任意修改,因为Flyway会检查文件的checksum,如果checksum不一致将导致迁移失败。
在软件的开发流程中,我们需要将软件部署到多个环境,经过多轮验证后才能最终上线。在不同的阶段中,软件的运行态可能是不一样的,比如本地开发时可能将所依赖的第三方系统stub掉;持续集成构建时可能使用的是测试用的内存数据库等等。为此,本文的示例项目推荐采用以下环境:
-
local:用于开发者本地开发
-
ci:用于持续集成
-
dev:用于前端开发联调
-
qa:用于测试人员
-
uat:类生产环境,用于功能验收(有时也称为staging环境)
-
prod:正式的生产环境
在前后端分离的系统中,前端单独部署,有时连域名都和后端不同,此时需要进行跨域处理。传统的做法可以通过JSONP,但这是一种比较“trick”的做法,当前更通用的实践是采用CORS机制,在Spring Boot项目中,启用CORS配置如下:
@Configuration
public class CorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(“/**”);
}
};
}
}
对于使用Spring Security的项目,需要保证CORS工作于Spring Security的过滤器之前,为此Spring Security专门提供了相应配置:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
…
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(“https://example.com”));
configuration.setAllowedMethods(Arrays.asList(“GET”,“POST”));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(“/**”, configuration);
return source;
}
}
这里列出一些比较常见的第三方库,开发者们可以根据项目所需引入:
-
Guava:来自Google的常用类库
-
Apache Commons:来自Apache的常用类库
-
Mockito:主要用于单元测试的mock
-
DBUnit:测试中管理数据库测试数据
-
Rest Assured:用于Rest API测试
-
Jackson 2:Json数据的序列化和反序列化
-
jjwt:Jwt token认证
-
Lombok:自动生成常见Java代码,比如equals()方法,getter和setter等;
-
Feign:声明式Rest客户端
-
Tika:用于准确检测文件类型
-
itext:生成Pdf文件等
-
zxing:生成二维码
-
Xstream:比Jaxb更轻量级的XML处理库
架构学习资料
由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
DBUnit:测试中管理数据库测试数据
-
Rest Assured:用于Rest API测试
-
Jackson 2:Json数据的序列化和反序列化
-
jjwt:Jwt token认证
-
Lombok:自动生成常见Java代码,比如equals()方法,getter和setter等;
-
Feign:声明式Rest客户端
-
Tika:用于准确检测文件类型
-
itext:生成Pdf文件等
-
zxing:生成二维码
-
Xstream:比Jaxb更轻量级的XML处理库
架构学习资料
[外链图片转存中…(img-hJKa1yQf-1715765210385)]
[外链图片转存中…(img-Ihl6W7cI-1715765210386)]
[外链图片转存中…(img-SQz7E5YG-1715765210386)]
[外链图片转存中…(img-IlPhhbxt-1715765210386)]
[外链图片转存中…(img-sxPBuV1W-1715765210387)]
由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!