引言
很久没有写文章了,因为也没在继续写项目,也很难遇到一些技术问题,其实最主要的是懒得去写了。
这是我第二次实习,但真正意义上算得上是第一次,我发现工作和个人开发还是有很大不同,个人开发追求的是极致的技术,要的是登上山顶的高度;而工作追求的是你能不能到达某个点,且会有很多限制,让你限制住一些腿脚,这不太舒服,但是也比较考验项目解决能力。所以开了这个专栏,来记录一些工作中遇到的问题。
需求介绍
这个背景也是蛮离谱,由于客户的内网策略导致无法访问 目标外网的任何资源,由于服务器资源也难以申请,也不能使用数据库等任何第三方,而且这是一个长连接(响应时间长会导致生产环境下触发超时策略)。那么我就只能这样设计,前端调后端接口带一个UUID,后端请求第三方api,把得到的数据及 UUID 作为 Key 存到内存里,然后前端拿着这个 uuid 去轮询,这样好像就万事大吉了。经理给我构思的也是这样,但是很明显这让我想到了,"内存泄漏",以前总是很难理解,只是记住定义:内存无法释放的情况是内存泄漏。
虽然我自己写的项目也有些深度,但确实很难遇到内存泄漏的情况,因为基本三段式的开发架构,请求都是无状态的,websocket会涉及到,但由于连接有自己的生命周期,可以直接挂载到其生命周期上就可以请以完成内存清理。
编程语言用的C#,java里的弱引用等等概念,我也不清楚.NET里有还是没有,这时候算法就派上用场了,我写代码依赖基本第一次深刻意识到算法的使用场景。
设计实现
存储这个数据肯定用map,因为这是典型的key-value结构,key存储uuid,value存储数据,前端带着uuid 发起请求,再轮询去请求,如果有数据就拿出来,删掉。但是这个系统它是无数据存储的,假如我点击生成数据之后,刷新页面就会导致 什么情况呢? 只生产不销售,这肯定会导致愈积愈多,光靠读完就删的策略肯定不够。
然后我就想要不这样,设置一个MAX容量,等容器超过MAX后,就删一个,可是啊map并未给你提供这个功能,接下来需要引用维护一个队列去保证,每次删掉的都是最旧的数据,这样可以基本保证数据可用。由于链表只存储一个uuid,内存占用很小,不对它进行维护。
刚开始,我这样设计:
- 当请求生成数据时,链表里存uuid,map里存uuid和数据;当添加完后,容量超出就取出queue的队尾元素,然后让map根据这个元素删值
- 当轮询时,轮询到了就根据删map
这个好像看起来天衣无缝,但这只只建立在map和queue数据同步的情况,由于只要生成queue里就会有数据,但删除queue的元素又是个棘手的问题(需要遍历),看起来乱七八糟,我写到这里也是混乱,那么就展开分析。
一共三种情况:
- 正常情况,正常存,正常取,map不变,queue多一数据
- 异常情况,正常存,不取,map + 1,queue也 + 1
所以queue一定会处于多于map的情况,而且是一直是这样,相当于map是queue的子集,所以删的每一个map的时候,可能会发生空删,但一定能删到,比如有下面一组数据。
[{1, null},{2, null},{3, null},{4, null},{5, null}]
[8, 3, 2, 4, 6, 1,5]
设置内存上限为5,现在要存一个{6, null},存完之后触发删除逻辑,拿出队尾元素,8去找map(O1复杂度),找不到,发生了空删,这时判断如果没有删到数据就接着删,直到有实际意义的删到,这样一来便削减了map的容量,实现了自动化,空间和时间复杂度耗费都较小。
这时就有人问了,你这肯定不对啊,那最坏情况,没人在生成数据的时候刷新,你这不是queue无限大了,map永远触发不了削减。是这样,但queue天生支持按时间顺序丢弃元素避免内存泄漏,当queue超过指定容量,直接弹出队尾元素即可。
本节完毕,由于这个需求过去了有段时间,所以语言表达可能不是很完善,望谅解。