blob: 5c837f6c1d8120a2e0c5d4a8ae2ed8fc99d89e50 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ceresdb;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ceresdb.common.Display;
import com.ceresdb.common.Endpoint;
import com.ceresdb.common.Lifecycle;
import com.ceresdb.common.signal.SignalHandlersLoader;
import com.ceresdb.common.util.MetricExecutor;
import com.ceresdb.common.util.MetricsUtil;
import com.ceresdb.models.Err;
import com.ceresdb.models.QueryOk;
import com.ceresdb.models.QueryRequest;
import com.ceresdb.models.Result;
import com.ceresdb.models.Rows;
import com.ceresdb.models.WriteOk;
import com.ceresdb.options.CeresDBxOptions;
import com.ceresdb.options.ManagementOptions;
import com.ceresdb.options.QueryOptions;
import com.ceresdb.options.RouterOptions;
import com.ceresdb.options.WriteOptions;
import com.ceresdb.rpc.Context;
import com.ceresdb.rpc.Observer;
import com.ceresdb.rpc.RpcClient;
import com.ceresdb.rpc.RpcFactoryProvider;
import com.ceresdb.rpc.RpcOptions;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
/**
* CeresDB client.
*
* @author jiachun.fjc
*/
public class CeresDBxClient implements Write, Query, Lifecycle<CeresDBxOptions>, Display {
private static final Logger LOG = LoggerFactory.getLogger(CeresDBxClient.class);
private static final Map<Integer, CeresDBxClient> INSTANCES = new ConcurrentHashMap<>();
private static final AtomicInteger ID = new AtomicInteger(0);
private static final String ID_KEY = "client.id";
private static final String VERSION_KEY = "client.version";
private static final String VERSION = loadVersion();
private final int id;
private final AtomicBoolean started = new AtomicBoolean(false);
private CeresDBxOptions opts;
private RouterClient routerClient;
private WriteClient writeClient;
private QueryClient queryClient;
// CeresDBxClient is only intended to manage the instance and does not
// intend to broker any of its behavior
private Management management;
// Note: We do not close it to free resources, as we view it as shared
private Executor asyncWritePool;
private Executor asyncReadPool;
static {
// load all signal handlers
SignalHandlersLoader.load();
// register all rpc service
RpcServiceRegister.registerStorageService();
// start scheduled metric reporter
MetricsUtil.startScheduledReporter(Utils.autoReportPeriodMin(), TimeUnit.MINUTES);
Runtime.getRuntime().addShutdownHook(new Thread(MetricsUtil::stopScheduledReporterAndDestroy));
}
public CeresDBxClient() {
this.id = ID.incrementAndGet();
}
@Override
public boolean init(final CeresDBxOptions opts) {
if (!this.started.compareAndSet(false, true)) {
throw new IllegalStateException("CeresDBx client has started");
}
this.opts = CeresDBxOptions.check(opts).copy();
final RpcClient rpcClient = initRpcClient(this.opts);
this.routerClient = initRouteClient(this.opts, rpcClient);
this.asyncWritePool = withMetricPool(this.opts.getAsyncWritePool(), "async_write_pool.time");
this.asyncReadPool = withMetricPool(this.opts.getAsyncReadPool(), "async_read_pool.time");
this.writeClient = initWriteClient(this.opts, this.routerClient, this.asyncWritePool);
this.queryClient = initQueryClient(this.opts, this.routerClient, this.asyncReadPool);
this.management = initManagementClient(this.opts, this.routerClient);
INSTANCES.put(this.id, this);
Utils.scheduleDisplaySelf(this, new LogPrinter(LOG));
return true;
}
@Override
public void shutdownGracefully() {
if (!this.started.compareAndSet(true, false)) {
return;
}
if (this.writeClient != null) {
this.writeClient.shutdownGracefully();
}
if (this.queryClient != null) {
this.queryClient.shutdownGracefully();
}
if (this.routerClient != null) {
this.routerClient.shutdownGracefully();
}
if (this.management != null) {
this.management.shutdownGracefully();
}
INSTANCES.remove(this.id);
}
@Override
public void ensureInitialized() {
if (this.started.get() && INSTANCES.containsKey(this.id)) {
return;
}
throw new IllegalStateException(String.format("CeresDBxClient(%d) is not started", this.id));
}
@Override
public CompletableFuture<Result<WriteOk, Err>> write(final Collection<Rows> data, final Context ctx) {
ensureInitialized();
return this.writeClient.write(data, attachCtx(ctx));
}
@Override
public StreamWriteBuf<Rows, WriteOk> streamWrite(final String metric, final Context ctx) {
ensureInitialized();
return this.writeClient.streamWrite(metric, attachCtx(ctx));
}
@Override
public CompletableFuture<Result<QueryOk, Err>> query(final QueryRequest req, final Context ctx) {
ensureInitialized();
return this.queryClient.query(req, attachCtx(ctx));
}
@Override
public void streamQuery(final QueryRequest req, final Context ctx, final Observer<QueryOk> observer) {
ensureInitialized();
this.queryClient.streamQuery(req, attachCtx(ctx), observer);
}
public boolean hasManagement() {
return this.management != null;
}
public Management management() {
return this.management;
}
private Executor withMetricPool(final Executor pool, final String name) {
return pool == null ? null : new MetricExecutor(pool, name);
}
private Context attachCtx(final Context ctx) {
final Context c = ctx == null ? Context.newDefault() : ctx;
return c.with(ID_KEY, id()).with(VERSION_KEY, version());
}
public int id() {
return this.id;
}
public String version() {
return VERSION;
}
public Executor asyncWritePool() {
return this.asyncWritePool;
}
public Executor asyncReadPool() {
return this.asyncReadPool;
}
public RouterClient routerClient() {
return this.routerClient;
}
public WriteClient writeClient() {
return this.writeClient;
}
public QueryClient queryClient() {
return this.queryClient;
}
@Override
public void display(final Printer out) {
out.println("--- CeresDBxClient ---") //
.print("id=") //
.println(this.id) //
.print("version=") //
.println(version()) //
.print("clusterAddress=") //
.println(this.opts.getClusterAddress()) //
.print("tenant=") //
.println(this.opts.getTenant().getTenant()) //
.print("userAsyncWritePool=") //
.println(this.opts.getAsyncWritePool()) //
.print("userAsyncReadPool=") //
.println(this.opts.getAsyncReadPool());
if (this.routerClient != null) {
out.println("");
this.routerClient.display(out);
}
if (this.writeClient != null) {
out.println("");
this.writeClient.display(out);
}
if (this.queryClient != null) {
out.println("");
this.queryClient.display(out);
}
if (this.management != null) {
out.println("");
this.management.display(out);
}
out.println("");
}
@Override
public String toString() {
return "CeresDBxClient{" + //
"id=" + id + //
"version=" + version() + //
", started=" + started + //
", opts=" + opts + //
", writeClient=" + writeClient + //
", asyncWritePool=" + asyncWritePool + //
", asyncReadPool=" + asyncReadPool + //
'}';
}
public static List<CeresDBxClient> instances() {
return new ArrayList<>(INSTANCES.values());
}
private static RpcClient initRpcClient(final CeresDBxOptions opts) {
final RpcOptions rpcOpts = opts.getRpcOptions();
rpcOpts.setTenant(opts.getTenant());
final RpcClient rpcClient = RpcFactoryProvider.getRpcFactory().createRpcClient();
if (!rpcClient.init(rpcOpts)) {
throw new IllegalStateException("Fail to start RPC client");
}
rpcClient.registerConnectionObserver(new RpcConnectionObserver());
return rpcClient;
}
private static RouterClient initRouteClient(final CeresDBxOptions opts, final RpcClient rpcClient) {
final RouterOptions routerOpts = opts.getRouterOptions();
routerOpts.setRpcClient(rpcClient);
final RouterClient routerClient = new RouterClient();
if (!routerClient.init(routerOpts)) {
throw new IllegalStateException("Fail to start router client");
}
return routerClient;
}
private static WriteClient initWriteClient(final CeresDBxOptions opts, //
final RouterClient routerClient, //
final Executor asyncPool) {
final WriteOptions writeOpts = opts.getWriteOptions();
writeOpts.setRoutedClient(routerClient);
writeOpts.setAsyncPool(asyncPool);
final WriteClient writeClient = new WriteClient();
if (!writeClient.init(writeOpts)) {
throw new IllegalStateException("Fail to start write client");
}
return writeClient;
}
private static QueryClient initQueryClient(final CeresDBxOptions opts, //
final RouterClient routerClient, //
final Executor asyncPool) {
final QueryOptions queryOpts = opts.getQueryOptions();
queryOpts.setRouterClient(routerClient);
queryOpts.setAsyncPool(asyncPool);
final QueryClient queryClient = new QueryClient();
if (!queryClient.init(queryOpts)) {
throw new IllegalStateException("Fail to start query client");
}
return queryClient;
}
private static Management initManagementClient(final CeresDBxOptions opts, final RouterClient routerClient) {
final ManagementOptions mOpts = opts.getManagementOptions();
if (mOpts == null) {
return null;
}
if (!CeresDBxManagementProvider.hasManagement()) {
return null;
}
final Management management = CeresDBxManagementProvider.createManagement();
mOpts.setRouterClient(routerClient);
if (!management.init(mOpts)) {
return null;
}
return management;
}
private static String loadVersion() {
try {
return Utils //
.loadProperties(CeresDBxClient.class.getClassLoader(), "client_version.properties") //
.getProperty(VERSION_KEY, "Unknown version");
} catch (final Exception ignored) {
return "Unknown version(err)";
}
}
static final class RpcConnectionObserver implements RpcClient.ConnectionObserver {
static final Counter CONN_COUNTER = MetricsUtil.counter("connection_counter");
static final Meter CONN_FAILURES = MetricsUtil.meter("connection_failures");
@Override
public void onReady(final Endpoint ep) {
CONN_COUNTER.inc();
MetricsUtil.counter("connection_counter", ep).inc();
}
@Override
public void onFailure(final Endpoint ep) {
CONN_COUNTER.dec();
CONN_FAILURES.mark();
MetricsUtil.counter("connection_counter", ep).dec();
MetricsUtil.meter("connection_failures", ep).mark();
}
@Override
public void onShutdown(final Endpoint ep) {
CONN_COUNTER.dec();
MetricsUtil.counter("connection_counter", ep).dec();
}
}
/**
* A printer use logger, the {@link #print(Object)} writes data to
* an inner buffer, the {@link #println(Object)} actually writes
* data to the logger, so we must call {@link #println(Object)}
* on the last writing.
*/
static final class LogPrinter implements Display.Printer {
private static final int MAX_BUF_SIZE = 1024 << 3;
private final Logger logger;
private StringBuilder buf = new StringBuilder();
LogPrinter(Logger logger) {
this.logger = logger;
}
@Override
public synchronized Printer print(final Object x) {
this.buf.append(x);
return this;
}
@Override
public synchronized Printer println(final Object x) {
this.buf.append(x);
this.logger.info(this.buf.toString());
truncateBuf();
this.buf.setLength(0);
return this;
}
private void truncateBuf() {
if (this.buf.capacity() < MAX_BUF_SIZE) {
this.buf.setLength(0); // reuse
} else {
this.buf = new StringBuilder();
}
}
}
}