在当前严峻的就业形势下,项目如果没什么亮点,可能连面试的机会都没有,但实际工作中,大部分人所做的事就是 CRUD(增删改查),那怎么才能即很好的体现我们的技术实力?又不会被面试官识破呢?
所以,今天我这个给大家提供一些通用的项目亮点,你学会之后可以应用到自己的项目中,这样这个棘手的问题就可以搞定了。
亮点1:并发编程+任务编排
1.1 并发编程
并发编程通常用在处理大量数据或者执行多个独立任务时,可以创建多个线程来并行处理,以提高效率和响应速度。
并发编程常见案例:例如 BI(Business Intelligence,商业智能系统)系统,一种集成了数据仓库、查询报表、数据分析、数据挖掘、数据可视化等多种功能的软件工具,用于帮助企业从大量数据中提取有价值的商业信息,以支持决策制定。BI 系统通常需要从多个(微服务)数据源并行导入大量数据,这样我们就可以利用多线程同时导入数据,这样可以显著减少总的导入时间。
多线程具体实现:在 Spring Boot 中定义全局线程池,实现代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(100000); // 任务队列:10w
taskExecutor.setThreadNamePrefix("my-threadpool-");
// 自定义拒绝策略【1.记录错误信息。2.通知消息系统,发送告警信息】
taskExecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1.记录错误信息
// 2.通知消息系统,发送告警信息
}
});
taskExecutor.initialize();
return taskExecutor;
}
}
多线程使用代码示例:
@Resource
private ThreadPoolTaskExecutor taskExecutor;
1.2 任务编排
任务编排常见案例和具体实现:以上 BI 系统再抽取多个数据源之后,需要对数据进行整合、处理和入库,在这种场景下就需要使用 CompletableFuture 来进行任务编排。
任务编排具体实现:使用 CompletableFuture,它是 JDK 8 提供的线程编排工具,它的常用方法有以下几个:
图片
例如,我们现在实现一个这样的场景:
图片
任务描述:任务一执行完之后执行任务二,任务三和任务一和任务二一起执行,所有任务都有返回值,等任务二和任务三执行完成之后,再执行任务四,它的实现代码如下:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
// 任务一:返回 "Task 1 result"
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 1 result";
});
// 任务二:依赖任务一,返回 "Task 2 result" + 任务一的结果
CompletableFuture<String> task2 = task1.handle((result1, throwable) -> {
try {
// 模拟耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 2 result " + result1;
});
// 任务三:和任务一、任务二并行执行,返回 "Task 3 result"
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(800); // 任务三可能比任务二先完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 3 result";
});
// 任务四:依赖任务二和任务三,等待它们都完成后执行,返回 "Task 4 result" + 任务二和任务三的结果
CompletableFuture<String> task4 = CompletableFuture.allOf(task2, task3).handle((res, throwable) -> {
try {
// 这里不需要显式等待,因为 allOf 已经保证了它们完成
return "Task 4 result with " + task2.get() + " and " + task3.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
});
// 获取任务四的结果并打印
String finalResult = task4.join();
System.out.println(finalResult);
}
}
亮点2:多级缓存
定义:多级缓存是指在系统中设置多个层次的缓存,每个层次的缓存具有不同的特性,它们协同工作来满足系统对数据访问速度和存储容量等多方面的需求。
图片
使用多级缓存可以从离用户请求最近的地方优先获取缓存信息,从而保证了更高的查询效率,多级缓存包括以下这些:
- 分布式缓存:例如,Redis、MemCached 等。
- 本地缓存:例如,Caffeine、Google Guava Cache、Spring Cache、Ehcache 等。
- Nginx 缓存:在 Nginx 中配置并开启缓存功能。
- CDN 缓存:是一种通过在离用户更近的位置存储数据副本,以提供更快速、高效地内容交付的技术。CDN缓存使用分布式网络和边缘节点来缓存和分发静态和动态内容,以减少用户请求的响应时间和带宽消耗。
- 浏览器缓存:它的实现主要依靠 HTTP 协议中的缓存机制,当浏览器第一次请求一个资源时,服务器会将该资源的相关缓存规则(如 Cache-Control、Expires 等)一同返回给客户端,浏览器会根据这些规则来判断是否需要缓存该资源以及该资源的有效期。
亮点3:缓存和数据库一致性问题解决
项目中使用缓存时会有 4 大经典的问题(雪崩、穿透、击穿、数据一致性),其中一个就是【缓存和数据库一致性问题】,所以在实际工作中解决了这个经典问题,也是一大亮点。
缓存和数据库一致性问题的常见解决方案有两个:
- 使用延迟双删+MQ。
- Canal 监听 MySQL Binlog 保证数据一致性:Canal 监听 binlog,再将数据库更新到 MQ(如 Kafka)中,再通过监听消息更新 Redis 缓存,Canal 执行流程如下:
图片
亮点4:自定义注解解决幂等性问题
使用了自定义注解实现了一些通用功能,例如,使用自定义注解实现幂等性处理、使用自定义注解实现了统一数据权限的验证等。
亮点5:分库分表技术
如项目中使用了分库分表技术增加数据库的并发处理性能,典型的技术实现有 MyCat 和 ShardingSphere。
分库分表技术介绍
首先来说,“分库分表”不是一个技术,而是多个技术实现:
- 分库
a.垂直分库
b.水平分库
- 分表
a.垂直分表
b.水平分表
① 垂直分库
垂直分库是按照业务将不同的表拆分到不同的数据库中。例如,在一个电商数据库中的用户表和订单表分别存放到不同的数据库中,如下图所示:
图片
② 水平分库
水平分库是将数据按照一定的规则(如用户 ID 取模、哈希等)分布到不同的数据库中。比如,根据用户 ID 对 10 取模,将用户数据分布到 10 个不同的数据库中,每个数据库都保存着完整的数据表结构,如下图所示:
图片
③ 垂直分表
分表:
图片
垂直分表:垂直分表是将一张表按照列的相关性拆分成多张表。例如,将一个包含大量字段的用户表,拆分为用户基本信息表和用户扩展信息表,如下图所示:
图片
④ 水平分表
水平分表是将一张表的数据按照行进行拆分。例如按照用户 ID 的范围或者哈希值将数据拆分到不同的表中。
图片
在实际业务中,各种分库分表技术可以混合使用。
MyCat VS Sharding Sphere
分库分表的主流实现技术有以下两种:
- MyCat
- Apache Sharding Sphere
Sharding Sphere 相比于 MyCat 来说,它的优势是:
- 功能更多:除了读写分离和分库分表之外,还提供了数据加密、流量质量、数据迁移等功能。
- 社区更活跃度和生态更好:Sharding Sphere 拥有活跃的社区和丰富的文档,生态系统较为完善,有更多的用户和开发者参与。
- 灵活性和扩展性:Sharding Sphere 灵活性更高,扩展性也更好,它可以方便地与其他技术集成,这方便 MyCat 支持的比较有限。
亮点6:项目中使用多种设计模式
例如以下这些设计模式:
- 责任链模式:在请求进入后端项目之后,会经过多个责任链拦截器(或过滤器),先鉴权、黑白名单效验、参数效验、敏感词过滤等操作之后,才能进行后续的访问和操作。
- 策略模式:支付功能会根据用户不同的选择,使用对应的支付代码实现支付功能。
- 模版方法模式:支付功能实现上,首先在接口中定义支付的基本流程,而具体的支付方式(如信用卡支付、三方平台支付等)则由子类实现。
- 装饰器模式:在不更改原功能的基础上,添加新的功能。例如,项目中方法的统计,不更改原来的方法执行,在方法执行前后添加了执行统计记录功能。
- 代理模式:日志代理功能的实现,在执行目标对象的方法前后记录日志,用于监控和调试。
- 生产者消费者模式:使用 MQ 实现生产者和消费者模式,处理异步任务。
- 建造者模式:实现类对象的创建和初始化。
- 单例模式:项目中的数据源、线程池等都使用了单例模式保证了资源的高效使用。