Redis入门使用手册 (Redis入门指南笔记)

网站推荐

Redis优点简述

来自Redis快速入门

  • 异常快速 : Redis是非常快的,每秒可以执行大约110000设置操作,81000个/每秒的读取操作。
  • 支持丰富的数据类型 : Redis支持最大多数开发人员已经知道如列表,集合,可排序集合,哈希等数据类型。这使得在应用中很容易解决的各种问题,因为我们知道哪些问题处理使用哪种数据类型更好解决。
  • 操作都是原子的 : 所有 Redis 的操作都是原子,从而确保当两个客户同时访问 Redis 服务器得到的是更新后的值(最新值)。
  • MultiUtility工具:Redis是一个多功能实用工具,可以在很多如:缓存,消息传递队列中使用(Redis原生支持发布/订阅),在应用程序中,如:Web应用程序会话,网站页面点击数等任何短暂的数据;
  • Redis 有三个主要使其有别于其它很多竞争对手的特点
    Redis是完全在内存中保存数据的数据库,使用磁盘只是为了持久性目的;
    Redis相比许多键值数据存储系统有相对丰富的数据类型;
    Redis可以将数据复制到任意数量的从服务器中。

Redis环境安装

  • 在 Ubuntu 上安装 Redis,打开终端输入以下命令:
$sudo apt-get update
$sudo apt-get install redis-server
# 启动 Redis
$redis-server
# 启动 客户端
$redis-cli

安全访问

  • Redis的安全设计是在“Redis运行在可信环境”这个前提下做出的。在生产环境运行时不能允许外界直接连接到Redis服务器上,而应该通过应用程序进行中转,运行在可信的环境中是保证Redis安全的最重要方法。
  • Redis的默认配置会接受来自任何地址发送来的请求,即在任何一个拥有公网IP的服务器上启动Redis服务器,都可以被外界直接访问到。配置文件中bind参数的设置参考配置redis外网可访问
  • 自由地设置访问规则需要通过防火墙来完成。
  • 数据库密码
    • 通过配置件中的requirepass参数为Redis设置一个密码。例如 requirepass asdfghjkl。客户端每次连接到Redis时都需要发送密码,否则Redis会拒绝执行客户端发来的命令。发送密码需要使用AUTH命令,例如 AUTH asdfghjkl
    • 由于Redis性能极高,并且输入密码错误后Redis并不会进行主动延迟(考虑到Redis的单线程模型),所以攻击者可以通过穷举法破解Redis的密码(1秒钟能够尝试十几万个密码),因此在设置时一定要选择复杂的密码
    • 此外,配置Redis复制的时候如果主数据库设置了密码,需要在从数据库的配置文件中通过masterauth参数设置主数据库的密码,以使从数据库连接主数据库时自动使用AUTH命令认证。

命令重命名

  • Redis支持在配置文件中将命令重命名,比如将FLUSHALL命令重命名为一个比较复杂的名字,以保证只有自己的应用可以使用该命令。例如rename-command FLUSHALL asdfghjkl。如果希望禁用命令可以重命名为空字符串""
  • 无论设置密码还是重命名命令,都需要保证配置文件的安全性,否则就没有任何意义了。

管理工具

  • Redis-cli 即原版客户端
    1. 当一条命令的执行时间超过限制时,Redis会将该命令的执行时间等信息加入耗时命令日志以供开发者查看。
      可以通过配置文件的slowlog-log-slower-than参数设置这一限制,要注意单位是微秒,默认值是10000。
      耗时命令日志存储在内存中,可以通过配置文件的slowlog-max-len参数来限制记录的条数。
      使用SLOWLOG GET命令获取。
      每条日志由以下4个部分组成:
      (1)该日志唯一ID;
      (2)该命令执行的Unix时间;
      (3)该命令的耗时时间,单位是微秒;
      (4)命令及参数。
    2. 命令监控 MONITOR。一个客户端使用该命令会降低Redis将近一半的负载能力。可以使用Redis-faina (Instagram团队开发的基于MONITOR命令的Redis查询分析程序)
  • phpRedisAdmin,图形化管理工具
    安装:

git clone https://github.com/ErikDubbelboer/phpRedisAdmin.git
cd phpRedisAdmin
git submodule init
git submodule update

  - 配置:�默认phpRedisAdmin会连接到127.0.0.1,端口6379,如果需要更改或者添加数据库信息可以编辑includes文件夹中的config.inc.php文件。
  - 由于Redis使用单线程处理命令,所以对生产环境下拥有大数据量的数据库来说不适宜使用该管理器。
- **Redis桌面管理**可从 [redisdesktop](http://redisdesktop.com/download) 下载。Redis 桌面管理器会提供管理 Redis 键和数据的用户界面。
- **Rdbtools**是一个Redis的快照文件解析器,可以根据快照文件导出JSON数据文件、分析Redis中每个键的占用空间情况等。

### 使用实例与技巧
1. 文章访问量统计,**使用字符串类型的`INCR posts:文章ID:page.view`来记录文章的访问量**。每次访问时键值递增。(另外文章数据也可以在序列化之后使用字符串类型存储)
2. 利用位操作(对于字符串类型键使用)命令可以非常紧凑地存储布尔值。比如如果网站的每个用户都有一个递增的整数ID,如果**使用一个字符串类型键配合位操作来记录每个用户的性别**(用户ID作为索引,二进制位值1和0表示男性和女性),那么记录100万个用户的性别只需占用100KB多的空间,而且由于GETBIT和SETBIT的时间复杂度都是O(1),所以**读取二进制位值性能很高**。(在一台2014年的MacBookPro笔记奔上,设置偏移量232-1的值(即分配500MB的内存)需要耗费将近1秒的时间)。要注意的是分配过大的偏移量不仅会造成服务器阻塞,还会造成空间浪费。

位操作:
SETBIT key offset valueGETBIT key offset
BITCOUNT key 可以获得字符串类型键中值是1的二进制位个数
BITOP可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键中

3. 利用散列类型存储文章数据。**使用`post:文章ID键+title/author/time/content等字段`存储**。美中不足的是散列类型没有类似字符串类型的MGET命令那样可以通过一条命令同时获得多个键的键值的版本,所以对于每个文章ID都需要请求一次数据库,也就都会产生一次`往返时延(round-trip delay time)`,可以使用**管道和脚本**来优化这个问题。
4. 由于列表类型内部是使用双向链表实现,获取头尾的元素的速度很快。**使用列表类型实现社交网站的新鲜事**(关心的只是最新的内容)。由于在两端插入记录的时间复杂度O(1),**使用列表类型来记录日志**,可以保证新加入日志的速度不会受到已有日志数量的影响。另外还可以做队列使用。
另外可以**使用列表类型键posts:list记录文章ID列表 和 文章评论列表**。当发布新文章时使用LPUSH命令把新文章的ID加入这个列表中,另外删除文章时把列表中的文章ID删除,就像这样:`LREM posts:list 1 要删除的文章ID`。存储评论时:
    # 将评论序列化成字符串
    $serializedComment = serialize($author, $email, $time, $content)
    LPUSH post:42:comments, $serializedComment
5. **利用集合类型存储文章标签(tag)**
6. **使用有序集合类型来实现按访问量排序的文章存储。**在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,使我们可以获得分数最高的前N个元素、指定分数范围的元素等。集合中的元素不同,但分数可以相同。在这个键中以文章ID作为元素,以该文章的点击量作为该元素的分数。将该键命名为`posts:page.view`,每次用户访问一次文章时,博客程序就通过`ZINCRBY posts:page.view 1 文章ID` 更新访问量。获取文章访问量通过`ZSCORE posts:page.view 文章ID` 来实现。
另外可以**实现文章按发布时间排序**,使元素的分数为文章发布的Unix时间。借助`ZREVRANGEBYSCORE`命令还可以轻松获取指定时间范围的文章列表,可以实现按月份查看文章的功能。
7. **使用列表类型键实现访问频率限制**。如果要精确地保证每分钟最多访问10次,需要记录下用户每次访问的时间。因此对每个用户,我们使用一个列表类型的键来记录他最近10次访问博客的时间。一旦键中的元素超过 10 个,就判断时间最早的元素距现在的时间是否小于1分钟。如果是则表示用户最近1分钟的访问次数超过了10次;如果不是就将现在的时间加入到列表中,同时把最早的元素删除。

$listLength = LLEN rate.limiting:$IP
if $listLength < 10
LPUSH rate.limiting:$IP, now()
else
$time = LINDEX rate.limiting:$IP, -1
if now() – $time < 60
print 访问频率超过了限制,请稍后再试。
else
LPUSH rate.limiting:$IP, now()
LTRIM rate.limiting:$IP, 0, 9

    代码中now()的功能是获得当前的Unix时间。由于需要记录每次访问的时间,所以当要限制"A时间最多访问B次"时,如果"B"的数值较大,此方法会占用较多的存储空间,实际使用时还需要开发者自己去权衡。除此之外该方法也会出现**竞态条件**,同样可以通过脚本功能避免。
8. **对列表,集合,有序集合类型键使用sort … by … 排序**。SORT命令可以使用于 集合,列表,有序集合。针对有序集合类型排序时会忽略元素的分数,只针对元素自身的值进行排序。除了可以排列数字外,sort命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素。
  - `SORT tag:ruby:posts BY post:*->time DESC` 由 `tag:ruby:posts`获得的值替换`*`,一般为ID,即依据`post:ID`的`time`的散列值来对`tag:ruby:posts`排序
  - 可以再加上`GET`参数,同样可以使用*号,`GET #`得到元素本身,还有`STORE key`参数

SORT tag:ruby:posts
BY post:->time DESC
GET post:
->title GET post:*->time GET #
STORE sort.result

 - 使用SORT命令时注意使用LIMIT参数只获取需要的数据(M),尽可能减少待排序键中元素的数量(N),尽可能在数据量大时缓存结果。时间复杂度为`O(n+mlog(m))`
9. **BRPOP实现任务队列,以及通知消费者的优先级队列**。BRPOP和RPOP的区别是当列表中没有元素时BRPOP命令会一直阻塞住连接,直到有新元素加入。BRPOP接收两个参数,第一个是键名(可以多个键),第二个是超时时间(0表示不限)。如果有多个键,当多个键都有元素则按照从左到右的顺序取第一个键中的一个元素。借此特性可以实现区分优先级的任务队列。

### Redis概念拾遗
- 利用Redis中的**事务**(transaction)来进行多个连续命令的原子操作。

def follow($currentUser, $targetUser)
SADD user:$currentUser:following, $targetUser
SADD user:$targetUser:followers, $currentUser

这种操作容易产生导致错误的竞态

利用事务来使多条数据库操作变为原子操作

MUTLI

EXEC

Redis保证**一个事务中执行的命令要么都执行,要么都不执行**。如果在发送EXEC之前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。Redis的事务能够保证一个事务中的命令依次执行不被其他命令插入。
- 限制Redis**最大可用内存大小**,修改配置文件的maxmemory参数(单位是字节),当超出了这个限制时Redis会依据maxmemory-policy参数指定的策略来删除不需要的键直到Redis占用的内存小于指定内存。
- Redis具有**发布/订阅模式**:publish/subscribe。与ROS(机器人操作系统)中的发布/订阅类似。
- 使用**管道、脚本**优化往返延时。
- 修改配置文件实现**内部编码优化**。
- Redis的每个键值都是使用一个**redisObject结构体**保存的,
```C
typedef struct redisObject {  
     unsigned type:4;  
     unsigned notused:2;     /* Not used */  
     unsigned encoding:4;  
     unsigned lru:22;        /* lru time (relative to server.lruclock) */  
     int refcount;  
     void *ptr;  
} robj; 

关于里面的特殊语法参考Redis源码阅读笔记,讲解很清楚。
Redis使用一个sdshdr类型的变量来存储字符串,而redisObject的ptr字段指向的是该变量的地址。sdshdr的定义如下:

struct sdshdr {  
     int len;  
     int free;  
     char buf[];  
}; 

其中len字段表示的是字符串的长度,free字段表示buf中的剩余空间,而buf字段存储的才是字符串的内容。所以当执行SET key foobar时,存储键值需要占用的空间是sizeof(redisObject) + sizeof(sdshdr) + strlen(“foobar”) = 30字节。而当键值内容可以用一个64位有符号整数表示时,Redis会将键值转换成long类型来存储。如SET key 123456,实际占用的空间是sizeof(redisObject) = 16字节,比存储”foobar”节省了一半的存储空间

  • 列表类型和有序集合类型辩异
    • 相似:
    1. 二者都是有序的。
    2. 二者都可以获得某一范围的元素。
  • 不同:
    1. 列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。
    2. 有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。
    3. 列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。
    4. 有序集合要比列表类型更耗费内存。

持久化

  • RDB方式,通过快照,即当符合一定条件时Redis会自动将内存中的所有数据生成一份副本并存储在硬盘上。
    条件:
    1. 根据配置规则进行自动快照
    2. 用户执行SAVE(同步)或BGSAVE(异步)命令
    3. 执行FLUSHALL命令
    4. 执行复制(replication)时
  • AOF方式,默认没有开启,通过appendonly yes配置参数启动。开启后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同。此外,由于操作系统的缓存机制,数据并没有真正地写入硬盘,而是进入了系统的硬盘缓存。在默认情况下系统每30秒会执行一次同步操作,以便将硬盘缓存中的内容真正写入硬盘。
    可以通过appendfsync参数设置同步的时机:
# appendfsync always
appendfsync everysec
# appendfsync no

lua脚本简要

使用脚本可以

  1. 减少网络开销:减少往返时延
  2. 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。无需担心竞态,无需使用事务。
  3. 复用:客户端发送的脚本会永久储存在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。

利用脚本实现访问频率限制:

local times = redis.call( 'incr', KEYS[1] )

if times == 1 then
    --KEYS[1]键刚创建,所以为其设置生存时间
    redis.call( 'expire', KEYS[1], ARGV[1] )
end

if times > tonumber(ARGV[2]) then
    return 0
end

return 1

通过

$redis-cli --eval /path/to/ratelimiting.lua rate.limiting:IP , 10 3

–eval是告诉redis-cli读取并运行后面的Lua脚本
脚本参数以‘空格,空格’分隔的是KEYS和ARGV参数,该命令的作用是将访问频率限制为每10秒最多3次。

    原文作者:treelake
    原文地址: https://www.jianshu.com/p/59bda2c62979
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞