性能优化首页投稿(暂停使用,暂停投稿)程序员

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

2016-08-14  本文已影响65253人  treelake

网站推荐


Redis优点简述

来自Redis快速入门


Redis环境安装

$sudo apt-get update
$sudo apt-get install redis-server
# 启动 Redis
$redis-server
# 启动 客户端
$redis-cli

安全访问

命令重命名

管理工具

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"节省了一半的存储空间

持久化

# 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次。

上一篇 下一篇

猜你喜欢

热点阅读