Computer >> Máy Tính >  >> Lập trình >> Cơ sở dữ liệu

PUBSUB đáng tin cậy và hoạt động danh sách chặn trong triển khai HA Redis

PUBSUB đáng tin cậy và hoạt động danh sách chặn trong triển khai HA Redis

Giải pháp Redis dự phòng tiêu chuẩn là chạy bản sao master / slave với Sentinel quản lý chuyển đổi dự phòng. Điều này dự kiến ​​sẽ được theo sau với a) Hỗ trợ máy khách và sử dụng Sentinel để phát hiện ra cái chính hiện tại hoặc b) Một proxy TCP ở phía trước Redis pod do Sentinel quản lý để trỏ tới cái chính. Cái trước là cách Redis Sentinel được thiết kế và cái sau là xu hướng đang phát triển - và cách ObjectRocket Redis được định cấu hình.

Ảnh hưởng của chuyển đổi dự phòng đối với các lệnh trong Redis

Đối với phần lớn các hoạt động của Redis, điều này hoạt động như mong đợi. Khi chuyển đổi dự phòng, mã máy khách sẽ phát hiện ra kết nối không thành công và a) tra cứu bản chính hiện tại và kết nối lại hoặc b) thử kết nối lại dựa trên các tình huống được đề cập ở trên. Kịch bản dựa trên proxy cũng giống như khởi động lại máy chủ Redis, do đó, điều này cũng có thể áp dụng cho trường hợp bạn có một phiên bản Redis duy nhất có thể được khởi động lại.

Tuy nhiên, một số ít lệnh Redis là lệnh chặn dài hạn hoặc không nguyên tử. Các lệnh đăng ký PUBSUB và đăng ký psubscribe không phải là nguyên tử trong đó lệnh sẽ ghi nhận yêu cầu của bất kỳ thông báo nào được gửi đến (các) kênh nhất định cũng được gửi đến nó, nhưng dữ liệu thực tế có thể đến muộn hơn nhiều - tức là. sau khi nó được xuất bản. Các lệnh BL * là lệnh dài hạn và hai lệnh pha giống như lệnh đăng ký, chúng sẽ chặn và không trả lại cho đến khi có một mục trong danh sách cần xóa.

Điểm chung chính giữa các lệnh này là chúng được phát hành và một hình thức đăng ký được thực hiện trên máy chủ, nhưng dữ liệu có thể đến sau. Giữa lệnh được đưa ra và máy chủ có dữ liệu để trả lời với chuyển đổi dự phòng và khởi động lại có thể xảy ra. Trong trường hợp khởi động lại hoặc chuyển đổi dự phòng, "đăng ký" không còn trên máy chủ. Điều này đưa chúng ta đến câu hỏi về điều gì sẽ xảy ra liên quan đến các lệnh này trong các trường hợp này.

Trước khi đi sâu vào những chi tiết đó, có một khía cạnh khác sẽ được đưa ra vì nó sẽ phát huy tác dụng trong quá trình thử nghiệm và có những ảnh hưởng đáng kể đến kết quả và cách viết mã cho các tình huống thất bại. Trên thực tế, có hai loại chuyển đổi dự phòng. Có một sự chuyển đổi dự phòng mà hầu hết chúng ta coi là - sự mất mát của bản gốc. Trong kiểu chuyển đổi dự phòng này, tổng thể không đáp ứng. Tôi gọi đây là "chuyển đổi dự phòng được kích hoạt". Lớp chuyển đổi dự phòng thứ hai mà tôi gọi là “chuyển đổi dự phòng bắt đầu”.

Quá trình chuyển đổi dự phòng bắt đầu diễn ra khi quản trị viên gửi lệnh chuyển đổi dự phòng sentinel tới Sentinel, sau đó cấu hình lại nhóm, thăng cấp một cái chính sau đó hạ cấp cái chính ban đầu. Nhìn bề ngoài, hai kịch bản này có vẻ giống nhau. Trong trường hợp của phần lớn các lệnh Redis, chúng có thể được xử lý giống nhau. Tuy nhiên, trong trường hợp các lệnh khối dài của chúng tôi, chúng phải được hiểu theo ngữ cảnh riêng của chúng.

Sự khác biệt về chức năng chính giữa hai lớp chuyển đổi dự phòng này là chuỗi các sự kiện. Khi một chuyển đổi dự phòng được kích hoạt xảy ra, đó là do thiết bị chính không phản hồi. Trong trường hợp đó, không có dữ liệu nào được thêm vào hoặc sửa đổi, và không có thông báo nào được xuất bản trên trang cái. Trong quá trình chuyển đổi dự phòng được khởi tạo, có một cửa sổ rất ngắn trong đó có hai bản gốc.

Các kỹ thuật và kịch bản thảo luận ở đây sẽ được mô tả chung và được trình bày bằng cách sử dụng thư viện Python Redis “redis-py”, nhưng áp dụng cho bất kỳ thư viện máy khách nào. Với nền tảng trong tay, bây giờ chúng ta hãy xem điều này ảnh hưởng như thế nào đến việc sử dụng các lệnh khối dài. Trước tiên, chúng ta hãy xem xét PUBSUB.

PUBSUB

Redis PUBSUB bao gồm ba lệnh:PUBLISH, SUBSCRIBE và PSUBSCRIBE. Lệnh đầu tiên được sử dụng để gửi một thông báo trong khi hai lệnh sau được sử dụng để đăng ký và nhận các thông báo được xuất bản thông qua lệnh PUBLISH. Trước tiên, chúng ta hãy kiểm tra những gì xảy ra trong lệnh PUBLISH.

Khi bạn thực hiện PUBLISH, nó sẽ xuất bản tin nhắn lên kênh đã cho ngay lập tức và trả về số lượng khách hàng đã nhận được nó. Theo cách nào thì một kịch bản thất bại làm cho điều này trở nên phù hợp? Hãy xem xét trường hợp chuyển đổi dự phòng. Máy khách sẽ phải kết nối lại khi kết nối TCP biến mất. Nếu nói chuyện với một proxy, nó sẽ kết nối lại trực tiếp và nhận được một máy chủ khác. Nếu thực hiện khám phá Sentinel thì sẽ lâu hơn một chút và vẫn kết nối được với một máy chủ khác. Vấn đề nằm ở đâu là thời gian. Nếu nhà xuất bản kết nối với máy chủ mới trước khi bất kỳ người đăng ký nào thực hiện, thông báo sẽ biến mất - bị mất. Nhưng điều đó có thể xảy ra không? Để hiểu điều đó, chúng tôi xem xét các lệnh SUBSCRIBE.

Khi mã của bạn đưa ra lệnh đăng ký, máy chủ sẽ đăng ký trong cấu trúc dữ liệu trong bộ nhớ mà kết nối cụ thể đó sẽ nhận được thông báo trên (các) kênh đã đăng ký. Thông tin này không được truyền cho nô lệ. Do đó, khi xảy ra lỗi và máy khách kết nối lại thì phải đưa ra lại lệnh đăng ký. Nếu không, máy chủ mới không biết rằng kết nối cụ thể cần nhận một số thông báo nhất định. Giờ đây, chúng tôi có tiềm năng rõ ràng để người đăng ký kết nối lại với trang chính mới sau nhà xuất bản. Các điều kiện để mất tin nhắn hiện đã tồn tại. Vậy làm thế nào để giảm thiểu hoặc ngăn chặn điều này? Có một số tùy chọn.

Đầu tiên, nhà xuất bản có thể tận dụng thực tế là PUBLISH trả lại số lượng khách hàng đã nhận được nó. Nếu không có khách hàng nào nhận được thì nhà xuất bản có thể thử lại. Đối với các hệ thống mà đây là số lượng thuê bao tĩnh hoặc ở đó “ít nhất một thuê bao” là đủ, điều này sẽ ngăn chặn việc mất tin nhắn. Đối với các hệ thống không đáp ứng tiêu chí này, có một tùy chọn khác, nếu kém mạnh mẽ hơn.

Tùy chọn thứ hai là điều khiển các cửa sổ kết nối lại. Quy tắc chung ở đây là thời gian trễ của nhà xuất bản ít nhất gấp ba lần thời gian trễ của người đăng ký. Điều này sẽ cung cấp ít nhất ba cơ hội để người đăng ký kết nối lại trước khi xuất bản tin nhắn. Vì vậy, miễn là người đăng ký trực tuyến trước, các tin nhắn sẽ không đi vào ether. Tuy nhiên, trong khi mạnh mẽ hơn là không kiểm soát nó vẫn có khả năng mất tin nhắn.

Tùy chọn thứ ba mà tôi sẽ trình bày để giảm thiểu tình trạng cuộc đua này là xây dựng trong một cơ chế khóa. Đây chắc chắn là con đường phức tạp nhất vì nó yêu cầu xây dựng hoặc sử dụng một cơ chế ngăn các nhà xuất bản xuất bản cho đến khi các khách hàng kết nối. Quá trình cơ bản là có một số hình thức vị trí được chia sẻ (chẳng hạn như qua Zookeeper, cơ sở dữ liệu, Lãnh sự, v.v.) nơi người đăng ký đăng ký rằng họ sẵn sàng nhận và người đăng ký kiểm tra các thuê bao hợp lệ và sẵn sàng trước khi xuất bản tin nhắn. Để quảng cáo thêm phức tạp, người đăng ký sẽ phải “tự đặt mình” trong cơ chế này khi họ mất kết nối TCP.

Nó cần phải được gọi ra rằng đây là tất cả cho một chuyển đổi dự phòng được kích hoạt. Nó sẽ hoạt động vì cái chính không phản hồi do đó không có thông báo nào đến nó. Trong tình huống chuyển đổi dự phòng đã khởi tạo, bản chính sẽ tiếp tục chấp nhận các thông báo cho đến khi nó không còn là bản chính nữa hoặc bị ngắt kết nối. Trong số các phương pháp trên, không có phương pháp nào sẽ bắt đúng và đầy đủ tình huống này.

Trong trường hợp bắt đầu chuyển đổi dự phòng (có thể xảy ra trong quá trình bảo trì hệ thống chẳng hạn), hiệu quả tốt nhất của bạn là sử dụng cả tùy chọn thứ nhất và thứ hai và chấp nhận rằng có thể có mất thông báo. Mức độ mất tin hoàn toàn phụ thuộc vào lịch xuất bản của bạn. Nếu bạn xuất bản một thông báo trung bình vài phút một lần thì có rất ít cơ hội cho bạn thực sự gặp phải cửa sổ mất mát. Khoảng thời gian xuất bản càng lớn thì rủi ro càng nhỏ. Ngược lại, nếu bạn đang xuất bản hàng trăm hoặc hàng nghìn tin nhắn mỗi giây, bạn chắc chắn sẽ mất một số. Đối với kịch bản proxy được quản lý, nó sẽ phụ thuộc vào tốc độ hoàn thành chuyển đổi dự phòng trong proxy của bạn. Đối với nền tảng ObjectRocket’s Redis, thời lượng này lên đến 1,5 giây.

Do đó, một cách tiếp cận về khoảng thời gian không thành công ở các giới hạn, là yêu cầu người đăng ký cố gắng kết nối lại ở khoảng thời gian 1 / 3d khoảng thời gian xuất bản của bạn. Do đó, nếu bạn xuất bản một tin nhắn mỗi phút một lần, thì hãy mã / định cấu hình người đăng ký của bạn kết nối lại thường xuyên ít nhất 20 giây một lần và nhà xuất bản của bạn cứ sau một hoặc hai phút. Khi khoảng thời gian xuất bản đến gần mốc 3 giây, điều này trở nên khó đạt được hơn vì quá trình kết nối lại chuyển đổi dự phòng (tra cứu, bắt tay TCP, AUTH, SUBSCRIBE, v.v.) có thể dễ dàng tổng cộng vài giây.

BLPOP và bạn bè

Điều này đưa chúng ta đến câu hỏi về cách các lệnh danh sách chặn hoạt động trong các điều kiện này. Tin tức ở đây tốt hơn là xuất bản. Trong trường hợp này, chúng tôi đang sửa đổi cấu trúc dữ liệu được sao chép. Hơn nữa, bởi vì đây là những sửa đổi đối với cấu trúc dữ liệu, chúng tôi không có cùng mức độ rủi ro mất thông báo do thứ tự kết nối lại. Nếu nhà sản xuất kết nối lại trước khi có người đăng ký thì không có thay đổi đối với hành vi mong đợi. Nhà sản xuất sẽ đưa mặt hàng đó vào danh sách (hoặc POP và PUSH) và khi người tiêu dùng kết nối dữ liệu sẽ ở đó. Với một ngoại lệ.

Trong trường hợp bắt đầu chuyển đổi dự phòng, chúng ta phải xem xét vấn đề đa tổng thể ngắn gọn. Trong một vài khoảnh khắc, theo thứ tự mili giây, bản gốc ban đầu trong quá trình chuyển đổi dự phòng được khởi tạo sẽ vẫn chấp nhận các sửa đổi dữ liệu và do một máy chủ đã được thăng cấp để làm chủ các sửa đổi này sẽ không sao chép. Lưu ý rằng cửa sổ này rất nhỏ. Trong thử nghiệm, nó là thứ tự của mili giây một chữ số, nhưng nó ở đó. Đối với việc xuất bản, đây về cơ bản là một điều kiện góc mà tỷ lệ cược của bạn, chẳng hạn như việc ban hành lệnh POP cho bản gốc cũ là rất hiếm mặc dù tăng lên đến các điều kiện có thể tưởng tượng được khi tỷ lệ nhà sản xuất và người tiêu dùng của bạn thực hiện các lệnh sửa đổi danh sách lên đến hàng nghìn mỗi giây.

Ví dụ:trong trường hợp một công nhân đang thực hiện BRPOPLPUSH, kết quả sẽ là mục được di chuyển sẽ “trở lại” vị trí ban đầu của nó sau khi chuyển đổi dự phòng. Trong trường hợp BLPOP, kết quả về cơ bản sẽ giống nhau. Sau khi chuyển đổi dự phòng hoàn tất, mục sẽ được xếp hàng lại. Nếu các mục của bạn là những công việc cần thiết, thì đây sẽ không phải là vấn đề. Bạn nên làm bao nhiêu mã phòng thủ để giải thích điều này trong trường hợp các công việc không phải là kết quả của việc xác định ảnh hưởng của một công việc đang được chạy hoặc mục được xử lý hai lần, bao gồm tần suất bạn thực hiện sửa đổi và khả năng gặp phải điều này tình hình. Cũng cần lưu ý rằng vì điều này chỉ xảy ra trong trường hợp bắt đầu chuyển đổi dự phòng, đây phải là thứ nằm trong tầm kiểm soát vận hành và tôi khuyên rằng nên thực hiện bảo trì bất cứ khi nào có thể vào thời điểm mà hệ thống của bạn ít sử dụng nhất để giảm thiểu hoặc thậm chí loại bỏ khả năng này.

Đối với chuyển đổi dự phòng được kích hoạt, không có mong đợi bị mất dữ liệu. Chuyển đổi dự phòng được kích hoạt xảy ra khi cái chính không phản hồi, do đó không có sửa đổi nào được thực hiện cho cái chính. Tuy nhiên, với mỗi tình huống này, vấn đề xử lý kết nối lại TCP thực tế là điều bắt buộc trong bất kỳ tình huống chuyển đổi dự phòng nào.

Kết nối lại ứng dụng khách

Trong trường hợp chuyển đổi dự phòng, được kích hoạt hoặc bắt đầu, máy khách sẽ phải phát hiện và kết nối lại - sau khi tra cứu Sentinel hoặc một cửa sổ ngắn đến cùng một địa chỉ. Trong trường hợp ObjectRocket Redis sử dụng lớp proxy được quản lý, máy khách sẽ chỉ cần kết nối lại. Quá trình chuyển đổi dự phòng thực tế có thể mất đến hai giây để hoàn thành. Vì vậy, mã khách hàng nên giải thích cho điều này. Lý tưởng nhất là thử lại ngay lập tức để xử lý các lỗi mạng trong đó kết nối bị đứt ở đâu đó dọc theo tuyến đường. Tuy nhiên, điều này phải đi kèm với thuật toán quay lại với các lần thử lại để giải quyết các trường hợp như khởi động lại máy chủ (như xảy ra trong quá trình khởi động lại Redis độc lập hoặc khởi động lại proxy).

Sau khi kết nối lại, bất kỳ người đăng ký nào sẽ cần phải đăng ký lại vì liên kết giữa các kênh yêu cầu và kết nối TCP mới phải được thiết lập. Vì các kênh PUBSUB của Redis được tạo khi người đăng ký hoặc nhà xuất bản cố gắng truy cập chúng, nên không cần "tạo lại" kênh.

Điều này đưa chúng ta đến cách làm điều này. Câu trả lời phụ thuộc nhiều vào thư viện khách đang sử dụng và cách nó xử lý các ngắt kết nối. Trong một kịch bản lý tưởng, kết nối sẽ có thể được định cấu hình để tự động thử lại với lỗi cuối cùng được trả về nếu các ranh giới được đáp ứng. Cho đến nay, tôi đã thử nghiệm một vài thư viện về cách họ xử lý vấn đề này và nó thay đổi đáng kể. Ở đây tôi sẽ thảo luận về một vấn đề khá phổ biến:redis-py.

Tin vui đầu tiên là redis-py dường như sẽ thử lại khi kết nối bị ngắt. Thật không may, nó ngay lập tức thử lại và do đó quá nhanh để khôi phục kết nối một cách đáng tin cậy trong quá trình chuyển đổi dự phòng. Hơn nữa, dường như không có cách nào để định cấu hình điều này. Do đó, mã của bạn phải bắt / phát hiện lần thử kết nối lại không thành công và tự quản lý việc kết nối lại.

Trước tiên, chúng ta hãy kiểm tra một số khối mã đăng ký và xuất bản redis-py tiêu chuẩn:

### Publisher example code
r.publish(channel,message)

### Subscriber example code
p = r.pubsub()
p.subscribe(channel)
for item in p.listen():
   # do stuff with message (item)

Điều này khá đơn giản. Tuy nhiên, khi quá trình thử lại ngay lập tức trong vòng lặp for của người đăng ký không thành công, bạn sẽ nhận được ngoại lệ redis.ConnectionError. Một chút khó khăn là nó xảy ra "bên trong" mục for trong dòng p.listen (). Vì vậy, để nắm bắt nó đúng cách, bạn phải bao bọc toàn bộ câu lệnh for trong một khối thử / ngoại trừ. Điều này tốt nhất là có vấn đề và dẫn đến sự phức tạp không cần thiết của mã.

Một lộ trình thay thế là làm như sau.

### Publisher example code
p = r.pubsub()
p.subscribe(channel)
while True:
    try:
        message = p.get_message()
    except redis.ConnectionError:
        # Do reconnection attempts here such as sleeping and retrying
        p = r.pubsub()
        p.subscribe(channel)
    if message:
        # do something with the message
    time.sleep(0.001)  # be nice to the system :)

Với phương thức này, chúng tôi gọi trực tiếp get_message (), cho phép chúng tôi bắt ngoại lệ tại điểm đó và thiết lập lại kết nối của đối tượng ‘p’. Khoảng thời gian để ngủ, nếu có, tùy thuộc vào yêu cầu mã của bạn. Tất nhiên nếu người đăng ký của bạn mong đợi xử lý một số lượng tin nhắn nhất định và vòng lặp hoạt động tốt hơn, nó vẫn sẽ hoạt động. Đối với nhà xuất bản, mã đơn giản hơn vì nó thường không được chạy trên trình lặp.

### Publisher example code
while True:
    try:
       rcvd = r.publish(channel,message)
       if rcvd >0:
          break
    except redis.ConnectionError:
       # handle reconnect attempts

Với phương pháp này, bạn có quyền kiểm soát việc thử lại, khi nào và tần suất thử. Điều này là rất quan trọng để xử lý minh bạch sự kiện lỗi đúng cách. Lưu ý rằng đây sẽ là điều tương tự cần thiết nếu mã đang sử dụng Sentinel trực tiếp hoặc ngay cả khi máy chủ Redis được đề cập đã khởi động lại. Vì vậy, tất cả mã xuất bản thực sự phải tuân theo cơ chế cơ bản này.

Đối với các lệnh danh sách chặn, chẳng hạn như BLPOP, thứ tự kết nối lại của ứng dụng khách không hoàn toàn quan trọng vì dữ liệu được duy trì. Phương thức thử / ngoại trừ được mô tả ở trên sẽ cần thiết để thiết lập lại kết nối khi việc thực thi lệnh dẫn đến một ngoại lệ “redis.ConnectionError” được ném ra.

Đối với redis-py cụ thể đối với nền tảng ObjectRocket Redis bằng cách sử dụng các kỹ thuật này với cửa sổ thử lại bao gồm 3 giây sẽ đảm bảo rằng không có kỳ vọng mất dữ liệu đối với chuyển đổi dự phòng được kích hoạt và khoảng 1,5 giây có khả năng mất thông báo đối với nhà xuất bản không ngừng trong quá trình chuyển đổi dự phòng bắt đầu chẳng hạn như trong quá trình thay đổi kích thước theo chiều dọc của phiên bản.

Mặc dù các ví dụ mã dành riêng cho redis-py, các kỹ thuật cơ bản nên được sử dụng với bất kỳ mã máy khách nào cần xử lý kết nối lại máy chủ và sử dụng lệnh PUBSUB hoặc các hoạt động danh sách chặn. Với kiến ​​thức này, giờ đây bạn có thể triển khai các kỹ thuật này để sử dụng nhóm Redis có tính khả dụng cao và biết rằng ứng dụng của bạn có thể xử lý chuyển đổi dự phòng mà không đánh thức nhóm hoạt động của bạn vào giữa đêm.