由于REST服务的远程调用性质,它们处于不断变为竞争状态的状态。比赛的日常资源之一是会议。为了切实可行,您需要能够在流程开始时锁定资源,并在完成任务后将其解除。
现在我的问题是,Spring Session是否有任何功能可以处理会话条目中的竞争条件?
或Java中的任何其他库/框架!!!
答案 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
之类的帮助下在负载均衡器上完成。其余的工作方式与单个会话相同,只存在一个容器,因此锁定会话就足够了。
如果您希望跨实例使用会话共享,则会对Tomcat和Jetty等容器提供支持
这些方法使用后端数据库或其他一些持久性机制来存储状态。
出于同样的目的,您可以尝试使用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)。
我认为,使用这个概念,再加上为商店正确设置数据库表以锁定插入/更新/删除条目,你就可以完成你想要的工作。
祝你好运!