Trong hành trình không ngừng phát triển và nâng cao bản thân trong lập trình Java, có một kỹ thuật mà đến 99,99% các ứng dụng - không riêng gì Java cần phải dùng nếu muốn tối ưu hóa tốc độ xử lý của nó đó chính là Caching, việc hiểu và sử dụng kỹ thuật caching dữ liệu là một chìa khóa quan trọng. Caching không chỉ giúp tăng tốc độ xử lý của ứng dụng mà còn mang lại hiệu suất và trải nghiệm người dùng tốt hơn. Trong bài viết này, chúng ta sẽ khám phá các phương pháp caching phổ biến trong Java - thứ mà bất kỳ lập trình viên Java nào cũng cần phải biết nếu muốn thành công trên con đường này.
Có một điều khá hay là đa số các lập tình viên mới, thường không được tiếp xúc tới khái niệm này, hay thậm chí là chưa từng nghe về nó cho đến khi bước vào dự án thực tế hoặc được người tuyển dụng nhắc đến trong buổi phỏng vấn. Nếu bạn nằm trong số những người đó, thì bài viết này sẽ là một hành trang tốt, góp phần giúp bạn trở nên tự tin hơn khi đối diện với một khái niệm vừa khó mà lại vừa dễ này. Nào không dài dòng thêm nữa, giờ chúng ta hãy cùng bước vào nội dung chính của bài viết nhé.
A. Caching là gì?
Caching là một kỹ thuật trong lập trình máy tính được sử dụng để lưu trữ tạm thời các dữ liệu, kết quả của các phép toán, hoặc các tài nguyên có thể được truy cập một cách nhanh chóng. Mục tiêu của caching là giảm thời gian truy cập dữ liệu, tăng cường hiệu suất và giảm gánh nặng cho hệ thống.
Khi một ứng dụng yêu cầu dữ liệu, trước hết nó sẽ kiểm tra xem dữ liệu đã được lưu trong bộ nhớ cache hay chưa. Nếu dữ liệu đã tồn tại trong cache, ứng dụng sẽ lấy nhanh chóng từ đó thay vì phải truy cập vào nơi lữu trữ gốc (ví dụ: cơ sở dữ liệu, API, hoặc tệp tin). Nếu dữ liệu không có trong cache, ứng dụng sẽ lấy từ nơi lưu trữ gốc và sau đó lưu vào cache để sử dụng cho các lần truy cập sau.
Caching thường được sử dụng để giảm độ trễ, tăng tốc độ xử lý, và giảm tải cho nguồn lực hệ thống. Các kỹ thuật caching có thể áp dụng cho nhiều loại dữ liệu và tài nguyên, bao gồm dữ liệu cơ sở dữ liệu, kết quả của phương thức tính toán, tệp tin tĩnh, và nhiều hơn nữa.
Để dễ hiểu hơn, mình sẽ lấy một ví dụ trong thực tế:
Giả sử bạn là chủ nhân của một quán cà phê nổi tiếng trong khu phố. Hàng ngày, nhiều người đến đây để thưởng thức cà phê và thư giãn. Một trong những thức uống phổ biến nhất của quán là "Cà phê muối"
Thay vì mỗi lần khách hàng đến và order Cà phê muối, bạn phải vào bếp kiểm tra từng thành phần, đo lường, và pha chế, bạn quyết định tạo ra một khu vực nhỏ trên quầy để lưu trữ sẵn một số lượng Cà phê muối đã được chuẩn bị sẵn. Điều này giống như một "khu vực nhớ" (bộ nhớ cache) nơi bạn lưu trữ Cà phê muối đã làm sẵn để phục vụ nhanh chóng. (Quy trình này giống như việc bạn tạo ra một bộ nhớ cache để lưu trữ dữ liệu thường xuyên được sử dụng vào trong và dữ liệu đó ở đây chính là Cà phê muối)
Khi một khách hàng đến và đặt một Cà phê muối, việc đầu tiên bạn sẽ kiểm tra xem có Cà phê muối đã làm sẵn trong khu vực nhớ không. Nếu có, bạn chỉ cần lấy ra và phục vụ ngay lập tức mà không phải đi vào quá trình pha chế từ đầu. Điều này giúp tiết kiệm thời gian và đảm bảo rằng bạn luôn có thể đáp ứng nhanh chóng đối với những đơn đặt hàng phổ biến. Và khách hàng của bạn chắc chắn sẽ rất vui vẻ vì sự phục vụ nhanh chóng này. (Quy trình này giống như việc bạn kiểm tra bộ nhớ cache trước - xem có dữ liệu mà mình cần không, nếu không mới đi vào nơi lưu trữ gốc để lấy dữ liệu ra)
B. Ưu điểm của Caching
Để nói về ưu diểm của caching thì chắc kể mỏi mồm, thực tế chứng minh điều này bởi vì nó được sử dụng bởi hầu hết các ứng dụng. Nhưng sau đây là một vài ưu điểm chính của caching mang lại.
1. Tăng hiệu suất
Caching giảm thời gian truy cập dữ liệu từ nơi lưu trữ gốc, giúp ứng dụng hoạt động nhanh chóng hơn từ đó góp phần rất lớn giúp cho ứng dụng của bạn tăng hiệu suất làm việc.
Ví dụ: Một trang web sử dụng caching để lưu trữ tạm thời hình ảnh và tệp CSS, giảm thời gian tải trang cho người dùng.
2. Giảm Gánh Nặng Cho Cơ Sở Dữ Liệu
Caching giảm số lượng truy vấn trực tiếp đến cơ sở dữ liệu, giảm áp lực lên cơ sở dữ liệu và cải thiện hiệu suất hệ thống. Với những ứng dụng nhỏ, dữ liệu không quá nhiều, thì việc truy cập vào DB có thể sẽ không phải là một vấn đề quá lo lắng. Nhưng với những ứng dụng lớn, với hàng trăm, hàng ngàn query vào cơ sở dữ liệu mỗi giây, thì caching là một giải pháp cứu cánh cho đôi vai của ứng dụng. 1s để lấy dữ liệu từ DB so với chỉ vài chục mili giây để lấy từ bộ nhớ cache - cũng xứng đáng đấy chứ.
Ví dụ: Một ứng dụng di động sử dụng caching để lưu trữ kết quả từ API và tránh việc gửi yêu cầu đến máy chủ mỗi lần người dùng mở ứng dụng.
3. Cải Thiện Trải Nghiệm Người Dùng
Caching giúp cung cấp trải nghiệm người dùng mượt mà hơn và giảm độ trễ. Giống như mình đã nói ở trên, việc lấy dữ liệu ở trong cache sẽ nhanh hơn nhiều khi lấy trong cơ sở dữ liệu, chính vì thế kết quả trả về cho người dùng sẽ nhanh hơn, thậm chí không cảm thấy có độ trễ - còn gì tuyệt vời hơn đúng không.
Ví dụ: Trình duyệt web lưu trữ tạm thời các tệp tin hình ảnh và script để tăng tốc độ tải trang web.
4. Cải Thiện Độ Ổn Định Hệ Thống
Caching giúp cải thiện độ ổn định của hệ thống bằng cách giảm độ trễ và gánh nặng, giúp hệ thống chạy mượt mà hơn.
Ví dụ: Một ứng dụng trò chơi trực tuyến sử dụng caching để lưu trữ thông tin về người chơi và giảm độ trễ trong quá trình chơi game.
C. Những lưu ý khi sử dụng Caching
Việc sử dụng Cache và áp dụng nó không phải là một vấn đề khó khăn. Cái khó của caching nằm ở việc bạn quản lý dữ liệu cache như thế để nó hoạt động đúng với những ưu điểm mà ta đã đề cập ở trên. Vì vậy sau đây sẽ là những lưu ý mà bạn cần chú ý khi sử dụng Caching trong dự án của mình.
1. Rủi Ro Dữ Liệu Lỗi Thời (Stale Data)
Nếu dữ liệu trong cache không được cập nhật thường xuyên, có thể dẫn đến sử dụng dữ liệu lỗi thời (stale). Quay lại với ví dụ ở trên, giả sử thời hạn sử dụng của Cà phê muối là 3 ngày là hỏng, nhưng cửa hàng bạn đóng cửa 7 ngày mới mở lại, sau khi mở lại bạn lại tiếp tục dùng số Cà phê muối đã pha sẵn để bán cho khách thì điều gì sẽ xảy ra. Hay một ví dụ khác: Một trang web tin tức không cập nhật cache đúng cách, dẫn đến việc hiển thị tin tức cũ cho người đọc.
Điều này cho thấy rằng bạn cũng cần phải kiểm tra và cập nhật dữ liệu trong cache thường xuyên để tránh cho nó rơi vào tình trạng như trên.
2. Quản Lý Thời Gian Sống (Time-to-Live) Cẩn Thận
Thiết lập thời gian sống (time-to-live) sao cho phù hợp với tính chất của dữ liệu và mức độ thay đổi. Nhưng bạn cũng phải cẩn thận và tính toán kỹ lưỡng cho khoảng thời gian này để tránh dữ liệu lỗi thời hoặc re-fresh quá nhiều khiến việc cache trở nên vô nghĩa. Giống như việc cứ sau 3 ngày thì bạn sẽ phải thay những cốc Cà phê muối pha sẵn còn trên kệ bằng những cốc mới vậy.
Ví dụ: Một trang web tin tức có thể cập nhật cache cho các bài viết nổi bật mỗi giờ để đảm bảo
3. Quản Lý Bộ Nhớ Hiệu Quả
Caching có thể làm tăng tiêu tốn bộ nhớ, đặc biệt là khi lưu trữ lớn lượng dữ liệu. Chính vì thế, kiểm soát lượng bộ nhớ sử dụng cho caching để tránh tình trạng chiếm dụng bộ nhớ quá mức. Ví dụ: Một ứng dụng di động lưu trữ nhiều hình ảnh chất lượng cao trong bộ nhớ để cải thiện trải nghiệm, nhưng có thể gặp vấn đề với các thiết bị có bộ nhớ thấp.
4. Xác Định Rõ Mục Tiêu và Áp dụng chiến lược hợp lý
Đặt rõ mục tiêu bạn muốn đạt được với caching, liệu bạn đang tập trung vào tăng tốc độ, giảm áp lực cho cơ sở dữ liệu hay cải thiện trải nghiệm người dùng từ đó lựa chọn chiến lược caching phù hợp với yêu cầu và tính chất của ứng dụng.
D. Các phương pháp caching dữ liệu trong Java Spring Boot
Có rất nhiều cách để caching dữ liệu tùy thuộc vào tính chất cũng như nhu cầu sủ dụng của từng dự án khác nhau, từ đó cũng sẽ có những lựa chọn khác nhau về công cụ cũng như cách caching dữ liệu. Dưới đây là tổng hợp các cách mà mình thấy khá phổ biến và được nhiều dự án sử dụng, nếu dự án các bạn đang sử dụng một cách khác để caching dữ liệu hãy để lại dưới comment để mình và mọi người cùng biết nhé. Lưu ý là những ví dụ dưới đây mình sẽ đưa ra trong ngữ cảnh dự án Java Spring Boot thay vì một dự án Java thông thường, do đa phần các dự án ngày này đều đang triển khai Spring Framework
Trước tiên, chúng ta sẽ chia những phương pháp này thành các loại khác nhau, mỗi loại lại có công cụ cũng như cách tiếp cận khác nhau nhưng tất cả đều hướng tới mục tiêu caching dữ liệu, tăng hiệt suất và giảm tải trên hệ thống.
1. Spring Cache Abstraction
Sử dụng các annotations (@Cacheable, @CacheEvict, @CachePut, @Caching) để quản lý cache một cách trừu tượng mà không cần quan tâm đến việc thực hiện cụ thể.
Nó không phải là một trình quản lý cache thực tế, mà là một API trừu tượng giúp bạn dễ dàng tích hợp với các trình quản lý cache khác như EhCache, Caffeine, Hazelcast, Guava, Redis, v.v.
a. Làm việc với Spring cache Abstraction
@Cacheable: Dùng để đánh dấu một phương thức rằng kết quả của nó có thể được cache. Khi phương thức được gọi, Spring sẽ kiểm tra xem kết quả của phương thức đó đã được cache chưa. Nếu có, kết quả từ cache sẽ được trả về thay vì thực hiện phương thức.
@CacheEvict: Dùng để loại bỏ dữ liệu khỏi cache. Bạn có thể cấu hình nó để xóa một mục cụ thể khỏi cache hoặc để xóa tất cả các mục trong một cache cụ thể.
@CachePut: Luôn thực hiện phương thức và kết quả của nó sẽ được đưa vào cache theo khóa chỉ định. Điều này hữu ích khi bạn muốn cập nhật cache với một kết quả mới.
@Caching: Cho phép bạn gộp nhiều hoạt động cache vào một phương thức duy nhất.
@CacheConfig: Dùng để chia sẻ một số thuộc tính cache giữa các phương thức trong một lớp.
b. Tính linh hoạt và cấu hình
Spring Cache rất linh hoạt. Bạn không chỉ có thể sử dụng các trình quản lý cache in-memory như EhCache hay Caffeine mà còn có thể tích hợp với các hệ thống cache phân tán như Redis hay Hazelcast.
Cấu hình của cache có thể được quản lý thông qua các file cấu hình (như application.properties hoặc application.yml trong Spring Boot) hoặc bằng cách sử dụng Java Configuration (@Configuration classes).
c. Ví dụ sử dụng
Mặc dù nó được thiết kế để làm việc với bất kỳ trình quản lý cache nào nhưng không yêu cầu phải tích hợp với một trình quản lý cụ thể. Khi bạn không cung cấp một cấu hình cache cụ thể, Spring sẽ tạo ra một SimpleCacheMemory với một ConcurrentMapChe mặc định, là một cách đơn giản để thực hiện caching in-memory mà không cần bất kỳ sự tích hợp đặc biệt nào
ConcurrentMapCache là một cách tiện lợi để bắt đầu với caching trong Spring nếu bạn không có nhu cầu phức tạp và không cần một giải pháp caching mạnh mẽ như Caffeine, Redis, EhCache, v.v. Đây là một cách tốt để thêm caching vào ứng dụng của bạn nếu bạn chỉ cần cải thiện hiệu suất cho các trường hợp sử dụng đơn giản và không yêu cầu tính năng đầy đủ của một trình quản lý cache chuyên nghiệp.
Bước 1: Thêm Dependencies
Đầu tiên, bạn cần thêm spring-boot-starter-cache vào file pom.xml hoặc file cấu hình tương đương nếu bạn sử dụng Gradle. Dependency này sẽ bao gồm các tùy chọn cần thiết để làm việc với Spring Cache Abstraction.
<dependencies> <!-- ... other dependencies ... --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- ... other dependencies ... -->
</dependencies>
Bước 2: Kích hoạt Caching
Để kích hoạt caching trong Spring Boot, bạn chỉ cần thêm annotation @EnableCaching vào một class cấu hình Java.** Spring Boot** sẽ tự động phát hiện và cấu hình caching với một CacheManager mặc định.
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration; @Configuration
@EnableCaching
public class CacheConfig { // Mặc định, Spring Boot sử dụng SimpleCacheManager với ConcurrentHashMapCache
}
Bước 3: Sử Dụng Caching trong Service
Bây giờ, bạn có thể sử dụng các annotation như @Cacheable, @CacheEvict, @CachePut để quản lý caching trong dịch vụ của bạn.
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class BookService { @Cacheable("books") public Book getBookByIsbn(String isbn) { // Giả định đây là phương thức tốn kém thời gian để thực hiện return findBookInDatabase(isbn); } private Book findBookInDatabase(String isbn) { // Logic để tìm kiếm sách trong cơ sở dữ liệu return new Book(isbn, "Some Book"); }
}
Trong ví dụ trên, mỗi khi phương thức getBookByIsbn được gọi, Spring sẽ kiểm tra cache có tên là books. Nếu có, nó sẽ trả về giá trị từ cache thay vì thực hiện phương thức findBookInDatabase. Nếu không, phương thức findBookInDatabase sẽ được gọi, và kết quả sẽ được thêm vào cache.
Bước 4: Custom Cache Key và Eviction Policy
Bạn cũng có thể tùy chỉnh key của cache entry và cấu hình các quy tắc xóa cache.
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut; @Service
public class BookService { @Cacheable(value = "books", key = "#isbn") public Book getBookByIsbn(String isbn) { return findBookInDatabase(isbn); } @CachePut(value = "books", key = "#book.isbn") public Book updateBook(Book book) { return updateBookInDatabase(book); } @CacheEvict(value = "books", allEntries = true) public void evictAllBooksCache() { // This method will clear all entries in the 'books' cache. }
}
Trong đoạn code trên, @CachePut sẽ cập nhật cache mỗi khi một quyển sách được cập nhật, và @CacheEvict sẽ loại bỏ tất cả các mục khỏi cache khi được gọi.
Thêm một điều nữa bạn cũng có thể tự cấu hình CacheManager được Spring Cache Abtraction sử dụng mặc định bằng cách sau:
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.Arrays; @Configuration
public class SimpleCacheConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList( new ConcurrentMapCache("books"), new ConcurrentMapCache("authors") )); return cacheManager; }
}
Trong ví dụ trên, mình sẽ tùy chỉnh SimpleCacheManager bằng cách chỉ định rõ các cache (books và authors) mà chúng tôi muốn sử dụng trong ứng dụng
2. In-Memory Cache
a. Đặc điểm và công cụ
In-Memory Cache là một hình thức caching trong đó dữ liệu được lưu trữ trực tiếp trong bộ nhớ chính (RAM) của máy chủ. Điều này giúp việc truy xuất dữ liệu nhanh hơn nhiều so với việc phải đọc từ ổ đĩa cứng hoặc một nguồn dữ liệu từ xa như cơ sở dữ liệu hoặc dịch vụ web.
Việc lưu trữ dữ liệu trong bộ nhớ giúp giảm đáng kể thời gian phản hồi của ứng dụng, tăng khả năng mở rộng và giảm tải cho các nguồn dữ liệu chậm hơn. Tuy nhiên, vì RAM có kích thước hạn chế và dữ liệu trong cache thường bị mất khi ứng dụng hoặc máy chủ bị tắt, cần phải có chiến lược để quản lý việc đặt vào và loại bỏ khỏi cache (như eviction policies), cũng như để đồng bộ hóa cache trong một hệ thống phân tán.
Công cụ phổ biến để thực hiện In-Memory Cache
+) HashMap hoặc ConcurrentHashMap: Sử dụng một bản đồ đơn giản trong bộ nhớ để lưu trữ dữ liệu. Đây là cách đơn giản nhất nhưng không cung cấp các tính năng quản lý cache tự động.
+) Caffeine: Một thư viện caching nhanh chóng, mạnh mẽ với các cơ chế quản lý cache như evictions, loading, và computation.
+) Guava: Trước khi có Caffeine, Guava là lựa chọn phổ biến cho in-memory cache với các tính năng tương tự nhưng không hiệu suất cao như Caffeine.
b. Ví dụ sử dụng HashMap
Sử dụng HashMap để thực hiện caching có thể là một giải pháp đơn giản trong một số trường hợp, tuy nhiên, cần lưu ý rằng HashMap chỉ giữ trạng thái caching trong phạm vi của một ứng dụng cụ thể và không hỗ trợ tính năng như tự động evictions hoặc khả năng mở rộng đối với các ứng dụng phân tán.
Ở trên, tự động evictions ở đây là tính năng "tự động loại bỏ" (automatic evictions) thường được sử dụng trong cơ chế caching để tự động loại bỏ các mục khỏi bộ nhớ cache dựa trên một số quy tắc hay chiến lược cụ thể, thì HashMap không hỗ trợ điều này.
Dưới đây là một ví dụ sử dụng HashMap để caching trong một ứng dụng Java Spring Boot:
import java.util.HashMap;
import java.util.Map; public class DataCacheExample { // Sử dụng HashMap để lưu trữ dữ liệu private Map<String, String> dataCache = new HashMap<>(); // Phương thức để đọc dữ liệu từ cache hoặc nếu chưa có thì đọc từ nguồn và lưu vào cache public String fetchData(String key) { String result = dataCache.get(key); // Kiểm tra xem dữ liệu đã tồn tại trong cache chưa if (result == null) { // Nếu chưa, thì đọc từ nguồn và lưu vào cache result = fetchDataFromSource(key); dataCache.put(key, result); } return result; } // Phương thức giả định để đọc dữ liệu từ nguồn (ví dụ: cơ sở dữ liệu, API, ...) private String fetchDataFromSource(String key) { // Giả sử đây là phần logic để đọc dữ liệu từ nguồn // Ở đây, chỉ đơn giản trả về một giá trị dựa trên key return "Data for key: " + key; } public static void main(String[] args) { DataCacheExample example = new DataCacheExample(); // Lấy dữ liệu từ cache hoặc đọc từ nguồn và lưu vào cache nếu chưa có String result1 = example.fetchData("key1"); System.out.println(result1); // In ra: Data for key: key1 // Lấy dữ liệu từ cache và lưu trữ trước đó String result2 = example.fetchData("key1"); System.out.println(result2); // In ra: Data for key: key1 }
}
Trong ví dụ này, dataCache là một đối tượng HashMap được sử dụng để lưu trữ dữ liệu. Phương thức fetchData kiểm tra xem dữ liệu có tồn tại trong cache không. Nếu có, nó trả về dữ liệu từ cache. Nếu không, nó gọi phương thức fetchDataFromSource để đọc dữ liệu từ nguồn và lưu vào cache trước khi trả về giá trị.
Ưu điểm:
Đơn giản và dễ triển khai.
Thích hợp cho các ứng dụng nhỏ và không phân tán.
Nhược điểm:
Không hỗ trợ tính năng như time-to-live, evictions tự động, và khả năng mở rộng. Không thích hợp cho các ứng dụng có kích thước dữ liệu lớn hoặc đòi hỏi khả năng mở rộng.
Khi nào nên sử dụng:
Khi bạn chỉ cần một giải pháp caching đơn giản cho ứng dụng nhỏ và không phân tán. Khi không cần các tính năng nâng cao của các giải pháp caching khác.
c.Ví sử dụng Caffeine Cache
Caffeine Cache là một thư viện caching được xây dựng trên nền tảng Guava Cache của Google.
Dưới đây là một hướng dẫn đơn giản về cách tích hợp Caffeine Cache vào dự án Spring Boot
Bước 1: Thêm Dependency Caffeine vào Dự Án Spring Boot
Mở file pom.xml nếu bạn sử dụng Maven hoặc build.gradle nếu bạn sử dụng Gradle và thêm dependency cho Caffeine:
Maven:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.0.0</version> <!-- Kiểm tra phiên bản mới nhất trên trang GitHub của Caffeine -->
</dependency>
Gradle:
Bước 2: Cấu Hình Caffeine Cache Trong Ứng Dụng Spring Boot
Trong file application.properties hoặc application.yml, thêm cấu hình cho Caffeine Cache:
# Cấu hình cho Caffeine Cache
spring.cache.caffeine.spec=maximumSize=100,expireAfterWrite=5m
spring.cache.caffeine.spec=maximumSize=100 nghĩa là cấu hình cache để có tối đa 100 mục. Khi số lượng mục trong cache đạt đến con số này, các mục cũ nhất sẽ bị loại bỏ để nhường chỗ cho mục mới.
expireAfterWrite=5m định rằng mỗi mục trong cache sẽ hết hạn sau 5 phút kể từ khi được viết vào cache. Nghĩa là sau khi một mục được thêm hoặc cập nhật trong cache, nó sẽ tồn tại trong 5 phút trước khi bị loại bỏ khỏi cache.
Hoặc các bạn cũng có thể làm điều này bằng code:
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; @Configuration
@EnableCaching
public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(caffeineCacheBuilder()); return cacheManager; } Caffeine<Object, Object> caffeineCacheBuilder() { return Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) // Thời gian tồn tại của cache entry là 10 phút .maximumSize(100) // Số lượng entry tối đa trong cache là 100 .recordStats(); // Ghi lại thông kê về cache (lưu ý: chỉ dể mục đích kiểm tra và giám sát) }
}
Trong ví dụ trên, chúng ta đã tạo một CacheManager sử dụng CaffeineCacheManager và cấu hình Caffeine thông qua phương thức caffeineCacheBuilder(). Các tùy chọn cấu hình như expireAfterWrite (thời gian tồn tại), maximumSize (số lượng entry tối đa), và recordStats (ghi lại thống kê) có thể được điều chỉnh theo nhu cầu của bạn.
Bước 3: Sử Dụng Caffeine Cache Trong mã nguồn
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service; @Service
public class MyService { @Autowired private CacheManager cacheManager; public String getData(String key) { Cache cache = cacheManager.getCache("myCache"); if (cache != null) { Cache.ValueWrapper valueWrapper = cache.get(key); if (valueWrapper != null) { return "Data for key: " + key + " from cache: " + valueWrapper.get(); } } // Đoạn code để lấy dữ liệu từ nguồn nào đó, ví dụ từ database hoặc external API String data = "Data for key: " + key; // Cache dữ liệu if (cache != null) { cache.put(key, data); } return data; }
}
Trong ví dụ này, chúng ta sử dụng CacheManager để lấy cache có tên "myCache". Sau đó, kiểm tra xem dữ liệu đã tồn tại trong cache hay chưa. Nếu có, nó sẽ được lấy từ cache; nếu không, phương thức sẽ lấy dữ liệu từ nguồn nào đó và cache lại.
Bây giờ bạn có thể sử dụng cache để thêm, truy xuất và loại bỏ dữ liệu như trong ví dụ.
Lưu ý rằng bạn có thể tuỳ chỉnh các thuộc tính khác của Caffeine Cache để đáp ứng yêu cầu cụ thể của dự án. Để biết thêm thông tin, hãy tham khảo Trang GitHub của Caffeine.
Lưu Ý: +) Đảm bảo rằng bạn đã kích hoạt @EnableCaching trong cấu hình chính của ứng dụng Spring Boot để sử dụng chức năng cache.
+) Tùy chỉnh các thuộc tính của Caffeine Cache như maximumSize, expireAfterWrite, expireAfterAccess tùy thuộc vào yêu cầu cụ thể của bạn.
+) Xác định kỹ thuật loại bỏ (eviction policies) và thời gian sống (TTL) cho cache dựa trên tính chất của dữ liệu và yêu cầu của ứng dụng.
3. Distributed Cache
a. Đặc điểm và công cụ
Distributed Cache là một hệ thống cache được thiết kế để hoạt động trên nhiều máy chủ hoặc nút trong một mạng. Mục đích chính của nó là để tăng tốc độ truy cập dữ liệu bằng cách lưu trữ bản sao của dữ liệu thường xuyên được truy cập hoặc tính toán tốn kém trên nhiều địa điểm trong mạng.
Các Đặc Điểm Chính:
+) Phân Tán và Đồng Bộ: Dữ liệu được phân tán giữa nhiều nút hoặc máy chủ và thường được đồng bộ hóa hoặc sao chép giữa các nút để đảm bảo tính nhất quán của dữ liệu.
+) Tăng Tốc Độ và Giảm Độ Trễ: Cung cấp dữ liệu từ cache gần với vị trí của người dùng giúp giảm độ trễ và tăng tốc độ truy cập dữ liệu.
+) Tăng Khả Năng Mở Rộng: Hỗ trợ khả năng mở rộng ngang, cho phép hệ thống mở rộng bằng cách thêm nhiều nút cache hơn mà không ảnh hưởng đến hiệu suất.
+) Tăng Khả Năng Chịu Lỗi: Một số hệ thống cache phân tán cung cấp khả năng chịu lỗi, nơi mất mát một nút không làm mất mát dữ liệu hoặc làm gián đoạn dịch vụ do dữ liệu có thể được truy cập từ các nút khác.
Các Công Cụ Distributed Cache Phổ Biến:
+) Redis: Một cơ sở dữ liệu key-value nhanh chóng, hỗ trợ các cấu hình như cache phân tán, với khả năng lưu trữ dữ liệu trong bộ nhớ và cung cấp các cấu trúc dữ liệu phức tạp.
+) Memcached: Một hệ thống caching phân tán nhẹ, dễ dàng cấu hình, tập trung vào việc lưu trữ dữ liệu key-value đơn giản trong bộ nhớ.
+) Hazelcast: Một in-memory data grid, cung cấp các cấu trúc dữ liệu phân tán và hỗ trợ transaction, được sử dụng để tạo ra các hệ thống có khả năng chịu lỗi và mở rộng cao.
Về bản chất các công cụ này cũng hoàn toàn có thể được sử dụng cho mục đích In-memory cache tuy nhiên khi nhắc đến việc caching trên các hệ thống phân tán, chúng sẽ là những lựa chọn hàng đầu. Khi mà các công cụ In-memory cache không đáp ứng được hoặc sẽ khó để triển khai trên các ứng dụng hệ thống phân tán.
5. Một vài phương pháp caching dữ liệu khác
Cache with Database:
+) EhCache: Một thư viện caching có thể được sử dụng độc lập hoặc như một phần của JPA/Hibernate, cung cấp cả in-memory và disk-based caching.
+)Spring Data Cache: Sử dụng Spring Data Repositories với các phương thức query đã được đánh dấu để tự động cache kết quả.
Application Server Caching: Nếu bạn đang sử dụng một application server như Wildfly hoặc JBoss, bạn có thể sử dụng cơ chế caching được tích hợp sẵn trong server.
HTTP Caching: Caching tại tầng HTTP bằng cách sử dụng headers như Cache-Control, Etag, và Last-Modified để giảm tải cho server và cải thiện thời gian tải cho client.
Web Browser Caching: Tận dụng cache của trình duyệt bằng cách cấu hình các tài nguyên tĩnh (CSS, JS, images) để được cache tại client.
CDN (Content Delivery Network): Sử dụng CDN để cache nội dung tĩnh và động trên mạng lưới của các server trên toàn cầu, giúp tăng tốc độ phân phối nội dung.
(CDN - Content Delivery Network - Mạng Phân Phối Nội Dung) là một hệ thống các máy chủ được phân bố rộng rãi địa lý và làm việc cùng nhau nhằm cung cấp nội dung số (digital content) một cách nhanh chóng đến người dùng dựa trên vị trí địa lý của họ.)
Database Caching: Một số cơ sở dữ liệu như MySQL hoặc PostgreSQL có cơ chế caching query results của riêng họ.
Custom Caching Strategies: Tùy chỉnh logic caching của riêng bạn, quản lý thủ công việc lưu trữ và loại bỏ cache dựa trên nhu cầu cụ thể của ứng dụng.
Đây là những phương pháp khác để caching dữ liệu, tuy nhiên mình sẽ không đi vào chi tiết mà dừng lại ở mức từ khóa để các bạn có thể research nếu muốn đi sâu vào bất cứ cách thức nào. Cách tiếp cận phù hợp nhất phụ thuộc vào yêu cầu cụ thể của ứng dụng của bạn, bao gồm yêu cầu về hiệu suất, độ phức tạp, môi trường triển khai và mức độ chịu đựng sự cố
E. Kết luận
Kết luận lại ta có thể thấy việc tối ưu hóa hiệu suất ứng dụng là một trong những thách thức lớn mà các nhà phát triển phải đối mặt. Qua bài viết này, chúng ta đã khám phá cách sử dụng caching dữ liệu trong Java như một công cụ quan trọng để đạt được mục tiêu này.
Việc triển khai caching dữ liệu trong Java không chỉ giúp tăng cường hiệu suất của ứng dụng mà còn giúp giảm áp lực lên các nguồn dữ liệu chính, cải thiện độ tin cậy và tăng trải nghiệm người dùng. Bằng cách sử dụng các thư viện, công cụ,... chúng ta có thể dễ dàng tích hợp và quản lý caching dữ liệu một cách hiệu quả.
Hãy nhớ rằng việc sử dụng caching dữ liệu là một phần không thể thiếu trong chiến lược tối ưu hóa hiệu suất của ứng dụng. Bằng cách áp dụng các nguyên tắc và kỹ thuật caching một cách thông minh, chúng ta có thể nâng cao kỹ năng lập trình của bản thân và tạo ra các ứng dụng Java mạnh mẽ hơn.
Hãy bắt đầu áp dụng caching dữ liệu trong ứng dụng của bạn ngay hôm nay và trải nghiệm sức mạnh của việc nâng tầm bản thân thông qua kỹ thuật này!