ทำ Caching ด้วย Spring + Redis Cache
การเรียกใช้ข้อมูลจากฐานข้อมูล (database) ในแต่ละครั้งนั้นจะใช้เวลาพอสมควร ถ้าเป็นระบบที่มีการเรียกใช้งานจำนวนมากๆ จะทำให้ load ของฐานข้อมูลมีจำนวนมหาศาล ดังนั้นการ Caching สามารถช่วนแบ่งเบาภาระสำหรับการดำเนินงานกันฐานข้อมูล ในกรณีที่มีข้อมูลในที่เป็นข้อมูลซ้ำๆ เดิม การอ่านข้อมูลจาก Cache จะเร็วกว่าการอ่านข้อมูลโดยตรงจากฐานข้อมูลมาก ซึ่งบทความนี้จะเสนอการจัดการ Cach ด้วย Spring Framework ซึ่งรองรับการ Caching และเก็บข้อมูลไว้บน Redis
Caching?
การ Cache เป็นกระบวนการสำหรับเก็บข้อมูลใน Cache ซึ่ง Cache เป็นที่เก็บข้อมูลชั่วคราวที่ซึ่งเก็บข้อมูลไว้ใช้ในภายหลัง ง่ายต่อการเข้าถึงทั้ง Client หรือ Server ตรงกันข้ามกับที่เก็บข้อมูลถาวรที่อาจจะเก็บไว้ใน Server ที่แตกต่างกันและใช้เวลาในการเข้าถึงที่นานกว่า (Database หรือ API ภายนอก)
Spring Cache
เป็น abstract class ของ Spring Framework ที่รองรับการเพิ่มการ caching เข้าไปใน Spring application ซึ่ง Caching abstraction ทำให้ใช้งาน caching solution ที่หลากหลาย ด้วยการเปลี่ยนแปลงโค้ดที่น้อยที่สุด
เพื่อใช้งาน Spring Caching Abstraction จะมี 2 สิ่งที่ต้องทำคือ
- Caching Declaration — เป็นการระบุ method สำหรับการ caching และ policy ของมัน
- Cache Configuration — เป็นการระบุระบบเบื้องหลัง cache ว่าจะเก็บข้อมูลไว้ที่ไหน หรือจะไปอ่านข้อมูลจากไหน
ซึ่งจะมี annotation สำหรับการ Caching ดังนี้
- @Cacheable — กระตุ้นการเรียกข้อมูลของ cache
- @CachEvict — ทำลายการ cache
- @CachePut — เปลี่ยนแปลงข้อมูลของ cache
- @Caching — เป็นการจัดกลุ่มหลายๆ cache operation เข้าด้วยกัน
- @CachConfig — ตั้งค่า config ของการ cache ในระดับ class
Create Spring Boot Project
สร้าง Spring Boot project ด้วย Spring Initializr เป็น Web
, Cache
, Redis
, H2
, JPA
และ Lombok
ใน pom.xml จะสังเกตุเห็นว่ามี dependency spring-boot-starter-cache
สำหรับ Cache และ spring-boot-starter-data-redis
สำหรับ Redis
ใช้ h2database
เพื่อเป็น In-Memory database และ spring-boot-starter-data-jpa สำหรับ Spring Data JPA ใช้ในการติดต่อกับ H2 database
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
...
</dependencies>
เพิ่ม application configuration ให้กับ project เพื่อตั้งค่าให้กับ Spring Cache เป็น redis
และตั้งค่าเวลาอายุของ cache เป็น 60000
วินาที และตั้งค่า host และ port ที่เชื่อมต่อกับ Redis เป็น localhost
และ 6379
spring.cache.type=redis
spring.cache.redis.time-to-live=60000
spring.redis.host=localhost
spring.redis.port=6379
Create RESTful Service
สร้าง RESTful Service สำหรับ CRUD operation สำหรับ User ง่ายๆ /user
Model [User]
Controller [UserController]
Service [UserService]
Repository [UserRepository]
Add Cache Abstraction
ใช้ @EnableCaching
annotation เพื่อเปิดใช้งาน Spring Cache
เพิ่ม Cache Abstraction ให้กับ UserService
เพราะว่าเราจะทำการ cache ใน Service level
ใช้ @Cacheable
annotation สำหรับ retrieveUserById
method ที่ query user data ด้วย User ID โดย parameter value
เป็นชื่อของ cache กำหนดเป็น “user”
, key
เป็น key ของ cache กำหนดเป็น “#id”
และ unless
เป็น parameter สำหรับกำหนดเงื่อนไขว่าผลลัพธ์ของ cache โดยกำหนดให้ “#result==null”
ซึ่งเป็นการเพิ่ม User data ให้กับ Cache data store
ใช้ @CachPut
annotation สำหรับ updateUserById
method ที่ update user data ด้วย User ID และ User Data โดยกำหนด parameter ให้เหมือนกับ @Cacheable
ซึ่งเป็นการแก้ไขข้อมูลของ User data ที่อยู่ใน Cache data store
ใช้ @CacheEvict
annotation สำหรับ deleteUser
method ที่ delete user data ด้วย User ID โดยกำหนด parameter value
เป็น “user”
ซึ่งเป็นการลบ User data ใน Cache data store
Run Redis
เริ่มต้นการทำงานของ Redis server ด้วยคำสั่ง redis-server
Run Spring Boot App
เริ่มต้นการทำงาน Spring Boot Application ด้วยคำสั่ง mvn spring-boot:run
Test Application
ใช้ Postman มาทดสอบว่า Spring Boot App ที่เราสร้างขึ้นมีการ cache หรือไม่ โดยเริ่มแรกสร้าง User data ก่อนด้วย POST
method และใช้ GET
method get data ด้วย User ID ที่สร้างขึ้น ซึ่งในที่นี้คือ User ID 1
POST /users
GET /user/1
สังเกตุใน application console จะเห็นว่ามีข้อความ “Retrieve User ID: 1”
ซึ่งเป็นข้อความจาก retrieveUserById
method เมือเรา GET request อีกครั้งจะเห็นได้ว่ามี response กลับมาจาก request แต่ว่าใน application console ไม่มีข้อความเพิ่มขึ้นอีก แสดงว่า User data กูกเก็บไว้ใน cache เรียบร้อยแล้ว
เมื่อใช้ redis-cli ดู key ที่อยู่ใน Redis จะเห็นว่ามี key user::1
อยู่ ถ้าลอง delete key ด้วยคำสั่ง FLUSHALL
ใช้ GET request อีกครั้งจะเห็นว่ามีข้อความ “Retrieve User ID: 1”
เพิ่มขึ้นเพราะว่ามีการไป query User data จาก database อีกครั้งและเก็บไว้ใน cache อีกครั้ง
สรุป
จากที่ได้ลองทำ Cache ของ RESTful web service ที่เก็บข้อมูลใน RDMS database ด้วย Spring + Redis Cache แล้วจะเห็นได้ว่า Spring framework รองรับการทำ Caching ด้วย data store ที่เป็น In-Memory NoSQL database อย่าง Redis ได้เป็นอย่างดีและการ configuration ต่างๆ เบื้องต้นก็ทำได้ไม่ยาก ทำให้เราลด database operation ลงได้มากถ้า web service บริการ request จำนวนมากด้วยข้อมูลเดิม