Redis Installation、Configuration、Program Based On Redis Learning
目录
1. Redis 简介 2. Redis安装配置 3. 编程使用Redis 4. 使用Lua脚本
1. Redis 简介
0x1: Redis是什么
Redis是一款Nosql类型的基于key-value的高速缓存系统,
从架构上看,redis有3种特性
1. key value store 是一个以key-value形式存储的数据库,定位直指MySQL,用来作为唯一的存储系统 2. memory cache 是一个把数据存储在内存中的高速缓存,用来在应用和数据库间提供缓冲,替代memcachd 3. data structrue server 把它支持对复杂数据结构的高速操作作为卖点,提供某些特殊业务场景的计算和展现需求。比如排行榜应用,Top 10之类的
在redis的键值的"值"中,它所支持的数据结构有:
1. String 1) 常用命令 set、get、decr、incr、mget等 2) 应用场景 String是最常用的一种数据类型,普通的key/value存储都可以归为此类 3) 实现方式 String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int 2. Hash 1) 常用命令 hget、hset、hgetall等 2) 应用场景 3) 实现方式 Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现 3.1) 这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap 3.2) 当成员数量增大时会自动转成真正的HashMap,此时encoding为ht 3. List 1) 常用命令 lpush、rpush、lpop、rpop、lrange等 2) 应用场景 Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现 3) 实现方式 Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构 4. Set 1) 常用命令 sadd、spop、smembers、sunion等 2) 应用场景 Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集
合内的重要接口,这个也是list所不能提供的 3) 实现方式: set的内部实现是一个value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因 5. Sorted set 1) 常用命令 zadd、zrange、zrem、zcard等 2) 使用场景 Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集
合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的 3) 实现方式 Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获
得比较高的查找效率,并且在实现上比较简单
Redis内部使用一个redisObject对象来表示所有的key和value
1. type 代表一个value对象具体是何种数据类型 2. encoding 不同数据类型在redis内部的存储方式,比如:type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个
字符串本身可以用数值表示,比如:"123" "456"这样的字符串 3. ptr 数据指针 4. vm 只有打开了Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的
我们可以发现Redis使用redisObject来表示所有的key/value数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给Redis不同数据类型提供一个统一的管理接口
0x2: Redis支持的指令集
Redis提供了丰富的命令(command)对数据库和各种数据类型进行操作,这些command可以在Linux终端使用(redis-cli)。也可以在编程时通过API方式使用,比如使用Redis 的Java语言包
1. 连接操作相关的命令 1) QUIT: 关闭连接(connection) 2) AUTH: 简单密码认证 2. 适合全体类型的命令 1) EXISTS(key) 确认一个 key 是否存在 2) DEL(key) 删除一个 key 3) TYPE(key) 返回值的类型 4) KEYS(pattern) 返回满足给定 pattern 的所有 key 5) RANDOMKEY:随机返回 key 空间的一个key 6) RENAME(oldname, newname) 将 key 由 oldname 重命名为 newname,若 newname 存在则删除 newname 表示的 key 7) DBSIZE:返回当前数据库中 key 的数目 8) EXPIRE(key,ttl) 设定一个 key 的生存时间 ttl(s) 9) TTL(key) 获得一个 key 的活动时间 10) SELECT(index) 按索引查询; 11) MOVE(key, dbindex) 将当前数据库中的 key 转移到有 dbindex 索引的数据库 12) FLUSHDB:删除当前选择数据库中的所有 key 13) FLUSHALL:删除所有数据库中的所有 key 3. 自定义扩展指令相关的指令 1) EVAL: EVAL script numkeys key [key ...] arg [arg ...] EVAL 和 EVALSHA 命令是从 Redis 2.6.0 版本开始的,使用内置的 Lua 解释器,可以对 Lua 脚本进行求值 3、对 STRING 操作的命令 1) SET(key, value) 给数据库中名称为 key 的 string 赋予值 value 2) GET(key) 返回数据库中名称为 key 的 string 的 value 3) GETSET(key, value) 给名称为 key 的 string 赋予上一次的value 4) MGET(key1, key2,…, key{$n}) 返回库中多个 string(它们的名称为key1,key2...)的value 5) SETNX(key, value) 如果不存在名称为 key 的 string,则向库中添加 string,名称为 key,值为 value; 6) SETEX(key, time, value) 向库中添加 string(名称为key,值为value)同时,设定过期时间time; 7) MSET(key1, value1, key2, value2,…key{$n}, value{$n}) 同时给多个 string 赋值,名称为 key{$i} 的 string 赋值 value{$i}; 8) MSETNX(key1, value1, key2, value2,…key{$n}, value{$n}) 如果所有名称为 key{$i} 的 string 都不存在,则向库中添加 string,名称 key{$i} 赋值为 value{$i}; 9) INCR(key) 名称为 key 的 string 增1操作; 10) INCRBY(key, integer) 名称为 key 的 string 增加 integer; 11) DECR(key) 名称为 key 的 string 减1操作; 12) DECRBY(key, integer) 名称为 key 的 string 减少 integer; 13) APPEND(key, value) 名称为 key的 string 的值附加 value; 14) SUBSTR(key, start, end) 返回名称为 key 的 string 的 value 的子串 4、对无索引序列 LIST 操作的命令 1) RPUSH(key, value) 在名称为 key 的 list 尾添加一个值为 value 的元素; 2) LPUSH(key, value) 在名称为 key 的 list 头添加一个值为 value 的 元素; 3) LLEN(key) 返回名称为 key 的 list 的长度; 4) LRANGE(key, start, end) 返回名称为 key 的 list 中 start 至 end 之间的元素(下标从0开始,下同) 5) LTRIM(key, start, end) 截取名称为 key 的 list,保留 start 至 end 之间的元素; 6) LINDEX(key, index) 返回名称为 key 的 list 中 index 位置的元素; 7) LSET(key, index, value) 给名称为 key 的 list 中 index 位置的元素赋值为 value; 8) LREM(key, count, value) 删除 count 个名称为 key 的 list 中值为value的元素。count 为0,删除所有值为 value 的元素,count>0从 头至尾删除 count 个值为 value 的元素,count<0从尾到头删除|count|个值为value的元素; 9) LPOP(key) 返回并删除名称为key的list中的首元素; 10) RPOP(key) 返回并删除名称为key的list中的尾元素; 11) BLPOP(key1, key2,… key{$n}, timeout) LPOP 命令的 block 版本。即当 timeout 为0时,若遇到名称为 key{$i} 的 list 不存在或该 list 为空,则命令结束。如果 timeout>0,则遇到上述情况时,等待 timeout 秒,如果问题没有解决,则对 key{$i}+1 开始的 list 执行 pop 操作; 12) BRPOP(key1, key2,… key{$n}, timeout) RPOP 的 block 版本。参考上一命令; 13) RPOPLPUSH(srckey, dstkey) 返回并删除名称为 srckey 的 list 的尾元素,并将该元素添加到名称为 dstkey 的 list 的头部。 5、对有索引无序集合 SET 操作的命令 1) SADD(key, member) 向名称为 key 的 set 中添加元素 member; 2) SREM(key, member) 删除名称为 key 的 set 中的元素 member; 3) SPOP(key) 随机返回并删除名称为 key 的 set 中一个元素; 4) SMOVE(srckey, dstkey, member) 将 member 元素从名称为 srckey 的集合移到名称为 dstkey 的集合; 5) SCARD(key) 返回名称为 key 的 set 的基数; 6) SISMEMBER(key, member) 测试 member 是否是名称为 key 的 set 的元素; 7) SINTER(key1, key2,…key{$n}) 求交集; 8) SINTERSTORE(dstkey, key1, key2,…key{$n}) 求交集并将交集保存到 dstkey 的集合; 9) SUNION(key1, key2,…key{$n}) 求并集; 10) SUNIONSTORE(dstkey, key1, key2,…key{$n}) 求并集并将并集保存到 dstkey 的集合; 11) SDIFF(key1, key2,…key{$n}) 求差集; 12) SDIFFSTORE(dstkey, key1, key2,…key{$n}) 求差集并将差集保存到 dstkey 的集合; 13) SMEMBERS(key) 返回名称为 key 的 set 的所有元素; 14) SRANDMEMBER(key) 随机返回名称为 key 的 set 的一个元素。 6、持久化 1) SAVE:将数据同步保存到磁盘; 2) BGSAVE:将数据异步保存到磁盘; 3) LASTSAVE:返回上次成功将数据保存到磁盘的 UNIX 时戳; 4) SHUNDOWN:将数据同步保存到磁盘,然后关闭服务 7、远程服务控制 1) INFO:提供服务器的信息和统计; 2) MONITOR:实时转储收到的请求; 3) SLAVEOF:改变复制策略设置; 4) CONFIG:在运行时配置 Redis 服务器
0x3: 配置密码登录
关于密码验证
1. 如果redis监听回环IP之外的地址 任何人都可以读取其信息,所以安全问题需要考虑,即redis是默认未授权可任意访问的 2. 即使显式设置了密码验证,但是redis服务器的login响应速度极快,因此官方文件中提醒设置比较复杂的密码,防止机器破解
redis配置密码
1. vim /etc/redis.conf 2. requirepass littlehann //littlehann是设置的密码 3. 重启redis sudo service redis restart #或者 sudo service redis stop sudo redis-server /etc/redis.conf 4. 尝试登录redis,在没有进行身份认证的情况下 (error) ERR operation not permitted 5. 进行身份认证 redis-cli -h 127.0.0.1 -p 6379 -a littlehann #或者 redis-cli auth littlehann
除了使用这种修改配置文件并重启的方式进行redis密码配置之外,还可以通过指令,动态地对redis进行密码配置(重启后失效)
1. config set requirepass my_redis 2. config get requirepass //无需重启redis,之前配置的老密码会失效(只在本次启动声明周期内有效)
Relevant Link:
http://try.redis.io/ http://www.redis.cn/ http://jandyu.diandian.com/post/2012-03-15/16145594 http://tech.it168.com/a2011/0818/1234/000001234478_all.shtml http://hedatou.com/archives/introduction_to_redis.html http://blog.csdn.net/tianmohust/article/details/7739739 http://www.redis.cn/commands.html http://www.lvtao.net/content/book/redis.htm http://blog.csdn.net/zyz511919766/article/details/4226821
2. Redis安装配置
0x1: 主程序安装
cd /usr/local wget http://download.redis.io/releases/redis-2.8.13.tar.gz tar xzf redis-2.8.13.tar.gz cd redis-2.8.13 make /*make命令执行完成后,会在当前src目录(/usr/local/redis-2.8.13/src)下生成本个可执行文件如下: 1. redis-server:Redis服务器的daemon启动程序 2. redis-cli:Redis命令行操作工具。当然,你也可以用telnet根据其纯文本协议来操作 3. redis-benchmark:Redis性能测试工具,测试Redis在你的系统及你的配置下的读写性能 4. redis-stat:Redis状态检测工具,可以检测Redis当前状态参数及延迟状况 */
0x2: 命令测试
//启动server ./redis-server //测试benchmark ./redis-benchmark //使用内置的客户端连接Redis ./redis-cli 127.0.0.1:6379> set foo bar OK 127.0.0.1:6379> get foo "bar" 127.0.0.1:6379>
0x3: 运行Redis所需要的内核参数优化
/* 1. overcommit_memory 指定了内核针对内存分配的策略,其值可以是0、1、2。 0: 表示内核将检查是否有足够的可用内存供应用进程使用 1) 如果有足够的可用内存,内存申请允许 2) 否则,内存申请失败,并把错误返回给应用进程 1: 表示内核允许分配所有的物理内存,而不管当前的内存状态如何 2: 表示内核允许分配超过所有物理内存和交换空间总和的内存 */ vim /etc/sysctl.conf //添加 vm.overcommit_memory=1 //刷新配置使之生效 sysctl vm.overcommit_memory=1
0x4: Redis配置文件
vim /usr/local/redis-2.8.13/redis.conf //1. 开启守护进程 daemonize yes //2. 每隔5秒输出一行监控信息(默认) daemonize no //3. 减小改变次数,这个参数可以根据情况进行指定 save 60 1000 //4. 分配256M内存 maxmemory 256000000 //5. pid文件位置 pidfile /var/run/redis.pid //6. 监听的端口号 port 6379 //7. 请求超时时间 timeout 0 //8. log信息级别 loglevel notice //9. 开启数据库的数量 databases 16 /* 10. 保存快照的频率 1) 第一个*表示多长时间 2) 第二个*表示执行多少次写操作 在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件 */ save * * //11. 是否使用压缩 rdbcompression yese //12. 数据快照文件名(只是文件名,不包括目录) dbfilename dump.rdb //13. 数据快照的保存目录(这个是目录) dir ./ //14. 是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率 appendonly no /* 15. appendonlylog如何同步到磁盘 1) always: 每次写都强制调用fsync 2) everysec: 每秒启用一次fsync 3) no: 不调用fsync等待系统自己同步 */ appendfsync everysec
配置好保存,重启redis,就可以正常启动了,和mysql一样,redis是基于socket监听端口的方式提供服务的,我们可以使用telnet、或者socket方式进行连接
Relevant Link:
http://www.redis.cn/documentation.html http://www.redis.cn/download.html http://www.php100.com/html/webkaifa/PHP/PHPyingyong/2011/0406/7873.html
3. 编程使用Redis
0x1: PHP连接Redis
使用php连接redis需要安装php的扩展
下载redis扩展源代码 http://pecl.php.net/package/redis 解压缩后进行编译 phpize ./configure --enable-hello make 关于php扩展的原理以及编译过程请参阅另一篇文章 http://www.cnblogs.com/LittleHann/p/3562259.html 将编译好的.so文件复制到php的扩展目录中 cp redis.so /usr/lib/php/modules/ 修改php.ini中的extension,增加redis扩展的自动启动 重启apache即可
Code
<?php $redis = new redis(); $result = $redis->connect('192.168.207.128', 6379); var_dump($result); //结果:bool(true) $result = $redis->set('name',"LittleHann"); var_dump($result); //结果:bool(true) $result = $redis->get('name'); var_dump($result); //结果:LittleHann $redis->delete('name'); var_dump($result); //结果:bool(true) ?>
Relevant Link:
https://github.com/nrk/predis https://github.com/Shumkov/Rediska https://github.com/jdp/redisent http://www.cnblogs.com/ikodota/archive/2012/03/05/php_redis_cn.html http://blog.51yip.com/cache/1439.html http://www.cnblogs.com/jackluo/p/3412670.html
0x2: Java连接Redis
Relevant Link:
http://outofmemory.cn/code-snippet/128/java-usage-redis-jiandan-usage http://www.cnblogs.com/edisonfeng/p/3571870.html
4. 使用Lua脚本(Redis将lua引擎静态编译包含使redis具备解析lua脚本的能力)
0x1: 初始化 Lua 环境
在初始化 Redis 服务器时, 对 Lua 环境的初始化也会一并进行,为了让 Lua 环境符合 Redis 脚本功能的需求, Redis 对 Lua 环境进行了一系列的修改, 包括添加函数库、更换随机函数、保护全局变量, 等等
整个初始化 Lua 环境的步骤如下
1. 调用 lua_open 函数,创建一个新的 Lua 环境 2. 载入指定的 Lua 函数库,包括: 1) 基础库(base lib) 2) 表格库(table lib) 3) 字符串库(string lib) 4) 数学库(math lib) 5) 调试库(debug lib) 6) 用于处理 JSON 对象的 cjson 库 7) 在 Lua 值和 C 结构(struct)之间进行转换的 struct 库 8) 处理 MessagePack 数据的 cmsgpack 库 3. 屏蔽一些可能对 Lua 环境产生安全问题的函数,比如 loadfile 4. 创建一个 Redis 字典,保存 Lua 脚本,并在复制(replication)脚本时使用。字典的键为 SHA1 校验和,字典的值为 Lua 脚本 5. 创建一个 redis 全局表格到 Lua 环境,表格中包含了各种对 Redis 进行操作的函数,包括 1) 用于执行 Redis 命令的 redis.call 和 redis.pcall 函数 2) 用于发送日志(log)的 redis.log 函数,以及相应的日志级别(level) redis.LOG_DEBUG redis.LOG_VERBOSE redis.LOG_NOTICE redis.LOG_WARNING 3) 用于计算 SHA1 校验和的 redis.sha1hex 函数 4) 用于返回错误信息的 redis.error_reply 函数和 redis.status_reply 函数 6. 用 Redis 自己定义的随机生成函数,替换 math 表原有的 math.random 函数和 math.randomseed 函数,新的函数具有这样的性质:每次执行 Lua 脚本时,除非显式地调用 math.randomseed ,否则 math.random 生成的伪随机数序列总是相同的 7. 创建一个对 Redis 多批量回复(multi bulk reply)进行排序的辅助函数 8. 对 Lua 环境中的全局变量进行保护,以免被传入的脚本修改 9. 因为 Redis 命令必须通过客户端来执行,所以需要在服务器状态中创建一个无网络连接的伪客户端(fake client),专门用于执行 Lua 脚本中包含的 Redis 命令:当 Lua 脚本需要执行 Redis 命令时,它通过伪客户端来向服务器发送命令请求,服务器在执行完命令之后,将结果返回给伪客户端,而伪客户端又转而将命令结果返回给 Lua 脚本 10. 将 Lua 环境的指针记录到 Redis 服务器的全局状态中,等候 Redis 的调用
这些步骤都执行完之后, Redis 就可以使用 Lua 环境来处理脚本了
0x2: 脚本的安全性
Redis 对 Lua 环境所能执行的脚本做了一个严格的限制 —— 所有脚本都必须是无副作用的纯函数(pure function)
为此,Redis 对 Lua 环境做了一些列相应的措施
1. 不提供访问系统状态状态的库(比如系统时间库) 2. 禁止使用 loadfile 函数 3. 如果脚本在执行带有随机性质的命令(比如 RANDOMKEY ),或者带有副作用的命令(比如 TIME )之后,试图执行一个写入命令(比如 SET ),那么 Redis 将阻止这个脚本继续运行,并返回一个错误 4. 如果脚本执行了带有随机性质的读命令(比如 SMEMBERS ),那么在脚本的输出返回给 Redis 之前,会先被执行一个自动的字典序排序,从而确保输出结果是有序的 5. 用 Redis 自己定义的随机生成函数,替换 Lua 环境中 math 表原有的 math.random 函数和 math.randomseed 函数,新的函数具有这样的性质:每次执行 Lua 脚本时,除非显式地调用 math.randomseed ,否则 math.random 生成的伪随机数序列总是相同的
经过这一系列的调整之后, Redis 可以保证被执行的脚本
1. 无副作用 2. 没有有害的随机性 3. 对于同样的输入参数和数据集,总是产生相同的写入命令
0x3: 脚本的执行
在脚本环境的初始化工作完成以后, Redis 就可以通过 EVAL 命令或 EVALSHA 命令执行 Lua 脚本了
1. EVAL(对输入的脚本代码体进行求值)
EVAL "return 'hello world'" 0
由于redis替换了lua中的基本库全局变量,产生了sandbox的效果,所以很多提权操作是不允许的
eval "return io.popen('ifconfig')" 0
2. EVALSHA 则要求输入某个脚本的 SHA1 校验和, 这个校验和所对应的脚本必须至少被 EVAL 执行过一次
Relevant Link:
http://www.redis.cn/commands/eval.html http://origin.redisbook.com/feature/scripting.html
Copyright (c) 2014 LittleHann All rights reserved