Tổng quan app:

Từ tháng 9/2017, mình join vào team phát triển app di động cho người chơi game Pokemon, ứng dụng có tên tiếng Nhật là 1秒マップforポケモンGO. Thông qua app, người dùng có thể biết được vị trí của những Pokemon mới xuất hiện, thông tin Gym, Battle, ngoài ra còn có 1 diễn đàn mọi người có thể chat với nhau, chia sẻ thông tin. Nay đúng đợt app gỡ khỏi store vì một số lý do, mình sẽ tổng hợp những gì mình học được từ việc xây dựng và phát triển ứng dụng này. Một ứng dụng mà mình rất tâm đắc về thiết kế của nó cũng như các bài toán mà nó giải quyết.

Kiến trúc:

スクリーンショット 2018-04-10 17.33.47

+ Server: triển khai trên nền tảng AWS.

  • Hook server: framework Loopback (nodejs)
  • Api server: Loopback
  • Worker server: nodejs
    + Database: Amazon Aurora
    + App: iOS, android

Các bài toán đã được giải quyết:

1. Lượng Request lớn từ app khi người dùng di chuyển Map

Giải pháp: Truy vấn dữ liệu theo geohash kết hợp với sử dụng CDN. Bạn nào đã từng làm ứng dụng bản đồ (map) thì chắc đã gặp phải bài toán khi người dùng truy vấn liên tục lên server khi di chuyển map. Nếu người dùng lên đến hàng triệu thì server bạn sẽ ko thể chịu tải nổi dù có scale nhiều đến thế nào. Giải pháp cho vấn đề này là dùng geohash, nếu ai chưa biết geohash là gì thì tìm hiểu thêm ở đây nhé: https://en.wikipedia.org/wiki/Geohash.
Geohash là một chuỗi string biểu diễn vị trí địa lý. Bề mặt trái đất được chia ra thành các block nằm sát nhau, mỗi block này tương ứng với 1 geohash. Khi người dùng trong block đó cần truy vấn thông tin (pokemon), thì các pokemon trong block đó sẽ được lấy ra để trả về cho người dùng, đầu vào input lúc truy vấn dữ liệu là geohash chứ ko còn là 1 vị trí cụ thể. Ngoài ra kết hợp với việc sử dụng CDN, dữ liệu về pokemon trong geohash sẽ được cached lại, dù người dùng có di chuyển bao nhiêu lần trên map thì chi phí truy vấn đã được tối ưu, server sẽ ko còn phải xử lý nhiều nữa.

2. Người dùng tăng lên đột ngột tại những giờ cụ thể.

Giải pháp hệ thống mình sử dụng là dùng chức năng auto scaling trong aws. Khi người dùng tăng lên, tài nguyên CPU cần thiết để xử lý cũng tăng lên, khi đó mình cài đặt khi CPU lên ngưỡng >60% thì gọi auto scaling và 1 instance EC2 mới được tạo ra. Đối với một số người đã từng triển khai các dịch vụ dùng AWS thì đây là việc đơn giản không có gì mới mẻ, nhưng đối với mình đây là lần đầu tiên mình tiếp xúc với AWS nên mình thấy các các dịch vụ của AWS thực sự rất tuyệt vời để giải quyết các bài toán nhiều người dùng.

3. Tối ưu hiển thị pokemon trên app: Dùng cache, cache và cache

Với data trong 1 geohash, mỗi lần trước khi hiển thị lên map, chúng đều được kiểm tra xem đã tồn tại image tương ứng trong cache chưa. Điều này sẽ giảm bớt việc sử dụng bộ nhớ của app đồng thời tăng tốc độ hiển thị.

4. Tối ưu truy vấn:

Mặc dù đã dùng geohash kết hợp với cdn để giảm tải cho server, app vẫn có nhiều chức năng khác cần cập nhật csdl thường xuyên như update vị trí user, update thông tin user…Với hơn 2 triệu người dùng mà ko tối ưu truy vấn này thì hệ thống phải mở rộng ra rất nhiều cũng như tốn nhiều chi phí. Giải pháp mình dùng ở đây là dung redis để cache truy vấn, với những người dùng mà ko có sự thay đổi thông tin khi gửi request, hoặc bán kính di chuyển nhỏ không đáng kể, mình sử dụng cache đã được lưu trong redis để trả về cho họ. Còn trong trường hợp thông tin đã được thay đổi, 1 query sẽ thực hiện thay đổi ấy và dữ liệu trong redis sẽ được cập nhật theo.

5. Quản lý bộ nhớ trên app:

Memory leak là vấn đề rất hay gặp đối với lập trình viên non kinh nghiệm. Trong việc phát triển app này, mình luôn chú ý việc quản lý bộ nhớ để sao cho không có xảy ra memory leak ở bất kỳ controller nào. Lúc lập trình mình luôn tuân theo quy tắc sau đây:

  • delegate phải là biến weak
  • Dùng closure thì phải thêm [weak self] khi cần gọi biến trong vc.
  • Luôn kiểm tra hàm deinit() có được gọi không khi close viewController …
6. Giảm tải cho database: Database replica

Lý do hệ thống dùng amazon aurora database bởi vì nó hỗ trợ replication database, giúp phân chia ra thành master database cho chức năng write và slave database cho chức năng read, với độ trễ tính bằng milisecond.

Cần cải thiện:

- Hiện tại hệ thống vẫn còn dùng khá nhiều tài nguyên do hạn chế xử lý của nodejs. Mình đã nghiên cứu chuyển sang dùng Golang để tận dùng sức mạnh xử lý concurrency dùng goroutines nhưng vì service phải dừng vì một số lý do nên mình chưa thể triển khai được.

Kết luận:

Trong thực tế khi triển khai còn nhiều thách thức khác cần phải giải quyết. Với người dùng ngày càng tăng thì đây chỉ là bước thiết kế cơ bản, sau này muốn mở rộng hơn nữa ta phải tính đến việc thiết kế lại cơ sở dữ liệu, thiết kế độc lập các chức năng để dễ dàng scale, và phân ra nhiều zone khác nhau. Chi tiết về thiết kế hệ thống lớn bạn có thể tham khảo bài viết rất hay trên amazon: https://aws.amazon.com/blogs/startups/scaling-on-aws-part-4-one-million-users/ Trong bất cứ hệ thống nào mình khuyên các bạn nên tận dụng tối đa chức năng cache bất cứ khi nào có thể, một cài đặt rất đơn giản nhưng sẽ tiết kiệm cho bạn được rất nhiều chi phí.