Xây dựng hệ thống cache với Spring AOP

 

Caching system

Trước tiên tôi xin cung cấp một định nghĩa đơn giản và tương đối đễ hiểu, khái niệm này dùng cho mọi loại hệ thống cache.

Cache (đọc là kets, hay còn gọi là cạc) là tên gọi của bộ nhớ đệm – nơi lưu trữ các dữ liệu nằm chờ các ứng dụng hay phần cứng xử lý.Mục đích của nó là để tăng tốc độ xử lý (Có thế hiểu là : có sẵn xài liền không cần tốn thời gian đi lùng sục tìm kéo về).

Nói một cách bài bản, cache là một cơ chế lưu trữ tốc độ cao đặc biệt. Nó có thể là một vùng lưu trữ của bộ nhớ chính hay một thiết bị lưu trữ tốc độ cao độc lập.Có hai dạng lưu trữ cache được dùng phổ biến trong máy tính cá nhân là memory caching (bộ nhớ cache hay bộ nhớ truy xuất nhanh) và disk caching (bộ nhớ đệm đĩa).

Như vậy hệ thống cache sẽ giúp chúng ta tăng tốc độ của chương trình và giảm tải việc truy xuất tài nguyện gốc quá nhiều, trong môi trường lập trình, phần lớn việc cache chúng ta hiểu sẽ giảm tài cho database.

Vậy vấn đề cache nằm ở đâu:

Thông thường, để xây dụng một hệ thống cache, bạn sẽ phải tác động trên từng đối tượng mà bạn mong muốn, giả sử bạn có một trình kết nối cơ sở dữ liệu tạm gọi là DAO và một object cần được caching là SinhVien.

Bạn gọi hàm như sau:

  

DAO.getSinhVien(A){

Mở kết nối xuống CSDL.

Cơ sở dữ liệu trả vào resultset.

Map vào object.

Trả dữ liệu về.

}

Và mỗi lần muốn lấy ra sinh viên A bạn sẽ gọi lại phương thức này, công việc kế tiếp đó là chương trình sẽ mở một kết nối xuống database yêu cầu dữ liệu, database sẽ trả về dữ liệu được yêu cầu, thông thường CSDL này sẽ nằm tại một máy khác, thậm chí rất xa, chi phí cho nó là khá lớn.

Và cũng rất thông thường, bạn sẽ cải tiến như sau:

DAO.getSinhVien(A){

Nếu Sinhvien A chưa có{

Mở kết nối xuống CSDL.

Cơ sở dữ liệu trả vào resultset.

Map vào object.

Cất vào bộ nhớ.

Trả dữ liệu về.

}

Nếu Sinhvien A có trong bộ nhớ : Lấy dữ liệu từ bộ nhớ và trả về.

}

Vấn đề sẽ đơn giản nếu như chỉ có thế, tuy nhiên bạn sẽ gặp 2 vấn đề sau:

  1. Số lượng phương thức cache tăng lên đáng kể : bạn sẽ phải code khắp các phương thức, để tùy vào từng hoàn cảnh cụ thể bản sẽ phải trả về những đối tượng tương ứng (cache sinhvien, lophoc, ..etc....
  2. Số lượng object được cache tăng lên: vấn lúc này bạn sẽ không phải cache một, hai đối tượng sinh viên nữa mà là hàng trăm, hàng ngàn, thậm chí nhiều hơn nữa, đòi hỏi phải có một thuật toán phức tạp để quản lý, khi đó vấn đề quản lý các object này sẽ lấy hết thời gian của bạn, và sẵn sàng kill hệ thống bất cứ lúc nào.

Bài viết này sẽ giúp bạn vượt qua vấn đề một cách đơn giản và hiệu quả.

Tôi sẽ sử dụng Spring AOP và cache Memory trong ví dụ này.

Chương trình tôi giới thiệu dưới đây là một bộ chắn AOP (AOP Interceptor) cho phép bạn xác định được phương thức nào sẽ được cache trong spring bean

Xem ví dụ sau:

Tôi bắt đầu bằng việc định nghĩa một đối tượng bookManager có chứa một phươngthức: 

<bean id="bookManager" class="com.example.BookManager"/>

Kế tiếp, Tôi định nghĩa AOP interceptor mà sẽ cache lại kết quả. Ví dụ này sẽ trả về một kết quả được cached trong bộ nhớ (danh sách các cuốn sách) thay vì một lời gọi thực sự đến 

bookManager.getRelated(Book)

, nếu phương thức được gọi với cùng một tham số Book.

Những cài đặt thường dùng cho CacheInterceptor là:

· MemoryCacheInterceptor : một cache được lưu trong bộ nhớ, thông thường cách làm này không được dùng trong thực tế, tuy nhiên nó sẽ được dùng làm demo trong chương trình này với lý do đơn giản, một khi bạn hiểu về hệ thống cache, việc cấu hình lại cách thức lưu trữ khá dễ dàng.

· EHCacheInterceptor : sử dụng EHCache (Hibernate đang sử dụng) và phải được cấu hình trong ehcached.xml được một tả trong tài liệu ehcache.

· SwarmCacheInterceptor : một cài đặt cache có khả năng phân tán.

· OSCacheInterceptor: sử dụng OSCache từ OpenSymphony, tôi sẽ giới thiệu hệ thống này trong thời gian tới. Khi sử dụng bạn sẽ có thêm những tính năng cao cấp hơn như :

  1. Cache trong một khoảng thời gian nào đó (sau 10 phút sẽ tự động loại bỏ cache).
  2. Cache theo một chiến lược nào đó (số lần truy xuất...)
  3. Lựa chọn cache trên ổ cứng hoặc Memory hay cả 2
  4. Sử dụng thuật toán tối ưu khi dữ liệu lớn (Thuật toán LRU)

Nhưng ở đây, hãy sử dụng một cache trong Memory, tôi tin sẽ dễ dàng hơn cho bạn vào lúc này.

<bean id="cacheInterceptor"
        class="com.cache.MemoryCacheInterceptor">
        <property name="identifiers">
            <props> <prop key="java.util.Map">toString</prop>
            </props>
        </property>
    </bean>

<!-- Phương thức để gọi những đối tượng không chuẩn như String, Boolean hay Number. Phương thức này phải là một phương thức đơn giản như getId hay toString(), nhưng phải là đặc điểm duy nhất cho object -->

Để có thể nhận biết một lời gọi đến phương thức với cùng tham số, tôi sử dụng identifiers property. ở đây, bạn có thể liệt kê hàm cần được gọi để nhận biết được tham số duy nhất cho class này.

Chúng ta phải xác định dựa trên tên phương thức và tham số truyền (có thể không có bất kỳ tham số nào).

Đây là cách tiếp cận tốt hơn sử dụng phương thức toString(), vì phương thức này có thể tạo một dòng rất dài, trong khi hầu hết thời gian một identifier đơn giản thì đã có sẵn.

(Giả sữ chúng ta có object SinhVien, chúng ta sẽ nhận ra 2 sinhviên khác nhau bằng toString, tuy nhiên, sẽ đơn giản và gần gũi hơn ta dùng sinhvien.getid đó là điều tôi muốn nói)

Không cần phải xác định những tham số mà là primitives (float, int), String hay Number (Float, Interger,...).

Tôi sẽ tạo tạo bean book1 và sử dụng seacherAdvisor để giám sát phương thức này, yêu cầu của nó cần có 1 proxy để thực hiện việc chắn interceptor, ở đây tôi sử dụng BeanNameAutoProxyCreate .

<bean id="book1" class="com.cache.Book" />

<bean id="searcherAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice">
            <ref local="cacheInterceptor" />
        </property>
        <property name="patterns">
            <list>
                <value>.*getResults.*</value>
            </list>
        </property>

</bean>

    <bean id="proxyCreator1"
        class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <value>book1</value>
           </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>searcherAdvisor</value>
            </list>
        </property>
    </bean>

Dĩ nhiên bạn có thể thêm nhiều beans để cache khi bạn muốn.

Tới đây, mọi việc cơ bản đã xong, bây giờ mời bạn theo dõi một vài kết quả đạt được qua unit test.

  

/**
* @author LangThang
*/
public class Book implements IBook {

    private int counter1 = 0;

    public String getResults1(String arg1) {
        return String.valueOf(this.counter1++);
    }

}

Đây là đoạn mã để test.

public void testInvokeOneArg() {
        ApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
        IBook book = (IBook) context.getBean("book1");
        assertNotNull(book);

        Object result1 = book.getResults1("test1");
        assertEquals("0", result1);

        //Gọi lại một lần nữa, kết quả trả về được lấy từ hệ thống cache
        result1 = book.getResults1("test1");
        assertEquals("0", result1);

        //Tiếp tục thử một lần nữa
        result1 = book.getResults1("test1");
        assertEquals("0", result1);

        //Gọi với một tham số khác.

        String result2 = book.getResults1("test2");
        assertEquals("1", result2);  

}

image

Hệ thống được test thành công.

Như vậy, tôi đã giới thiệu với bạn hệ thống cache được thiết kế dựa trên Spring AOP và sử dụng bộ cache trên RAM, trong lần tới, tôi sẽ giới thiệu về OSCache và thực hiện cache trên hệ thống này thay cho cache trong bộ nhớ. Xin cám ơn.

Download Demo

About Langthang

This is a short description in the author block about the author. You edit it by entering text in the "Biographical Info" field in the user admin panel.
    Blogger Comment
    Facebook Comment

5 comments :

  1. Đúng bài em cần tìm rồi nhưng sao em không download được vậy anh?

    ReplyDelete
  2. Mình upload những project demo lên trang esnips.com, cho nên có lẽ bạn cần một account trên đó để download bạn à.
    Chúc bạn vui và ủng hộ.

    ReplyDelete