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

com.navercorp.nbasearc.gcp.GatewayConnectionPool Maven / Gradle / Ivy

There is a newer version: 1.5.4
Show newest version
/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed 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.navercorp.nbasearc.gcp;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.navercorp.redis.cluster.gateway.AffinityState;
import com.navercorp.redis.cluster.gateway.GatewayAffinity;

import io.netty.util.concurrent.ScheduledFuture;

public class GatewayConnectionPool {
    private static final Logger log = LoggerFactory.getLogger(GatewayConnectionPool.class);
    
    private final SingleThreadEventLoopTrunk eventLoopTrunk;
    private final ConcurrentMap gwMap;
    /**
     * vcConcurrentSet is accessed from multiple thraeds.
     */
    private final Set vcConcurrentSet;
    private final AtomicBoolean closed;

    private volatile SettableFuture closeFuture;

    private final AtomicReference> gatewayList;
    private final Affinity affinity;
    
    private final Random random;
    private final AtomicInteger roundGatewayIndex;

    public GatewayConnectionPool(int threadPoolSize, boolean healthCheckUsed) {
        this.eventLoopTrunk = new SingleThreadEventLoopTrunk(threadPoolSize);
        this.gwMap = new ConcurrentHashMap();
        this.vcConcurrentSet = Sets.newConcurrentHashSet();
        this.closed = new AtomicBoolean();
        this.affinity = new Affinity();
        this.roundGatewayIndex = new AtomicInteger(0);
        this.gatewayList = new AtomicReference>();
        this.random = new Random(System.currentTimeMillis());
        
        if (healthCheckUsed) {
            this.eventLoopTrunk.roundrobinEventLoop().getEventLoopGroup().scheduleAtFixedRate(
                    healthCheckJob, 1, 1,TimeUnit.SECONDS);
        }
    }
    
    private final Runnable healthCheckJob = new Runnable() {
        @Override
        public void run() {
            List gwList = gatewayList.get();
            for (Gateway gw : gwList) {
                gw.healthCheck();
            }
        }
    };

    public ListenableFuture close() {
        // Check whether this instance is already closed
        if (closed.getAndSet(true)) {
            return closeFuture;
        }

        // Close VirtualConnections
        SettableFuture vcFuture = closeVirtualConnections();

        // Close Gateways
        SettableFuture gwFuture = closeGateways(vcFuture);

        // Close EventLoops && Set closeFuture
        closeFuture = closeEventLoops(gwFuture);

        return closeFuture;
    }

    private SettableFuture closeVirtualConnections() {
        final SettableFuture future = SettableFuture.create();

        if (vcConcurrentSet.isEmpty()) {
            future.set(null);
        } else {
            for (VirtualConnection vc : vcConcurrentSet) {
                vc.close().addListener(new Runnable() {
                    @Override
                    public void run() {
                        if (vcConcurrentSet.isEmpty()) {
                            future.set(null);
                        }
                    }
                }, MoreExecutors.directExecutor());
            }
        }

        return future;
    }

    private SettableFuture closeGateways(SettableFuture previousCloseJob) {
        final SettableFuture future = SettableFuture.create();
        previousCloseJob.addListener(new Runnable() {
            @Override
            public void run() {
                final AtomicInteger closeGwCnt = new AtomicInteger(gwMap.size());

                if (gwMap.isEmpty()) {
                    future.set(null);
                    return;
                }
                
                for (Integer gwid : gwMap.keySet()) {
                    delGw(gwid).addListener(new Runnable() {
                        @Override
                        public void run() {
                            if (closeGwCnt.decrementAndGet() == 0) {
                                future.set(null);
                            }
                        }
                    }, MoreExecutors.directExecutor());
                }
            }
        }, MoreExecutors.directExecutor());

        return future;
    }

    private SettableFuture closeEventLoops(SettableFuture previousCloseJob) {
        final SettableFuture future = SettableFuture.create();
        previousCloseJob.addListener(new Runnable() {
            @Override
            public void run() {
                eventLoopTrunk.close().addListener(new Runnable() {
                    @Override
                    public void run() {
                        future.set(null);
                    }
                }, MoreExecutors.directExecutor());
            }
        }, MoreExecutors.directExecutor());
        return future;
    }

    public ListenableFuture addGw(final Integer gwid, final String ip, final int port, final int concnt,
            final int reconnectInterval) {
        Gateway gw = new Gateway(gwid, ip, port, concnt);
        if (gwMap.putIfAbsent(gwid, gw) != null) {
            throw new IllegalArgumentException("Gateway " + gwid + " is already exist.");
        }

        updateGatewayList();

        return gw.init(eventLoopTrunk, reconnectInterval);
    }

    public ListenableFuture delGw(final Integer gwid) {
        Gateway gw = gwMap.remove(gwid);
        if (gw != null) {
            return gw.unuse(vcConcurrentSet);
        }

        updateGatewayList();

        SettableFuture sf = SettableFuture.create();
        sf.set(null);
        return sf;
    }
    
    private void updateGatewayList() {
        List newGatewayList = new ArrayList();
        for (Gateway gw : gwMap.values()) {
            newGatewayList.add(gw);
        }
        gatewayList.set(newGatewayList);
    }

    public VirtualConnection newVc(int qSize) {
        VirtualConnection vc = new VirtualConnection(this, qSize);
        vcConcurrentSet.add(vc);
        return vc;
    }

    void delVc(VirtualConnection vc) {
        vcConcurrentSet.remove(vc);
    }
    
    public void updateAffinity(ConcurrentHashMap affinityInfos) {
        affinity.reload(affinityInfos);
    }

    private PhysicalConnection _bestCon(int hash, AffinityState affinityState) {
        PhysicalConnection bestPc = null;
        long bestCost = Long.MAX_VALUE;

        // Affinity first
        List affinityGateways = affinity.affinityGateway(hash, affinityState);
        if (affinityGateways != null && affinityGateways.size() != 0) {
            Gateway gw = gwMap.get(affinityGateways.get(random.nextInt(affinityGateways.size())));
            if (gw != null && gw.getActive() > 0) {
                PhysicalConnection pc = gw.bestPc();
                if (pc != null) {
                    return pc;
                }
            }
        }
        
        // Random selection
        List candidates = gatewayList.get();
        int index = roundGatewayIndex.incrementAndGet() % candidates.size();
        if (index < 0) {
            index = index + candidates.size();
        }
        for (int i = 0; i < Math.min(3, candidates.size()); i++) {
            index = (index + 1) % candidates.size();
            
            Gateway gw = candidates.get(index);
            if (gw.getActive() > 0) {
                PhysicalConnection pc = gw.bestPc();
                if (pc == null) {
                    continue;
                }
                
                final long cost = pc.busyCost();
                if (bestCost > cost) {
                    bestCost = cost;
                    bestPc = pc;
                } else if (bestPc != null && bestCost == cost && bestPc.getReferenceCount() > pc.getReferenceCount()) {
                    bestCost = cost;
                    bestPc = pc;
                }
            }
        }

        return bestPc;
    }
    
    PhysicalConnection bestCon(int hash, AffinityState affinityState) {
        while (true) {
            PhysicalConnection pc = _bestCon(hash, affinityState);
            if (pc == null) {
                // No available connection
                return null;
            }
            
            synchronized (pc) {
                if (pc.getState() == PhysicalConnection.State.CONNECTED) {
                    pc.increaseReferenceCount();
                    return pc;
                }
            }
        }
    }

    ScheduledFuture scheduleAtFixedRate(Runnable runnable, long delay, long period, TimeUnit tu) {
        return eventLoopTrunk.roundrobinEventLoop().getEventLoopGroup().scheduleAtFixedRate(runnable, delay, period,
                tu);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy