范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

SpringDataRedis两个问题内存泄露和并发europace

  我们最近将会话管理从 MongoDB 迁移到了 Redis。迁移本身是由我们使用 MongoDB 的经验推动的,它不能特别好地处理高频率更新和更频繁地读取。另一方面,Redis 被称为经过验证的存储,可以准确处理该用例。
  数据库迁移并不总是那么容易,因为我们需要学习其他服务的新模式、最佳实践和怪癖。我们的目标是让我们的 Java 服务层尽可能简单,使其稳定且面向未来:会话管理当然是具有相当稳定功能集的服务之一,并且不会经常触及其代码。因此,对于几年后窥探它的任何人来说,保持它的简单易懂是一个重要方面。
  我们面临两个问题: Spring Data 实现二级索引的概念以及失效问题,这些导致Redis内存使用量不断增长。 Redis 的原子性范围和 Spring Data 的更新机制
  本文总结了我们在使用 Spring Data 作为持久层的瘦 Java 服务中采用 Redis 的经验。
  带有二级索引和 EXPIRE/TTL 的 Spring Data Redis
  在 Redis 中采用 Spring Data可直接开始:您需要的只是 Gradle 或 Maven 构建的依赖项以及@EnableRedisRepositoriesSpring Boot 应用程序中的注释。Spring Boot 的大多数默认设置都是有意义的,并且可以让您非常顺利地运行 Redis 实例。
  但是会遭遇:Redis内存使用量不断增长的问题,下面看看这个认识过程:
  不需要通用存储库的实际实现,因为 Spring Data 允许您interface在运行时声明一个简单的通向通用实例。我们的存储库是这样开始的: import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository;  @Repository public interface SessionDataCrudRepository extends CrudRepository { }
  我们由该存储库管理的实体也开始变得尽可能简单:import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.TimeToLive;  import java.util.concurrent.TimeUnit;  @RedisHash("SessionData") public class SessionData {    @Id   private String sessionId;   @TimeToLive(unit = TimeUnit.MINUTES)   private Long ttl;    ... } 
  您会注意到我们选择对ttl属性建模,该属性被@TimeToLive转换为 EXPIRE 实体。我们不想手动跟踪过期会话,但希望 Redis 透明地删除过期会话。该ttl会定期刷新用户活动期间,如果手工删除,可能会被注销。
  当用户实际按下注销按钮时会发生什么,或者我们如何禁用用户帐户并使正在运行的会话无效?简单:我们也有一个userId作为会话数据SessionData的一部分,并且可以执行以userId查询查找每个会话。上述类型所需的更改如下所示:
  SessionDataCrudRepository:@Repository public interface SessionDataCrudRepository extends CrudRepository {     List findByUserId(String userId); }
  SessionData:+import org.springframework.data.redis.core.index.Indexed;  @RedisHash("SessionData") public class SessionData {      @Id     private String sessionId;     @TimeToLive(unit = TimeUnit.MINUTES)     private Long ttl;  +    @Indexed +    private String userId;      ... } 
  @Indexed注解在 Spring Data 中触发了一个特殊的行为:该注解实际上告诉 Spring Data在实体上创建和维护另一个索引,以便我们可以根据给定userId查询SessionData.
  但是,二级索引和实体自动到期的组合使设置变得更加复杂。当引用的实体被删除时,Redis 不会自动更新二级索引,因此 Spring Data 需要处理这种情况。
  然而,Spring Data 不会经常查询 Redis 的过期实体(键),这就是为什么 Spring Data 依赖于 R Redis Keyspace Notifications for expiring keys 所谓的 Phantom Copies( 幻影副本 )来失效过期键:
  当到期时间设置为正值时,将运行相应的 EXPIRE 命令。除了保留原始副本外,Redis 中还保留了一个幻影副本,并设置为在原始副本之后 5 分钟过期。这样做是为了使 Repository 支持发布 RedisKeyExpiredEvent,只要一个键过期 expiring key ,就会在 Spring 的 ApplicationEventPublisher 中间保存过期的值,即使原始值已经被删除。
  下一段有一个小细节需要注意:
  默认情况下,初始化应用程序时禁用 expiring keys 侦听器。可以在 @EnableRedisRepositories 或 RedisKeyValueAdapter 中调整启动模式,以使用应用程序或在第一次插入具有 TTL 的实体时启动侦听器。有关可能的值,请参阅 EnableKeyspaceEvents。
  遗憾的是,当时我们还没有阅读到这点。这就是为什么我们体验到启用EXPIRE禁用的expiring keys侦听器以及不断增长的二级索引的效果的原因。长话短说:我们观察到越来越多的键和不断增长的内存使用量 - 直到达到 Redis 的内存限制。
  检查 Redis 键可以很明显地找到配置错误的位置,最终启用键空间事件的注释@EnableRedisRepositories使我们修复了内存泄露。
  我们还禁用了 的自动服务器配置notify-keyspace-events property,因为我们在服务器端启用了该设置:import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;  import static org.springframework.data.redis.core.RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP;  @EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP, keyspaceNotificationsConfigParameter = "") @SpringBootApplication public class SessionManagementApplication {     ... } 
  我们还必须手动清理陈旧的数据,所以我们还要提一下,在处理大型数据集时,您应该总是更选择 SCAN 而不是KEYS。Netflix 的 nf-data-explorer 可能会有所帮助,如果您不喜欢使用本机redis-cli.
  并发读取和写入期间缺少实体
  随着内存使用量不断增长的问题得到解决,我们最终将新服务作为我们会话的主要来源。
  当请求击中我们的安全链时,我们总是验证用户的会话是否有效。这些验证是在会话管理中的简单查找sessionId。通常,404 NOT FOUND会话管理的状态指示sessionId无效(未知)或会话已过期(并被 Redis 删除)。
  除了使用新 API 的应用程序中的一些相关更改外,我们还观察到了另一种奇怪的行为:无法找到某些会话,尽管我们 100% 确定会话应该仍然有效(已知且未过期)。在会话查找失败后,大多数重试都成功了,所以我们知道数据没有丢失,只是无法找到。
  我们无法主动重现错误行为,收集日志、指标和跟踪也没有起到作用。在此过程中,我们添加了缓存和其他解决方法,并进行了一些更改以改进整体行为,但我们实际上并未解决该问题。
  如果您仔细阅读本文的第一部分,您可能还记得有关我们刷新ttl. 我们不仅刷新ttl,而且还刷新作为SessionData的一部分lastResponse时间戳:@RedisHash("SessionData") public class SessionData {    @Id   private String sessionId;   @TimeToLive(unit = TimeUnit.MINUTES)   private Long ttl;   private LocalDateTime lastResponse;    @Indexed   private String userId;    ... } 
  因此,让我们更详细地了解有关会话管理的请求处理。用户发送一个请求,以及一个sessionId,表明他们已登录。我们使用它执行查找sessionId以验证用户的会话。如果会话被认为是有效的,则应用程序可以继续执行请求的操作。应用程序处理完请求后,安全链会定期更新会话,重置ttl和写入当前lastResponse时间戳。通常,用户执行多个请求——可能不是真正的人,而是在浏览器中运行的前端应用程序。该前端应用程序并不真正关心它发送新请求的频率,因此我们可以假设多个请求可能同时到达我们的后端。
  正在验证多个请求。多个请求触发会话刷新以及SessionData的写操作.
  我们仍然使用 Spring DataCrudRepository来读取和更新会话,使用以下代码:读: SessionDataCrudRepository repository;  public Optional getSession(String sessionId) {   Optional session = repository.findById(sessionId);   ...   return session; }  更新: SessionDataCrudRepository repository;  public Optional refreshSessionTtl(String sessionId) {   Optional session = repository.findById(sessionId);    AtomicLong updatedTtl = new AtomicLong();   session.ifPresent(data -> {     data.setLastResponse(LocalDateTime.now(clock).truncatedTo(SECONDS));     data.setTtl(SESSION_TIMEOUT.toMinutes());      SessionData saved = repository.save(data);     updatedTtl.set(saved.getTtl());   }   return Optional.of(updatedTtl.longValue()); }
  有时,repository.findById(...)没有产生任何东西,所以我们专注于那部分。不过,问题是由repository.save(...)电话引发的。经过几周的谷歌搜索并盯着日志和跟踪,我们发现了refreshSessionTtl和getSession调用之间的相关性。
  互联网上的许多文章已经训练我们将 Redis 视为单线程服务,按顺序执行每个请求。谷歌搜索"spring data redis concurrent writes",我们找到了stackoverflow和 spring-projects/spring-data-redis/issues/1826 中的问题,在那里描述甚至解释了我们的问题 - 以及修复.
  长话短说:Spring Data 将更新实现为DEL和HMSET两个步骤时,没有任何事务保证。换句话说:通过 CrudRepositories 更新实体不提供原子性。我们的HGETALL请求有时恰好发生在DEL和之间HMSET,导致空结果,或者有时有结果,但结果为 负ttl 。
  我们的问题现在可以通过集成测试重现并使用 PartialUpdate .
  所以上面的实现改为:KeyValueOperations keyValueOperations;  public Optional refreshSessionTtl(String sessionId) {   Optional session = repository.findById(sessionId);    AtomicLong updatedTtl = new AtomicLong(-3);   session.ifPresent(data -> {     PartialUpdate update = new PartialUpdate<>(data.getSessionId(), SessionData.class)         .refreshTtl(true)         .set("ttl", SESSION_TIMEOUT.toMinutes())         .set("lastResponse", LocalDateTime.now(clock).truncatedTo(SECONDS));     keyValueOperations.update(update);     Optional saved = repository.findById(data.getSessionId());     if (saved.isPresent()) {       updatedTtl.set(saved.get().getTtl());     }   }   return Optional.of(updatedTtl.longValue()); } 
  概括
  过期键、二级索引和将所有魔法委托给 Spring Data Redis 的组合需要正确配置键空间事件侦听器。否则,由于幻影副本,您使用的内存会随着时间的推移而增长。考虑@EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP)在您的应用中使用类似的配置。
  在并发读取和更新的环境,提防Spring Data的CrudRepository工具的更新的过程分为两个步骤DEL和HMSET。如果您观察到零星丢失的键或结果为负值TTL,则您可能遇到了并发问题。检查您的写入操作并考虑使用 PartialUpdate和 Spring Data 的RedisKeyValueTemplate update方法更新需要改变的属性 。

腾讯视频会员涨价腾讯VIP提高了会员的价格。VIP每月价格从20元调整为25元,VIP连续套餐季节从58元涨到68元,而VIP的年度持续套餐年将从218元提高至238元。去年4月,腾讯视频宣布调整米家新风空调立式3匹开卖消息称鸿蒙3。0用户版5月内测科技犬今日,米家新风空调立式3匹正式在小米商城小米有品等官方渠道上架预售,预售价6999元。据悉,米家新风空调立式3匹无需扩孔可实现75m新风量,单独开新孔新风量可达215mh,可安兔兔发布2022年3月全球最强的10款安卓手机安兔兔已发布了对中端智能手机的评级信息。iQOOZ5排第一,realmeGTMaster排第二,小米11Lite排第三。前两款设备搭载骁龙778G处理器,小米11Lite搭载骁龙7马斯克星链计划,是否有不可告人的目的?你想,你细细想,你想有什么就有什么,不过有一点可以告诉你,假如你能超过马斯克,马斯克一定会祝贺你,并向你学习,而不是想你会有什么不可告人的目的,我预言一下星链计划不可能成功!只能说15000左右的预算,买索尼a7m3还是尼康z6,手头没有索尼尼康镜头?对这个问题可以参考我刚刚发一篇文章。这篇文章我是从日本价格网站上编译过来的,这篇文章主要讲的是佳能的eosreosrp和尼康的z6,还有索尼的a7m3,这四款机器的主要性能和拍摄画美团要求部分商家不上饿了么平台,如果上饿了么就要增加百分之十的扣点,是否合理?之前和一些商户有聊过,在一些城市里面,情况基本是存在的,今年美团也确实推出了商户如果和饿了么以及其他平台也有合作的话,美团平台就要提高点数,如果商户还是只和美团签独家,则抽成的点数4月千元机如何买?这三款手机售价亲民,性能还一款比一款高到了4月份新手机也陆陆续续的上新了不少,这其中有售价高昂的旗舰机,也有入手门槛极低的百元机,但是对于消费者来说最关心的还是千元机,毕竟随着手机行业的蓬勃发展,千元机不仅有着够用的性从5299腰斩至2799,拥有2K曲面屏IP68防水顶级旗舰,目前真香3000档是性价比旗舰手机的价位,竞争品牌一般是RedmirealmeiQOO等子品牌,目前这个价位最受关注的就是RedmiK50Pro,因为搭载了联发科天玑9000处理器,性能为首款搭载骁龙8Gen1芯片的手机或将于6月发布近日,GSMArena报道,高通骁龙8Gen1芯片组可能比预期来得要更快。供应链人士透露,搭载骁龙8Gen1的安卓旗舰手机最早将于6月发布,最晚于7月推出。首批搭载骁龙8Gen1的摩托罗拉edgeX40曝光2K超级屏18GB大运存,真香预定随着手机市场的优胜劣汰,以至于很多曾经知名的手机品牌跌落神坛,尤其是那些不思进取的手机品牌更是惨遭淘汰。摩托罗拉手机是一个大家都耳熟能详的手机品牌,是一个在行业里拥有较高地位的手机新一轮快递停发,电商们该何去何从3月份疫情爆发,我们这个小县城也受到了一波冲击,一夜之间,全城都静了下来,被封闭隔离两周左右,陆续解封后,紧接着是我们本地的生鲜水果蔬菜陆续要上市了,露天的桃花开了,香椿芽陆续发芽
3999元起售!OPPOFindX5系列正式发布,天玑9000芯片来啦OPPOFindX5系列手机正式发布,号称一帧影像,动用两块芯片,不止带来自研马里亚纳X芯片,还有联合哈苏共同调教影像系统。FindX5作为标准版手机,FindX5配置方面和另外两2022年年初手机盘点这三款手机不仅性能强悍,价格还亲民马上就进入三月份了,今年年初各大手机品牌持续发力,推出了不少新机型。对于想要购买手机的朋友来讲,选择更多了,但同时也增添了购买手机的难度,今天妖妖就给大家推荐三款,目前来看,值得大从2699跌至1979元,256GBOIS防抖,vivo爆款旗舰加速退场你是否还记得照亮你的美这句广告词,没错,这是早些年vivo为了宣传自家的手机,特意打造的广告语。很多人因为这句广告语,喜欢上了vivo手机。凭借着超强的品牌号召力,vivo旗下的诸为什么iPadMini6是(几乎)完美的iPad关键要点M1Mac使iPadsPro的吸引力降低。iPadmini是Mac卡车的完美汽车。iPadmini是尺寸和功能的完美结合。iPadmini6不是最强大的iPad,它没有最好这款四川造论文下载查重双免费!无论你是科技工作者,还是在校大学生,是否遇到过这样的问题呢想要下载一份文献资料,但是需要花钱。写好的学术论文想要查重,也需要花钱。四川省科协给你支个招,快注册使用天府科技云。在上面第三方支付有新平台?国家推出直销银行,有什么影响呢?当今社会发展非常迅速,稍纵即逝间人们的生活方式就发生了巨大的变化,在三四十年前,很多人都不知道手机是何物,不知道通过手机能实现买东西和卖东西,更不知道不需要用现金就可以完成一系列的文化科技融合的倍增效应党的十八大以来,我国文化创意产业呈现出高速发展态势。科技和文化二元共生,数字科技给文化产业价值链带来变革,文化为本元灵魂,赋予产业生命力。文化科技融合既是提升文化软实力的重要手段,C语言typedef和defineC语言允许为一个数据类型起一个新的别名,就像给人起绰号一样。起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如有一个结构体的名字是stu,要想定义一个结构体变量就得这样写互联网废品回收行业将在我国循环经济体系中发挥重要作用传统的废品回收行业,伴随着城市的发展而产生,至今已经走过了数年的时光,属于我们认知中的老行当,但是近些年出于对环境的保护,在绿色经济发展的要求之下,它固有的不足之处就被放大化,比如拥抱虚拟世界中国电子游戏制造商创造元宇宙品牌据香港南华早报网站报道,热门游戏原神制作商米哈游在全球扩张之际创立元宇宙品牌。中国电子游戏制造商著名手游原神的开发商米哈游公司启动了一个名为HoYoverse的全球元宇宙品牌,成为为什么感觉钱越来越不值钱?定律羊毛出在养身最后羊买单。一根冰棍,生产成本1毛钱,出厂两毛钱,到代理商手里变成两毛五分钱,到零售商手里变成五毛钱,到消费者手里变成一块钱。出厂两毛钱,工厂赚取1毛钱(赚钱靠大量