【Spring大揭秘】系統(tǒng)性能提升!帶你解鎖系統(tǒng)優(yōu)化大法
環(huán)境:Spring5.3.23
Spring在各大公司基本上都是標(biāo)配,它提供了豐富的功能和靈活性,但在使用過程中如果不注意性能優(yōu)化,可能會導(dǎo)致系統(tǒng)運行緩慢或出現(xiàn)其他問題。以下是一些Spring編程中性能優(yōu)化的實際案例:
使用AOP實現(xiàn)日志記錄優(yōu)化
在Spring中,可以使用AOP(面向切面編程)來實現(xiàn)日志記錄的優(yōu)化。在系統(tǒng)中有大量的日志記錄時,如果每個請求都進(jìn)行日志記錄,會占用大量的系統(tǒng)資源,導(dǎo)致系統(tǒng)性能下降。因此,可以使用AOP技術(shù),根據(jù)一定的條件對日志記錄進(jìn)行篩選和優(yōu)化。例如,可以定義一個切面(Aspect),在切面中實現(xiàn)日志記錄的功能,并根據(jù)一定的條件判斷是否需要進(jìn)行日志記錄。這樣可以避免每個請求都進(jìn)行日志記錄,從而提高系統(tǒng)的性能。示例代碼如下:
優(yōu)化前:
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class) ;
@Resource
private UserRepository userRepository ;
public User queryById(long userId) {
User user = this.userRepository.findById(userId) ;
log.info("queryById - User - {}", user) ;
return user ;
}
}
在優(yōu)化前的代碼中,我們直接打印用戶信息到日志中。
接下來,我們將使用AOP來實現(xiàn)日志記錄的優(yōu)化。首先,我們需要定義一個切面(Aspect),在切面中實現(xiàn)日志記錄的功能,并根據(jù)一定的條件判斷是否需要進(jìn)行日志記錄。以下是優(yōu)化后的代碼示例:
優(yōu)化后:
@Aspect
@Component
public class UserServiceAspect {
@Pointcut("execution(* query*(long))")
private void log() {}
@Before("log()")
public void logBefore(JoinPoint joinPoint) {
long userId = (int) joinPoint.getArgs()[0] ;
// 只有當(dāng)userId不合規(guī)才打印日志
if (userId <= 0) {
log.info("queryById - start - userId: {}", userId) ;
}
}
@AfterReturning(pointcut = "execution(public User query*(long))", returning = "user")
public void logAfter(JoinPoint joinPoint, User user) {
// 只有查詢到用戶了才記錄用戶信息到日志
if (user != null) {
long userId = (int) joinPoint.getArgs()[0] ;
log.info("queryById - end - userId={}, user info={}", userId, user);
}
}
}
通過切面,我們就可以根據(jù)條件篩選出需要日志記錄的請求,避免了對所有請求都進(jìn)行日志記錄,從而提高系統(tǒng)的性能。
使用二級緩存
在Spring框架中,可以使用二級緩存來優(yōu)化數(shù)據(jù)的訪問性能。二級緩存是指將數(shù)據(jù)緩存在內(nèi)存中,以避免頻繁的數(shù)據(jù)庫訪問操作。在Spring中,可以使用@Cacheable注解將一個方法標(biāo)記為可緩存的,這樣該方法的返回值就會被緩存在內(nèi)存中。當(dāng)同一個方法被調(diào)用時,直接從緩存中獲取返回值,而不需要再次訪問數(shù)據(jù)庫。這樣可以減少數(shù)據(jù)庫訪問次數(shù),從而提高系統(tǒng)的性能。
優(yōu)化前:
@Service
public class UserService {
@Resource
private UserRepository userRepository ;
public User queryById(long userId) {
User user = this.userRepository.findById(userId) ;
return user ;
}
}
優(yōu)化前每次獲取用戶都會從數(shù)據(jù)庫中查詢。
接下來,我們將使用二級緩存來實現(xiàn)數(shù)據(jù)訪問的優(yōu)化。首先,我們需要定義一個緩存管理器(CacheManager),用于管理緩存。以下是優(yōu)化后的代碼示例:
優(yōu)化后:
// 為了方便演示,這里我們自定義一個緩存管理器
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("user") ;
}
}
接下來,我們需要在UserService中注入CacheManager,并使用@Cacheable注解將queryById方法標(biāo)記為可緩存的。以下是優(yōu)化后的代碼示例:
@Service
@CacheConfig(cacheManager = "cacheManager") // 注入CacheManager
public class UserService {
@Resource
private UserRepository userRepository ;
@Autowired
private CacheManager cacheManager;
// 標(biāo)記為可緩存的,并指定緩存值和鍵
@Cacheable(value = "user", key = "#userId")
public User queryById(long userId) {
User user = userRepository.findById(userId) ;
return user ;
}
}
這樣,該方法的返回值會被緩存在內(nèi)存中,當(dāng)同一個方法的調(diào)用時,直接從緩存中獲取返回值,而不需要再次訪問數(shù)據(jù)庫。這樣可以減少數(shù)據(jù)庫訪問次數(shù),從而提高系統(tǒng)的性能。
減少數(shù)據(jù)庫查詢次數(shù)
在一個訂單管理系統(tǒng)中,有一個訂單詳細(xì)信息(OrderDetail)實體,該實體與訂單表(Order)在數(shù)據(jù)庫中有1對1的關(guān)系。在獲取訂單詳細(xì)信息時,不需要每次都查詢Order表。通過使用JPA的fetch屬性,可以將Order表的數(shù)據(jù)在一次查詢中一并獲取。這樣,每個訂單詳細(xì)信息實體只會引發(fā)一次數(shù)據(jù)庫查詢,而不是之前的每次獲取都查詢。
優(yōu)化前:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Order findByOrderId(Long orderId);
}
@Service
public class OrderService {
@Resource
private OrderRepository orderRepository;
public Order getOrderById(Long orderId) {
return orderRepository.findByOrderId(orderId);
}
}
上面每次獲取訂單都會發(fā)送多條SQL進(jìn)行數(shù)據(jù)查詢。優(yōu)化后:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Select("SELECT o.*, od.* FROM Order o LEFT JOIN o.orderDetails od WHERE o.id = ?1")
Order findWithOrderDetailsByOrderId(Long orderId);
}
這樣,我們只需一次數(shù)據(jù)庫查詢就可以獲取到訂單及其所有詳細(xì)信息。
使用多線程池
在Spring框架中,可以使用多線程池來優(yōu)化任務(wù)的執(zhí)行性能。當(dāng)系統(tǒng)中有大量的異步任務(wù)需要執(zhí)行時,如果每個任務(wù)都創(chuàng)建一個新的線程來執(zhí)行,會導(dǎo)致系統(tǒng)資源浪費和性能下降。因此,可以使用多線程池來管理任務(wù)的執(zhí)行。在Spring中,可以使用ThreadPoolTaskExecutor來實現(xiàn)多線程池的配置和管理。這樣可以避免每個任務(wù)都創(chuàng)建新的線程,從而提高系統(tǒng)的性能。
優(yōu)化前:
@Service
public class UserService {
@Resource
private UserRepository userRepository ;
@Override
public List<User> getUsers() {
List<User> users = userRepository.findAll();
for (User user : users) {
// 處理用戶數(shù)據(jù)
}
return users ;
}
}
優(yōu)化前處理用戶在一個線程中執(zhí)行,時間可能會很長影響系統(tǒng)整體性能。接下來,我們將使用多線程池來實現(xiàn)并發(fā)處理的優(yōu)化。可以考慮使用Java中的Executor框架來管理線程池。我們可以創(chuàng)建ThreadPoolExecutor類來定義線程池,并指定線程池的核心線程數(shù)、最大線程數(shù)和線程存活時間等參數(shù)。在處理每個用戶時,我們可以將任務(wù)分配給線程池中的一個線程進(jìn)行處理,這樣可以同時處理多個用戶,提高系統(tǒng)的并發(fā)性能。以下是優(yōu)化后的代碼示例:
優(yōu)化后:
@Service
public class UserService {
@Resource
private UserRepository userRepository ;
private ThreadPoolExecutor pool;
@Override
public List<User> getUsers() {
int coreThreads = 10; // 核心線程數(shù)
int maxThreads = 20; // 最大線程數(shù)
long keepAliveTime = 60L; // 線程存活時間(單位:秒)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
coreThreads,
maxThreads,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()) ;
List<User> users = userRepository.findAll() ;
for (final User user : users) {
pool.execute(() -> {
// TODO
});
}
pool.shutdown(); // 關(guān)閉
return users;
}
}
注意要在最后調(diào)用pool的shutdown方法來關(guān)閉線程池(非阻塞)。這樣,系統(tǒng)可以同時處理多個用戶,提高并發(fā)性能。
優(yōu)化數(shù)據(jù)庫查詢
在Spring框架中,數(shù)據(jù)庫查詢是常見的高負(fù)載點之一。因此,優(yōu)化數(shù)據(jù)庫查詢是提高系統(tǒng)性能的關(guān)鍵??梢詮囊韵聨讉€方面對數(shù)據(jù)庫查詢進(jìn)行優(yōu)化:
- 使用索引:為數(shù)據(jù)庫中的字段添加索引(根據(jù)情況最好是聯(lián)合索引)可以加速查詢速度。
- 避免使用SELECT *:避免查詢所有字段,只查詢需要的字段可以提高查詢速度,盡可能的應(yīng)用覆蓋索引。
- 分頁查詢:使用分頁查詢可以減少查詢的數(shù)據(jù)量,從而提高查詢速度。
- 批量操作:盡可能減少與數(shù)據(jù)庫的交互次數(shù),可以批量操作來減少查詢次數(shù)。
- 使用連接池:連接池可以管理數(shù)據(jù)庫連接,避免頻繁的創(chuàng)建和銷毀連接,從而提高性能。
以上是一些Spring編程中性能優(yōu)化的實際案例。通過對這些案例的分析和學(xué)習(xí),可以更好地應(yīng)用Spring框架,提高系統(tǒng)的性能和可靠性。