Есть ли опция useDirtyFlag для конфигурации кластера Tomcat 6? - PullRequest
3 голосов
/ 21 апреля 2010

В Tomcat 5.0.x у вас была возможность установить useDirtyFlag = "false" на принудительную репликацию сеанса после каждого запроса вместо проверки вызовов set / removeAttribute.

<Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                 managerClassName="org.apache.catalina.cluster.session.SimpleTcpReplicationManager"
                 expireSessionsOnShutdown="false"
                 **useDirtyFlag="false"**
                 doClusterLog="true"
                 clusterLogName="clusterLog"> ...

В комментариях в файле server.xml указано, что это можно использовать для выполнения следующей работы:

<%
    HashMap map = (HashMap)session.getAttribute("map");
    map.put("key","value");
%>

т.е. измените состояние объекта, который уже был помещен в сеанс , и вы можете быть уверены, что этот объект все еще будет реплицирован на другие узлы в кластере.

В соответствии с документацией Tomcat 6 у вас есть только два параметра «Менеджер» - DeltaManager и BackupManager ... ни один из них не позволяет использовать этот параметр или что-либо подобное. В моем тестировании настройка по умолчанию:

  <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

там, где вы получаете DeltaManager по умолчанию, он определенно ведет себя как useDirtyFlag = "true" (как я и ожидал).

Так что мой вопрос - есть ли эквивалент в Tomcat 6?

Глядя на источник, я вижу реализацию менеджера "org.apache.catalina.ha.session.SimpleTcpReplicationManager", которая имеет useDirtyFlag, но комментарии javadoc в этом состоянии - "Репликация сессии Tomcat для Tomcat 4.0" ... Я не знаю, нормально ли это для использования - я предполагаю, что нет, поскольку это не упомянуто в основной документации конфигурации кластера.

Ответы [ 2 ]

4 голосов
/ 05 мая 2010

Я разместил практически тот же вопрос в списке рассылки tomcat-users, и ответы на него вместе с некоторой информацией в bugzilla tomcat ([43866]) привели меня к следующим выводам:

  1. Нет эквивалента useDirtyFlag, если вы помещаете изменяемые (то есть изменяющиеся) объекты в сеанс, вам нужно решение с пользовательским кодом.
  2. Tomcat ClusterValve, кажется, является подходящим местом для этого решения - подключитесь к механизму кластера, манипулируйте атрибутами, чтобы показать DeltaManager, что все атрибуты в сеансе изменены. Это вызывает репликацию всего сеанса.

Шаг 1: Запишите ForceReplicationValve (расширяет ValveBase реализует ClusterValve)

Я не буду включать весь класс, но ключевой бит логики (исключая регистрацию и проверку экземпляров):

@Override
public void invoke(Request request, Response response) 
        throws IOException, ServletException {
    getNext().invoke(request, response);
    Session session = request.getSessionInternal();        
    HttpSession deltaSession = (HttpSession) session;
    for (Enumeration<String> names = deltaSession.getAttributeNames(); 
            names.hasMoreElements(); ) {
        String name = names.nextElement();
        deltaSession.setAttribute(name, deltaSession.getAttribute(name));
    }
}

Шаг 2. Изменение конфигурации кластера (в conf/server.xml)

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
            channelSendOptions="8">        
    <Valve className="org.apache.catalina.ha.tcp.ForceReplicationValve"/>
    <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
          filter=".*\.gif;.*\.jpg;.*\.png;.*\.js;.*\.htm;.*\.html;.*\.txt;.*\.css;"/>
    <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

    <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
    <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>

Репликация сеанса на все узлы кластера теперь будет выполняться после каждого запроса.

В сторону: обратите внимание на настройку channelSendOptions. Это заменяет replicationMode=asynchronous/synchronous/pooled из Tomcat 5.0.x. См. документацию кластера для возможных значений int.

Приложение: Источник полного клапана по запросу

package org.apache.catalina.ha.tcp;

import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.ha.ClusterValve;
import org.apache.catalina.ha.session.ReplicatedSession;
import org.apache.catalina.ha.session.SimpleTcpReplicationManager;
import org.apache.catalina.util.LifecycleSupport;
//import org.apache.catalina.util.StringManager;
import org.apache.catalina.valves.ValveBase;

/**
 * <p>With the {@link SimpleTcpReplicationManager} effectively deprecated, this allows
 * mutable objects to be replicated in the cluster by forcing the "dirty" status on 
 * every request.</p> 
 * 
 * @author Jon Brisbin (via post on tomcat-users http://markmail.org/thread/rdo3drcir75dzzrq)
 * @author Kevin Jansz
 */
public class ForceReplicationValve extends ValveBase implements Lifecycle, ClusterValve {
    private static org.apache.juli.logging.Log log =
        org.apache.juli.logging.LogFactory.getLog( ForceReplicationValve.class );

    @SuppressWarnings("hiding")
    protected static final String info = "org.apache.catalina.ha.tcp.ForceReplicationValve/1.0";

// this could be used if ForceReplicationValve messages were setup 
// in org/apache/catalina/ha/tcp/LocalStrings.properties
//    
//    /**
//     * The StringManager for this package.
//     */
//    @SuppressWarnings("hiding")
//    protected static StringManager sm =
//        StringManager.getManager(Constants.Package);

    /** 
     * Not actually required but this must implement {@link ClusterValve} to 
     * be allowed to be added to the Cluster.
     */
    private CatalinaCluster cluster = null ;

    /**
     * Also not really required, implementing {@link Lifecycle} to allow 
     * initialisation and shutdown to be logged. 
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);    


    /**
     * Default constructor
     */
    public ForceReplicationValve() {
        super();
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": created");
        }
    }

    @Override
    public String getInfo() {
        return info;
    }

    @Override
    public void invoke(Request request, Response response) throws IOException,
            ServletException {

        getNext().invoke(request, response);

        Session session = null;
        try {
            session = request.getSessionInternal();
        } catch (Throwable e) {
            log.error(getInfo() + ": Unable to perform replication request.", e);
        }

        String context = request.getContext().getName();
        String task = request.getPathInfo();
        if(task == null) {
            task = request.getRequestURI();
        }
        if (session != null) {
            if (log.isDebugEnabled()) {
                log.debug(getInfo() + ": [session=" + session.getId() + ", instanceof=" + session.getClass().getName() + ", context=" + context + ", request=" + task + "]");
            }
            if (session instanceof ReplicatedSession) {
                // it's a SimpleTcpReplicationManager - can just set to dirty
                ((ReplicatedSession) session).setIsDirty(true);
                if (log.isDebugEnabled()) {
                    log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] maked DIRTY");
                }
            } else {
                // for everything else - cycle all attributes
                List cycledNames = new LinkedList();

                // in a cluster where the app is <distributable/> this should be
                // org.apache.catalina.ha.session.DeltaSession - implements HttpSession
                HttpSession deltaSession = (HttpSession) session;
                for (Enumeration<String> names = deltaSession.getAttributeNames(); names.hasMoreElements(); ) {
                    String name = names.nextElement();
                    deltaSession.setAttribute(name, deltaSession.getAttribute(name));

                    cycledNames.add(name);                    
                }

                if (log.isDebugEnabled()) {
                    log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] cycled atrributes=" + cycledNames + "");
                }
            }
        } else {
            String id = request.getRequestedSessionId();
            log.warn(getInfo()  + ": [session=" + id + ", context=" + context + ", request=" + task + "] Session not available, unable to send session over cluster.");
        }
    }


    /* 
     * ClusterValve methods - implemented to ensure this valve is not ignored by Cluster  
     */

    public CatalinaCluster getCluster() {
        return cluster;
    }

    public void setCluster(CatalinaCluster cluster) {
        this.cluster = cluster;
    }


    /* 
     * Lifecycle methods - currently implemented just for logging startup 
     */

    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    /**
     * Get the lifecycle listeners associated with this lifecycle. If this 
     * Lifecycle has no listeners registered, a zero-length array is returned.
     */
    public LifecycleListener[] findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to remove
     */
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    public void start() throws LifecycleException {
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": started");
        }
    }

    public void stop() throws LifecycleException {
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": stopped");
        }
    }

}
2 голосов
/ 25 февраля 2013

Большое спасибо kevinjansz за предоставление источника для ForceReplicationValve.

Я настроил его для Tomcat7, вот, если это кому-нибудь нужно:

package org.apache.catalina.ha.tcp;

import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.ha.ClusterValve;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.LifecycleState;
// import org.apache.tomcat.util.res.StringManager;

/**
 * <p>With the {@link SimpleTcpReplicationManager} effectively deprecated, this allows
 * mutable objects to be replicated in the cluster by forcing the "dirty" status on 
 * every request.</p> 
 * 
 * @author Jon Brisbin (via post on tomcat-users http://markmail.org/thread/rdo3drcir75dzzrq)
 * @author Kevin Jansz
 */
public class ForceReplicationValve extends ValveBase implements Lifecycle, ClusterValve {
    private static org.apache.juli.logging.Log log =
        org.apache.juli.logging.LogFactory.getLog( ForceReplicationValve.class );

    @SuppressWarnings("hiding")
    protected static final String info = "org.apache.catalina.ha.tcp.ForceReplicationValve/1.0";

// this could be used if ForceReplicationValve messages were setup 
// in org/apache/catalina/ha/tcp/LocalStrings.properties
//    
//    /**
//     * The StringManager for this package.
//     */
//    @SuppressWarnings("hiding")
//    protected static StringManager sm =
//        StringManager.getManager(Constants.Package);

    /** 
     * Not actually required but this must implement {@link ClusterValve} to 
     * be allowed to be added to the Cluster.
     */
    private CatalinaCluster cluster = null;

    /**
     * Also not really required, implementing {@link Lifecycle} to allow 
     * initialisation and shutdown to be logged. 
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);    


    /**
     * Default constructor
     */
    public ForceReplicationValve() {
        super();
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": created");
        }
    }

    @Override
    public String getInfo() {
        return info;
    }

    @Override
    public void invoke(Request request, Response response) throws IOException,
            ServletException {

        getNext().invoke(request, response);

        Session session = null;
        try {
            session = request.getSessionInternal();
        } catch (Throwable e) {
            log.error(getInfo() + ": Unable to perform replication request.", e);
        }

        String context = request.getContext().getName();
        String task = request.getPathInfo();
        if(task == null) {
            task = request.getRequestURI();
        }
        if (session != null) {
            if (log.isDebugEnabled()) {
                log.debug(getInfo() + ": [session=" + session.getId() + ", instanceof=" + session.getClass().getName() + ", context=" + context + ", request=" + task + "]");
            }
            //cycle all attributes
            List<String> cycledNames = new LinkedList<String>();

            // in a cluster where the app is <distributable/> this should be
            // org.apache.catalina.ha.session.DeltaSession - implements HttpSession
            HttpSession deltaSession = (HttpSession) session;
            for (Enumeration<String> names = deltaSession.getAttributeNames(); names.hasMoreElements(); ) {
                String name = names.nextElement();
                deltaSession.setAttribute(name, deltaSession.getAttribute(name));

                cycledNames.add(name);                    
            }

            if (log.isDebugEnabled()) {
                log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] cycled atrributes=" + cycledNames + "");
            }
        } else {
            String id = request.getRequestedSessionId();
            log.warn(getInfo()  + ": [session=" + id + ", context=" + context + ", request=" + task + "] Session not available, unable to send session over cluster.");
        }
    }


    /* 
     * ClusterValve methods - implemented to ensure this valve is not ignored by Cluster  
     */

    public CatalinaCluster getCluster() {
        return cluster;
    }

    public void setCluster(CatalinaCluster cluster) {
        this.cluster = cluster;
    }


    /* 
     * Lifecycle methods - currently implemented just for logging startup 
     */

    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    /**
     * Get the lifecycle listeners associated with this lifecycle. If this 
     * Lifecycle has no listeners registered, a zero-length array is returned.
     */
    public LifecycleListener[] findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to remove
     */
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    protected synchronized void startInternal() throws LifecycleException {
        setState(LifecycleState.STARTING);
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": started");
        }
    }

    protected synchronized void stopInternal() throws LifecycleException {
        setState(LifecycleState.STOPPING);
        if (log.isInfoEnabled()) {
            log.info(getInfo() + ": stopped");
        }
    }

}
...