Redis使用示例

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-07-03

安装:brew install redis mac

启动服务:nohup redis-server &

在Redis中是通过键来访问数据的,因此,可以将Redis想象成是JavaScript对象的方式存储数据的。Redis中的文档结构总是扁平的,举例来说,即使一个键包含类似哈希的JavaScript对象,却不能支持嵌套的数据结构。

Redis设计的初衷是内存存储,搭配可配置的磁盘持久化思路,所以速度很快,有一点很重要,需要记住:持久化到磁盘是很重要的,因为任何存储在内存中的东西都是不稳定的,而且会随着系统崩溃或者重启而受到影响。

Redis查询语言

Redis 查询语言,就好比 Redis 中的 SQL,首先确保服务器正常运行,随后执行如下命令。

# 与 Redis 服务器建立 telnet连接
redis-cli

# 设置一个值
redis 127.0.0.1:6379 > SET my.key test
OK

# 读取一个值
redis 127.0.0.1:6379 > GET my.key
test

# 返回所有的键
redis 127.0.0.1:6379 > KEYS *
1) "my.key"

对数字进行递增和递减

redis 127.0.0.1:6379 > SET online.users 0
OK

redis 127.0.0.1:6379 > INCR online.users
(integer) 1

redis 127.0.0.1:6379 > INCR online.users
(integer) 2

普通操作,如果要存储的数据下

{
  "profile1": { "name": "Guillermo", "last": "Rauch"},
  "profile2": { "name": "Tobi", "last": "Rauch"},
}

如何操作

# 设值 等同于 obj[profile1].name = 'Guillermo';
redis 127.0.0.1:6379 > HSET profile1 name Guillermo

# 获取一个指定哈希中所有的键和值
redis 127.0.0.1:6379 > HGETALL profile1
1) "name"
2) "Guillermo"

redis 127.0.0.1:6379 > HSET profile1 last Rauch
redis 127.0.0.1:6379 > HSET profile1 programmer 1

# 在哈希中删除一个键 等同于 delete obj[profile1].programmer
redis 127.0.0.1:6379 > HDEL profile1 programmer

# 检查某个字段是否存在 等同于 typeof obj[profile1].programmer != 'undefined'
redis 127.0.0.1:6379 > HEXISTS profile1 programmer

# 列表
# RPUSH push到右侧也就是列表的尾端 LPUSH push到左侧,也就是列表的顶端
redis 127.0.0.1:6379 > RPUSH profile1.jobs "job 1"
(integer 1)

redis 127.0.0.1:6379 > RPUSH profile1.jobs "job 2"
(integer 2)

# 获取指定返回的数组
redis 127.0.0.1:6379 > LRANGE profile1.jobs 0 -1
1) "job 1"
2) "job 2"

redis 127.0.0.1:6379 >  LPUSH profile1.jobs "job 0"

redis 127.0.0.1:6379 >  LRANGE profile1.jobs 0 -1
1) "job 0"
2) "job 1"
3) "job 2"


# 数据集 
# 保存的是单个值(字符串),没有键。Redis允许在数据集、联合(union)获取到随机元素之间做交集操作

# 添加一个元素到数据集中 已存在的值不会重复添加
redis 127.0.0.1:6379 > SADD myset "a member"
(integer 1)

# 获取数据集的所有元素
redis 127.0.0.1:6379 > SMEMBERS myset
1) "a membe"

# 以相同的值再次调用 SADD不会发生任何事
redis 127.0.0.1:6379 > SADD myset "a member"
(integer 0)

redis 127.0.0.1:6379 > SMEMBERS myset
1) "a membe"

# 移除数据集中的某个元素
redis 127.0.0.1:6379 > SREM myset "a member"

为什么使用Redis来存储用户的session数据,而不是Node本身来存储,其中的原因是:

  • 应用程序永远都无法享受到多线程带来的好处,如果存在Node中,随着应用程序负载不断增长,单进程无法承受所有的负载,需要将应用程序扩展到多进程或者多台计算机。

  • 每次重启应用都会丢失session数据,比如:在部署新代码的时候总是需要重启的。

应用:使用node-redis实现一个社交图谱

安装:npm install redis

需求:创建用户数据,用户与用户之间的关注、取关,用户查询自己的关注列表、粉丝列表、互粉列表。

// model.js
var redis = require('redis');

var client = redis.createClient();
/**
 * 用户模型 id标识符 data数据 follows 关注 followers 粉丝
 * 当 id1 关注了 id2
 * Add user id "2" to the user id "1" follows
 * Add user id "1" to the user id "2" followers
 */
function User(id, data) {
  this.id = id;
  this.data = data;
}

// 定义静态方法 用来从Redis查询结果中构建一个User实例
User.find = function (id, fn) {
  client.hgetall("user:" + id + ":data", function (err, obj) {
    if(err) return fn(err);
    // 这个new至关重要,因为水合的时候就需要将获得的数据变成User对象,之后用来操作
    fn(null, new User(id, obj)); 
  })
}

// 添加save方法,用来创建和修改用户信息
User.prototype.save = function (fn) {
  // 如果没有传入id 给一个随机数作为id
  if (!this.id) {
    this.id = String(Math.random()).substr(3);
  }

  // key value callback
  client.hmset('user:' + this.id + ':data', this.data, fn);
}

// 点击关注:关注谁(id), 回调
User.prototype.follow = function (user_id, fn) {
  client.multi()
  .sadd('user:' + user_id + ':followers', this.id) // 将当前用户设置为被关注人的粉丝
  .sadd('user:' + this.id + ':follows', user_id) // 将关注的人 添加当前用户的 关注列表中
  .exec(fn);
  // multi意味着告诉redis客户端 所有的命令必须等到exec执行后才能执行
}

// 取消关注
User.prototype.unfollow = function (user_id, fn) {
  client.multi()
  .srem('user:' + user_id + ':followers', this.id) 
  .srem('user:' + this.id + ':follows', user_id)
  .exec(fn);
}

// 获取当前用户的粉丝列表
User.prototype.getFollowers = function (fn) {
  client.smembers('user:' + this.id + ':followers', fn)
}

// 获取当前用户的关注列表
User.prototype.getFollows = function (fn) {
  client.smembers('user:' + this.id + ':follows', fn)
}

// 获取互相关注的列表 即关注者和粉丝的交集
User.prototype.getFriends = function (fn) {
  client.sinter('user:' + this.id + ':follows', 'user:' + this.id + ':followers', fn)
}

module.exports = User; // 导出模块

上面是用来构建模型,下面是测试功能的代码。

var User = require('./model');

// 创建测试用户
var testUsers = {
  'mark@facebook.com': {"name": 'Mark Zuckerberg'},
  'bill@microsoft.com': {"name": 'Bill Gates'},
  'jeff@amazon.com': {"name": 'Jeff Bezos'},
  'fred@fedex.com': {"name": 'Fred Smith'}
}

/**
 *
 * 用来创建用户的函数
 * @param {Object} users data
 * @param {Function} fn callback
 */
function create(users, fn) {
  var total = Object.keys(users).length;
  for (var key in users) {
    (function (email, data) {
      // 以邮箱作为id
      var user = new User(email, data);
      user.save(function (err) {
        if(err) throw err;
        --total || fn(); // 如果全部创建完成 就调用回调 total为0则代表全部创建完成
      })
    })(key, users[key])
  }
}


/**
 * 用于水合用户
 * 水合:将testUsers 从 {'mark@facebook.com': {name: 'Mark Zuckerberg'}}
 * 格式化为 User对象 {'mark@facebook.com': {id:'mark@facebook.com', data:{name: 'Mark Zuckerberg'}}}
 * @param {*} users
 * @param {*} fn
 */
function hydrate(users, fn) {
  var total = Object.keys(users).length;
  for (var key in users) {
    (function (email) {
      User.find(email, function (err, user) {
        if(err) throw err;
        console.log(user); // User { id: 'mark@facebook.com', data: { name: 'Mark Zuckerberg' } }
        // 此时的user已经是User对象
        users[email] = user; 
        --total || fn(); // 水合完成 调用回调
      })
    })(key)
  }
}

// 正式创建并水合
create(testUsers, function () {
  hydrate(testUsers, function () {
    console.log(testUsers);
    // 水合后,就可以测试模型上的其他操作了
    testUsers['bill@microsoft.com'].follow('jeff@amazon.com', function (err) {
      if(err) throw err;
      console.log('+ bill followed jeff'); // 关注

      testUsers['jeff@amazon.com'].getFollowers(function (err, users) {
        if(err) throw err;
        console.log('+ jeff\'s followers', users); // 获取粉丝列表

        testUsers['jeff@amazon.com'].getFriends(function (err, users) {
          if(err) throw err;
          console.log('+ jeff\'s friends', users); // 获取互粉列表

          testUsers['jeff@amazon.com'].follow('bill@microsoft.com', function (err) {
            if(err) throw err;
            console.log('+ jeff followed bill'); // 互粉

            testUsers['jeff@amazon.com'].getFriends(function (err, users) {
              if(err) throw err;
              console.log('+ jeff\'s friends', users); // 再次获取互粉列表
            })
            // + bill followed jeff
            // + jeff's followers [ 'bill@microsoft.com' ]
            // + jeff's friends []
            // + jeff followed bill
            // + jeff's friends [ 'bill@microsoft.com' ]
          })
        })
      })
    })
  })
})
// testUsers
// { 
//   'mark@facebook.com': { id: 'mark@facebook.com', data: { name: 'Mark Zuckerberg' } },
//   'bill@microsoft.com':  { id: 'bill@microsoft.com', data: { name: 'Bill Gates' } },
//   'jeff@amazon.com': { id: 'jeff@amazon.com', data: { name: 'Jeff Bezos' } },
//   'fred@fedex.com': { id: 'fred@fedex.com', data: { name: 'Fred Smith' } } 
// }

// 使用redis查询
// 127.0.0.1:6379> KEYS *
// 1) "user:jeff@amazon.com:data"
// 2) "user:fred@fedex.com:data"
// 3) "user:mark@facebook.com:data"
// 4) "user:bill@microsoft.com:data"
// 127.0.0.1:6379> HGETALL "user:jeff@amazon.com:data"
// 1) "name"
// 2) "Jeff Bezos"