基于Spring Session实现JIM分布式Session

前引

在实际项目中,应用程序经常会以集群方式部署线上,一般来说无状态的应用程序是理想的部署方式,一旦应用程序拥有状态(比如Session、线程内缓存、唯一全局ID等),那么会出现状态之间无法共享,对此通常解决方案时:使用第三方系统进行状态的分布式统一管理,比如Redis、Memecache、Zookeeper等。

我们经常会在Web程序使用Session,需要分布式Session。 分布式Session有几种实现方式,这里不加阐述,有兴趣的话,大家可以百度搜索下。这里使用最常用的方式:基于Spring Session实现

网上很多直接使用SpringSession + Redis实现分布式Session,由于SpringSession实现了Redis Session,直接简单集成就可以,但如何集成其他第三方KV缓存就很少有该文章。以下就是基于SpringSession实现第三方KV缓存实现分布式Session。

JIM是第三方KV缓存,也是基于Redis改造的KV缓存

代码实现

  1. 引用Spring Session的Maven
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session</artifactId><version>1.2.2.RELEASE</version>
</dependency>
  1. 定义注解EnableJimHttpSession
package com.sample.web.session;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.TYPE})
@Documented
@Import(JimHttpSessionConfiguration.class)
@Configuration
public @interface EnableJimHttpSession {int maxInactiveIntervalInSeconds() default 1800;
}
  1. 继承SpringHttpSessionConfiguration
package com.sample.web.session;import org.springframework.cache.Cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;import java.util.Map;/*** 继承SpringHttpSessionConfiguration* @author sunchangtan* @date 2019/1/23 20:51*/@Configuration
@EnableScheduling
public class JimHttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware {private Integer maxInactiveIntervalInSeconds = 1800;@Overridepublic void setImportMetadata(AnnotationMetadata importMetadata) {Map<String, Object> enableAttrMap = importMetadata.getAnnotationAttributes(EnableJimHttpSession.class.getName());AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);this.maxInactiveIntervalInSeconds = enableAttrs.getNumber("maxInactiveIntervalInSeconds");}@Beanpublic JimOperationsSessionRepository jimSessionRepository(Cache cache) {JimOperationsSessionRepository repository = new JimOperationsSessionRepository(cache);repository.setMaxInactiveIntervalInSeconds(this.maxInactiveIntervalInSeconds);return repository;}
}
  1. 核心逻辑实现FindByIndexNameSessionRepository接口,定义KV缓存的CRUD操作
package com.sample.web.session;import com.google.common.collect.Maps;
import com.sample.service.cache.jimdb.CacheCluster;
import com.sample.service.cache.jimdb.JimdbCache;
import com.sample.service.cache.jimdb.SingleJimdbCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.util.Assert;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** 基于JIM的session操作* @author sunchangtan* @date 2019/1/23 21:08*/
@Slf4j
public class JimOperationsSessionRepository implements FindByIndexNameSessionRepository<JimOperationsSessionRepository.JimSession> {private static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";private static final String CREATION_TIME_ATTR = "creationTime";private static final String MAX_INACTIVE_ATTR = "maxInactiveInterval";private static final String LAST_ACCESSED_ATTR = "lastAccessedTime";private static final String SESSION_ATTR_PREFIX = "sessionAttr:";private Integer defaultMaxInactiveInterval;private final Cache cache;public JimOperationsSessionRepository(Cache cache) {this.cache = cache;}void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {this.defaultMaxInactiveInterval = maxInactiveIntervalInSeconds;}@Overridepublic Map<String, JimSession> findByIndexNameAndIndexValue(String indexName, String indexValue) {return null;}@Overridepublic JimSession createSession() {JimOperationsSessionRepository.JimSession jimSession = new JimOperationsSessionRepository.JimSession();if (this.defaultMaxInactiveInterval != null) {jimSession.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval);}return jimSession;}/*** 获取JIMDB集群对象, 可以自定义*/private CacheCluster getCacheCluster() {if (cache instanceof SingleJimdbCache) {return ((SingleJimdbCache) cache).getNativeCache();} else if (cache instanceof JimdbCache) {return ((JimdbCache) cache).getNativeCache();} else {throw new NotImplementedException();}}/*** 保存SESSION对象* @param session*/@Overridepublic void save(JimSession session) {session.saveDelta();}/*** 获取SESSION对象* @param sessionId* @return*/@Overridepublic JimSession getSession(String sessionId) {return getSession(sessionId, false);}/*** 删除对象* @param sessionId*/@Overridepublic void delete(String sessionId) {JimOperationsSessionRepository.JimSession session = getSession(sessionId, true);if (session == null) {return;}String expireKey = getSessionKey(sessionId);this.getCacheCluster().delObject(expireKey);session.setMaxInactiveIntervalInSeconds(0);save(session);}/*** 保存Session数据*/private void save(String sessionId, Map<String, Object> sessionData) {final String key = getSessionKey(sessionId);final CacheCluster cacheCluster = this.getCacheCluster();sessionData.forEach((mapKey, mapValue) -> cacheCluster.setHObject(key, mapKey, ""+mapValue));}/*** 获取Session数据*/@SuppressWarnings("unchecked")private JimSession getSession(String sessionId, boolean allowExpired) {String key = getSessionKey(sessionId);try {Map<String, String> obj = this.getCacheCluster().getHObject(key);if (obj == null || obj.isEmpty()) {return null;}Map<Object, Object> entries = Maps.newHashMapWithExpectedSize(obj.size());obj.forEach(entries::put);MapSession loaded = loadSession(sessionId, entries);if (!allowExpired && loaded.isExpired()) {return null;}JimSession result = new JimSession(loaded);result.originalLastAccessTime = loaded.getLastAccessedTime();return result;} catch (Exception e) {log.error(e.getMessage(), e);}return null;}private MapSession loadSession(String id, Map<Object, Object> entries) {MapSession loaded = new MapSession(id);for (Map.Entry<Object, Object> entry : entries.entrySet()) {String key = (String) entry.getKey();if (CREATION_TIME_ATTR.equals(key)) {loaded.setCreationTime(Long.valueOf(entry.getValue().toString()));} else if (MAX_INACTIVE_ATTR.equals(key)) {loaded.setMaxInactiveIntervalInSeconds(Integer.valueOf(entry.getValue().toString()));} else if (LAST_ACCESSED_ATTR.equals(key)) {loaded.setLastAccessedTime(Long.valueOf(entry.getValue().toString()));} else if (key.startsWith(SESSION_ATTR_PREFIX)) {loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()),entry.getValue());}}return loaded;}private String getSessionAttrNameKey(String attributeName) {return SESSION_ATTR_PREFIX + attributeName;}private String getSessionKey(String sessionId) {return DEFAULT_SPRING_SESSION_REDIS_PREFIX + "sessions:" + sessionId;}/*** 封装MappSession操作session对象*/final class JimSession implements ExpiringSession {private final MapSession cached;private Map<String, Object> delta = new HashMap<>();long originalLastAccessTime;JimSession() {this(new MapSession());this.delta.put(CREATION_TIME_ATTR, getCreationTime());this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());}JimSession(MapSession cached) {Assert.notNull(cached, "MapSession cannot be null");this.cached = cached;}@Overridepublic void setLastAccessedTime(long lastAccessedTime) {this.cached.setLastAccessedTime(lastAccessedTime);this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());saveDelta();}@Overridepublic boolean isExpired() {return this.cached.isExpired();}@Overridepublic long getCreationTime() {return this.cached.getCreationTime();}@Overridepublic String getId() {return this.cached.getId();}@Overridepublic long getLastAccessedTime() {return this.cached.getLastAccessedTime();}@Overridepublic void setMaxInactiveIntervalInSeconds(int interval) {this.cached.setMaxInactiveIntervalInSeconds(interval);this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());saveDelta();}@Overridepublic int getMaxInactiveIntervalInSeconds() {return this.cached.getMaxInactiveIntervalInSeconds();}/*** 获取Session中的属性值*/@SuppressWarnings("unchecked")@Overridepublic Object getAttribute(String attributeName) {return this.cached.getAttribute(attributeName);}@Overridepublic Set<String> getAttributeNames() {return this.cached.getAttributeNames();}/*** 设置Session中的属性值*/@Overridepublic void setAttribute(String attributeName, Object attributeValue) {this.cached.setAttribute(attributeName, attributeValue);this.delta.put(getSessionAttrNameKey(attributeName), attributeValue);saveDelta();}/*** 删除Session中的属性值*/@Overridepublic void removeAttribute(String attributeName) {this.cached.removeAttribute(attributeName);this.delta.put(getSessionAttrNameKey(attributeName), null);saveDelta();}/*** 保存增量的session属性*/private void saveDelta() {if (this.delta.isEmpty()) {return;}String sessionId = getId();save(sessionId, this.delta);this.delta.clear();}}}
  1. SpringBoot的主程序入口中注解@EnableJimHttpSession
@EnableJimHttpSession
@SpringBootApplication
public class Application extends SpringBootServletInitializer {public static void main(String[] args) throws Exception {SpringApplicationBuilder builder = new SpringApplicationBuilder(Application.class);builder.bannerMode(Banner.Mode.OFF);builder.run(args);}
}

基于Spring Session实现JIM分布式Session相关推荐

  1. spring+redis自主实现分布式session(非spring-session方式)

    为什么80%的码农都做不了架构师?>>>    背景:最近对一个老项目进行改造,使其支持多机部署,其中最关键的一点就是实现多机session共享.项目有多老呢,jdk版本是1.6,s ...

  2. 手写基于Spring Cloud的TCC分布式事务框架

    如何简单实现TCC分布式事务框架 最近听到很多其他公司的小伙伴谈分布式事务的问题,各种业务场景都有,可能就是这两年很多公司都在往微服务发展,现在各个子系统都拆分.建设的差不多了,实现了模块化开发,但是 ...

  3. Spring Session + Redis实现分布式Session共享

    2019独角兽企业重金招聘Python工程师标准>>> 通常情况下,Tomcat.Jetty等Servlet容器,会默认将Session保存在内存中.如果是单个服务器实例的应用,将S ...

  4. 基于Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构(附源码)

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 作者:zheng gitee ...

  5. 基于Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:又一程序员进了ICU:压垮一个家庭,一张结算单就够 个人原创100W+访问量博客:点击前往,查看更多 前言 本项 ...

  6. sae session php,SAE 分布式session丢失的问题

    分布式Session SAE Java 平台中的应用部署在分布式环境中,因此SAE为每个应用提供了分布式Session功能,以保证Session能多节点共享.Session信息使用分布式的Memcac ...

  7. .Net 基于Memcache集群的分布式Session

    简述 基于Memcache的Session大家都各有各的说法,比方说:当memcached集群发生故障(比如内存溢出)或者维护(比如升级.增加或减少服务器)时,用户会无法登录,或者被踢掉线等等,每种技 ...

  8. 基于Spring Cloud + MyBatis的分布式架构网约车平台(DD 打车)后端原型系统设计与实现

    资源下载地址:https://download.csdn.net/download/sheziqiong/85638879 资源下载地址:https://download.csdn.net/downl ...

  9. spring boot 分布式session实现

    spring boot 分布式session实现 主要是通过包装 HttpServletRequest 将 session 相关的方法进行代理. 具体是的实现就是通过 SessionRepositor ...

最新文章

  1. Django博客系统(首页文章数据展示)
  2. 关于BCH交易规范排序(CTOR)的优缺
  3. 数据库---聚合查询
  4. 每日一笑 | 在托运行李时,怎样才能不会因为超重被罚钱?
  5. io流,装饰者模式_流与装饰器
  6. 哪些行业形势好?大数据帮你预测今年就业“风口”
  7. java基础集合操作工具类Collections简述(java集合四)
  8. h5-localStorage实现缓存ajax请求数据
  9. 数字图像处理入门(冈萨雷斯第三版)
  10. 收银怎样挂单和取单_挂单取单(PC收银)
  11. 密封橡胶圈尺寸缺陷视觉检测系统
  12. 详解低延时高音质:声音的美化与空间音效篇
  13. chrome操作系统_Google Chrome操作系统:事实与谬论
  14. 360抢票 网站维护中 你的登录被踢了!
  15. 如何独立开发 APP 赚钱?
  16. mysql中日期相减_解放双手!用这3个日期函数解决入职、工龄等天数的计算
  17. CCM 摄像模组结构组成部分
  18. Ceph常见问题处理(octopus 15.2.13)
  19. 在计算机储存中读写速度最快的是,储存器中存储速度最快的是哪个
  20. android控制创维电视,创维电视怎么连接手机 创维电视连接手机的方法【详细介绍】...

热门文章

  1. python把http字符串转码https%3A%2F%2F转https://
  2. 初中计算机考试wps文字,初中信息技术WPS表格测试题
  3. 品牌对比|斯凯奇 VS 李宁
  4. 计算机网络英文论文,计算机网络与因特网论文(英文版).doc
  5. [转载]形容女人的词语
  6. 怎么申请域名 域名的申请方式与流程 如何怎么买域名
  7. 淘宝天猫整店店铺商品API,店铺商品分类接口代码教程
  8. 薅资本主义羊毛新姿势,英伟达K80免费用
  9. 河南大学计算机专业就业率,2021考研择校择专业:河南大学就业率和薪酬情况...
  10. 键盘钢琴c语言,键盘钢琴c