Các cấp độ Isolation trong Database dễ hiểu

Sơ lược về Database

Về term “database”, bạn có thể đọc bài viết sau:

https://quochung.cyou/tim-hieu-li-do-can-database-datalake-data-warehouse/

Transaction trong Database

ACID:

  • Đầu tiên, ta cần phải hiểu về Transaction trong Database. Một phiên (Transaction) trong database có thể được coi tạo lên bởi 4 tính chất ACID
  • Một transaction (phiên) thể hiên một nhóm các câu lệnh được chạy trong database, thông thường chứa nhiều lệnh khác nhau.
image 48 - quochung.cyou PTIT

Hãy tưởng tượng bạn chuyển 1 triệu đồng từ tài khoản A sang tài khoản B. Transaction sẽ bao gồm 2 bước:

  1. Trừ 1 triệu từ tài khoản A
  2. Cộng 1 triệu vào tài khoản B

Nếu bước 1 thành công nhưng bước 2 thất bại (vì lý do nào đó), transaction sẽ rollback (hoàn tác) – tức là hoàn lại 1 triệu vào tài khoản A. Như vậy sẽ không có tiền nào bị “biến mất”.

Các cấp độ isolation trong Database

Read Uncommitted Isolation Level (Cấp độ “đọc chưa hoàn thành”)

  • Đây là cấp độ khá lỏng lẻo, khi mà ta sẽ đọc các dữ liệu chưa được commit của row. Tức là bất kì lệnh cập nhật ,thêm vào mà chưa thật sự commit vào database cũng sẽ có luôn trong phiên hiện tại của mình. Cấp độ này thường được dùng trong các hệ thống đặt vé, đặt lịch hẹn khi mà các transaction khác đang cố update tình trạng của 1 vé, kể cả khi phiên ngoài đó chưa thực thi xong, nhưng ta cũng sẽ coi như là nó đã được chạy.
  • Cấp độ này thường k đảm bảo tính toàn vẹn của dữ liệu đọc được, tuy nhiên nếu chúng có thể chấp nhận được thì chúng có thể được dùng để giảm bớt tình trạng deadlock, … (Ví dụ như đọc toàn bộ dân số việt nam, ta có thể chấp nhận sai số khoảng 100-200 người chẳng hạn)
image 53 - quochung.cyou PTIT
  1. Transaction A bắt đầu và thực hiện một phép UPDATE (cộng 100k vào một tài khoản). Tại thời điểm này, sự thay đổi chỉ tồn tại trong phiên của Transaction A và chưa được xác nhận (commit) vào cơ sở dữ liệu.
  2. Transaction B bắt đầu và đọc dữ liệu từ chính tài khoản đó. Do mức cô lập (isolation level) của cơ sở dữ liệu thấp, Transaction B đã đọc phải dữ liệu “bẩn” (dirty data) – là dữ liệu đã được Transaction A thay đổi nhưng chưa được commit.
  3. Transaction A gặp lỗi hoặc quyết định hủy bỏ, do đó nó thực hiện lệnh ROLLBACK. Mọi thay đổi của Transaction A sẽ bị hủy bỏ, và dữ liệu trong tài khoản quay trở lại giá trị ban đầu như trước khi A bắt đầu.
  4. Kết quả: Transaction B bây giờ đang giữ một giá trị không còn tồn tại trong cơ sở dữ liệu, dẫn đến sự không nhất quán về dữ liệu.

Read Committed Isolation Level (cấp độ “đọc phải hoàn thành”)

Đặc điểm: Chỉ đọc được dữ liệu đã được commit. Đây là mức độ phổ biến nhất.

  • Transaction A đang cộng 100k vào tài khoản của bạn
  • Transaction B sẽ không thấy số tiền tăng cho đến khi A commit thành công
  • Nếu A rollback, B không hề biết có giao dịch A

Ưu điểm: An toàn, tránh đọc dữ liệu “bẩn” (dirty read).

image 50 - quochung.cyou PTIT
  1. Transaction A bắt đầu và thực hiện một phép UPDATE (cộng 100k vào một tài khoản). Tại thời điểm này, sự thay đổi chỉ tồn tại trong phiên của Transaction A và chưa được xác nhận (commit) vào cơ sở dữ liệu.
  2. Transaction B bắt đầu và đọc dữ liệu từ chính tài khoản đó. Do mức cô lập (isolation level) của cơ sở dữ liệu thấp, Transaction B đã đọc phải dữ liệu “bẩn” (dirty data) – là dữ liệu đã được Transaction A thay đổi nhưng chưa được commit.
  3. Transaction A gặp lỗi hoặc quyết định hủy bỏ, do đó nó thực hiện lệnh ROLLBACK. Mọi thay đổi của Transaction A sẽ bị hủy bỏ, và dữ liệu trong tài khoản quay trở lại giá trị ban đầu như trước khi A bắt đầu.
  4. Kết quả: Transaction B bây giờ đang giữ một giá trị không còn tồn tại trong cơ sở dữ liệu, dẫn đến sự không nhất quán về dữ liệu.

Trong Spring: Đây là mức độ mặc định khi bạn dùng @Transactional.

Repeatable Reads Isolation Level (Cấp độ “đọc lại”)

  • Giả sử, ta có một bảng chứa số tiền lương của nhân viên, và ta có một transaction chứa 2 lệnh như sau
  • Lệnh 1: Đếm số lượng nhân viên
  • Lệnh 2: Đếm tổng tiền lương của nhân viên

Lúc này, giả sử một trường hợp mà có một nhân viên mới được thêm vào ngay sau khi Lệnh 1 hoàn thành. Lúc này kết quả của lệnh 2 sẽ bị thay đổi (do có 1 nhân viên mới). Lúc này chúng ta thường sẽ chọn cấp độ Repeatable Read, đó là đảm bảo rằng số lượng row (hàng) nằm trong phiên hiện tại giữ nguyên giá trị cho đến khi hết transaction.

  • Điều này đảm bảo số lượng row nhân viên phải được đảm bảo từ đầu phiên, nên nhân viên mới kia (1 row mới) không được count vào, và lệnh 2 đảm bảo giá trị vẫn đúng.
image 51 - quochung.cyou PTIT
  • Transaction 1 (T1) bắt đầu với mức cô lập Repeatable Read. Điều này có nghĩa là bất kỳ dữ liệu nào T1 đọc trong quá trình thực hiện sẽ giữ nguyên (nhất quán) trong suốt thời gian của transaction, ngay cả khi các transaction khác (như T2) thực hiện các thay đổi và commit.
  • Khi T1 thực hiện Lệnh 1 (SELECT COUNT(*) FROM Employees), nó đọc được số lượng nhân viên là 10. Tại thời điểm này, một “snapshot” (ảnh chụp nhanh) của dữ liệu được tạo ra cho T1.
  • Transaction 2 (T2) chèn thêm một nhân viên mới và commit. Lúc này, về mặt vật lý, cơ sở dữ liệu đã có 11 nhân viên.
  • Tuy nhiên, khi T1 thực hiện Lệnh 2 (SELECT SUM(Salary) FROM Employees), nhờ cơ chế Repeatable Read, nó vẫn đọc dữ liệu dựa trên “snapshot” ban đầu của nó. Điều này có nghĩa là T1 sẽ không thấy được nhân viên mới mà T2 đã thêm vào. Do đó, tổng lương vẫn là 100,000,000 VND (tương ứng với 10 nhân viên ban đầu), đảm bảo tính nhất quán (không bị non-repeatable read) trong cùng một transaction.

Serializable Isolation Level

  • Đây là cấp độ mạnh nhất trong isolation
  • Không một transaction nào khác được quyền đọc hoặc ghi cho đến khi transaction này hoàn thành
  • Cấp độ này giải quyết đa số vấn đề mà 3 cấp độ kia có, nhưng mà nó thường làm giới hạn, chỉ cho phép 1 query chạy 1 lúc trong hệ thống, tạo thành 1 điểm nghẽn và khó scale hệ thống hơn.

Nghe thì có vẻ Serializable giúp ta thoải mái và đảm bảo dữ liệu đúng, tuy nhiên bản chất, nó ngăn chặn mọi hành động đa luồng trong database vào 1 vùng dữ liệu, và ta chỉ chạy được 1 phiên trên 1 vùng vào 1 thời điểm

image 52 - quochung.cyou PTIT
  • Transaction A bắt đầu và đọc dữ liệu. Khi Transaction A thực hiện các thao tác đọc và ghi, cơ sở dữ liệu sẽ áp dụng một khóa độc quyền (exclusive lock) lên các dữ liệu mà A đang truy cập.
  • Trong khi A đang giữ khóa, Transaction B cố gắng đọc cùng một dữ liệu. Tuy nhiên, do dữ liệu đang bị khóa bởi A ở mức độ Serializable, Transaction B sẽ bị chặn (blocked) và phải đợi cho đến khi A hoàn thành.
  • Khi Transaction A thực hiện COMMIT, khóa độc quyền được giải phóng.
  • Lúc này, Transaction B mới có thể tiếp tục và đọc được dữ liệu, đảm bảo rằng nó luôn đọc được dữ liệu ở trạng thái nhất quán và đã được commit cuối cùng.

Tham khảo:

7 plugin cho InteliJ IDEA để tăng hiệu suất làm việc của bạn

Json2Pojo

image 13 - quochung.cyou PTIT
  • Đây là một plugin hỗ trợ tạo 1 class Java đơn giản (POJO – Plain Old Java Object) từ JSON. Khi cài đặt, chỉ cần chuột phải vào 1 package, New -> Generate POJOS from JSON, và chọn tên class

Jump to Line

image 14 - quochung.cyou PTIT
  • Cho phép bạn nhảy đến bất kì dòng code nào khi đang ở chế độ debug. Hiểu đơn giản, bạn có thể nhảy để chạy luôn một đoạn code mà không cần chạy các code trước đó

SonarLint

image 15 - quochung.cyou PTIT
  • Một plugin cực kì mạnh giúp bạn tìm và sửa bug, ngoài ra nó cũng có thể detect các vấn đề bảo mật, các đoạn code không clean, … khi bạn đang viết
  • SonarLint hỗ trợ nhiều ngôn ngữ giúp lập trình viên viết code clean hơn và nhanh hơn

JPA Buddy

image 16 - quochung.cyou PTIT
  • Một bộ công cụ giúp bạn làm việc với JPA dễ dàng hơn, có giao diện và khả năng sinh code để làm việc với các thư viện nổi tiếng như HIbernate, Mapstruct, Lombok, …

Maven Helper

image 17 - quochung.cyou PTIT
  • Giúp bạn kiểm tra và tìm các dependency có vấn đề với nhau trong maven.

GenerateAllSetter

image 18 - quochung.cyou PTIT
image 19 - quochung.cyou PTIT
image 20 - quochung.cyou PTIT
  • Giúp bạn nhanh chóng sinh hàm setter nhanh với giá mặc định

CodeGlance

image 21 - quochung.cyou PTIT
  • Giúp bạn có một cái nhìn tổng quan về file code hiện tại, dễ dàng di chuyển đến các mục code bạn đang cần tìm

3 cách thực hiện Dependency Injection (DI) và vấn đề với @Autowired trong Spring

Lời mở đầu

Khi code các ứng dụng backend bằng Java với Spring Framework, khả năng cao là bạn đã gặp qua một đoạn code sử dụng @Autowired. Tuy nhiên, nếu bạn sử dụng một số phần mềm lint để kiểm tra code, bạn có thể thấy các phần mềm sẽ cảnh báo @Autowired is deprecated (Autowired không được khuyến khích sử dụng nữa, cách làm không tốt)

image - quochung.cyou PTIT
  • Nhìn sơ qua thì đây có vẻ là một cách đơn giản và nhanh gọn để thực hiện DI qua Spring. Chỉ cần khai báo các dependency rồi thêm một annotation (@Autowired), các phần liên kết sẽ được Spring xử lí
  • Tuy nhiên, cách làm phổ biến này lại có thể dẫn đến nhiều vấn đề về sau này. Hãy thử xem qua các thủ pháp DI được sử dụng qua Spring Framework và các ưu nhược điểm của chúng

3 cách thực hiện Dependency Injection trong Spring Framework

Field Injection

  • Field Injection chính là tên gọi của cách inject dependency khi ta khai báo bằng @Autowired
  • Về cơ bản khi khai báo annotation này trên 1 field, method, constructor. Spring sẽ tự tìm dependency phù hợp và kết nối chúng với nhau.
  • @Autowired về mặc định không sai, tuy nhiên nếu sử dụng không hợp lí chúng có thể gây ra một số vấn đề về sau.
  • Ví dụ với một code như sau:
image 1 - quochung.cyou PTIT
  • Khai báo một Multiplier trong Calculator nhanh chóng bằng @Autowired sẽ không báo lỗi gì khi compile, về cách hoạt động, khi chạy chương trình, Spring sẽ tự tìm kiếm dependency và kết nối chúng với nhau
  • Tuy nhiên, vì chúng tự động, nên ta thiếu kiểm soát hơn phần này. Tức là lúc này ta không có cách nào để thêm thủ công dependency nữa cho các hoàn cảnh khác ngoài lúc chạy ứng dụng (VD khi chạy test)
image 2 - quochung.cyou PTIT
  • Ví dụ như code trên sẽ không báo lỗi, tuy nhiên khi chạy test thì chúng có thể throw NullPointer
  • Lí do là ở file test trên thì chúng ta đang không có chỗ nào liên kết với Spring cả, vì vậy Spring sẽ không quản lý các bean mà ta đã tạo (Khai báo @Component ở các class kia) nên chúng sẽ không nối vào nhau được -> Ta sẽ bị null pointer vì Multiplier không được khởi tạo lên
  • Để xử lý vấn đề này, thì nếu sử dụng 2 thủ pháp DI bên dưới (Constructor Injection và Setter Injection) , ta có thể có nhiều khả năng kiểm soát hơn và thêm dependency một cách thủ công mà không qua Spring, tạo nhiều khả năng hơn cho việc test
  • Hoặc, ta có thể khai báo @SpringBootTest để Spring quản lý file test này và tự động thực hiện việc gắn các bean mà không cần gắn chay
image 20 - quochung.cyou PTIT

Setter Injection

image 3 - quochung.cyou PTIT
  • Cách làm trên như tên của nó, ta sẽ set một dependency của class lớn hơn ở thời điểm runtime
  • Cách làm này cho phép ta dễ dàng thay đổi dependency của một class trong runtime, ví dụ như đổi 1 repo khác 1 service nào đó, …
  • Tuy nhiên, ngoài ưu điểm là sự flexible khi set ở runtime, thì nó cũng đi cùng vài nhược điểm khác
  • Do được set trong runtime, ta đôi khi có thể gặp phải NullPointer khi dependency chưa được khởi tạo, là null, … (vì lúc này chúng là optional, không bắt buộc với class nữa)
  • Do nó lỏng lẻo hơn nên lúc này với các method trong class gọi đến dependency, ta không được đảm bảo là dependency đã được set, hoặc ta lại phải thêm code để kiểm tra là có dependency chưa, 1 là thêm code, 2 là dẫn tới nullpointer
image 21 - quochung.cyou PTIT
  • Với cách làm như thế này, ta có thể không cần Spring quản lý các bean mà có thể test chay hơn, có nhiều quyền kiểm soát hơn vào việc quản lý các dependency

Constructor Injection

image 4 - quochung.cyou PTIT
  • Như tên của thủ pháp, ta sẽ thêm dependency cho 1 class ngay tại thời điểm khởi tạo class cha
  • Ở file test trên, mình đang sử dụng mock để dựng class multiplier lên theo kèm để gắn vào Calculator qua constructor
  • Việc này đảm bảo 1 class luôn có đủ dependency để khởi tạo (tuỳ theo cách custom constructor)
  • Hoặc mình hoàn toàn có thể làm như thế này
image 22 - quochung.cyou PTIT
  • (Việc sử dụng mock cho phép nhiều khả năng khác trong test hơn, ví dụ mình có thể kiểm tra xem một hàm trong class đó được gọi bao nhiêu lần, … , chúng được quản lý theo dạng Proxy Design Pattern, mình sẽ nói ở bài viết khác)

Bạn có thể đọc thêm bài viết này về một số khái niệm trong Spring như IoC, Bean, …

Tham khảo:

Vấn đề khi sử dụng số hoặc UUID cho Primary Key trong database

Lưu trữ dạng số

image 23 - quochung.cyou PTIT

Đây có lẽ là cách lưu trữ thường được thấy và dễ tưởng nhất. Trường primary key sẽ đóng vai trò như số thứ tự, giúp phân biệt giữa các row khác nhau, cách lưu trữ bằng số có thể được DB tự gen bằng cách gen sequence.

  • Khá tiện quản lý, sử dụng trong tầng dữ liệu
  • Dễ hiểu, dễ đọc, dễ tìm

Điểm trừ của cách làm này là:

  • Giả sử bạn đang lưu trữ dữ liệu cho 1 tệp khách hàng, được đánh id từ 1-1000, một ngày nào đó, bạn cần migrate data từ các bảng khác, từ những bảng cũ, … (kiểu sát nhập công ty, …) và cũng có tập dữ liệu được đánh số từ 1-100, 500-1000 gì đó. Điều này làm việc sát nhập trở lên khó khăn hơn do key chính để phân biệt các hàng đã bị trùng
  • Việc đánh số tuần tự cũng có thể tạo các risk về security, … Ví dụ bài viết đang được lưu đơn giản đánh bài viết số 1,2,… Từ đó từ bên ngoài có thể đoán được và truy cập được 1 bài viết bất kì.

Lưu trữ bằng UUID

image 24 - quochung.cyou PTIT

Lúc này, một cách làm khác đã thường được sử dụng là UUID (Universally unique identifier). Sẽ khó có ai có thể đoán được uuid của 1 bài viết được sinh ra ngẫu nhiên.

  • UUID được sinh ngẫu nhiên, tỉ lệ trùng lặp là rất thấp
  • Tránh được việc user tự mở 1 bài viết bất kì
image 25 - quochung.cyou PTIT

Tuy nhiên nó cũng đi kèm một số nhược điểm quan trọng

  • UUID dài. Để lưu trữ 1 uuid, thông thường ta cần tới 16 byte. Nó tốn dữ liệu hơn nhiều trong database so với lưu số thông thường. Sau này, khi ta sử dụng các database quan hệ giữa nhiều bảng, việc join các cột foreign keys sẽ ngày càng lớn hơn giữa các primary key
  • UUID do được sinh random, nên database sẽ khó lòng index một bảng để truy vấn nhanh hơn (VD MySQL sử dụng B+ Tree, cần một trường nào đó đánh theo một dãy giá trị có thể sắp xếp được: số, …).

Một số cách làm khác

  • ULID (Universally Unique Lexicographically Sortable Identifier). Vẫn là 16 byte, nhưng phần đầu sẽ chứa dữ liệu của timestamp (ngày tháng), và còn lại là ngẫu nhiên. Điều này giúp xử lí việc index database, dễ sắp xếp hơn
  • TSID (Time-Sorted Unique Identifiers). Cũng giống với ý tưởng của ULID nhưng chỉ tốn 8 byte. Tuy nhiên chúng cũng đi kèm với 1 số tradeoff

Đi sâu vào TSID

Có nhiều cách để triển khai TSID, trong bài này mình sẽ nói về Hypersistence TSID (Thư viện OSS), cho phép tạo 1 TSID 64-bit gồm 2 phần

  • 42-bit là dữ liệu thời gian
  • 22 bit là dữ liệu random

Thêm vào maven

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-tsid</artifactId>
    <version>${hypersistence-tsid.version}</version>
</dependency>

Tạo 1 object TSID

TSID tsid = TSID.fast();

`Từ TSID, ta có thể extract ra được thời gian

image 26 - quochung.cyou PTIT
image 27 - quochung.cyou PTIT
image 28 - quochung.cyou PTIT

Sử dụng vào database

Vì TSID là một số 64-bit (có thể sắp xếp), ta có thể lưu nó vào database dưới dạng bigint

CREATE TABLE post (
    id bigint NOT NULL,
    title varchar(255),
    PRIMARY KEY (id)
)

Lưu ở phía Entity

@Entity
@Table(name = "post")
public class Post {
 
    @Id
    private Long id;
 
    private String title;
     
}

Tổng kết về TSID

  • Đa phần giữ được các ưu điểm của UUID như khó đoán ở phía người dùng, tạo sự phân biệt, …
  • Lưu trữ nhẹ hơn UUID (8 byte vs 16 byte)
  • Tuy nhiên TSID có thể có tỉ lệ bị sinh ra ngẫu nhiên trùng cao hơn do chỉ có 22bit ngẫu nhiên (xác suất sinh trùng là 0.3%)

Xử lí việc sinh trùng, bạn có thể thêm số lần retry để tự động sinh lại nếu xảy ra collision

image 29 - quochung.cyou PTIT

Khi nào nên sử dụng TSID

  • Phần ID có thể expose cho phía client người dùng, ví dụ như id bài viết, video, …. còn các bảng phụ hỗ trợ liên quan có thể cứ dùng dạng số để thuận tiện và tiết kiệm
  • Các database expose cho người dùng, cần tính đến việc scale lâu dài, và tiết kiệm

Tham khảo:

Tìm hiểu về Serverless dễ hiểu bằng trà sữa

1 kvYDcS71SZXcyP yeqQMww - quochung.cyou PTIT

Serverless là gì, IaaS, CaaS, FaaS là gì, ..

Trước tiên để tìm hiểu về cấu trúc Serverless, trước hết ta hãy xem thử các cấu trúc truyền thống thường được sử dụng, và một số từ khoá và định nghĩa của chúng.

Mô tả cấu trúc Monolithic truyền thống

Ví dụ với một cấu trúc như trên, toàn bộ ứng dụng của chúng ta sẽ được chứa chung trong 1 khối lớn, và cùng tương tác vào 1 database. Để dễ hiểu hơn, mình sẽ ví dụ chúng như sau:

Một cửa hàng bán trà sữa bán 4 loại trà sữa:

  • Trà sữa dâu
  • Trà sữa ô long
  • Trà sữa bạc hà
  • Trà sữa socola

Tất cả trà sữa này sẽ được nhân viên pha bằng cùng 1 chiếc máy, cái máy này có thể pha được cả 4 loại trà sữa trên, rất đa năng tiện dụng. Ngoài ra, để có thể pha trà sữa bằng máy, ta cần nhiên liệu pha trà sữa, các nhiên liệu cho 4 loại trà sữa thì khác nhau, và được để chung trong 1 thùng nhiên liệu.

  • Máy pha trà sữa – Code/Server xử lí Logic tính toán, ..
  • Thùng nhiên liệu – Database, cơ sở dữ liệu tổng
  • Nhiên liệu – Các dữ liệu

Vậy ta có thể thấy, với một lượng người dùng chưa lớn, xây dựng cấu trúc đưa toàn bộ các logic vào 1 server như trên có thể khả thi, tuy nhiên, khi người dùng ngày càng tăng lên, việc làm trà sữa nào cùng phải dùng chung 1 cái máy, và việc lấy nhiên liệu nào cũng phải lấy từ 1 thùng nhiên liệu lớn sẽ khiến cho 1 khối chung quá tải, một trong những cách tối ưu cho vấn đề này là chia nhỏ các công việc nhỏ ra.

Tối ưu: Chia nhỏ các vấn đề

Lúc này, ta sẽ chia nhỏ các vấn đề thành 4 luồng như sau, vẫn tại cùng 1 địa điểm, 1 server cửa hàng đó, ta chia cấu trúc thành:

  • Máy pha trà sữa 1, chỉ pha trà sữa dâu. Cùng 1 thùng nhiên liệu chỉ cho trà sữa dâu
  • Máy pha trà sữa 2, chỉ pha trà sữa ô long. Cùng 1 thùng nhiên liệu chỉ cho trà sữa ô long
  • Máy pha trà sữa 3, chỉ pha trà sữa bạc hà. Cùng 1 thùng nhiên liệu chỉ cho trà sữa bạc hà
  • Máy pha trà sữa 4, chỉ pha trà sữa socola. Cùng 1 thùng nhiên liệu chỉ cho trà sữa socola

Lúc này, với các khách order các trà sữa khác nhau, nhân viên có thể đến các máy pha và thùng nhiên liệu khác nhau, chia tách thành các luồng nên sẽ giảm tải hơn một chút. Tuy nhiên, lại có một số vấn đề như sau:

  • Các luồng sẽ không giống nhau. Ví dụ trà sữa dâu thường được gọi nhiều hơn trà sữa socola, nên đúng ra cái máy, hay tài nguyên server (cpu, ram,…) nên được phân bố nhiều hơn cho trà sữa socola
  • Nếu một máy trà sữa bị lỗi, ví dụ hãy tưởng tượng về code, dù lúc này đã chia nhỏ nhiều function ra, nhưng nếu hỏng cái gì thì thường ta phải khởi động lại cả server chung, đồng nghĩa các function khác đang chạy tốt cũng phải khởi động lại.

Lúc này, ta sẽ thử nói về một số từ khoá trong triển khai cấu trúc để xử lí vấn đề trên

image 1 - quochung.cyou PTIT

Lại tưởng tượng về cửa hàng bán trà sữa, hãy coi cả cửa hàng như một cái máy chủ, một cái máy tính để có thể chạy các code (các máy pha trà sữa). Chúng ta có thể mua một mặt bằng để đặt cửa hàng (mua đứt). Tương tự, ta có thể mua các server về nhà chúng ta, về công ty để tự triển khai, tự lắp đặt.

image 8 - quochung.cyou PTIT

Triển khai 1: Tự cài đặt server

Một server thì nhìn chung có cấu trúc tương tự như một máy tính, laptop bình thường. Tuy nhiên được thiết kế để chịu tải tốt hơn, chạy 24/7, chịu đựng các điều kiện như nhiệt độ, thời tiết, bụi, … tốt hơn, và một số đặc tính khác nữa.

image 2 - quochung.cyou PTIT

Việc tự mua một server về triển khai thường được các công ty lớn sử dụng, tuy nhiên lúc này ta sẽ phải lo các vấn đề như nhiệt độ, cần bật máy làm mát, điều hoà, quạt,… để server mát mẻ 24/7. Hay phải lo các vấn đề như điện để tải server, tránh các trường hợp mất điện thì server cũng hỏng, ….

Nó giống với việc bạn mua mặt bằng để làm cửa hàng trà sữa, sau khi mua xong thì không thể mở rộng đất ra nếu muốn mở rộng cửa hàng được, phải tự lo các vấn đề như vệ sinh mặt bằng, điện nước, …. Điều này không phù hợp nếu ta chỉ cần một dịch vụ nhỏ, hoặc muốn mở rộng lớn ra hơn sau này.

Tối ưu: Đưa các dịch vụ lên các nền tảng riêng

Triển khai 2: infrastructure as a service (IaaS) – Cơ sở hạ tầng như một dịch vụ

image 3 - quochung.cyou PTIT

Như tên của nó, cơ sở hạ tầng như một dịch vụ. Tức là ta sẽ thuê luôn một mặt bằng cho cửa hàng trà sữa, một cái máy chủ về để sử dụng. Các đơn vị cho thuê thường là các công ty lớn như nước ngoài có Amazon, Microsoft, Google. Trong nước thì có Viettel Cloud, … Các nhà cho thuê, đơn vị này sẽ bảo đảm vấn đề điện duy trì server ổn định, băng thông mạng cho server tốc độ cao, ….

Ngoài ra nếu server các bạn bị tấn công, ddos, các đơn vị này cũng sẽ có thể chuyển bạn sang một server, cụm network mạng khác để tránh, …

Nhìn chung việc cho thuê hay mua dịch vụ này đã giảm tải bớt rất nhiều sự đau đầu cho bạn. Lúc này bạn như có một cái máy tính ảo có thể truy cập từ xa, và đặt các code của mình lên đây để chạy.

Tối ưu: Các dịch vụ tự co giãn theo nhu cầu, tiết kiệm chi phí

Triển khai 3: Container as a Service (CaaS) – Một khối container như dịch vụ

image 7 - quochung.cyou PTIT

Để giải thích việc này thì khá trừu tượng, nhưng lúc này bạn sẽ được cung cấp dịch vụ như các container. Các container này thì gọn nhẹ hơn, tốn ít tài nguyên, tiền bạc để thuê, và có thể co giãn được (tức là có thể tự tăng ram, cpu khi nhu cầu nâng cao)

Bạn tưởng tượng thì nếu bạn chia nhỏ các phần của ứng dụng của mình đủ nhỏ, để chúng có thể triển khai độc lập trên 1 container nhỏ, lúc này các hệ thống container có thể tạo ra rất nhiều bản container của phần ứng dụng đó của bạn, tự nâng cấu hình khi nhu cầu sử dụng tăng cao, hoặc tự triển khai một container khác chạy thay thế nếu container hiện tại bị sập, …

image 4 - quochung.cyou PTIT

Triển khai 4: Function as a Service (FaaS) – Một hàm, chức năng như một dịch vụ

Với triết lý lập trình hàm (Function do only one thing), khi làm việc với kiến trúc Functions as a service, chỉ nên để function làm một việc duy nhất

Điểm mạnh nhất của FaaS là nó độc lập các function, nếu function chỉ thực thi một nhiệm vụ duy nhất. FaaS sẽ làm rất tốt công việc của nó được giao. Tuy nhiên, nếu gọi các function lồng nhau, rất dễ xảy ra vấn đề về performance.

Tức là lúc này bạn có thể chia các máy bán trà sữa này làm một tác vụ duy nhất như 1 hàm, trong đó có cả thùng nhiên liệu là database nhỏ của riêng loại trà sữa đó thôi. Lúc này các máy bán trà sữa này được đặt tại nhiều điểm khác nhau, (bạn chỉ đang thuê cái máy – một function như một dịch vụ). Do kết nối internet hạ tầng đủ nhanh, chúng vẫn có thể chấp nhận được với cửa hàng trà sữa của bạn. Và nếu 1 máy hỏng thì các máy khác vẫn không làm sao cả.

Các FaaS cũng có thể có các tính năng kế thừa như CaaS, tức là tự tạo một điểm khác nếu quá tải, tự tạo ra nhiều điểm vẫn là cùng 1 máy bán trà sữa và chia nhỏ luồng hơn nữa để xử lý tốt hơn, hay tự khởi động một điểm khác khi chỗ hiện tại bị sập, …

image 5 - quochung.cyou PTIT

Một số từ khoá

Auto-scaling: Các máy chủ nhỏ này sẽ tự động co giãn ssd, ram, cpu, …. để phù hợp với nhu cầu, ví dụ máy pha trà sữa này nhiều khách order thì tự tăng tài nguyên lên, còn nếu ít khách thì tự giảm xuống

Event-driven: Các máy pha trà sữa nếu không ai gọi thì tự tắt đi, và chỉ bật lên (cold-start), sẽ bật lên (đủ nhanh tuỳ theo độ nặng của code bạn) khi có khách gọi (một event – sự kiện)

pay-per-execution: Một số chỗ sẽ thu phí bạn thuê theo số lần gọi dịch vụ chứ không phải thời gian thuê. ví dụ máy bán trà sữa dâu khá ế, 1 tháng có đúng 4 khách gọi 4 thời điểm khác nhau, bạn không cần bật máy cả tháng, mà chỉ tính tiền thuê 4 lần đó thôi

pay-as-use: Một số chỗ thu phí theo thời gian, tuy nhiên nhỏ lẻ hơn. Kiểu theo tiếng, theo ngày, … khi đó nếu bạn chỉ cần dùng 1 thời gian ngắn rồi tắt đi thì sẽ tiết kiệm chi phí hơn nhiều

Tóm lại về serverless

image 6 - quochung.cyou PTIT

Hi vọng qua ví dụ trà sữa trên bạn sẽ có một cái nhìn tổng quan hoặc cơ bản về serverless.

Serverless – Không có server, không có nghĩa là sẽ không có cái server máy chủ nào chạy code cả. Mà cơ bản bạn sẽ không cần sở hữu một cái máy chủ, tự bảo quản và vận hành nó, mà có thể thuê các đơn vị khác đảm bảo dịch vụ và ổn định hơn để thực thi các tác vụ của mình.

Như lúc này cửa hàng trà sữa có thể đưa 4 máy pha trà sữa, 4 đoạn code độc lập này lên 4 điểm khác nhau. Lúc này ví dụ 1 điểm code bị bug, có vấn đề thì các nơi khác vẫn hoạt động bình thường.

Ngoài ra các điểm này có thể tự co giãn – (tham khảo thêm ở phần từ khoá bên trên) để phục vụ tốt hơn nếu có nhu cầu nhiều hơn ở một luồng nào đó.

image 9 - quochung.cyou PTIT

Ưu điểm của serverless

  • Tiết kiệm chi phí, không cần thuê/mua cả mặt bằng – cả 1 server để chứa mấy cái máy pha, mà chỉ cần thuê cái máy pha là được
  • Giảm bớt nỗi lo quản lý mặt bằng – server như điện, tiền mạng, ….

Nhược điểm của serverless hay FaaS

  • Test và debug khó hơn, do nó chỉ bật khi mình cần, hơn nữa bạn không nhìn được rõ chúng đang chạy như thế nào như code trên máy mình, vì lúc này toàn bộ code đang ở một điểm khác.
  • Vấn đề bảo mật, lúc này bạn đang để database và code ở một đơn vị khác cho thuê. Nhìn chung thì chắc chắn việc bảo mật không thể bằng tự mình quản lí được.
  • Không phù hợp cho các tác vụ chạy liên tục 24/7. Do điểm tốt của serverless là tiết kiệm tiền, tự tắt đi khi mà không cần sử dụng. Nhưng nếu bạn cần dùng liên tục thì nên thuê CaaS hoặc IaaS
  • Giảm hiệu năng. Do các máy pha chỉ bật khi ta cần đến, nên sẽ tốn thêm thời gian bật lên. Bạn cần chia nhỏ function nhất có thể để chúng độc lập, và bật đủ nhanh để không ảnh hưởng người dùng

Trigger một form input để giả lập như người dùng gõ bằng JavaScript thuần

Khởi tạo các biến const cho Event

const EVENT_OPTIONS = {bubbles: true, cancelable: false, composed: true};
const EVENTS = {
    BLUR: new Event("blur", EVENT_OPTIONS),
    CHANGE: new Event("change", EVENT_OPTIONS),
    INPUT: new Event("input", EVENT_OPTIONS),
};

Thay đổi giá trị của form input mình cần sửa đổi và cập nhật value cho nó

    findInputBox = document.querySelector(".table-sm > thead > tr:nth-child(2) > td:nth-child(2) > input");
    findInputBox.focus();
    findInputBox.value = name; //name là giá trị cần set
    const tracker = findInputBox._valueTracker;
    tracker && tracker.setValue(name);
    findInputBox.dispatchEvent(EVENTS.INPUT);
    findInputBox.dispatchEvent(EVENTS.BLUR);