Redis

说一下 Redis 和 Memcached 的区别和共同点?

🙋‍♂答:

相同点

  • 都是基于内存的数据库,一般都用来当做缓存使用。
  • 都有过期淘汰策略。
  • 两者的性能都非常高。

区别

  • Redis的数据结构更加丰富,Memcached只支持key-value数据类型;
  • Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 没有持久化功能,数据全部存在内存之中,Memcached 重启或者挂掉后,数据就没了;
  • Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持;
  • Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务功能,Memcached不支持事务。

总之,Memcached是解决简单缓存问题的可靠选择。然而,一般来说,「Redis通过提供更丰富的功能和各种各样的特性而优于Memcached,这些特性对于解决复杂的场景更有优势」

为什么用Redis作为MySQL的缓存?

  • 主要是因为Redis 具备「高性能」和「高并发」两种特性

    • 高性能:假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。

    • 高并发:单台设备的 Redis 的 QPS(Query Per Second,每秒钟处理完请求的次数) 是 MySQL 的 10 倍,Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。直接访问 Redis 能够承受的请求是远远大于直接访问 MySQL 的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

项目中哪里用到了Redis?

  1. 使用Redis作为缓存数据库来保存数据,比如点赞的数据、关注的数据。
  2. 验证码、登录凭证、用户信息都缓存在Redis中。
  3. 统计网站DAU、UV的数据也保存到了Redis中。

为什么用Redis保存点赞/关注的数据?使用了哪些数据类型?用到了哪些命令?

因为点赞、关注是一个很高频的操作,如果直接在数据库中使用Count,效率很低,所以可以使用Redis来提升性能,从缓存中查询数据,会比直接在硬盘中查询数据要快。

使用的数据类型:

  • 给帖子、评论点赞使用Set来存储。key代表某个帖子或者评论,value表示点赞的用户Id。
    • 使用Set进行存储,可以防止同一个用户多次点赞的情况,因为Set可以去重。
  • 用户收到的赞使用String来存储。key代表具体用户,value就是收到的赞数。
    • 比如用户的帖子获得一条点赞,那么就在给帖子增加点赞数的同时,给用户收到的赞 + 1 即可,所以就使用String来进行存储。
  • 关注、粉丝数据使用Zset来存储,key代表用户的关注 / 粉丝 ,value代表关注 / 粉丝 userId。关联的score 来保存关注时间,这样我们可以按照关注时间进行排序。
    • 使用Zset可以防止重复关注的问题,并且我们可以根据关注事件进行排序。

使用的命令:

  • Set:add、remove、ismember、size
  • String:decrement、increment
  • Zset:add、remove、ZCard【统计元素个数】、ZREVRANGE【时间从大到小排序】,ZRANGE【从小到大排序】

为什么用Redis保存验证码、登录凭证、用户信息?

用Redis保存验证码:

  1. 验证码需要频繁的刷新和访问,对性能的要求比较高
  2. 并且验证码不需要永久保存,需要设计过期时间来节约空间。
  3. 之前验证码都是保存在session中,在分布式部署中session会出现共享问题。将验证码保存到Redis中避免分布式session不可用的问题。

用Redis保存登录凭证、用户信息:

  1. 之前将登录凭证保存到MySQL中,导致每次都需要去login_ticket表中查询登录凭证,由于每次访问页面都需要在拦截器中对登录凭证进行检查,访问频率很高,所以用Redis进行存储。Redis是内存型数据库,访问很快。
  2. 需要在拦截器中根据登录凭证去查询用户信息,访问频率很高,所以用Redis进行存储。

为什么用Redis保存UV/DAU?使用了哪些数据类型?用到了哪些命令?

用户每访问一次网页,就需要向数据库中插入一条数据,如果日活很高,会消耗数据库资源。
并且如果要进行月度、年度的统计会使用count、group by这样效率很低,所以使用Redis来保存UV / DAU。

  • UV:独立访客,通过IP进行排重计算。HyperLogLog就可以满足去重要求。并且每个 HyperLogLog 键只需要花费 12 KB 内存,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。
    • ADD方法:传入VALUE = IP进行统计。
    • UNION方法:合并HyperLogLog的集合到新的HyperLogLog。
  • DAU:日活跃用户,通过用户ID进行去重。使用Bitmap一连串的二进制数组,可以统计精确结果,并且占用空间很小。
    • SETBIT方法:通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作。SETBIT login_status 101 1表示ID = 101 的用户 已登录。
    • OR运算:统计一周、月度的活跃用户。

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。

用过Redis的事务吗?怎么使用的?

Redis的事务提供了三个操作:

  • MULTI:事务开始执行的命令。
  • 在MULTI和EXEC之间发送的所有命令都会被记录下来,并在执行EXEC命令时按照顺序执行。这些命令并不会立即执行,而是被放入一个队列中等待执行。
  • EXEC:执行队列中的命令。

Redis事务的主要作用就是串联多个命令防止别的命令插队。项目中点赞操作就使用了事务。当用户执行点赞操作,我们需要在Set中增加一条数据,并且给被点赞的用户获赞数 + 1。这是两个操作,防止别的命令插队,所以使用事务。

注意:Redis不提供事务的回滚。所以这里实际的作用不大。

Redis 有哪些数据类型?SDS 了解么?

String、List、Hash、Set、Zet都是比较常用的数据类型。其中 String 数据类型的底层数据结构是 SDS(simple dynamic string,SDS)。

SDS这个数据结构中存在几个字段:

  • len,记录了字符串的实际长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
  • alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,并且根据需求自动修改空间大小,也不会出现前面所说的缓冲区溢出的问题。
  • buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。不会出现二进制安全的问题。
  • flags,用来表示不同类型的 SDS

对比C语言,主要有以下优势:

  • O(1)复杂度获取字符串长度
    • C 语言的字符串长度获取 strlen 函数,需要通过遍历的方式来统计字符串长度,时间复杂度是 O(N)。
    • 而 Redis 的 SDS 结构因为加入了 len 成员变量,那么获取字符串长度的时候,直接返回这个成员变量的值就行,所以复杂度只有 O(1)
  • 二进制安全
    • 因此, SDS 的 API 都是以处理二进制的方式来处理 SDS 存放在 buf[] 里的数据,程序不会对其中的数据做任何限制,数据写入的时候时什么样的,它被读取时就是什么样的。
      通过使用二进制安全的 SDS,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,也可以保存任意格式的二进制数据。
  • 不会发生缓冲区溢出
    • Redis 的 SDS 结构里引入了 alloc 和 len 成员变量,这样 SDS API 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用。
    • 而且,当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小,以满足修改所需的大小。
  • 节省内存空间
    • SDS 设计了不同类型的结构体,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。比如,在保存小字符串时,结构头占用空间也比较少。
    • 使用了专门的编译优化来节省内存空间,即在 struct 声明了 __attribute__ ((packed)) ,它的作用是:告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐

完。

05-29 04:44