Trả lời nhanh: Với ứng dụng web, cách gọn nhất là một dòng cron hệ thống gọi bộ lập lịch của framework mỗi phút, rồi khai báo mọi tác vụ định kỳ trong code. Nhờ vậy lịch chạy nằm trong Git, xem lại được và triển khai cùng mã nguồn. Ba việc phải làm kèm theo, thiếu cái nào cũng sinh sự cố âm thầm: chặn chạy trùng khi lượt trước chưa xong, ghi nhật ký kết quả, và cảnh báo khi một tác vụ im lặng quá lâu.
Tác vụ định kỳ là phần hạ tầng ít được chú ý nhất cho tới ngày nó ngừng chạy mà không ai biết. Bài này nói cách đặt cho đúng chỗ và ba lớp bảo vệ khiến bạn phát hiện sự cố trước khi khách hàng phát hiện.
Hai cách đặt và cách nên chọn
Cách một: mỗi tác vụ một dòng cron hệ thống. Đơn giản nhưng lịch nằm ngoài Git, người mới không biết máy chủ đang chạy những gì, và triển khai lên máy mới phải nhớ chép lại.
Cách hai: một dòng cron gọi bộ lập lịch của framework.
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1Rồi khai báo trong code:
Schedule::command('bao-cao:ngay')->dailyAt('06:00');Schedule::command('don:het-han')->hourly()->withoutOverlapping();Schedule::command('dong-bo:ton-kho')->everyFifteenMinutes();Cách hai gần như luôn tốt hơn: lịch nằm trong Git nên xem lại được và đi cùng mã nguồn, đổi lịch chỉ là một lần triển khai, và máy chủ mới chỉ cần đúng một dòng cron.
Chặn chạy trùng
Đây là lỗi âm thầm nguy hiểm nhất. Tác vụ chạy mười lăm phút một lần mà lượt này mất hai mươi phút thì lượt sau khởi động trong khi lượt trước chưa xong. Hai tiến trình cùng xử lý một tập dữ liệu, và hậu quả thường là bản ghi trùng hoặc số liệu sai.
Phần lớn framework có sẵn tùy chọn chặn trùng. Nếu tự viết bằng cron hệ thống, dùng cơ chế khóa file:
* * * * * flock -n /tmp/dong-bo.lock /usr/bin/php /var/www/app/dong-bo.phpTham số ở đây nghĩa là nếu đang có tiến trình giữ khóa thì bỏ qua lượt này thay vì chờ. Với tác vụ định kỳ, bỏ qua thường đúng hơn là xếp hàng, vì xếp hàng sẽ tạo ra một dãy tiến trình dồn ứ khi hệ thống bận.
Ghi nhật ký và cảnh báo
Tác vụ định kỳ hỏng thường không ai biết, vì không có người dùng nào phàn nàn ngay. Ba lớp:
Ghi nhật ký từng lượt: thời điểm bắt đầu, thời điểm kết thúc, số bản ghi đã xử lý, và lỗi nếu có. Ghi vào bảng riêng trong database thì tiện xem hơn ghi ra file.
Cảnh báo khi thất bại: gửi vào kênh nội bộ của đội.
Cảnh báo khi im lặng: lớp quan trọng nhất và cũng hay bị bỏ nhất. Nếu một tác vụ đáng lẽ chạy mỗi giờ mà đã ba giờ không có bản ghi nào, đó là sự cố. Cảnh báo khi thất bại không bắt được trường hợp này, vì tác vụ không chạy thì cũng không thất bại.
Cách kiểm chứng đơn giản: một tác vụ khác chạy mỗi giờ, kiểm tra bảng nhật ký và báo nếu có tác vụ nào quá hạn.
Ba bẫy hay gặp
Múi giờ. Cron hệ thống chạy theo múi giờ của máy chủ, còn bộ lập lịch của framework có thể theo cấu hình riêng. Đặt cả hai về cùng một múi giờ và ghi rõ trong tài liệu, nếu không báo cáo ngày sẽ chạy lệch giờ so với mong đợi.
Biến môi trường không có. Cron chạy với môi trường rất tối giản, không có các biến mà shell đăng nhập của bạn có. Đây là nguyên nhân kinh điển của lỗi không tìm thấy lệnh, và cách chữa là dùng đường dẫn tuyệt đối.
Tác vụ nặng chạy vào giờ cao điểm. Một tác vụ tổng hợp báo cáo quét cả bảng lớn sẽ làm chậm cả trang nếu chạy lúc đông khách. Hãy xếp chúng vào giờ vắng, và với tác vụ rất nặng thì cân nhắc chạy trên bản sao dữ liệu.
Khi nào nên chuyển sang hàng đợi
Tác vụ định kỳ hợp với việc chạy theo lịch. Khi việc bắt đầu có đặc điểm khác, hãy chuyển sang hàng đợi:
- Việc mất nhiều phút: để tác vụ định kỳ chỉ đẩy công việc vào hàng đợi, còn tiến trình nền xử lý.
- Việc cần thử lại khi thất bại: hàng đợi có sẵn cơ chế đó, còn cron thì không.
- Việc phát sinh theo sự kiện chứ không theo giờ: đó vốn không phải việc của cron.
Mô hình phổ biến và gọn: tác vụ định kỳ chạy mỗi mười lăm phút, tìm những việc cần làm rồi đẩy vào hàng đợi, và tiến trình nền xử lý theo tốc độ của nó. Cách dựng hàng đợi nằm ở bài về hàng đợi.
Hai cách đặt và cách chọn
| Cách đặt | Hợp khi nào | Điểm yếu |
|---|---|---|
| Trình lập lịch của hệ điều hành trên máy chủ | Bạn có máy chủ riêng và việc chạy định kỳ đều đặn | Chỉ chạy trên đúng máy đó, máy tắt là việc không chạy |
| Bộ lập lịch bên trong ứng dụng | Ứng dụng luôn chạy và bạn muốn dùng chung mã với phần còn lại | Nhân bản ứng dụng ra nhiều tiến trình là nhân bản luôn việc định kỳ |
| Dịch vụ gọi từ bên ngoài vào một địa chỉ | Bạn dùng nền tảng không cho chạy nền | Địa chỉ đó phải được bảo vệ, nếu không ai cũng gọi được |
Với ứng dụng đặt trên máy chủ riêng, cách thứ nhất đơn giản và đáng tin nhất. Cách thứ ba chỉ nên chọn khi hai cách kia không dùng được, và bắt buộc phải có khóa bí mật trong lời gọi.
Chặn chạy trùng, phần hay bị bỏ quên nhất
# Van de: viec truoc chua xong thi viec sau da bat dau# Ket qua: gui trung email, tru tien hai lan, ghi trung du lieu # Cach don gian nhat tren mot may chu:* * * * * flock -n /tmp/viec-dinh-ky.lock /duong/dan/toi/lenh.sh # flock -n nghia la: neu dang co ban chay khac thi bo qua lan nay# Khong xep hang, khong cho, vi cho se dan toi don ung # Kiem tra viec co that su chay khong:grep CRON /var/log/syslog | tail -n 20 # Kiem tra thoi diem chay theo mui gio may chu, khong theo mui gio bandatetimedatectl | grep "Time zone"Múi giờ, nhật ký và cảnh báo, ba chỗ hay vấp
- Múi giờ của máy chủ khác múi giờ bạn nghĩ. Việc đặt chạy lúc nửa đêm chạy vào bảy giờ sáng. Luôn kiểm bằng lệnh trên máy chủ chứ đừng giả định.
- Không ghi nhật ký nên không biết việc có chạy hay không. Ghi cả lúc bắt đầu, lúc kết thúc và mã kết thúc. Việc chạy im lặng là việc bạn sẽ phát hiện hỏng sau ba tuần.
- Không có cảnh báo khi việc thất bại. Tối thiểu là gửi một thông báo khi mã kết thúc khác không. Không cần hệ thống phức tạp, cần đúng một dòng trong kịch bản.
Câu hỏi thường gặp
Nên dùng cron hệ thống hay bộ lập lịch của framework?
Bộ lập lịch của framework, với đúng một dòng cron hệ thống gọi nó. Nhờ vậy lịch nằm trong Git, xem lại được, và máy chủ mới chỉ cần một dòng cấu hình duy nhất.
Làm sao biết tác vụ định kỳ đã ngừng chạy?
Ghi nhật ký từng lượt vào một bảng, rồi có một tác vụ kiểm tra bảng đó và cảnh báo khi có tác vụ quá hạn. Cảnh báo khi thất bại không bắt được trường hợp này, vì không chạy thì cũng không có lỗi.
Vì sao lệnh chạy tay được mà trong cron thì lỗi?
Vì cron chạy với môi trường tối giản, không có các biến và đường dẫn mà shell đăng nhập của bạn có. Hãy dùng đường dẫn tuyệt đối tới trình thông dịch và tới file cần chạy.
Tác vụ nặng nên chạy lúc nào?
Vào giờ vắng khách, và nếu rất nặng thì cân nhắc chạy trên bản sao dữ liệu để không ảnh hưởng tới người dùng. Ổ đĩa nhanh giúp rút ngắn thời gian các tác vụ quét bảng lớn.
