- vừa được xem lúc

[Redis] - Spring Boot Cache-Aside design pattern

0 0 73

Người đăng: TheLight

Theo Viblo Asia

Trong bài viết này, chúng ta sẽ tìm hiểu cách tích hợp Redis với Spring Boot để cải thiện hiệu suất bằng cách sử dụng Cache-Aside design pattern.

Có thể một số bạn chưa biết Redis là viết tắt của Remote Dictionary Server. Nó là một NoSQL DB trong bộ nhớ, theo mình biết là nhanh nhất đến thời điểm hiện tại, chủ yếu được sử dụng để lưu vào bộ nhớ đệm dữ liệu được sử dụng thường xuyên và ít bị thay đổi. Nó cũng có rất nhiều tính năng khác mà chúng ta sẽ nói trong bài viết này và trong các bài viết khác.

Tích hợp Spring Boot Redis

Trong kiến trúc Microservices, chúng ta có một vài dịch vụ và chúng nói chuyện với nhau để hoàn thành một nghiệp vụ nào đó. Trong một số trường hợp, một số microservice có thể nhận được nhiều GET request để lấy về thông tin cụ thể của một tài nguyên. Ví dụ: product-service có thể thường xuyên nhận request từ các service khác để lấy được một số thông tin sản phẩm. Thay vì mỗi lần request như vậy phải lấy thông tin sản phẩm từ DB, các microservice có thể lưu thông tin này vào bộ nhớ cache - để chúng ta có thể tránh việc gọi các lệnh vào DB không cần thiết liên quan đến nhiều phép join bảng. Microservice có thể lưu thông tin này vào bộ nhớ cục bộ của nó. Trong kiến trúc ngày nay, chúng tôi chạy nhiều phiên bản chạy của cùng một dịch vụ. Lưu cục bộ có thể không giúp ích cho các trường hợp khác. Sử dụng kho lưu trữ bộ nhớ cache tập trung cũng có thể giúp ích cho các trường hợp khác.

Ứng dụng

Hãy xem xét một ứng dụng như sau, product-service chịu trách nhiệm cung cấp thông tin sản phẩm dựa trên mã sản phẩm (product_id). DB chúng ta sử dụng cho product-service là Cơ sở dữ liệu PostgreSQL. Ứng dụng của chúng ta sẽ có 2 API.

  • GET: API để cung cấp thông tin sản phẩm theo mã sản phẩm
  • PATCH: API để cập nhật một số thông tin sản phẩm

Cài đặt DB

Chúng ta sử dụng docker-compose để setup Postgres DB:

version: "3"
services: postgres: image: postgres container_name: postgres environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=admin - POSTGRES_DB=productdb volumes: - ./db:/var/lib/postgresql/data pgadmin: image: dpage/pgadmin4 container_name: pgadmin environment: - _@.com - PGADMIN_DEFAULT_PASSWORD=admin ports: - 80:80

Tạo bảng product lưu thông tin sản phẩm (mọi người tự thêm dữ liệu vào nhé)

CREATE TABLE product( id serial PRIMARY KEY, description VARCHAR (500), price numeric (10,2) NOT NULL, qty_available integer NOT NULL
);
  • Product entity:
@Entity
public class Product { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String description; private double price; private long qtyAvailable; //getters & setters }
  • Product Repository:
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
  • Product DTO:
public class ProductDto { private long id; private String description; private double price; private long quantityAvailable; // getters & setters }
  • Product service:
public interface ProductService { Optional<ProductDto> getProduct(long id); void updateProduct(ProductDto productDto);
}
@Service
public class ProductServiceImpl implements ProductService { @Autowired private ProductRepository productRepository; @Override public Optional<ProductDto> getProduct(long id) { return this.productRepository .findById(id) .map(this::entityToDto); } @Override public void updateProduct(ProductDto productDto) { this.productRepository .findById(productDto.getId()) .map(p -> this.setQuantityAvailable(p, productDto)) .ifPresent(this.productRepository::save); } private ProductDto entityToDto(Product product){ ProductDto dto = new ProductDto(); dto.setId(product.getId()); dto.setDescription(product.getDescription()); dto.setPrice(product.getPrice()); dto.setQuantityAvailable(product.getQtyAvailable()); return dto; } private Product setQuantityAvailable(Product product, ProductDto dto){ product.setQtyAvailable(dto.getQuantityAvailable()); return product; }
}
  • Product Controller:
@RestController
public class ProductController { @Autowired private ProductService productService; @GetMapping("/product/{id}") public ResponseEntity<ProductDto> getProduct(@PathVariable long id){ return this.productService.getProduct(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.noContent().build()); } @PatchMapping("/product") public void updateProduct(@RequestBody ProductDto dto){ this.productService.updateProduct(dto); } }
  • application.yaml:
spring: datasource: url: jdbc:postgresql://localhost:5432/productdb username: admin password: admin

Chạy application lên và test thử, nếu kết quả tương tự bên dưới là setup thành công.

// http://localhost:8080/product/2 { "id":2, "description":"Product2", "price":1297.23, "quantityAvailable":69
}

Cấu hình Spring Boot Redis

Đầu tiên chúng ta thêm dependency Spring Data Redis (tìm kiếm trên maven ripository).

Ý tưởng ở đây là khi request tới service, thì sẽ kiểm tra dữ liệu trong Redis DB trước, nếu không tồn tại sẽ đi tiếp đến Postgres DB để lấy dữ liệu.

  • Thêm @EnableCaching vào application
@EnableCaching
@SpringBootApplication
public class CacheAsideApplication { public static void main(String[] args) { SpringApplication.run(CacheAsideApplication.class, args); } }
  • Để setup DB nhanh chúng ta sẽ chạy Postgres DB và Redis sử dụng docker-compose
version: "3"
services: postgres: image: postgres container_name: postgres environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=admin - POSTGRES_DB=productdb volumes: - ./db:/var/lib/postgresql/data ports: - 5432:5432 pgadmin: image: dpage/pgadmin4 container_name: pgadmin environment: - _@.com - PGADMIN_DEFAULT_PASSWORD=admin ports: - 80:80 redis: container_name: redis image: redis ports: - 6379:6379 redis-commander: container_name: redis-commander image: rediscommander/redis-commander:latest environment: - REDIS_HOSTS=local:redis:6379 ports: - 8081:8081
  • Thêm cấu hình Redis trong file application.yml
spring: datasource: url: jdbc:postgresql://localhost:5432/productdb username: vinsguru password: admin cache: type: redis redis: host: localhost port: 6379

Bây giờ Spring Boot application của chúng ta đã sẵn sàng để làm việc với Redis. Tuy nhiên, chúng ta sẽ phải cho Spring Boot biết khi nào cần thực hiện và loại thông tin nào chúng ta muốn lưu vào bộ nhớ cache Redis.

Cacheable & CacheEvict

Spring Boot đơn giản hóa việc cấu hình với các annotation của nó.

  • Chúng ta sử dụng annotation @Cacheable trên bất kỳ phương thức nào để lưu vào bộ nhớ cache giá trị phản hồi mà phương thức trả về.
  • Nếu chúng ta muốn xóa bộ nhớ cache trong một số trường hợp, hãy sử dụng annotation @CacheEvict.

Ví dụ: sử dụng @Cacheable@CacheEvict trong trường hợp update thông tin sản phẩm như sau:

  • @Cacheable: lưu vào bộ nhớ cache giá trị return của phương thức với key là mã sản phẩm
  • @CacheEvict: chúng ta sẽ xóa bộ nhớ cache bất cứ khi nào cập nhật thông tin cho sản phẩm. Nếu không, dữ liệu sẽ không được đồng bộ. Khi bộ nhớ cache bị xóa, ở request GET lấy thông tin sản phẩm lần tiếp theo với mã sản phẩm sẽ cập nhật bộ nhớ cache với thông tin mới nhất.
 @Override @Cacheable("product") public Optional<ProductDto> getProduct(long id) { return this.productRepository .findByDescription("Product"+id) .map(this::entityToDto); } @Override @CacheEvict(value = "product", key = "#productDto.id") public void updateProduct(ProductDto productDto) { this.productRepository .findById(productDto.getId()) .map(p -> this.setQuantityAvailable(p, productDto)) .ifPresent(this.productRepository::save); }

Tổng kết

Vậy là chúng ta vừa hoàn thành cấu hình Spring Boot Redis. Hầu hết các ứng dụng CRUD thực hiện READ nhiều hơn WRITE. Vì vậy, bộ nhớ cache thông tin được truy cập thường xuyên có thể cải thiện hiệu suất của ứng dụng. Khi chúng ta sử dụng design pattern này, hãy nhớ rule loại bỏ thông tin cũ khỏi bộ nhớ cache.

Hi vọng bài viết hữu ích với mọi người.

Nguồn: https://thenewstack.wordpress.com/2021/11/25/redis-spring-boot-cache-aside-design-pattern/

Follow me: thenewstack.wordpress.com

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 277

- vừa được xem lúc

Giới thiệu về Builder Design Pattern

Nguồn: refactoring.guru. Builder. Ý đồ.

0 0 34

- vừa được xem lúc

Một ví dụ nhỏ về Factory method

Trong bài viết trước mình đã giới thiệu tới các bạn về Abstract Factory pattern, các bạn quan tâm có thể theo dõi lại tại đây. Để tiếp tục về chủ đề design pattern trong bài viết này mình sẽ trình bày những khái niệm, ưu nhược điểm và các sử dụng của một creational design pattern khác đó là Factory

0 0 27

- vừa được xem lúc

Tôi đã dùng Service Pattern trong NuxtJS như thế nào ?

Giới thiệu. Trong quá trình làm VueJS NuxtJS hay thậm chí là Laravel mình cũng hay áp dụng các pattern như Service hoặc Repository.

0 0 59

- vừa được xem lúc

Hướng dẫn Adapter Design Pattern

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Adapter Design Pattern qua cấu trúc, cánh triển khai, ví dụ, ưu điểm nhược điểm và ứng dụng của nó. Đây là bài viết đầu tiên của mình nên sẽ không trán

1 1 53

- vừa được xem lúc

Giới thiệu về Prototype Design Pattern

Ý đồ. Prototype là một creational design pattern cho phép bạn sao chép các object hiện có mà không làm cho code của bạn phụ thuộc vào các class của chúng.

0 0 44