Redis
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> Redis

Tính khả dụng cao với Redis Sentinels:Kết nối với Redis Master / Slave Sets

Kết nối với một máy chủ Redis độc lập, đủ đơn giản:chỉ cần trỏ tới máy chủ, cổng và cung cấp mật khẩu xác thực, nếu có. Hầu hết các ứng dụng Redis thậm chí còn cung cấp hỗ trợ cho một số loại đặc tả kết nối URI.

Tuy nhiên, để đạt được Tính khả dụng cao (HA), bạn cần triển khai (các) cấu hình chính và phụ. Trong bài đăng này, chúng tôi sẽ hướng dẫn bạn cách kết nối với máy chủ Redis trong cấu hình HA thông qua một điểm cuối duy nhất.

Tính khả dụng cao trong Redis

Tính khả dụng cao trong Redis đạt được thông qua sao chép master-slave. Một máy chủ Redis chính có thể có nhiều máy chủ Redis làm nô lệ, tốt hơn là được triển khai trên các nút khác nhau trên nhiều trung tâm dữ liệu. Khi cái chính không khả dụng, một trong các nô lệ có thể được thăng cấp để trở thành cái chính mới và tiếp tục cung cấp dữ liệu mà ít hoặc không bị gián đoạn.

Với sự đơn giản của Redis, có nhiều công cụ có tính khả dụng cao có thể giám sát và quản lý cấu hình bản sao chính-tớ. Tuy nhiên, giải pháp HA phổ biến nhất đi kèm với Redis là Redis Sentinels. Redis Sentinels chạy dưới dạng một tập hợp các quy trình riêng biệt, kết hợp giám sát các bộ chủ-tớ của Redis và cung cấp chuyển đổi dự phòng và cấu hình lại tự động.

Kết nối qua Redis Sentinels

Redis Sentinels cũng đóng vai trò là nhà cung cấp cấu hình cho các bộ master-slave. Tức là, một ứng dụng khách Redis có thể kết nối với Redis Sentinels để tìm ra trạng thái tổng thể và tình trạng chung hiện tại của tập bản sao chủ / nô lệ. Tài liệu của Redis cung cấp chi tiết về cách khách hàng nên tương tác với Sentinels. Tuy nhiên, cơ chế kết nối với Redis này có một số nhược điểm:

  • Cần hỗ trợ từ khách hàng :Kết nối với Redis Sentinels cần một ứng dụng khách "biết" của Sentinel. Hầu hết các khách hàng phổ biến của Redis hiện đã bắt đầu hỗ trợ Redis Sentinels nhưng một số vẫn chưa. Ví dụ:node_redis (Node.js), phpredis (PHP) và scala-redis (Scala) là một số ứng dụng đề xuất mà vẫn chưa hỗ trợ Redis Sentinel.
  • Độ phức tạp :Việc định cấu hình và kết nối với Redis Sentinels không phải lúc nào cũng đơn giản, đặc biệt khi việc triển khai ở khắp các trung tâm dữ liệu hoặc vùng khả dụng. Ví dụ:các Sentinel nhớ địa chỉ IP (không phải tên DNS) của tất cả các máy chủ dữ liệu và các vệ tinh mà họ từng gặp và có thể bị định cấu hình sai khi các nút di chuyển động trong các trung tâm dữ liệu. Redis Sentinels cũng chia sẻ thông tin IP với các Sentinels khác. Thật không may, chúng chuyển xung quanh IP cục bộ, điều này có thể gây ra vấn đề nếu khách hàng ở trong một trung tâm dữ liệu riêng biệt. Những vấn đề này có thể làm tăng thêm sự phức tạp đáng kể cho cả Hoạt động và Phát triển.
  • Bảo mật :Bản thân máy chủ Redis cung cấp xác thực nguyên thủy thông qua mật khẩu máy chủ, bản thân các Sentinel không có tính năng này. Vì vậy, Redis Sentinel được mở trên Internet sẽ hiển thị toàn bộ thông tin cấu hình của tất cả các bản chính mà nó được cấu hình để quản lý. Vì vậy, Redis Sentinels phải luôn được triển khai sau tường lửa được định cấu hình chính xác. Việc cài đặt đúng cấu hình tường lửa, đặc biệt là đối với cấu hình đa vùng có thể rất khó.

Một điểm cuối

Một điểm cuối kết nối mạng duy nhất cho tập hợp chủ-tớ có thể được cung cấp theo nhiều cách. Nó có thể được thực hiện thông qua các IP ảo hoặc ánh xạ lại tên DNS hoặc bằng cách sử dụng máy chủ proxy (ví dụ:HAProxy) phía trước máy chủ Redis. Bất cứ khi nào lỗi của chủ hiện tại được phát hiện (bởi Sentinel), tên IP hoặc DNS sẽ không được chuyển cho nô lệ đã được Redis Sentinels thăng cấp trở thành chủ mới. Lưu ý rằng điều này mất thời gian và kết nối mạng với điểm cuối sẽ cần được thiết lập lại. Các Redis Sentinel chỉ nhận ra một chủ nhân là chủ nhân bị thất bại sau một khoảng thời gian (mặc định là 30 giây) và sau đó bỏ phiếu để thăng cấp cho một nô lệ. Khi thăng cấp nô lệ, địa chỉ IP / mục nhập DNS / proxy cần phải thay đổi để trỏ đến trang chủ mới.

Kết nối với bộ Master-Slave

Điều quan trọng cần cân nhắc khi kết nối với các tập hợp bản sao chính-phụ bằng cách sử dụng một điểm cuối duy nhất là người ta phải cung cấp thử nghiệm lại các lỗi kết nối để giải quyết mọi lỗi kết nối trong quá trình chuyển đổi dự phòng tự động của tập hợp bản sao.

Chúng tôi sẽ hiển thị điều này với các ví dụ trong Java, Ruby và Node.js. Trong mỗi ví dụ, chúng ta có thể viết và đọc từ một cụm HA Redis trong khi chuyển đổi dự phòng xảy ra ở chế độ nền. Trong thế giới thực, số lần thử lại sẽ bị giới hạn trong thời lượng hoặc số lượng cụ thể .

Kết nối với Java

Jedis là ứng dụng khách Java được đề xuất cho Redis.

Ví dụ về điểm cuối duy nhất

public class JedisTestSingleEndpoint {
...
    public static final String HOSTNAME = "SG-cluster0-single-endpoint.example.com";
    public static final String PASSWORD = "foobared";
...
    private void runTest() throws InterruptedException {
        boolean writeNext = true;
        Jedis jedis = null;
        while (true) {
            try {
                jedis = new Jedis(HOSTNAME);
                jedis.auth(PASSWORD);
                Socket socket = jedis.getClient().getSocket();
                printer("Connected to " + socket.getRemoteSocketAddress());
                while (true) {
                    if (writeNext) {
                        printer("Writing...");
                        jedis.set("java-key-999", "java-value-999");
                        writeNext = false;
                    } else {
                        printer("Reading...");
                        jedis.get("java-key-999");
                        writeNext = true;
                    }
                    Thread.sleep(2 * 1000);
                }
            } catch (JedisException e) {
                printer("Connection error of some sort!");
                printer(e.getMessage());
                Thread.sleep(2 * 1000);
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
...
}

Đầu ra của mã kiểm tra này trong quá trình chuyển đổi dự phòng trông giống như sau:

Wed Sep 28 10:57:28 IST 2016: Initializing...
Wed Sep 28 10:57:31 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379 << Connected to node 1
Wed Sep 28 10:57:31 IST 2016: Writing...
Wed Sep 28 10:57:33 IST 2016: Reading...
..
Wed Sep 28 10:57:50 IST 2016: Reading...
Wed Sep 28 10:57:52 IST 2016: Writing...
Wed Sep 28 10:57:53 IST 2016: Connection error of some sort! << Master went down!
Wed Sep 28 10:57:53 IST 2016: Unexpected end of stream.
Wed Sep 28 10:57:58 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379
Wed Sep 28 10:57:58 IST 2016: Writing...
Wed Sep 28 10:57:58 IST 2016: Connection error of some sort!
Wed Sep 28 10:57:58 IST 2016: java.net.SocketTimeoutException: Read timed out  << Old master is unreachable
Wed Sep 28 10:58:02 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379
Wed Sep 28 10:58:02 IST 2016: Writing...
Wed Sep 28 10:58:03 IST 2016: Connection error of some sort!
...
Wed Sep 28 10:59:10 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.214.164.243:6379  << New master ready. Connected to node 2
Wed Sep 28 10:59:10 IST 2016: Writing...
Wed Sep 28 10:59:12 IST 2016: Reading...

Đây là một chương trình thử nghiệm đơn giản. Trong cuộc sống thực, số lần thử lại sẽ được cố định theo thời lượng hoặc số lượng.

Ví dụ về Redis Sentinel

Jedis cũng hỗ trợ Redis Sentinels. Vì vậy, đây là mã thực hiện tương tự như ví dụ trên nhưng bằng cách kết nối với Sentinels.

public class JedisTestSentinelEndpoint {
    private static final String MASTER_NAME = "mymaster";
    public static final String PASSWORD = "foobared";
    private static final Set sentinels;
    static {
        sentinels = new HashSet();
        sentinels.add("mymaster-0.servers.example.com:26379");
        sentinels.add("mymaster-1.servers.example.com:26379");
        sentinels.add("mymaster-2.servers.example.com:26379");
    }

    public JedisTestSentinelEndpoint() {
    }

    private void runTest() throws InterruptedException {
        boolean writeNext = true;
        JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels);
        Jedis jedis = null;
        while (true) {
            try {
                printer("Fetching connection from pool");
                jedis = pool.getResource();
                printer("Authenticating...");
                jedis.auth(PASSWORD);
                printer("auth complete...");
                Socket socket = jedis.getClient().getSocket();
                printer("Connected to " + socket.getRemoteSocketAddress());
                while (true) {
                    if (writeNext) {
                        printer("Writing...");
                        jedis.set("java-key-999", "java-value-999");
                        writeNext = false;
                    } else {
                        printer("Reading...");
                        jedis.get("java-key-999");
                        writeNext = true;
                    }
                    Thread.sleep(2 * 1000);
                }
            } catch (JedisException e) {
                printer("Connection error of some sort!");
                printer(e.getMessage());
                Thread.sleep(2 * 1000);
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
...
}

Hãy xem hoạt động của chương trình trên trong quá trình chuyển đổi dự phòng được quản lý Sentinel:

Wed Sep 28 14:43:42 IST 2016: Initializing...
Sep 28, 2016 2:43:42 PM redis.clients.jedis.JedisSentinelPool initSentinels
INFO: Trying to find master from available Sentinels...
Sep 28, 2016 2:43:42 PM redis.clients.jedis.JedisSentinelPool initSentinels
INFO: Redis master running at 54.71.60.125:6379, starting Sentinel listeners...
Sep 28, 2016 2:43:43 PM redis.clients.jedis.JedisSentinelPool initPool
INFO: Created JedisPool to master at 54.71.60.125:6379
Wed Sep 28 14:43:43 IST 2016: Fetching connection from pool
Wed Sep 28 14:43:43 IST 2016: Authenticating...
Wed Sep 28 14:43:43 IST 2016: auth complete...
Wed Sep 28 14:43:43 IST 2016: Connected to /54.71.60.125:6379
Wed Sep 28 14:43:43 IST 2016: Writing...
Wed Sep 28 14:43:45 IST 2016: Reading...
Wed Sep 28 14:43:48 IST 2016: Writing...
Wed Sep 28 14:43:50 IST 2016: Reading...
Sep 28, 2016 2:43:51 PM redis.clients.jedis.JedisSentinelPool initPool
INFO: Created JedisPool to master at 54.214.164.243:6379
Wed Sep 28 14:43:52 IST 2016: Writing...
Wed Sep 28 14:43:55 IST 2016: Reading...
Wed Sep 28 14:43:57 IST 2016: Writing...
Wed Sep 28 14:43:59 IST 2016: Reading...
Wed Sep 28 14:44:02 IST 2016: Writing...
Wed Sep 28 14:44:02 IST 2016: Connection error of some sort!
Wed Sep 28 14:44:02 IST 2016: Unexpected end of stream.
Wed Sep 28 14:44:04 IST 2016: Fetching connection from pool
Wed Sep 28 14:44:04 IST 2016: Authenticating...
Wed Sep 28 14:44:04 IST 2016: auth complete...
Wed Sep 28 14:44:04 IST 2016: Connected to /54.214.164.243:6379
Wed Sep 28 14:44:04 IST 2016: Writing...
Wed Sep 28 14:44:07 IST 2016: Reading...
...

Rõ ràng từ nhật ký, ứng dụng khách hỗ trợ Sentinels có thể khôi phục từ sự kiện chuyển đổi dự phòng khá nhanh.

Kết nối với Ruby

Redis-rb là ứng dụng Ruby được đề xuất cho Redis.

Ví dụ về điểm cuối duy nhất

require 'redis'

HOST = "SG-cluster0-single-endpoint.example.com"
AUTH = "foobared"
...

def connect_and_write
  while true do
    begin
      logmsg "Attempting to establish connection"
      redis = Redis.new(:host => HOST, :password => AUTH)
      redis.ping
      sock = redis.client.connection.instance_variable_get(:@sock)
      logmsg "Connected to #{sock.remote_address.ip_address}, DNS: #{sock.remote_address.getnameinfo}"
      while true do
        if $writeNext
          logmsg "Writing..."
          redis.set("ruby-key-1000", "ruby-value-1000")
          $writeNext = false
        else
          logmsg "Reading..."
          redis.get("ruby-key-1000")
          $writeNext = true
        end
        sleep(2)
      end
    rescue Redis::BaseError => e
      logmsg "Connection error of some sort!"
      logmsg e.message
      sleep(2)
    end
  end
end

...
logmsg "Initiaing..."
connect_and_write

Đây là kết quả mẫu trong quá trình chuyển đổi dự phòng:

"2016-09-28 11:36:42 +0530: Initiaing..."
"2016-09-28 11:36:42 +0530: Attempting to establish connection"
"2016-09-28 11:36:44 +0530: Connected to 54.71.60.125, DNS: [\"ec2-54-71-60-125.us-west-2.compute.amazonaws.com\", \"6379\"] " << Connected to node 1
"2016-09-28 11:36:44 +0530: Writing..."
"2016-09-28 11:36:47 +0530: Reading..."
...
"2016-09-28 11:37:08 +0530: Writing..."
"2016-09-28 11:37:09 +0530: Connection error of some sort!"  << Master went down!
...
"2016-09-28 11:38:13 +0530: Attempting to establish connection"
"2016-09-28 11:38:15 +0530: Connected to 54.214.164.243, DNS: [\"ec2-54-214-164-243.us-west-2.compute.amazonaws.com\", \"6379\"] " << Connected to node 2
"2016-09-28 11:38:15 +0530: Writing..."
"2016-09-28 11:38:17 +0530: Reading..."

Một lần nữa, mã thực phải chứa một số lần thử lại hạn chế.

Ví dụ về Redis Sentinel

Redis-rb cũng hỗ trợ Sentinels.

AUTH = 'foobared'

SENTINELS = [
  {:host => "mymaster0.servers.example.com", :port => 26379},
  {:host => "mymaster0.servers.example.com", :port => 26379},
  {:host => "mymaster0.servers.example.com", :port => 26379}
]
MASTER_NAME = "mymaster0"

$writeNext = true
def connect_and_write
  while true do
    begin
      logmsg "Attempting to establish connection"
      redis = Redis.new(:url=> "redis://#{MASTER_NAME}", :sentinels => SENTINELS, :password => AUTH)
      redis.ping
      sock = redis.client.connection.instance_variable_get(:@sock)
      logmsg "Connected to #{sock.remote_address.ip_address}, DNS: #{sock.remote_address.getnameinfo} "
      while true do
        if $writeNext
          logmsg "Writing..."
          redis.set("ruby-key-1000", "ruby-val-1000")
          $writeNext = false
        else
          logmsg "Reading..."
          redis.get("ruby-key-1000")
          $writeNext = true
        end
        sleep(2)
      end
    rescue Redis::BaseError => e
      logmsg "Connection error of some sort!"
      logmsg e.message
      sleep(2)
    end
  end
end

Redis-rb quản lý quá trình chuyển đổi dự phòng Sentinel mà không có bất kỳ sự gián đoạn nào.

"2016-09-28 15:10:56 +0530: Initiaing..."
"2016-09-28 15:10:56 +0530: Attempting to establish connection"
"2016-09-28 15:10:58 +0530: Connected to 54.214.164.243, DNS: [\"ec2-54-214-164-243.us-west-2.compute.amazonaws.com\", \"6379\"] "
"2016-09-28 15:10:58 +0530: Writing..."
"2016-09-28 15:11:00 +0530: Reading..."
"2016-09-28 15:11:03 +0530: Writing..."
"2016-09-28 15:11:05 +0530: Reading..."
"2016-09-28 15:11:07 +0530: Writing..."
...
<<failover>>
...
"2016-09-28 15:11:10 +0530: Reading..."
"2016-09-28 15:11:12 +0530: Writing..."
"2016-09-28 15:11:14 +0530: Reading..."
"2016-09-28 15:11:17 +0530: Writing..."
...
# No disconnections noticed at all by the application

Kết nối với Node.js

Node_redis là ứng dụng khách Node.js được đề xuất cho Redis.

Ví dụ về điểm cuối duy nhất

...
var redis = require("redis");
var hostname = "SG-cluster0-single-endpoint.example.com";
var auth = "foobared";
var client = null;
...

function readAndWrite() {
  if (!client || !client.connected) {
    client = redis.createClient({
      'port': 6379,
      'host': hostname,
      'password': auth,
      'retry_strategy': function(options) {
        printer("Connection failed with error: " + options.error);
        if (options.total_retry_time > 1000 * 60 * 60) {
          return new Error('Retry time exhausted');
        }
        return new Error('retry strategy: failure');
      }});
    client.on("connect", function () {
      printer("Connected to " + client.address + "/" + client.stream.remoteAddress + ":" + client.stream.remotePort);
    });
    client.on('error', function (err) {
      printer("Error event: " + err);
      client.quit();
    });
  }

  if (writeNext) {
    printer("Writing...");
    client.set("node-key-1001", "node-value-1001", function(err, res) {
      if (err) {
        printer("Error on set: " + err);
        client.quit();
      }
      setTimeout (readAndWrite, 2000)
    });

    writeNext = false;
  } else {
    printer("Reading...");
    client.get("node-key-1001", function(err, res) {
      if (err) {
        client.quit();
        printer("Error on get: " + err);
      }
      setTimeout (readAndWrite, 2000)
    });
    writeNext = true;
  }
}
...
setTimeout(readAndWrite, 2000);
...

Đây là cách chuyển đổi dự phòng sẽ trông như thế nào:

2016-09-28T13:29:46+05:30: Writing...
2016-09-28T13:29:47+05:30: Connected to SG-meh0-6-master.devservers.mongodirector.com:6379/54.214.164.243:6379 << Connected to node 1
2016-09-28T13:29:50+05:30: Reading...
...
2016-09-28T13:30:02+05:30: Writing...
2016-09-28T13:30:04+05:30: Reading...
2016-09-28T13:30:06+05:30: Connection failed with error: null << Master went down
...
2016-09-28T13:30:50+05:30: Connected to SG-meh0-6-master.devservers.mongodirector.com:6379/54.71.60.125:6379 << Connected to node 2
2016-09-28T13:30:52+05:30: Writing...
2016-09-28T13:30:55+05:30: Reading...

Bạn cũng có thể thử nghiệm với tùy chọn ‘retry_strategy’ trong quá trình tạo kết nối để điều chỉnh logic thử lại để đáp ứng nhu cầu của bạn. Tài liệu khách hàng có một ví dụ.

Ví dụ về Redis Sentinel

Node_redis hiện không hỗ trợ Sentinels, nhưng ứng dụng khách Redis phổ biến cho Node.js, ioredis hỗ trợ Sentinels. Tham khảo tài liệu của nó về cách kết nối với Sentinels từ Node.js.

Bạn đã sẵn sàng mở rộng quy mô chưa? Chúng tôi cung cấp dịch vụ lưu trữ cho Redis ™ * và các giải pháp được quản lý hoàn toàn trên đám mây do bạn lựa chọn. So sánh chúng tôi với những người khác và xem lý do tại sao chúng tôi giúp bạn tiết kiệm tiền mặt và rắc rối.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Các trường hợp sử dụng cho Điểm và Tính năng xếp hạng của Redis cho Bộ

  2. Redis tìm các băm theo giá trị trường

  3. sự khác biệt trung bình giữa Nest và các viên ngọc không gian tên redis là gì khi chúng tôi sử dụng redis với rails / ruby

  4. Redis, Node.js và Socket.io:Xác thực máy chủ chéo và hiểu node.js

  5. Băm nhất quán như một cách để chia tỷ lệ ghi