【问题标题】:How to get exclusive access to some session entry?如何获得对某些会话条目的独占访问权限?
【发布时间】:2016-07-14 02:55:58
【问题描述】:

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

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

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

【问题讨论】:

  • 有什么负面影响??
  • @StevenLandow 我的问题和您提到的链接谈论的是完全不同的事情!不过还是谢谢。

标签: java session locking spring-session


【解决方案1】:

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

RequestMappingHandlerAdapter.setSynchronizeOnSession-boolean-

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

HttpSession.setAttribute 是线程安全的。但是 getAttribute 后跟 setAttribute 必须手动确保胎面安全。

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

在 Spring session bean 的情况下也可以这样做。

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

#编辑

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

如果您想跨实例使用会话共享,则容器上支持 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 锁定一样工作。

【讨论】:

  • 如果只有一个 Web 容器实例/正在运行的代码,您的解决方案是有效的。如果我决定使用负载均衡器和我的代码的多个实例怎么办?你对此有什么建议吗?另外你锁定了整个会话,如果我只想锁定会话的一个条目怎​​么办?
  • 更新了我的答案,看看吧。
  • 感谢您抽出宝贵时间,但这里有几个问题: 1/ 粘性会话:我知道它有效,但不是一个大粉丝,因为它不是均匀分配负载的好解决方案。真正的负载分配解决方案应该将请求随机分散到服务器上。
  • 2/ Spring Session+Redis:我现在正在使用这个解决方案,但我认为你使用事务的建议不会奏效,因为它们是通过将一堆命令分组并将它们作为Redis 中的原子之一。这是没用的,因为我需要在请求开始时从 Redis 中获取一个值,然后再将其设置回来,这意味着这两个不能作为一个 Redis 命令执行(我需要在两者之间执行耗时的东西) .这个解决方案还有一个问题是Redis连接是由Spring Session管理的,而Redis连接不在我的范围内。
  • 3/ 似乎 Hazelcast 可能是解决我的问题的正确选择,但它又不像锁定会话条目。我的意思是它可以用作辅助内存存储,我必须通过 Spring Session 的 API 以外的接口与之通信。现在我正在使用 Redisson,我相信它提供了与 Hazelcast 相同的功能,我希望找到一个与 Spring Session 兼容的解决方案,到目前为止似乎还没有。
【解决方案2】:

正如前面的回答所说。如果您使用的是 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

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

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

复制会话主要在节点出现故障的情况下很有帮助。通过复制会话,当一个节点出现故障时,未来对现有会话的请求将被定向到另一个节点,该节点将拥有原始会话的副本,并使故障转移对用户透明。

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

回应您在@11thdimension 发帖的cmets:

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

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

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

概念描述:

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

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

Hazelcast 支持 read-through、write-through 和 write-behind 持久性模式,这些模式将在以下小节中进行说明。

有趣的模式是直写:

直写

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

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

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

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

祝你好运!

【讨论】:

  • 不要重复我自己,请您阅读@11thdimension帖子上的cmets!?它们也适用于您的解决方案。到目前为止,我会说我的问题的答案是“你不能”。谢谢。
  • 我不确定我是否明白了!您的write-through 提议如何融入图片中?我认为它提供了一种原子写入,这与锁定条目完全不同。为了消除会话条目上的竞争条件,需要以下步骤:1.Acquire lock2.Read entry3.Process4.Write entry back5.Unlock。如果我理解您的write-through 解决方案,它只满足执行步骤#4 的原子方式。但是访问同一会话条目的并发请求应该在开始时相互阻塞,这使得它们的访问顺序(这是不可避免的)
  • 可以设置 setSynchronizeOnSession(true);处理集群中单个节点上的并发访问。持久存储中的原子写入确保了“先到先服务器”在集群中的节点之间得到处理。当节点 1 更新会话属性时,阻止节点 2 没有任何好处或意义,因为最终结果是相同的。一旦节点 1 完成更新,节点 2 无论如何都会应用它的更新。
  • 因此,最后一种可能性是您希望节点 1 完成其更新,而节点 2 通过读取来查看更改,从而做出更新决定。这可以通过在存储中的事务中读取锁定并将整个会话修改逻辑包装在事务中来处理。
  • 总之,通过同步每个节点的会话访问+利用数据库持久存储和一些事务处理,您应该能够做到这一点。
猜你喜欢
  • 1970-01-01
  • 2016-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多