Java 如何以独占方式访问某些会话条目?

Java 如何以独占方式访问某些会话条目?,java,session,locking,spring-session,Java,Session,Locking,Spring Session,由于REST服务的远程调用性质,它们经常处于相互竞争的状态。每天比赛的资源之一就是训练。为了实用,您需要能够在流程开始时锁定资源,并在处理完资源后将其提升 现在我的问题是,Spring会话是否有任何功能来处理会话条目上的竞争条件 或任何其他Java库/框架 如果您使用的是Spring控制器,那么您可以使用 这将使每个控制器方法在存在会话时同步 HttpSession.setAttribute是线程安全的。但是,必须手动使getAttribute后跟setAttribute synchroniz

由于REST服务的远程调用性质,它们经常处于相互竞争的状态。每天比赛的资源之一就是训练。为了实用,您需要能够在流程开始时锁定资源,并在处理完资源后将其提升

现在我的问题是,Spring会话是否有任何功能来处理会话条目上的竞争条件


或任何其他Java库/框架

如果您使用的是Spring控制器,那么您可以使用

这将使每个控制器方法在存在会话时同步

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

synchronized(session) {
    session.setAttribute("foo", "bar");
    session.getAttribute("foo");
}
对于spring会话bean也可以这样做

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

如果有多个容器具有正常的spring会话bean,则必须使用
粘性会话
。这将确保在一个容器上存储一个会话状态,并且每次请求同一会话时都会访问该容器。这必须在负载平衡器上通过类似
BigIP cookies
的帮助完成。Rest的工作方式与单个会话的工作方式相同,因为存在单个容器,所以锁定会话就足够了

如果您希望跨实例使用会话共享,则在容器上有支持,如和

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

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

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

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

我个人曾与Hazelcast合作,它确实提供了帮助

#Edit2

尽管我相信处理事务应该足够使用Spring会话和Redis,以确保您需要分布式锁定。锁定对象必须从Redis本身获取。因为Redis是单线程的,所以个人实现也可以通过使用

算法如下所示

//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已经通过提供了这种分布式锁实现。有关的更多文档

如果你决定使用没有弹簧的普通绝地武士,那么这里有一个针对绝地武士的分布式锁:


这两种方法的工作原理应该与Hazelcast锁定完全相同。

正如前面的回答所述。如果您使用的是Spring会话,并且您关心会话并发访问的线程安全,则应设置:

RequestMappingHandlerAdapter.setSynchronizeOnSession(true);
这里可以找到一个例子:

关于集群应用程序和会话,这里有一篇非常好的帖子,讨论了这个主题:

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

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

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

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

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

多会话存储/复制框架肯定支持外部存储概念,我相信Reddis也支持。我最熟悉Hazelcast,并将以此为例

Hazelcast允许配置会话持久性以使用公共数据库。 如果您查看第节,它将显示一个示例和选项说明

对概念的描述说明如下:

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

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

Hazelcast支持读通、写通和写后持久化模式,这些将在下面的小节中解释

有趣的模式是直写模式:

写完

通过将写入延迟秒属性设置为0,可以将MapStore配置为直写。这意味着条目将同步地放入数据存储

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

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

我认为,使用这个概念,再加上为存储正确设置数据库表,以便在插入/更新时锁定条目/
RequestMappingHandlerAdapter.setSynchronizeOnSession(true);
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;
    }
}