SEBSERV-250 improved missing ping update
This commit is contained in:
		
							parent
							
								
									dfb8afb740
								
							
						
					
					
						commit
						06b433e6cc
					
				
					 4 changed files with 77 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
 | 
			
		||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.security.Principal;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
| 
						 | 
				
			
			@ -488,7 +489,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
 | 
			
		|||
            // delete stored ping if this is a distributed setup
 | 
			
		||||
            if (this.isDistributedSetup) {
 | 
			
		||||
                this.distributedPingCache
 | 
			
		||||
                        .deletePingForConnection(updatedClientConnection.id);
 | 
			
		||||
                        .deletePingIndicator(updatedClientConnection.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            reloadConnectionCache(connectionToken);
 | 
			
		||||
| 
						 | 
				
			
			@ -542,7 +543,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
 | 
			
		|||
            // delete stored ping if this is a distributed setup
 | 
			
		||||
            if (this.isDistributedSetup) {
 | 
			
		||||
                this.distributedPingCache
 | 
			
		||||
                        .deletePingForConnection(updatedClientConnection.id);
 | 
			
		||||
                        .deletePingIndicator(updatedClientConnection.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            reloadConnectionCache(connectionToken);
 | 
			
		||||
| 
						 | 
				
			
			@ -794,9 +795,20 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private Consumer<ClientConnectionDataInternal> missingPingUpdate(final long now) {
 | 
			
		||||
 | 
			
		||||
        return connection -> {
 | 
			
		||||
            final ClientEventRecord clientEventRecord = connection.pingIndicator.updateLogEvent(now);
 | 
			
		||||
            if (clientEventRecord != null) {
 | 
			
		||||
 | 
			
		||||
            if (connection.pingIndicator.missingPingUpdate(now)) {
 | 
			
		||||
                final boolean missingPing = connection.pingIndicator.isMissingPing();
 | 
			
		||||
                final ClientEventRecord clientEventRecord = new ClientEventRecord(
 | 
			
		||||
                        null,
 | 
			
		||||
                        connection.getConnectionId(),
 | 
			
		||||
                        (missingPing) ? EventType.ERROR_LOG.id : EventType.INFO_LOG.id,
 | 
			
		||||
                        now,
 | 
			
		||||
                        now,
 | 
			
		||||
                        new BigDecimal(connection.pingIndicator.getValue()),
 | 
			
		||||
                        (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal");
 | 
			
		||||
 | 
			
		||||
                // store event and and flush cache
 | 
			
		||||
                this.eventHandlingStrategy.accept(clientEventRecord);
 | 
			
		||||
                if (this.isDistributedSetup) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
 | 
			
		|||
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
 | 
			
		||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
 | 
			
		||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService.PingUpdate;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractPingIndicator extends AbstractClientIndicator {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +88,6 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
 | 
			
		|||
        return this.EMPTY_SET;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public abstract ClientEventRecord updateLogEvent(final long now);
 | 
			
		||||
    public abstract boolean missingPingUpdate(final long now);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,20 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientIndicatorRec
 | 
			
		|||
@Lazy
 | 
			
		||||
@Component
 | 
			
		||||
@WebServiceProfile
 | 
			
		||||
/** This service is only needed within a distributed setup where more then one webservice works
 | 
			
		||||
 * simultaneously within one SEB Server and one persistent storage.
 | 
			
		||||
 * </p>
 | 
			
		||||
 * This service handles the SEB client ping updates within such a setup and implements functionality to
 | 
			
		||||
 * efficiently store and load ping time indicators form and to shared store.
 | 
			
		||||
 * </p>
 | 
			
		||||
 * The update from the persistent store is done periodically within a batch while the ping time writes
 | 
			
		||||
 * are done individually per SEB client when they arrive but within a dedicated task executor with minimal task
 | 
			
		||||
 * queue to do not overflow other executor services when it comes to a leak on storing lot of ping times.
 | 
			
		||||
 * In this case some ping time updates will be just dropped and not go to the persistent store until the leak
 | 
			
		||||
 * is resolved.
 | 
			
		||||
 * </p>
 | 
			
		||||
 * Note that the ping time update and read operations are also not within a transaction for performance reasons
 | 
			
		||||
 * and because it is not a big deal to loose one ore two ping updates for a SEB client. */
 | 
			
		||||
public class DistributedPingService implements DisposableBean {
 | 
			
		||||
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(DistributedPingService.class);
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +81,10 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        this.clientPingMapper = clientPingMapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Initializes the service by attaching it to the scheduler for periodical update.
 | 
			
		||||
     * If the webservice is not initialized within a distributed setup, this will do nothing
 | 
			
		||||
     *
 | 
			
		||||
     * @param initEvent the SEB Server webservice init event */
 | 
			
		||||
    @EventListener(SEBServerInitEvent.class)
 | 
			
		||||
    public void init(final SEBServerInitEvent initEvent) {
 | 
			
		||||
        final ApplicationContext applicationContext = initEvent.webserviceInit.getApplicationContext();
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +104,7 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
 | 
			
		||||
            try {
 | 
			
		||||
                this.taskRef = taskScheduler.scheduleAtFixedRate(
 | 
			
		||||
                        this::persistentPingUpdate,
 | 
			
		||||
                        this::updatePingCache,
 | 
			
		||||
                        distributedPingUpdateInterval);
 | 
			
		||||
 | 
			
		||||
                SEBServerInit.INIT_LOGGER.info("------> distributed ping service successfully initialized!");
 | 
			
		||||
| 
						 | 
				
			
			@ -101,10 +119,13 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ClientPingMapper getClientPingMapper() {
 | 
			
		||||
        return this.clientPingMapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** This initializes a SEB client ping indicator on the persistent storage for a given SEB client
 | 
			
		||||
     * connection identifier.
 | 
			
		||||
     * If there is already such a ping indicator for the specified SEB client connection identifier, returns
 | 
			
		||||
     * the id of the existing one.
 | 
			
		||||
     *
 | 
			
		||||
     * @param connectionId SEB client connection identifier
 | 
			
		||||
     * @return SEB client ping indicator identifier (PK) */
 | 
			
		||||
    @Transactional
 | 
			
		||||
    public Long initPingForConnection(final Long connectionId) {
 | 
			
		||||
        try {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,21 +170,12 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Transactional(readOnly = true)
 | 
			
		||||
    public Long getPingRecordIdForConnectionId(final Long connectionId) {
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            return this.clientPingMapper
 | 
			
		||||
                    .pingRecordIdByConnectionId(connectionId);
 | 
			
		||||
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error("Failed to get ping record for connection id: {} cause: {}", connectionId, e.getMessage());
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Deletes a existing SEB client ping indicator for a given SEB client connection identifier
 | 
			
		||||
     * on the persistent storage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param connectionId SEB client connection identifier */
 | 
			
		||||
    @Transactional
 | 
			
		||||
    public void deletePingForConnection(final Long connectionId) {
 | 
			
		||||
    public void deletePingIndicator(final Long connectionId) {
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            if (log.isDebugEnabled()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -198,11 +210,18 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Long getLastPing(final Long pingRecordId, final boolean missing) {
 | 
			
		||||
    /** Use this to get the last ping time indicator value with a given indicator identifier (PK)
 | 
			
		||||
     * This fist tries to get the ping time from internal cache. If not present, tries to get
 | 
			
		||||
     * the ping indicator value from persistent storage and put it to the cache.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pingRecordId The ping indicator record id (PK). Get one for a given SEB client connection identifier by
 | 
			
		||||
     *            calling: initPingForConnection
 | 
			
		||||
     * @return The actual (last) ping time. */
 | 
			
		||||
    public Long getLastPing(final Long pingRecordId) {
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            Long ping = this.pingCache.get(pingRecordId);
 | 
			
		||||
            if (ping == null && !missing) {
 | 
			
		||||
            if (ping == null) {
 | 
			
		||||
 | 
			
		||||
                if (log.isDebugEnabled()) {
 | 
			
		||||
                    log.debug("*** Get and cache ping time: {}", pingRecordId);
 | 
			
		||||
| 
						 | 
				
			
			@ -211,11 +230,6 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
                ping = this.clientPingMapper.selectPingTimeByPrimaryKey(pingRecordId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // if we have a missing ping we need to check new ping from next update even if the cache was empty
 | 
			
		||||
            if (ping != null || missing) {
 | 
			
		||||
                this.pingCache.put(pingRecordId, ping);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return ping;
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error("Error while trying to get last ping from storage: {}", e.getMessage());
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +237,12 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void persistentPingUpdate() {
 | 
			
		||||
    /** Updates the internal ping cache by loading all actual SEB client ping indicators from persistent storage
 | 
			
		||||
     * and put it in the cache.
 | 
			
		||||
     * This is internally periodically scheduled by the task scheduler but also implements an execution drop if
 | 
			
		||||
     * the last update was less then 2/3 of the schedule interval ago. This is to prevent task queue overflows
 | 
			
		||||
     * and wait with update when there is a persistent storage leak or a lot of network latency. */
 | 
			
		||||
    private void updatePingCache() {
 | 
			
		||||
        if (this.pingCache.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -263,8 +282,8 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        this.lastUpdate = millisecondsNow;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update last ping time on persistent storage asynchronously within a defines thread pool with no
 | 
			
		||||
    // waiting queue to skip further ping updates if all update threads are busy
 | 
			
		||||
    /** Update last ping time on persistent storage asynchronously within a defines thread pool with no
 | 
			
		||||
     * waiting queue to skip further ping updates if all update threads are busy **/
 | 
			
		||||
    void updatePingAsync(final PingUpdate pingUpdate) {
 | 
			
		||||
        try {
 | 
			
		||||
            this.pingUpdateExecutor.execute(pingUpdate);
 | 
			
		||||
| 
						 | 
				
			
			@ -275,12 +294,17 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Create a PingUpdate for a specified SEB client connectionId.
 | 
			
		||||
     *
 | 
			
		||||
     * @param connectionId SEB client connection identifier
 | 
			
		||||
     * @return PingUpdate for a specified SEB client connectionId */
 | 
			
		||||
    PingUpdate createPingUpdate(final Long connectionId) {
 | 
			
		||||
        return new PingUpdate(
 | 
			
		||||
                this.getClientPingMapper(),
 | 
			
		||||
                this.clientPingMapper,
 | 
			
		||||
                this.initPingForConnection(connectionId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Encapsulates a SEB client ping update on persistent storage */
 | 
			
		||||
    static final class PingUpdate implements Runnable {
 | 
			
		||||
 | 
			
		||||
        private final ClientPingMapper clientPingMapper;
 | 
			
		||||
| 
						 | 
				
			
			@ -292,6 +316,7 @@ public class DistributedPingService implements DisposableBean {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        /** Processes the ping update on persistent storage by using the current time stamp. */
 | 
			
		||||
        public void run() {
 | 
			
		||||
            try {
 | 
			
		||||
                this.clientPingMapper
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@
 | 
			
		|||
 | 
			
		||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
 | 
			
		||||
import org.joda.time.DateTimeUtils;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +24,6 @@ import ch.ethz.seb.sebserver.gbl.Constants;
 | 
			
		|||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
 | 
			
		||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
 | 
			
		||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
 | 
			
		||||
 | 
			
		||||
@Lazy
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +127,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
 | 
			
		|||
    public final double computeValueAt(final long timestamp) {
 | 
			
		||||
        if (!this.cachingEnabled && super.pingUpdate != null) {
 | 
			
		||||
 | 
			
		||||
            final Long lastPing = this.distributedPingCache.getLastPing(super.pingUpdate.pingRecord, this.missingPing);
 | 
			
		||||
            final Long lastPing = this.distributedPingCache.getLastPing(super.pingUpdate.pingRecord);
 | 
			
		||||
            if (lastPing != null) {
 | 
			
		||||
                final double doubleValue = lastPing.doubleValue();
 | 
			
		||||
                return Math.max(Double.isNaN(this.currentValue) ? doubleValue : this.currentValue, doubleValue);
 | 
			
		||||
| 
						 | 
				
			
			@ -142,35 +140,21 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ClientEventRecord updateLogEvent(final long now) {
 | 
			
		||||
    public boolean missingPingUpdate(final long now) {
 | 
			
		||||
        final long value = now - (long) super.currentValue;
 | 
			
		||||
        if (this.missingPing) {
 | 
			
		||||
            if (this.pingErrorThreshold > value) {
 | 
			
		||||
                this.missingPing = false;
 | 
			
		||||
                return new ClientEventRecord(
 | 
			
		||||
                        null,
 | 
			
		||||
                        this.connectionId,
 | 
			
		||||
                        EventType.INFO_LOG.id,
 | 
			
		||||
                        now,
 | 
			
		||||
                        now,
 | 
			
		||||
                        new BigDecimal(value),
 | 
			
		||||
                        "Client Ping Back To Normal");
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (this.pingErrorThreshold < value) {
 | 
			
		||||
                this.missingPing = true;
 | 
			
		||||
                return new ClientEventRecord(
 | 
			
		||||
                        null,
 | 
			
		||||
                        this.connectionId,
 | 
			
		||||
                        EventType.ERROR_LOG.id,
 | 
			
		||||
                        now,
 | 
			
		||||
                        now,
 | 
			
		||||
                        new BigDecimal(value),
 | 
			
		||||
                        "Missing Client Ping");
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue