koa2开发后台服务 - (5) nodejs连接redis

redis是一个内存数据库,和类似mysql这种硬盘关系型数据库相比,它的典型特点就是数据存储在内存中,访问速度快。通常,我们在处理用户登陆权限的问题中,就是使用redis来存储用户的session信息,它适合的典型场景就是:访问比较频繁的数据。

背景知识

  • http是一个无状态协议,由于http的无状态性,为了使某个域名下的所有网页能够共享某些数据,session和cookie出现了.

  • 什么是session?

session是一种服务器端的状态管理技术

当浏览器访问服务器时,服务器创建一个session对象(该对象有一个唯一的id号,称之为sessionId),服务器在默认情况下,会将sessionId以cookie的方式(set-cookie消息头)发送给浏览器,浏览器会将sessionId保存到内存。当浏览器再次访问服务器时,会将sessionId发送给服务器,服务器依据sessionId就可找到之前创建的session对象。

  • 客户端访问服务器端的流程:

客户端访问服务器端的流程

用户向服务器发送用户名和密码,认证成功之后,服务端会在当前会话session中保存相应信息(比如用户角色等)

服务器向用户返回一个 session_id,写入用户的 Cookie

用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器

服务器收到 session_id,找到前期保存的数据,由此得知用户的身份

用cookie存储用户敏感信息session行不行?

如果全部用cookie,数据量大的时候客户端是没有那么多空间的。账户信息全部保存在客户端,一旦被劫持,全部信息都会泄露

并且客户端数据量变大,网络传输的数据量也会变大(每次http请求的头中都携带cookie信息

扩展性不好,不适合服务器集群共享session,所以一般都是引入 session 数据持久化 - 写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败

使用redis存储session

  • 根据上面的介绍,大家应该明白了cookie-session来验证用户信息的机制,但是有人会问,既然你启动nodejs服务,他会启动一个进程,为什么要使用redis来存储session的信息,而不用nodejs进程的内存来存储?

不同的操作系统会限制进程的内存大小(32位操作系统最大分给nodejs进程0.7G/64位操作系统为1.4G)

单个进程的内存有限,硬件的内存远大于此,资源浪费。

进程之间的数据是隔离的,而且一般线上环境都是多进程的。会造成系统异常。

koa2中使用redis(redis-server和redis-cli)

启动redis-server

redis-server

启动redis-cli

redis-cli // 显示127.0.0.1:6379

nodejs中连接redis

  • 1)安装启动redis

    npm i redis –save

  • 2)读取配置建立redisClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
  /**
* @description 连接redis的方法
* @author unclepis
*/

const redis = require('redis')

const { redisConfig } = require('../conf/db')


// 创建redis客户端

const redisClient = redis.createClient(redisConfig.port, redisConfig.host); // 3306 localhost
redisClient.on('error', err => {
console.log('redis error', err)
})

/**
* 封装redis的get方法获取key对应的数据,一般需要存储成字符串,需要转化成json
* @param {string} key key
*/
let get = (key) => {
return new Promise((resolve, reject) => {
redisClient.get(key, (err, val) => {
if(err) {
reject(err)
return
}

if(!val) {
resolve(null)
return
}

try {
resolve(JSON.parse(val)) // 尝试进行json转换
} catch(error) {
resolve(val)
}
})
})


}


/**
* redis的set方法
* @param {string} key key
* @param {*} value value
* @param {*} timeout 过期时间 单位是s
*/
let set = (key, value, timeout = 60 * 60) => {
if(typeof value === 'object') {
value = JSON.stringify(value)
}
redisClient.set(key, value)
redisClient.expire(key, timeout)
}

module.exports = {
get,
set
}

在koa中使用redis存储session

npm i koa-redis koa-generic-session --save 安装插件

app.js引入中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    // 引入redis的配置
const redisStore = require('koa-redis')
const session = require('koa-generic-session')
const { redisConfig } = require('./conf/db')

// sessionId 进行加密处理
app.keys = [redisConfig.salt]

app.use(session({
key: 'sessionId', // cookie中存储sessionId的名字,默认是koaId
prefix: 'blog:sess:', // 在redis中对key增加一个前缀,默认是koa:sess:
cookie: {
path: '/',
httpOnly: true, // 不允许客户端修改
maxAge: 24 * 60 * 60 * 1000 // ms
},
ttl: 24 * 60 * 60 * 1000, //redis的过期时间,默认就是和cookie的过期时间一样
store: redisStore({
all: `${redisConfig.host}:${redisConfig.port}`
})

}))

在路由中使用ctx.session

1
2
3
4
5
6
7
8
9
10
11
router.get('/test', async (ctx, next) => {
const session = ctx.session;
if(!session.viewNum) {
session.viewNum = 0
}
session.viewNum++;
ctx.body = {
title: 'koa2 json',
viewNum: session.viewNum
}
})

可以看到/test的请求cookie中已经存在sessionId:xxxxx,而且在redis-cli中输入 keys * 也可以看到一个blog:sess:为前缀的key - blog:sess:xxxxxx,key的后半部分xxxxx和cookie中的xxxxx值一样。

在redis-cli中输入ttl可以查看redis的过期时间和cookie的过期时间也是差不多的(redis的过期时间为s,cookie为ms)

在登陆权限控制中的使用

  • 在login登陆之后,可以将用户的信息,比如username存入redis中(ctx.username=username),然后同一个用户在请求后台的时候,就会从cookie中获取sessionId,查询redis中存储的username信息

  • 就可以在中间件中,根据ctx.username是否存在判断用户是否登陆。有访问权限。

redis常用操作(持续补充)

  • get key 获取数据

  • set key value 设置数据

  • keys * 所有的key列表

  • flushall 可以清除所有数据库的值

  • flushdb 清除当前数据库的值

  • exit 退出

参考文献

彻底弄懂session,cookie,token

初到贵宝地,有钱的给个钱场,没钱的挤一挤给个钱场