如何获得对某些会话条目的独占访问权限?

时间:2016-03-26 12:40:51

标签: java session locking spring-session

由于REST服务的远程调用性质,它们处于不断变为竞争状态的状态。比赛的日常资源之一是会议。为了切实可行,您需要能够在流程开始时锁定资源,并在完成任务后将其解除。

现在我的问题是,Spring Session是否有任何功能可以处理会话条目中的竞争条件?

或Java中的任何其他库/框架!!!

2 个答案:

答案 0 :(得分:3)

如果您正在使用Spring Controllers,那么您可以使用

RequestMappingHandlerAdapter.setSynchronizeOnSession-boolean-

这将使每个Controller方法在会话中同步。

HttpSession.setAttribute是线程安全的。但是,getAttribute后跟setAttribute必须手动安全。

synchronized(session) {
    session.setAttribute("foo", "bar");
    session.getAttribute("foo");
}

在春季会话bean的情况下也可以这样做。

synchronized(session) {
    //do something with the session bean
}

<强> #Edit

如果多个容器具有普通的spring会话bean,则必须使用sticky sessions。这将确保一个会话状态存储在一个容器上,并且每次请求相同会话时都访问该容器。这必须在BigIP cookies之类的帮助下在负载均衡器上完成。其余的工作方式与单个会话相同,只存在一个容器,因此锁定会话就足够了。

如果您希望跨实例使用会话共享,则会对TomcatJetty等容器提供支持

这些方法使用后端数据库或其他一些持久性机制来存储状态。

出于同样的目的,您可以尝试使用Spring Session。使用Redis进行配置非常简单。由于Redis是单线程的,因此它确保以原子方式访问条目的一个实例。

以上方法是非侵入性的。数据库和基于Redis的方法都支持transactions

但是,如果您想要更好地控制分布式状态和锁定,可以尝试使用分布式数据网格,如Hazelcast和Gemfire。

我亲自与Hazelcast合作过,它确实提供了methods to lock entries made in the map

<强>#EDIT2

虽然我认为处理事务应该足以满足Spring Session和Redis,以确保您需要分布式锁定。必须从Redis本身获取锁定对象。由于Redis是单线程的,因此使用INCR

之类的东西也可以实现个人实现

算法将如下所示

//lock_num is the semaphore/lock object

lock_count = INCR lock_num
while(true) {
    if(lock_count != 1) {
        DECR lock_num
    } else {
        break
    }
    wait(wait_time_period)
}

//do processing in critical section

DECR lock_num

但是,幸运的是,Spring已经通过RedisLockRegistry提供了这种分布式锁实现。有关usage is here的更多文档。

如果您决定使用没有弹簧的普通Jedis,那么这里是Jedis的分布式锁:Jedis Lock

//from https://github.com/abelaska/jedis-lock
Jedis jedis = new Jedis("localhost");
JedisLock lock = new JedisLock(jedis, "lockname", 10000, 30000);
lock.acquire();
try {
  // do some stuff
}
finally {
  lock.release();
}

这两个都应该像Hazelcast锁定一样工作。

答案 1 :(得分:3)

如先前的答案所述。如果您正在使用Spring Session并且您担心并发访问Session时的线程安全性,则应设置:

RequestMappingHandlerAdapter.setSynchronizeOnSession(true);

可在此处找到一个示例EnableSynchronizeOnSessionPostProcessor

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

public class EnableSynchronizeOnSessionPostProcessor implements BeanPostProcessor {
    private static final Logger logger = LoggerFactory
        .getLogger(EnableSynchronizeOnSessionPostProcessor.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // NO-OP
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
            RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
            logger.info("enable synchronizeOnSession => {}", adapter);
            adapter.setSynchronizeOnSession(true);
        }
        return bean;
    }
}

Sticky Sessions and Session Replication

关于群集应用程序和Sessions,SO上有一篇非常好的帖子,讨论了这个主题:Sticky Sessions and Session Replication

根据我的经验,你会想要Sticky Session和Session复制。 您使用粘性会话来消除跨节点的并发会话访问,因为粘性会话会将会话固定到单个节点,并且同一会话的每个后续请求将始终指向该节点。这消除了跨节点会话访问问题。

复制会话主要有助于节点发生故障。通过复制会话,当节点发生故障时,将来对现有会话的请求将被定向到另一个节点,该节点将具有原始会话的副本,并使故障转移对用户透明。

有许多框架支持会话复制。我用于大型项目的是开源Hazelcast

回应您在@ 11thdimension帖子上发表的评论:

我认为你处在一个充满挑战的领域。基本上,您希望强制所有会话操作在群集中的节点之间是原子的。这使我倾向于跨越节点的公共会话存储,其中访问是同步的(或类似的)。

多个会话存储/复制框架肯定支持外部存储概念,我相信Reddis确实如此。我最熟悉Hazelcast并将其作为一个例子。

Hazelcast允许配置会话持久性以使用公共数据库。 如果您查看Map Persistence部分,则会显示示例和选项说明。

概念的描述说明:

  

Hazelcast允许您从/向持久数据存储(如关系数据库)加载和存储分布式映射条目。为此,您可以使用Hazelcast的MapStore和MapLoader接口。

     

数据存储需要是一个可从所有Hazelcast节点访问的集中式系统。本地文件系统的持久性不支持

     

Hazelcast支持直读,直写和后写持久性模式,这些模式将在以下小节中介绍。

有趣的模式是直写:

  

直写式

     

通过将write-delay-seconds属性设置为0,可以将MapStore配置为直写。这意味着条目将同步放入数据存储。

     

在此模式下,当map.put(key,value)调用返回时:

     

成功调用了MapStore.store(key,value),因此该条目保持不变。   内存条目已更新。   在其他JVM上成功创建内存中备份副本(如果backup-count大于0)。   map.remove(key)调用也是同样的行为。唯一的区别是当条目被删除时调用MapStore.delete(key)。

我认为,使用这个概念,再加上为商店正确设置数据库表以锁定插入/更新/删除条目,你就可以完成你想要的工作。

祝你好运!