Các dịch vụ tự động mở rộng quy mô và tự vệ ở Golang
Các Raygun dịch vụ được tạo thành từ nhiều bộ phận và mỗi chuyên ngành lại nhận một nhiệm vụ cụ thể. Một trong những quy trình này được viết bằng Golang và sẽ chịu trách nhiệm giải quyết các báo cáo sự cố iOS, tra cứu các tệp dSYM liên quan và xử lý chúng.
Trên thực tế hoạt động của dsym-worke rất đơn giản, nó nhận công việc thông qua một người tiêu dùng duy nhất được gắn vào Redis queue. Tiếp theo nó sẽ sử dụng 1 Redis queue để thực hiện công việc ghi nhận queue và repeats.
Tuy nhiên, sẽ có một vài điều xảy ra với equire on-call maintenance:
Increased load: đôi khi vào cuối tuần khi mà mọi người sử dụng thiết bị iOS nhiều hơ thì số lượng báo cáo sự cố iOS đến sẽ rất nhiều và lúc này công việc có thể sẽ không được xử lý cùng một thời điểm. Nếu việc đó xảy ra, nhiều quy trình dsym-worker cần được bắt đầu theo cách thủ công để xử lý tải. Mỗi quy trình được bắt đầu sẽ gắn một người tiêu dùng vào hàng đợi công việc. Thư viện hàng đợi Golang Redis sẽ phân phối các công việc cho từng người tiêu dùng để có thể thực hiện nhiều công việc cùng một lúc.
Unresponsiveness: Trong trường hợp này, quá trình sử dụng vẫn sẽ đang chạy nhưng lại không thực hiện bất cứ công việc nào. Đây là một trạng thái rất xấu vì quá trình giám sát cho thấy rằng hệ điều hành vẫn dạng chạy và chỉ khi hàng đợt đạt đến ngưỡng lúc này cảnh báo mới được đưa ra. Nếu điều này xảy ra thì quá trình cần được huỷ theo cách thủ công và bắt đầu một quy trình mới.
Termination: Với trường hợp thứ 3 này, toàn bộ quá trình sẽ bị treo và tắt hoàn toàn. Điều này lại chưa bao giờ xảy ra với dsym-worker, nhưng nếu mã được cập nhật thì chúng ta cũng không thể loại trừ khả năng này sẽ xảy ra. Khi toàn bộ quá trình bị treo màn hình sẽ cảnh báo rằng quá trình này đã chết và nó cần được khởi động lại theo cách thủ công.
Về tổng thể, chúng ta cần tự động mở rộng quy mô để xử lý lượng tải thay đổi/tăng lên. Đồng thời cũng cần phát hiện và thay thế các quy trình không được phản hồi cùng với các quy trình đã chết.
Tự động chia tỷ lệ
Quy trình chính bắt đầu bằng cách quay một quy trình thường xuyên xác định số quy trình sẽ chạy để xử lý tải. Sau đó, quy trình chính này bắt đầu hoặc dừng các quy trình của worker để khớp với số này. Việc tính toán số lượng công nhân mong muốn rất đơn giản. Nó xem xét cả độ dài hiện tại của hàng đợi, cũng như tốc độ mà số lượng hàng đợi đang thay đổi. Hàng đợi càng dài hoặc các công việc đang được xếp hàng càng nhanh, thì càng có nhiều công nhân cần được tạo ra.
func watch() {
procs := make(map[int]*Worker)
for {
// Check the health of each worker
checkProcesses(&procs)
queueLength, rate := queueStats()
// Calculate desired worker count, then start/stop workers to match
desiredWorkerCount := calculateDesiredWorkerCount(queueLength, rate, len(procs))
if len(procs) != desiredWorkerCount {
manageWorkers(&procs, desiredWorkerCount)
}
time.Sleep(30000 * time.Millisecond)
}
}
Độ dài của bản đồ quy trình này được sử dụng để xác định khóa tiếp theo sẽ sử dụng khi thêm nhân viên và cũng là khóa cần xóa khi xóa nhân viên.
func manageWorkers(procs *map[int]*Worker, desiredWorkerCount int) {
currentCount := len(*procs)
if currentCount < desiredWorkerCount {
// Add workers:
for currentCount < desiredWorkerCount { StartWorker(procs, currentCount) currentCount++ } } else if currentCount > desiredWorkerCount {
// Remove workers:
for currentCount > desiredWorkerCount {
StopWorker(procs, currentCount - 1)
currentCount--
}
}
}
Golang cung cấp một gói hệ điều hành/thực thi để quản lý quy trình cấp cao. Quy trình chính sử dụng gói này để tạo ra các quy trình công nhân mới. Quy trình chính và quy trình công nhân được triển khai vào cùng một thư mục, vì vậy “./dsym-worker” có thể được sử dụng để bắt đầu.
func StartWorker(procs *map[int]*Worker, index int) {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
// Handle error
}
cmd := exec.Command(dir + "/dsym-worker")
// Some other stuff gets done here and stored in the Worker object
// such as retrieving the standard in and out pipes as explained later
cmd.Start()
worker := NewWorker(cmd, index)
(*procs)[index] = worker
}
Việc xác định số lượng công nhân mong muốn cho khối lượng công việc và sau đó bắt đầu / dừng công nhân để đáp ứng số đó thực sự là tất cả những gì cần thiết để tự động điều chỉnh tỷ lệ.
Giao tiếp giữa các quá trình trong Golang
Cách tiếp cận đơn giản để phát hiện một nhân viên không phản hồi được thực hiện bằng cách mỗi nhân viên báo cáo thời gian họ hoàn thành công việc lần cuối. Nếu quy trình tổng thể nhận thấy rằng một công nhân đã không thực hiện một công việc quá lâu, thì hãy coi nó không phản hồi và thay thế nó bằng một công nhân mới.
Để thực hiện điều này, các quy trình công nhân cần giao tiếp theo một cách nào đó với quy trình chủ để chuyển tiếp thời gian hiện tại bất cứ khi nào nó hoàn thành công việc. Có nhiều cách để đạt được điều này:
- Đọc và ghi vào một tệp
- Thiết lập hệ thống hàng đợi cục bộ như Redis hoặc RabbitMQ
- Sử dụng gói Golang rpc
- Truyền dữ liệu gobbed thông qua kết nối mạng cục bộ
- Sử dụng bộ nhớ được chia sẻ
- Thiết lập các đường ống được đặt tên
Sau khi bắt đầu một quy trình mới thông qua lệnh execute.Command như đã mô tả trước đó, có thể lấy đường dẫn tiêu chuẩn của quy trình thông qua:
stdoutPipe, err := cmd.StdoutPipe()
Khi có đường dẫn ra tiêu chuẩn, chúng tôi có thể chạy một quy trình. Trong quy trình, tôi đã sử dụng máy quét để đọc từ đường ống như đã thấy ở đây:
scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
line := scanner.Text()
// Process the line here
}
Mã sau lệnh gọi scanner.Text () sẽ được thực thi mỗi khi một dòng văn bản được ghi vào đường dẫn ra tiêu chuẩn từ quy trình công nhân.
Tất cả các quy trình tạo nên dsym-worker đều sử dụng Raygun để báo cáo bất kỳ lỗi nào xảy ra. Điều này đã làm cho việc theo dõi và khắc phục sự cố trở nên dễ dàng hơn.