我是傻x,被迫看了 1 天源码,千万别学我!

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 大家好,我是零一,之前一直很忙,业余时间的输入和输出都 24k铝合金人眼可见 得下降,这不最近上海疫情严重么,算了一下居家办公也已经将近 1个月了,这才有些许时间学习,所以最近也是一直在鼓捣点新东西,不为别的,主要是想再多输入一些新的知识

我这两天在学习 node 相关的知识时,做出了一些 愚蠢行为


在做用户登录相关业务时涉及到了 cookie、session 的存取,一搜就找到了 express-session 这个中间件,真香!配几个配置就可以自动生成 cookiesessionId


express-session 对于 session 的存储默认是存在内存中的


6d2690b66190fa61b405e12803e7ad36.png


这肯定不合适!


  • 项目一挂 或者 重启,session信息全丢了,所有用户都需要重新登录


  • 多个进程之间也无法共享 session 数据


然后去搜了下有没有 redis(key - value 形式的数据库) 相关的库,咔,又出来两个:


  • redis(github上叫 node-redis,npm上叫 redis


  • connect-redis


前者是提供了 js 可调用的操作 redis 数据库的底层方法;后者就是获取 redis 数据库实例,成为 express-session 存取 session 信息的载体


官网的使用说明也很清楚:


1e24838ae72b3966a2af3b4d9691f7db.png


后来在我调试时发现出现了很多的问题,比如第一次调用 API(不携带cookie),接口迅速响应,并带回了 Set-cookies 头,但是! 我接口响应的数据呢???我明明返回了


96cd47aee8af90ea35b13c78788c019e.jpg


最终 接口 15s 未返回,超时了


7408e27ce22fe0a52c480fb24ab308d2.png


因为这个问题应该很明显了,接口是有响应返回的,但返回数据这块儿出了问题,先快速定位了问题所在,就是 express-sessionredisconnect-redis 这三个库其中一个有问题,因为在接入这几个库之前,session存储在内存中是能正常返回的


于是我就去这几个库的 issue 里搜了一下是否有类似的问题,毕竟都是这么多 star 的库,明显的问题肯定早就被人提出来了,然而!没有


我又开始在搜索引擎搜索,也同样没有!


怎么回事... 合着就我一个人有问题,难道说...?


我要发现一个惊天大bug,然后给他们提pr,从此人生一帆风顺了?(hhhhh,不知道大家有时候有没有这样的想法)


这就开始了我的 debug 看源码之路


于是我 "咔",在 express-session 里打了个断点,同时也给 redis 服务开启了 monitor 监控,调试时发现 redis 存取值的时候 key 竟然不一致


0e002b2b8dabff0e65794c60064db1c9.png


啥玩意儿?SET 的 key 是一段 hash + cookie的json字符串,value 是一个函数字符串(存个函数干啥?此时我非常非常疑惑);GET 的 key 却只是一小段 hash


我找到源码里存取key的位置打上了断点,发现存取值的key确实不一样,但又因为此时我还没看多少源码,所以对这块儿的逻辑我暂且表示赞同,因为可能这就是别人库的 feature 呢?


提前说明一下,最终发现破案也是因为这里,但当时的我是没法断定的


没办法了,继续从头 debug 吧


res.status(200).send({ name: '01' })


先是我接口函数里返回了 http 状态码 200(怪不得我的接口立马返回了200)


391ed417544c40dccd1f95becf7f7c7f.png


然后 send 函数调用走到了 end 函数中


43ec0dc750303e2299e9c8430058c5d1.png


看了大致的逻辑,发现 express-session 重写了 resend 函数,在里面判断 session 信息有无修改,进而判断需不需要保存 session 到 store 里


// 存储原始的 end 函数
var _end = res.end;
var _write = res.write;
var ended = false;
// 重写 end 函数
res.end = function end(chunk, encoding) {
  // 执行额外的逻辑
  // ...
  // 最终执行原始的 end 函数
  return _end.call(res, chunk, encoding);
};


既然 debug 都走到 end 里了,说明问题快出来了,能猜到可能是原始的 end 函数没有被调用导致的了


e07c4ab7d72b24a0a7f14233118f3265.png


走到了调用 set 函数的地方,他第一个参数传了个数组,第二个参数传了个回调函数,该回调函数执行就会走到 原始 end 函数的调用,按道理来说就没问题了啊!


我继续下一步调试,发现根本没走到回调函数里,去查了一下 this.client.set 的 TS 类型


b073412e9e5b74332241cdd1f688d6b7.png


这次又证明了 redis 为什么 SET了key为一长串字符串,value为一个函数


this.client 是 redis 这个库提供的方法,我们是将 redis 生成的实例对象传给 express-session 作为 session 存储的载体,那现在看起来是 这两个库没互相兼容??


目前内心OS:两个这么大的库,更新了都不做互相兼容的么


于是我又跑回去看文档,gan!


a625abe782c9e0919e60b8360983f62b.png


还记得这个图么?这是我一开始准备用这些库时看的,但我没看到这个信息:当 redis 库为 v4 版本时,createClient 时需要加一个 legacyMode: true 的参数,开启传统模式?然后再回到我刚才发现的问题,这个参数是不是就是为了兼容 express-session 的?


于是我赶紧加上这个参数,嚯,真的好了!接口也迅速返回内容了


所以最终是 redis 做了版本升级,更改了 api 的使用方式


// 旧版 redis
client.set(key, value, cb)
// 新版 redis
await client.set(key, value)


确实还是新版的api使用起来舒服,换作旧版的,大家使用前可能还需要封装一下


但是 express-session 并没有更改 set 函数调用的传参方式,这也很正常,毕竟这个库只是为了 session 管理用的,而 redis 以及各种 db 库又不止是专门服务于 express-session 的,它们的关系是这样的:


02092821e9f4ddcda22b3b6f3b08076c.png


然而当 db 库进行了更新,就需要中间一层来连接了,也就是类似我们本文用到的 connect-redis,此时它们的关系是这样的


a6a45009afa45eec7fa667291db4f9d6.png


所以我们在用 connect-redis 时,传一个 legacyMode: true 参数就可以让 redis 兼容 express-session 的使用了


结论


该说啥呢,被自己蠢哭了,一开始不好好看文档,导致后面花了一天时间去排查问题,还被迫看了这么多源码,你要问我后悔吗?我又不后悔:被倒逼着去看了一个库的源码梳理了大致的逻辑学到了思想巩固了 cookie、session 的知识学会了 redis 库的调试和各种命令长了个教训(以后要好好看文档)


什么时候比较适合看源码呢?当然是有需要的时候,侬,比如我这次的经历


大家千万别学我一样,粗心大意,文档还是要好好看,这就跟以前上学的时候答题不仔细看题目一样,是大忌啊!!!


我是零一,分享技术,不止前端!我们下期见~

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
Java 程序员
IT学不好没什么,大不了躺平
IT学不好没什么,大不了躺平
|
Apache 云计算 开发者
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(1)
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!
161 0
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(1)
|
安全 小程序 程序员
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(2)
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!
108 0
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(2)
|
消息中间件 Kubernetes Cloud Native
记一次内部分享——瞎扯淡
记一次内部分享——瞎扯淡
记一次内部分享——瞎扯淡
|
Java 中间件 程序员
最网最全bug定位套路,遇见bug再也不慌了
最网最全bug定位套路,遇见bug再也不慌了
318 0
|
芯片
程序人生 - 手上总有静电该怎么处理?
程序人生 - 手上总有静电该怎么处理?
145 0
程序人生 - 手上总有静电该怎么处理?
|
JSON Java 测试技术
如何写出让人抓狂的代码?
如何写出让人抓狂的代码?
如何写出让人抓狂的代码?
|
安全 Java
老爷子这代码,看跪了! (下)
老爷子这代码,看跪了! (下)
130 0
老爷子这代码,看跪了! (下)
|
Java 程序员
老爷子这代码,看跪了! (中)
老爷子这代码,看跪了! (中)
141 0
老爷子这代码,看跪了! (中)
|
人工智能 安全 数据挖掘
这么一搞,再也不怕线程打架了
假如我们需要处理一个文本文件,里面有 100万行数据,需要对每条数据做处理,比如将每行数据的数字做一个运算,放入到另一个文件里。
145 0
这么一搞,再也不怕线程打架了