如何获得对某些会话条目的独占访问权?
How to get exclusive access to some session entry?
由于 REST 服务的远程调用特性,它们经常处于 运行 相互竞争的状态。每天要争夺的资源之一是会话。为了实用,您需要能够在流程开始时锁定资源,并在完成时将其解除。
现在我的问题是,Spring 会话是否有任何功能来处理会话条目的竞争条件?
或 Java 中的任何其他库/框架!!!
如果您使用 Spring 控制器,则可以使用
RequestMappingHandlerAdapter.setSynchronizeOnSession-boolean-
这将使每个控制器方法在存在会话时同步。
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,您将不得不使用 sticky sessions
。这将确保一个会话状态存储在一个容器中,并且每次请求同一会话时都会访问该容器。这必须借助 BigIP cookies
之类的东西在负载均衡器上完成。 Rest 的工作方式与存在单个容器的单个会话相同,因此锁定会话就足够了。
如果您想跨实例使用会话共享,可以在容器上提供支持,例如 Tomcat and Jetty
这些方法使用后端数据库或其他一些持久性机制来存储状态。
出于同样的目的,您可以尝试使用 Spring Session。使用 Redis
配置是微不足道的。由于 Redis 是单线程的,它确保以原子方式访问条目的一个实例。
以上方法是非侵入性的。基于数据库和 Redis 的方法都支持 transactions.
但是,如果您想更好地控制分布式状态和锁定,您可以尝试使用分布式数据网格,如 Hazelcast 和 Gemfire。
我亲自使用过 Hazelcast,它确实提供了 methods to lock entries made in the map。
#Edit2
尽管我认为使用 Spring Session 和 Redis 处理事务应该足够了,以确保您需要分布式锁定。必须从 Redis 本身获取 Lock 对象。由于 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. More documentation on usage is here.
提供了这种分布式锁实现
如果您决定在没有 spring 的情况下使用普通 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 锁定一样工作。
正如之前的回答所述。如果您正在使用 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 上有一个非常好的 post,讨论了这个主题:Sticky Sessions and Session Replication
根据我的经验,您可能需要 Sticky Session 和 Session 复制。
您使用粘性会话来消除跨节点的并发会话访问,因为粘性会话会将会话固定到单个节点,并且同一会话的每个后续请求将始终定向到该节点。这消除了跨节点会话访问问题。
复制会话主要在节点出现故障时有用。通过复制会话,当一个节点出现故障时,未来对现有会话的请求将被定向到另一个节点,该节点将拥有原始会话的副本,并使故障转移对用户透明。
有很多框架支持会话复制。我用于大型项目的是开源 Hazelcast.
回应您在@11thdimension post 上发表的评论:
我认为您处在一个具有挑战性的领域。基本上,您希望强制所有会话操作在集群中的节点之间是原子的。这使我倾向于跨节点的公共会话存储,其中访问是同步的(或类似的东西)。
多会话存储/复制框架肯定支持外部存储概念,我相信 Reddis 支持。我最熟悉 Hazelcast 并将以它为例。
Hazelcast 允许配置会话持久性以使用公共数据库。
如果您查看 Map Persistence 部分,它会显示示例和选项说明。
概念说明:
Hazelcast allows you to load and store the distributed map entries from/to a persistent data store such as a relational database. To do this, you can use Hazelcast's MapStore and MapLoader interfaces.
Data store needs to be a centralized system that is accessible from all Hazelcast Nodes. Persistence to local file system is not supporte
Hazelcast supports read-through, write-through, and write-behind persistence modes which are explained in below subsections.
有趣的模式是直写:
Write-Through
MapStore can be configured to be write-through by setting the write-delay-seconds property to 0. This means the entries will be put to the data store synchronously.
In this mode, when the map.put(key,value) call returns:
MapStore.store(key,value) is successfully called so the entry is persisted.
In-Memory entry is updated.
In-Memory backup copies are successfully created on other JVMs (if backup-count is greater than 0).
The same behavior goes for a map.remove(key) call. The only difference is that MapStore.delete(key) is called when the entry will be deleted.
我认为,使用这个概念,加上正确设置商店的数据库表以锁定 insert/update/deletes 上的条目,您可以完成您想要的。
祝你好运!
由于 REST 服务的远程调用特性,它们经常处于 运行 相互竞争的状态。每天要争夺的资源之一是会话。为了实用,您需要能够在流程开始时锁定资源,并在完成时将其解除。
现在我的问题是,Spring 会话是否有任何功能来处理会话条目的竞争条件?
或 Java 中的任何其他库/框架!!!
如果您使用 Spring 控制器,则可以使用
RequestMappingHandlerAdapter.setSynchronizeOnSession-boolean-
这将使每个控制器方法在存在会话时同步。
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,您将不得不使用 sticky sessions
。这将确保一个会话状态存储在一个容器中,并且每次请求同一会话时都会访问该容器。这必须借助 BigIP cookies
之类的东西在负载均衡器上完成。 Rest 的工作方式与存在单个容器的单个会话相同,因此锁定会话就足够了。
如果您想跨实例使用会话共享,可以在容器上提供支持,例如 Tomcat and Jetty
这些方法使用后端数据库或其他一些持久性机制来存储状态。
出于同样的目的,您可以尝试使用 Spring Session。使用 Redis
配置是微不足道的。由于 Redis 是单线程的,它确保以原子方式访问条目的一个实例。
以上方法是非侵入性的。基于数据库和 Redis 的方法都支持 transactions.
但是,如果您想更好地控制分布式状态和锁定,您可以尝试使用分布式数据网格,如 Hazelcast 和 Gemfire。
我亲自使用过 Hazelcast,它确实提供了 methods to lock entries made in the map。
#Edit2
尽管我认为使用 Spring Session 和 Redis 处理事务应该足够了,以确保您需要分布式锁定。必须从 Redis 本身获取 Lock 对象。由于 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. More documentation on usage is here.
提供了这种分布式锁实现如果您决定在没有 spring 的情况下使用普通 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 锁定一样工作。
正如之前的回答所述。如果您正在使用 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 上有一个非常好的 post,讨论了这个主题:Sticky Sessions and Session Replication
根据我的经验,您可能需要 Sticky Session 和 Session 复制。 您使用粘性会话来消除跨节点的并发会话访问,因为粘性会话会将会话固定到单个节点,并且同一会话的每个后续请求将始终定向到该节点。这消除了跨节点会话访问问题。
复制会话主要在节点出现故障时有用。通过复制会话,当一个节点出现故障时,未来对现有会话的请求将被定向到另一个节点,该节点将拥有原始会话的副本,并使故障转移对用户透明。
有很多框架支持会话复制。我用于大型项目的是开源 Hazelcast.
回应您在@11thdimension post 上发表的评论:
我认为您处在一个具有挑战性的领域。基本上,您希望强制所有会话操作在集群中的节点之间是原子的。这使我倾向于跨节点的公共会话存储,其中访问是同步的(或类似的东西)。
多会话存储/复制框架肯定支持外部存储概念,我相信 Reddis 支持。我最熟悉 Hazelcast 并将以它为例。
Hazelcast 允许配置会话持久性以使用公共数据库。 如果您查看 Map Persistence 部分,它会显示示例和选项说明。
概念说明:
Hazelcast allows you to load and store the distributed map entries from/to a persistent data store such as a relational database. To do this, you can use Hazelcast's MapStore and MapLoader interfaces.
Data store needs to be a centralized system that is accessible from all Hazelcast Nodes. Persistence to local file system is not supporte
Hazelcast supports read-through, write-through, and write-behind persistence modes which are explained in below subsections.
有趣的模式是直写:
Write-Through
MapStore can be configured to be write-through by setting the write-delay-seconds property to 0. This means the entries will be put to the data store synchronously.
In this mode, when the map.put(key,value) call returns:
MapStore.store(key,value) is successfully called so the entry is persisted. In-Memory entry is updated. In-Memory backup copies are successfully created on other JVMs (if backup-count is greater than 0). The same behavior goes for a map.remove(key) call. The only difference is that MapStore.delete(key) is called when the entry will be deleted.
我认为,使用这个概念,加上正确设置商店的数据库表以锁定 insert/update/deletes 上的条目,您可以完成您想要的。
祝你好运!