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

org.jgroups.protocols.MERGE3 Maven / Gradle / Ivy

package org.jgroups.protocols;


import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.conf.AttributeType;
import org.jgroups.stack.Protocol;
import org.jgroups.util.UUID;
import org.jgroups.util.*;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;


/**
 * Protocol to discover subgroups; e.g., existing due to a network partition (that healed). Example: group
 * {p,q,r,s,t,u,v,w} is split into 3 subgroups {p,q}, {r,s,t,u} and {v,w}. This protocol will eventually send
 * a MERGE event with the views of each subgroup up the stack: {p,r,v}. 

* Works as follows (https://issues.redhat.com/browse/JGRP-1387): every member periodically broadcasts its address (UUID), * logical name, physical address and ViewID information. Other members collect this information and see if the ViewIds * are different (indication of different subpartitions). If they are, the member with the lowest address (first in the * sorted list of collected addresses) sends a MERGE event up the stack, which will be handled by GMS. * The others do nothing.

* The advantage compared to MERGE2 is that there are no merge collisions caused by multiple merges going on. * Also, the INFO traffic is spread out over max_interval, and every member sends its physical address with INFO, so * we don't need to fetch the physical address first. * * @author Bela Ban, Nov 2011 * @since 3.1 */ @MBean(description="Protocol to discover subgroups existing due to a network partition") public class MERGE3 extends Protocol { /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="Minimum time in ms before sending an info message",type=AttributeType.TIME) protected long min_interval=1000; @Property(description="Interval (in milliseconds) when the next info " + "message will be sent. A random value is picked from range [1..max_interval]",type=AttributeType.TIME) protected long max_interval=10000; @Property(description="The max number of merge participants to be involved in a merge. 0 sets this to unlimited.") protected int max_participants_in_merge=100; /* ---------------------------------------------- JMX -------------------------------------------------------- */ @Property(description="Interval (in ms) after which we check for view inconsistencies",type=AttributeType.TIME) protected long check_interval; @ManagedAttribute(description="Number of cached ViewIds") public int getViews() {return views.size();} /* --------------------------------------------- Fields ------------------------------------------------------ */ protected volatile View view; protected TimeScheduler timer; protected final InfoSender info_sender=new InfoSender(); protected Future info_sender_future; protected Future view_consistency_checker; // hashmap to keep track of view-id sent in INFO messages. Keys=senders, values = ViewId sent protected final Map views=new HashMap<>(); protected final ResponseCollector view_rsps=new ResponseCollector<>(); protected boolean transport_supports_multicasting=true; protected String cluster_name; protected final Consumer discovery_rsp_cb=this::sendInfoMessage; protected final Event ASYNC_DISCOVERY_EVENT=new Event(Event.FIND_MBRS_ASYNC, discovery_rsp_cb); @ManagedAttribute(description="Whether or not the current member is the coordinator") protected volatile boolean is_coord; @ManagedAttribute(description="Number of times a MERGE event was sent up the stack") protected int num_merge_events; public int getNumMergeEvents() {return num_merge_events;} @ManagedAttribute(description="Is the view consistency checker task running") public synchronized boolean isViewConsistencyCheckerRunning() { return view_consistency_checker != null && !view_consistency_checker.isDone(); } @ManagedAttribute(description="Is the view consistency checker task running") public boolean isMergeTaskRunning() {return isViewConsistencyCheckerRunning();} @ManagedAttribute(description="Is the info sender task running") public synchronized boolean isInfoSenderRunning() { return info_sender_future != null && !info_sender_future.isDone(); } @ManagedOperation(description="Lists the contents of the cached views") public String dumpViews() { StringBuilder sb=new StringBuilder(); for(Map.Entry> entry: convertViews().entrySet()) sb.append(entry.getKey()).append(": [") .append(Util.printListWithDelimiter(entry.getValue(), ", ", Util.MAX_LIST_PRINT_SIZE)).append("]\n"); return sb.toString(); } @ManagedOperation(description="Clears the views cache") public void clearViews() {synchronized(views) {views.clear();}} @ManagedOperation(description="Send INFO") public void sendInfo() { new InfoSender().run(); } @ManagedOperation(description="Check views for inconsistencies") public void checkInconsistencies() {new ViewConsistencyChecker().run();} public void init() throws Exception { if(min_interval >= max_interval) throw new IllegalArgumentException("min_interval (" + min_interval + ") has to be < max_interval (" + max_interval + ")"); if(check_interval == 0) check_interval=computeCheckInterval(); else { if(check_interval <= max_interval) { log.warn("set check_interval=%d as it is <= max_interval", computeCheckInterval()); check_interval=computeCheckInterval(); } } if(max_interval <= 0) throw new Exception("max_interval must be > 0"); transport_supports_multicasting=getTransport().supportsMulticasting(); } public void start() throws Exception { super.start(); timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer cannot be retrieved"); } public void stop() { super.stop(); is_coord=false; stopViewConsistencyChecker(); stopInfoSender(); } public long getMinInterval() { return min_interval; } public MERGE3 setMinInterval(long i) { if(min_interval < 0 || min_interval >= max_interval) throw new IllegalArgumentException("min_interval (" + min_interval + ") has to be < max_interval (" + max_interval + ")"); min_interval=i; return this; } public long getMaxInterval() { return max_interval; } public MERGE3 setMaxInterval(long val) { if(val <= 0) throw new IllegalArgumentException("max_interval must be > 0"); max_interval=val; check_interval=computeCheckInterval(); return this; } public long getCheckInterval() {return check_interval;} public MERGE3 setCheckInterval(long ci) {this.check_interval=ci; return this;} public int getMaxParticipantsInMerge() {return max_participants_in_merge;} public MERGE3 setMaxParticipantsInMerge(int m) {this.max_participants_in_merge=m; return this;} public boolean isCoord() {return is_coord;} protected long computeCheckInterval() { return (long)(max_interval * 1.6); } protected boolean isMergeRunning() { Object retval=up_prot.up(new Event(Event.IS_MERGE_IN_PROGRESS)); return retval instanceof Boolean && (Boolean)retval; } protected synchronized void startInfoSender() { if(info_sender_future == null || info_sender_future.isDone()) info_sender_future=timer.scheduleWithDynamicInterval(info_sender, getTransport() instanceof TCP); } protected synchronized void stopInfoSender() { if(info_sender_future != null) { info_sender_future.cancel(true); // info_sender.stop(); info_sender_future=null; } } protected synchronized void startViewConsistencyChecker() { if(view_consistency_checker == null || view_consistency_checker.isDone()) view_consistency_checker=timer.scheduleWithDynamicInterval(new ViewConsistencyChecker()); } protected synchronized void stopViewConsistencyChecker() { if(view_consistency_checker != null) { view_consistency_checker.cancel(true); view_consistency_checker=null; } } public Object down(Event evt) { switch(evt.getType()) { case Event.CONNECT: case Event.CONNECT_USE_FLUSH: case Event.CONNECT_WITH_STATE_TRANSFER: case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: cluster_name=evt.getArg(); break; case Event.DISCONNECT: case Event.TMP_VIEW: stopViewConsistencyChecker(); stopInfoSender(); break; case Event.VIEW_CHANGE: stopViewConsistencyChecker(); // should already be stopped stopInfoSender(); // should already be stopped Object ret=down_prot.down(evt); view=evt.getArg(); clearViews(); if(ergonomics && max_participants_in_merge > 0) max_participants_in_merge=Math.max(100, view.size() / 3); startInfoSender(); startViewConsistencyChecker(); Address coord=view.getCoord(); if(Objects.equals(coord, local_addr)) is_coord=true; else { is_coord=false; clearViews(); } return ret; } return down_prot.down(evt); } public Object up(Message msg) { MergeHeader hdr=msg.getHeader(getId()); if(hdr == null) return up_prot.up(msg); return handle(hdr, msg); } public void up(MessageBatch batch) { for(Iterator it=batch.iterator(); it.hasNext();) { Message msg=it.next(); MergeHeader hdr=msg.getHeader(id); if(hdr != null) { it.remove(); handle(hdr, msg); } } if(!batch.isEmpty()) up_prot.up(batch); } public static List detectDifferentViews(Map map) { final List ret=new ArrayList<>(); for(View view: map.values()) { if(view == null) continue; ViewId vid=view.getViewId(); if(!Util.containsViewId(ret, vid)) ret.add(view); } return ret; } public static ByteArray marshal(View view) { try { return Util.streamableToBuffer(view); } catch(Exception e) { return null; } } protected View readView(byte[] buffer, int offset, int length) { try { return buffer != null? Util.streamableFromBuffer(View::new, buffer, offset, length) : null; } catch(Exception ex) { log.error("%s: failed reading View from message: %s", local_addr, ex); return null; } } protected Object handle(MergeHeader hdr, Message msg) { Address sender=msg.getSrc(); switch(hdr.type) { case INFO: addInfo(sender, hdr.view_id, hdr.logical_name, hdr.physical_addr); break; case VIEW_REQ: View viewToSend=view; Message view_rsp=new BytesMessage(sender) .putHeader(getId(), MergeHeader.createViewResponse()).setArray(marshal(viewToSend)); log.trace("%s: sending view rsp: %s", local_addr, viewToSend); down_prot.down(view_rsp); break; case VIEW_RSP: View tmp_view=readView(msg.getArray(), msg.getOffset(), msg.getLength()); log.trace("%s: received view rsp from %s: %s", local_addr, msg.getSrc(), tmp_view); if(tmp_view != null) view_rsps.add(sender, tmp_view); break; default: log.error("Type %s not known", hdr.type); } return null; } protected MergeHeader createInfo() { PhysicalAddress physical_addr=local_addr != null? (PhysicalAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)) : null; return MergeHeader.createInfo(view.getViewId(), NameCache.get(local_addr), physical_addr); } /** Adds received INFO to views hashmap */ protected void addInfo(Address sender, ViewId view_id, String logical_name, PhysicalAddress physical_addr) { if(logical_name != null && sender instanceof UUID) NameCache.add(sender, logical_name); if(physical_addr != null) down(new Event(Event.ADD_PHYSICAL_ADDRESS, new Tuple<>(sender, physical_addr))); synchronized(views) { ViewId existing=views.get(sender); if(existing == null || existing.compareTo(view_id) < 0) views.put(sender, view_id); } } protected Map> convertViews() { Map> retval=new HashMap<>(); synchronized(views) { for(Map.Entry entry : views.entrySet()) { Address key=entry.getKey(); ViewId view_id=entry.getValue(); Set

existing=retval.get(view_id); if(existing == null) retval.put(view_id, existing=new ConcurrentSkipListSet<>()); existing.add(key); } } return retval; } protected boolean differentViewIds() { ViewId first=null; synchronized(views) { for(ViewId view_id : views.values()) { if(first == null) first=view_id; else if(!first.equals(view_id)) return true; } } return false; } protected void sendInfoMessage(PingData data) { if(data == null) return; Address target=data.getAddress(); if(local_addr.equals(target)) return; Address dest=data.getPhysicalAddr(); if(dest == null) { log.warn("%s: physical address for %s not found; dropping INFO message to %s", local_addr, target, target); return; } MergeHeader hdr=createInfo(); Message info=new EmptyMessage(dest).putHeader(getId(), hdr); down_prot.down(info); } protected class InfoSender implements TimeScheduler.Task { public void run() { if(view == null) { log.warn("%s: view is null, cannot send INFO message", local_addr); return; } MergeHeader hdr=createInfo(); if(transport_supports_multicasting) { Message msg=new EmptyMessage().putHeader(getId(), hdr).setFlag(Message.TransientFlag.DONT_LOOPBACK); down_prot.down(msg); } else down_prot.down(ASYNC_DISCOVERY_EVENT); } public long nextInterval() { return Math.max(min_interval, Util.random(max_interval) + max_interval/2); } public String toString() { return MERGE3.class.getSimpleName() + ": " + getClass().getSimpleName(); } } protected class ViewConsistencyChecker implements TimeScheduler.Task { public void run() { try { MergeHeader hdr=createInfo(); addInfo(local_addr, hdr.view_id, hdr.logical_name, hdr.physical_addr); if(!differentViewIds()) { log.trace("%s: found no inconsistent views: %s", local_addr, dumpViews()); return; } _run(); } finally { clearViews(); } } protected void _run() { SortedSet
coords=new TreeSet<>(); Map> converted_views=convertViews(); converted_views.keySet().stream().map(ViewId::getCreator).forEach(coords::add); // add merge participants coords.addAll(converted_views.values().stream().filter(set -> !set.isEmpty()) .map(set -> set.iterator().next()).collect(Collectors.toList())); if(coords.size() <= 1) { log.trace("%s: cancelling merge as we only have 1 coordinator: %s", local_addr, coords); return; } log.trace("%s: merge participants are %s", local_addr, coords); if(max_participants_in_merge > 0 && coords.size() > max_participants_in_merge) { int old_size=coords.size(); coords.removeIf(next -> coords.size() > max_participants_in_merge); log.trace("%s: reduced %d coords to %d", local_addr, old_size, max_participants_in_merge); } // grab views from all members in coords view_rsps.reset(coords); for(Address target: coords) { if(target.equals(local_addr)) { if(view != null) view_rsps.add(local_addr, view); continue; } Message view_req=new EmptyMessage(target).putHeader(getId(), MergeHeader.createViewRequest()); down_prot.down(view_req); } view_rsps.waitForAllResponses(check_interval / 10); Map results=view_rsps.getResults(); log.trace("%s: got all results: %s", local_addr, results); Map merge_views=new HashMap<>(); results.entrySet().stream().filter(entry -> entry.getValue() != null).forEach(entry -> merge_views.put(entry.getKey(), entry.getValue())); view_rsps.reset(); if(merge_views.size() >= 2) { Collection tmp_views=merge_views.values(); if(Util.allEqual(tmp_views)) { log.trace("%s: all views are the same, suppressing sending MERGE up. Views: %s", local_addr, tmp_views); return; } up_prot.up(new Event(Event.MERGE, merge_views)); num_merge_events++; } else log.trace("%s: %d merged views. Nothing to do", local_addr, merge_views.size()); } public long nextInterval() { return check_interval; } public String toString() { return String.format("%s: %s (interval=%dms", MERGE3.class.getSimpleName(), getClass().getSimpleName(), check_interval); } } public static class MergeHeader extends Header { protected Type type=Type.INFO; protected ViewId view_id; protected String logical_name; protected PhysicalAddress physical_addr; public MergeHeader() { } public static MergeHeader createInfo(ViewId view_id, String logical_name, PhysicalAddress physical_addr) { return new MergeHeader(Type.INFO, view_id, logical_name, physical_addr); } public static MergeHeader createViewRequest() { return new MergeHeader(Type.VIEW_REQ, null, null, null); } public static MergeHeader createViewResponse() { return new MergeHeader(Type.VIEW_RSP, null, null, null); } protected MergeHeader(Type type, ViewId view_id, String logical_name, PhysicalAddress physical_addr) { this.type=type; this.view_id=view_id; this.logical_name=logical_name; this.physical_addr=physical_addr; } public short getMagicId() {return 75;} public Supplier create() {return MergeHeader::new;} @Override public int serializedSize() { int retval=Global.BYTE_SIZE; // for the type retval+=Util.size(view_id); retval+=Global.BYTE_SIZE; // presence byte for logical_name if(logical_name != null) retval+=logical_name.length() +2; retval+=Util.size(physical_addr); return retval; } @Override public void writeTo(DataOutput outstream) throws IOException { outstream.writeByte(type.ordinal()); // a byte if ok as we only have 3 types anyway Util.writeViewId(view_id,outstream); Bits.writeString(logical_name,outstream); Util.writeAddress(physical_addr, outstream); } @Override public void readFrom(DataInput instream) throws IOException, ClassNotFoundException { type=Type.values()[instream.readByte()]; view_id=Util.readViewId(instream); logical_name=Bits.readString(instream); physical_addr=(PhysicalAddress)Util.readAddress(instream); } public String toString() { return String.format("%s: %s, logical_name=%s, physical_addr=%s", type, view_id != null? "view_id=" + view_id : "", logical_name, physical_addr); } protected enum Type {INFO, VIEW_REQ, VIEW_RSP} } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy