All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.redis.client.impl.RedisSentinelClient Maven / Gradle / Ivy

There is a newer version: 4.5.10
Show newest version
/*
 * Copyright 2019 Red Hat, Inc.
 * 

* All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. *

* The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html *

* The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php *

* You may elect to redistribute this code under either of these licenses. */ package io.vertx.redis.client.impl; import io.vertx.core.*; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.redis.client.*; import io.vertx.redis.client.impl.types.ErrorType; import java.util.List; import java.util.Random; import static io.vertx.redis.client.Command.*; import static io.vertx.redis.client.Request.cmd; public class RedisSentinelClient implements Redis { // We don't need to be secure, we just want so simple // randomization to avoid picking the same slave all the time private static final Random RANDOM = new Random(); private static class Pair { final L left; final R right; Pair(L left, R right) { this.left = left; this.right = right; } } private static final Logger LOG = LoggerFactory.getLogger(RedisSentinelClient.class); private final Context context; private final ConnectionManager connectionManager; private final RedisOptions options; private RedisConnection sentinel; public RedisSentinelClient(Vertx vertx, RedisOptions options) { this.context = vertx.getOrCreateContext(); this.options = options; // validate options // validate options if (options.getMaxPoolSize() < 2) { throw new IllegalStateException("Invalid options: maxPoolSize must be at least 2"); } if (options.getMaxPoolWaiting() < options.getMaxPoolSize()) { throw new IllegalStateException("Invalid options: maxPoolWaiting < maxPoolSize"); } // sentinel (HA) requires 2 connections, one to watch for sentinel events and the connection itself this.connectionManager = new ConnectionManager(vertx, options); this.connectionManager.start(); } @Override public void close() { this.connectionManager.close(); } @Override public Redis connect(Handler> onCreate) { // sentinel (HA) requires 2 connections, one to watch for sentinel events and the connection itself createConnectionInternal(options, options.getRole(), createConnection -> { if (createConnection.failed()) { onCreate.handle(Future.failedFuture(createConnection.cause())); return; } final RedisConnection conn = createConnection.result(); createConnectionInternal(options, RedisRole.SENTINEL, create -> { if (create.failed()) { LOG.error("Redis PUB/SUB wrap failed.", create.cause()); return; } sentinel = create.result(); sentinel .handler(msg -> { if (msg.type() == ResponseType.MULTI) { if ("MESSAGE".equalsIgnoreCase(msg.get(0).toString())) { // we don't care about the payload if (conn != null) { ((RedisConnectionImpl) conn).fail(ErrorType.create("SWITCH-MASTER Received +switch-master message from Redis Sentinel.")); } else { LOG.warn("Received +switch-master message from Redis Sentinel."); } } } }); sentinel.send(cmd(SUBSCRIBE).arg("+switch-master"), send -> { if (send.failed()) { LOG.error("Unable to subscribe to Sentinel PUBSUB", send.cause()); } }); sentinel.exceptionHandler(t -> LOG.error("Unhandled exception in Sentinel PUBSUB", t)); }); // no need to wait just return it onCreate.handle(Future.succeededFuture(conn)); }); return this; } private void createConnectionInternal(RedisOptions options, RedisRole role, Handler> onCreate) { final Handler> createAndConnect = resolve -> { if (resolve.failed()) { onCreate.handle(Future.failedFuture(resolve.cause())); return; } // wrap a new client connectionManager.getConnection(context, resolve.result(), null, onCreate); }; switch (role) { case SENTINEL: resolveClient(this::isSentinelOk, options, createAndConnect); break; case MASTER: resolveClient(this::getMasterFromEndpoint, options, createAndConnect); break; case SLAVE: resolveClient(this::getSlaveFromEndpoint, options, createAndConnect); } } /** * We use the algorithm from http://redis.io/topics/sentinel-clients * to get a sentinel client and then do 'stuff' with it */ private static void resolveClient(final Resolver checkEndpointFn, final RedisOptions options, final Handler> callback) { // Because finding the master is going to be an async list we will terminate // when we find one then use promises... iterate(0, checkEndpointFn, options, iterate -> { if (iterate.failed()) { callback.handle(Future.failedFuture(iterate.cause())); } else { final Pair found = iterate.result(); // This is the endpoint that has responded so stick it on the top of // the list final List endpoints = options.getEndpoints(); String endpoint = endpoints.get(found.left); endpoints.set(found.left, endpoints.get(0)); endpoints.set(0, endpoint); // now return the right address callback.handle(Future.succeededFuture(found.right)); } }); } private static void iterate(final int idx, final Resolver checkEndpointFn, final RedisOptions argument, final Handler>> resultHandler) { // stop condition final List endpoints = argument.getEndpoints(); if (idx >= endpoints.size()) { resultHandler.handle(Future.failedFuture("No more endpoints in chain.")); return; } // attempt to perform operation checkEndpointFn.resolve(endpoints.get(idx), argument, res -> { if (res.succeeded()) { resultHandler.handle(Future.succeededFuture(new Pair<>(idx, res.result()))); } else { // try again with next endpoint iterate(idx + 1, checkEndpointFn, argument, resultHandler); } }); } // begin endpoint check methods private void isSentinelOk(String endpoint, RedisOptions argument, Handler> handler) { connectionManager.getConnection(context, endpoint, null, onCreate -> { if (onCreate.failed()) { handler.handle(Future.failedFuture(onCreate.cause())); return; } final RedisConnection conn = onCreate.result(); // Send a command just to check we have a working node conn.send(cmd(PING), info -> { if (info.failed()) { handler.handle(Future.failedFuture(info.cause())); } else { handler.handle(Future.succeededFuture(endpoint)); } // connection is not needed anymore conn.close(); }); }); } private void getMasterFromEndpoint(String endpoint, RedisOptions options, Handler> handler) { connectionManager.getConnection(context, endpoint, null, onCreate -> { if (onCreate.failed()) { handler.handle(Future.failedFuture(onCreate.cause())); return; } final RedisConnection conn = onCreate.result(); final String masterName = options.getMasterName(); // Send a command just to check we have a working node conn.send(cmd(SENTINEL).arg("GET-MASTER-ADDR-BY-NAME").arg(masterName), getMasterAddrByName -> { if (getMasterAddrByName.failed()) { handler.handle(Future.failedFuture(getMasterAddrByName.cause())); } else { // Test the response final Response response = getMasterAddrByName.result(); handler.handle( Future.succeededFuture("redis://" + response.get(0).toString() + ":" + response.get(1).toInteger())); } // we don't need this connection anymore conn.close(); }); }); } private void getSlaveFromEndpoint(String endpoint, RedisOptions options, Handler> handler) { connectionManager.getConnection(context, endpoint, null, onCreate -> { if (onCreate.failed()) { handler.handle(Future.failedFuture(onCreate.cause())); return; } final RedisConnection conn = onCreate.result(); final String masterName = options.getMasterName(); // Send a command just to check we have a working node conn.send(cmd(SENTINEL).arg("SLAVES").arg(masterName), sentinelSlaves -> { if (sentinelSlaves.failed()) { handler.handle(Future.failedFuture(sentinelSlaves.cause())); } else { final Response response = sentinelSlaves.result(); // Test the response if (response.size() == 0) { handler.handle(Future.failedFuture("No slaves linked to the master: " + masterName)); } else { Response slaveInfoArr = response.get(RANDOM.nextInt(response.size())); if ((slaveInfoArr.size() % 2) > 0) { handler.handle(Future.failedFuture("Corrupted response from the sentinel")); } else { int port = 6379; String ip = null; for (int i = 0; i < slaveInfoArr.size(); i += 2) { if ("port".equals(slaveInfoArr.get(i).toString())) { port = slaveInfoArr.get(i + 1).toInteger(); } if ("ip".equals(slaveInfoArr.get(i).toString())) { ip = slaveInfoArr.get(i + 1).toString(); } } if (ip == null) { handler.handle(Future.failedFuture("No IP found for a SLAVE node!")); } else { handler.handle(Future.succeededFuture("redis://" + ip + ":" + port)); } } } } // connection is not needed anymore conn.close(); }); }); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy