1.频繁 FullGC 的源码原因竟然是 “开源代码”? | 京东云技术团队
2.spring boot与redis 实现session共享步骤详解
3.Laravel Session 源码解析
4.SpringBoot整合SpringSeesion实现Redis缓存
5.Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享
频繁 FullGC 的原因竟然是 “开源代码”? | 京东云技术团队
首先,Java语言的源码特性是无需手动释放内存,因为Java本身具备垃圾回收机制(GC),源码主要目的源码是释放无用的空间,避免内存泄露。源码JVM运行时最大的源码易语言植物大战僵尸源码内存空间为堆内存,另外栈区和方法区占用有限,源码本文主要探讨堆内存中的源码空间分为年轻代和老年代,垃圾回收分为年轻代的源码Young GC和老年代的Full GC。实际上,源码Full GC包括新生代、源码老年代、源码元空间等的源码回收。频繁Full GC的源码原因可能是老年代内存空间不足。
项目背景:团队开发的源码后台管理系统使用Shiro框架进行权限控制,引入了Shiro-Redis组件作为缓存层以方便管理用户登录信息。在引入组件时,出现了内存泄露问题。
事件还原:在周五中午:,收到监控报警,系统频繁进行Full GC,持续到点半。登录监控平台查看系统指标,发现确实在频繁FullGC。保留一台机器收集证据,其他机器重启,Full GC正常。执行堆栈信息操作指令jmap -F -dump:live,format=b,file=/jmapfile.hprof ,导出堆栈文件。通过监控平台、JVM启动参数、代码排除、python tornado源码指令分析,初步怀疑老年代内存不足是造成Full GC频繁的原因。
指标信息:JVM核心参数、Tomcat核心参数。通过分析得出堆最大空间为M,年轻代占用空间M(包括Eden区M,Survivor FromM,Survivor ToM),老年代最大占用空间M,系统初始化堆内存M。根据JVM启动参数,当堆内存达到%时进行垃圾回收,系统进行垃圾回收时堆内存占比%一直大于%,使用内存为0.*M。在查询堆栈文件时发现有个org.apache.tomcat.util.threads.TaskThread占用空间很大,共占用空间.%,每个TaskThread实例占用空间M左右。最终原因分析:内存泄漏是每个线程中有一个ThreadLocal存储大量SessionInMemory对象,由于Tomcat启动核心线程数为个,每个线程的内存占用M左右,共占用1.8G。当老年代内存达到%时进行垃圾回收,1.8G内存比.2M稍大,导致系统频繁FullGC。
定位问题:通过分析代码,发现SessionInMemory对象是引用Shiro-Redis工具包中的对象,主要封装Session信息和创建时间。主要作用是在当前线程的JVM中做一层缓存,当系统频繁获取Session时,避免每次都去Redis获取。SessionInMemary对象是苹果淘宝源码Shiro判断用户登录成功时存储的数据,主要包括用户信息、认证信息、权限信息等,因为用户登录后不会重复认证,Shiro对不同用户进行权限判断。分析代码发现处理本地缓存Session的流程存在问题,多个线程会复制多份相同Session,导致内存成倍增加。同时,旧Session无法清除,只要用户重新登录,必定有一个旧的Session会保留到线程中。
解决方案:当前问题解决方案有四种,针对我们系统使用了方案1和方案4。疑问解答:在RedisSessionDAO中只定义了一个ThreadLocal变量,为何会是个线程复制相同Session?这是因为ThreadLocal的结构,ThreadLocal有一个静态类ThreadLocalMap,里面有一个Entry,我们的key和value就保存在Entry中,key是一个弱引用的ThreadLocal类型,实际上就是定义的静态变量sessionsInThread。ThreadLocalMap在每次创建线程时都会new一个,所以每个线程中的ThreadLocalMap都是不同的,但里面Entry存储的key都是一样的。当线程需要获取Entry中存储的value时,调用sessionsInThread.get()方法,这个方法会获取当前线程的实例,再从线程实例中获取ThreadLocalMap,最后从ThreadLocalMap中根据ThreadLocal这个key获取指定的value。获取Thread中的ThreadLocalMap,从ThreadLocalMap中获取指定的寒霜3源码value。已经结束的线程为何还会存活,里面的对象也不会消失?因为设置的最小空闲线程数是,业务量不大并发数没有超过,tomcat会保留最小的线程数量不会新建也不用回收。访问一次接口是否就会生成一个sessionId?访问接口先判断用户信息是否有效,无效才会重新登录获取新的sessionId。shiro-redis在本地保存Session为什么设置1秒过期时间?因为运营后台不同于业务接口会持续调用,后台接口大部分场景是用户访问一个页面并停留在页面上做一些操作,访问一个页面时浏览器会加载多个资源,包括静态资源html、css、js等和接口的动态数据,整个资源加载过程尽量保持在一秒内完成,如果超过一秒系统体验性能较差,所以本地缓存一秒足够。
收获总结:在报警前,需要熟悉第三方jar包的工作原理,尤其是个人开发工具包,需要格外小心;可以使用jvisualvm进行本地压测观察JVM情况;关注监控报警,掌握监控平台操作,从监控中查询系统各项指标信息;根据业务合理配置JVM参数和Tomcat参数。报警后,能够第一时间抓取系统的JVM信息,比如堆栈、GC信息、线程栈等;通过使用MAT内存辅助软件帮助自己分析问题原因。
作者:京东科技 郭银利 来源:京东云开发者社区
spring boot与redis 实现session共享步骤详解
这次带来的是spring boot + redis 实现session共享的教程。
在spring boot的文档中,告诉我们添加@EnableRedisHttpSession来开启spring session支持,配置如下:
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
}
而@EnableRedisHttpSession这个注解是由spring-session-data-redis提供的,所以在pom.xml文件中添加:
org.springframework.boot
spring-boot-starter-redis
org.springframework.session
spring-session-data-redis
接下来,源码倚天指标则需要在application.properties中配置redis服务器的位置了,在这里,我们就用本机:
spring.redis.host=localhost
spring.redis.port=
这样以来,最简单的spring boot + redis实现session共享就完成了,下面进行下测试。
首先我们开启两个tomcat服务,端口分别为和,在application.properties中进行设置下载地址 :
server.port=
接下来定义一个Controller:
@RestController
@RequestMapping(value = "/admin/v1")
public class QuickRun {
@RequestMapping(value = "/first", method = RequestMethod.GET)
public Map
Map
request.getSession().setAttribute("request Url", request.getRequestURL());
map.put("request Url", request.getRequestURL());
return map;
}
@RequestMapping(value = "/sessions", method = RequestMethod.GET)
public Object sessions (HttpServletRequest request){
Map
map.put("sessionId", request.getSession().getId());
map.put("message", request.getSession().getAttribute("map"));
return map;
}
}
启动之后进行访问测试,首先访问端口的tomcat,返回 获取下载地址 :
{ "request Url":"http://localhost:/admin/v1/first"}
接着,我们访问端口的sessions,返回:
{ "sessionId":"efccc0-9ad2-a6-af-b5","message":http://localhost:/admin/v1/first}
最后,再访问端口的sessions,返回:
{ "sessionId":"efccc0-9ad2-a6-af-b5","message":http://localhost:/admin/v1/first}
可见,与两个服务器返回结果一样,实现了session的共享
如果此时再访问端口的first的话,首先返回:
{ "request Url":"http://localhost:/admin/v1/first"}
而两个服务器的sessions都是返回:
{ "sessionId":"efccc0-9ad2-a6-af-b5","message":"http://localhost:/admin/v1/first"}
通过spring boot + redis来实现session的共享非常简单,而且用处也极大,配合nginx进行负载均衡,便能实现分布式的应用了。
本次的redis并没有进行主从、读写分离等等配置(_(:з」∠)_其实是博主懒,还没尝试过.......)
而且,nginx的单点故障也是我们应用的障碍......以后可能会有对此次博客的改进版本,比如使用zookeeper进行负载均衡,敬请期待。
Laravel Session 源码解析
Laravel的Session服务是为了解决HTTP协议的无状态性问题,通过在客户端和服务器之间共享用户数据。Session的核心是Session Manager,它负责管理各种后台驱动程序的创建和访问。SessionServiceProvider在框架启动时注册Session服务,其中包括SessionManager、SessionHandler和StartSession中间件的创建。
SessionManager通过创建器实例化不同的驱动器,如文件、数据库或Redis等,这些驱动器通过SessionHandler统一访问数据存储。开发者通过Session门面或$request->session()调用的session方法,实际上是通过SessionManager转发给对应的驱动器执行相应的操作。
数据的加载和持久化由StartSession中间件处理。在每次请求进入时,它会启动Session,设置session id到客户端的Cookie中,若使用CookieSessionHandler,还会将session数据存入Cookie。响应发送后,非CookieSessionHandler的驱动器会在terminate方法中进行数据持久化,但具体原因可能在Cookie服务的源码中能找到答案。
整个Session机制确保了用户状态在请求间的连续性,但具体实现细节涉及StartSession中间件的配置和驱动器的交互。更多深入的源码分析,可参考系列文章。
SpringBoot整合SpringSeesion实现Redis缓存
使用Spring Boot开发项目时我们经常需要存储Session,因为Session中会存一些用户信息或者登录信息。传统的web服务是将session存储在内存中的,一旦服务挂了,session也就消失了,这时候我们就需要将session存储起来,而Redis就是用来缓存seesion的一种非关系型数据库,我们可以通过配置或者注解的方式将Spring Boot和Redis整合。而在分布式系统中又会涉及到session共享的问题,多个服务同时部署时session需要共享,Spring Session可以帮助我们实现这一功能。将Spring Session集成到Spring Boot框架中并使用Redis进行缓存是目前非常流行的解决方案,接下来就跟着小编一起学习吧。
工具/材料
IntelliJ IDEA
1、首先我们创建一个Spring Boot 2.x的项目,在application.properties配置文件中添加Redis的配置,Spring和Redis的整合可以参考小编其他的文章,此处不再详解。我们设置服务端口server.port为端口用于启动第一个服务。
2、接下来我们需要在pom文件中添加spring-boot-starter-data-redis和spring-session-data-redis这两个依赖,spring-boot-starter-data-redis用于整合Spring Boot和Redis,spring-session-data-redis集成了spring-session和spring-data-redis,提供了session与redis的整合方案。
3、接下来我们创建一个配置类RedisSessionConfig,这个类使用@Configuration注解表明这是一个配置类。在这个类上我们同时添加注解@EnableRedisHttpSession,表示开启Redis的Session管理。如果需要设置失效时间可以使用@EnableRedisHttpSession(maxInactiveIntervalInSeconds = )表示一小时后失效。若同时需要设置Redis的命名空间则使用@EnableRedisHttpSession(maxInactiveIntervalInSeconds=, redisNamespace={ spring.session.redis.namespace}) ,其中{ spring.session.redis.namespace}表示从配置文件中读取这个命名空间。
4、配置完成后我们写一个测试类SessionController,在这个类中我们写两个方法,一个方法用于往session中存数据,一个用于从session中取数据,代码如下图所示,我们存取请求的url。启动类非常简单,一般都是通用的,我们创建一个名为SpringbootApplication的启动类,使用main方法启动。
5、接下来我们使用Postman分别请求上面两个接口,先请求存数据接口,再请求取数据接口,结果如下图所示,我们可以看到数据已从redis中取出。另外需要注意sessionId的值,这是session共享的关键。
6、为了验证两个服务是否共享了session,我们修改项目的配置文件,将服务端口server.port改为,然后再启动服务。此时我们不必在请求存数据的接口,只需要修改请求端口号再一次请求取数据的接口即可。由下图可以看到两次请求的sessionId值相同,实现了session的共享。
7、以上我们完成了SpringBoot整合SpringSeesion实现Redis缓存的功能,在此我们还要推荐一个Redis的可视化工具RedisDesktopManager,我们可以配置Redis数据库的连接,然后便可以非常直观地查看到存储到Redis中的session了,如下图所示,session的命名空间是share,正是从配置文件中读取到的。
特别提示
如果Redis服务器是很多项目共用的,非常建议配置命名空间,否则同时打开多个项目的浏览器页面可能会导致session错乱的现象。
Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享
Shiro权限管理框架第二篇深入讲解了如何结合Redis实现分布式环境下的Session共享。在集群环境中,单台服务器已无法满足高并发访问的需求,需要部署集群服务器以分担压力。然而,随着集群服务器的引入,如何在不同服务器间保持用户会话状态成为了一个挑战。
在无状态的HTTP协议下,通过Session和Cookie机制可以实现用户状态的持久化。用户在首次访问服务器时,服务器为其创建Session,并将唯一SessionId存储在Cookie中,以便在后续请求中识别用户。但随着集群环境的使用,同一用户在不同服务器间的Session无法共享,导致用户需要在每个服务器重新登录,这显然无法提供良好的用户体验。
为了解决这个问题,通常有两种方式:一是将用户请求固定到某一台服务器,通过IP算法或其他机制实现负载均衡。二是将所有服务器的Session进行共享,使得任何一台服务器都能访问到其他服务器的Session,确保用户在不同服务器间的连续性。Shiro结合Redis实现分布式Session共享,正是基于后一种策略。
通过继承Shiro的AbstractSessionDAO类,开发者可以轻松实现Session的增删改查操作,结合Redis作为分布式存储,可以高效地实现Session的分布式共享。Shiro框架本身已经封装了大部分流程,开发者只需关注具体的业务实现和配置,从而简化了复杂性。
实现过程包括自定义RedisSessionDAO、注入SessionManager、配置Shiro安全管理器等步骤,确保所有服务器间Session的一致性和可访问性。测试环节验证了分布式Session共享的正确性,确保了用户在不同服务器间登录状态的一致性。
基于Redis实现的Session共享,不仅简化了开发过程,而且提高了系统的扩展性和可用性。Shiro框架的使用,使得在不深入源码的情况下,即可实现强大的功能,这体现了框架设计的优秀性和实用性。然而,对于深入理解框架内部工作原理和机制,以提升开发者的编程能力和系统理解,同样重要。因此,深入Shiro源码的探索,将有助于开发者更全面地掌握这一框架的精髓。
通过Shiro结合Redis实现的分布式Session共享,不仅解决了集群环境下的用户会话一致性问题,还展示了框架设计如何通过抽象和封装,将复杂的系统设计简化为易于理解和使用的API,为开发者提供了高效解决问题的工具。这一过程不仅提高了开发效率,还促进了对框架核心机制的深入理解,为未来的项目开发和维护打下了坚实的基础。