深入理解Redis
刚开始只是使用想学习一种用做缓存的数据库,边看视频,边翻阅官方的文档才发现Redis如此强大。Redis不仅仅是一个简单的键值对数据库,它实际上是一个数据结构服务器,支持不同类型的数据。传统的键值对数据库,你只能给字符类型的键绑定字符类型的数值,在Redis中,可以绑定到更加复杂的数据结构。
1 为org-babel添加对redis的支持
配置org-redis
cd ~/.emacs.d/private |
修改.spacemacs添加如下两行
(add-to-list 'load-path |
2 安装redis
2.1 直接安装
sudo apt-get install wget curl build-essential tcl -y |
2.2 编译安装
wget http://download.redis.io/redis-stable.tar.gz |
配置文件更改这几行
##Default port to listen reids. You can also change it as per your need. |
添加service
[Unit] |
3 常见数据类型操作
3.1 Strings类型数据
Strings是最基本的类型,一个Key对应一个Value,string类型是二进制安全的。Redis的string可以包含任何数据,比如jpg图片或者序列化Ruby对象。 String的值最大长度是512MB。
3.1.1 set
设置key对应的值为string类型的value。命令如下:
set name "wing" get name |
用例:分布式锁1。
public class RedisTool { |
3.1.2 setnx
3.1.3 setex
设置key对应的值为string类型的value,并指定键值对应的有效期
setex action 10 start get action |
3.1.4 setrange
设置指定key的value值的字符创
set email wing-ho@163.com get email setrange email 8 gmail.com get email |
3.1.5 mset
一次设置多个值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置。
mset name wing passwd 1234 age 30 get name get passwd get age |
3.1.6 msetnx
一次设置多个值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置,但不会覆盖已存在的可以。
flushall set name wing msetnx name wing passwd 1234 age 30 get passwd |
3.1.7 get
获取key对应的string值,如果key不存在返回nil
3.1.8 getset
设置key的值,并返回key的旧值
set age 30 getset age 20 |
3.1.9 getrange
获取key的value值的子字符串
set name hegenrong getrange name 0 4 |
3.1.10 mget
一次获取多个key的值,如果对应的key不存在则对应返回nil
mget name passwd age |
3.1.11 incr
3.1.12 incrby
同incr类似,加指定值。
incrby counter 10 |
3.1.13 decr
对key的值做–操作。
3.1.14 decrby
同decr类似,减指定值。
3.1.15 append
给指定key的字符串追加value,返回新字符串的长度。
用例:记录时序数据3
append timeseries "0043" append timeseries "0035" getrange timeseries 0 3 getrange timeseries 4 7 |
3.1.16 strlen
取指定key的value值的长度。key不存在时,返回0。
strlen email strlen noexistkey |
3.2 Hashes类型
Redis hash是一个string类型的field和value的映射表。它的添加、删除操作都是O(1)(平均)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个stirng类型。将一个对象存储在hash类型中会占用更少的内存,而且可以更方便的存取整个对象。
3.2.1 hset
设置hash field为指定值,如果key不存在,则先创建。
hset myhash field1 Hello hget myhash field1 |
3.2.2 hsetnx
设置hash field为指定值,如果key不存在,则先创建,如果存在返回0。
flushdb hsetnx myhash field1 hello hsetnx myhash field1 hello |
3.2.3 hmset
同时设置bash的多个field。
hmset myhash field1 hello field2 world hmget myhash field1 field2 hmget myhash field1 field2 nofield |
3.2.4 hmget
同时全部指定bash field,当指定field不存的时候,返回nil
hmget myhash field1 field2 nofield |
3.2.5 hincrby
指定的hash field加上给定的值。值为64位有符号数。
hset myhash field 5 hincrby myhash field 2 hincrby myhash field -2 |
3.2.6 hlen
返回指定hash的field数量。
hlen myhash |
3.2.7 hdel
删除指定hash的field。
hdel myhash field |
3.2.8 hkeys
返回hash的所有field。
hkeys myhash |
3.2.9 hvals
返回hash的所有value。
hvals myhash |
3.2.10 hgeall
获取某个hash中全部的field及value。
hgetall myhash |
3.3 Lists类型
List是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等,操作中key理解为链表的名字。Redis的List类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push、pop操作从链表的头部或者尾部添加删除元素,这样List即可以作为栈,又可以作为队列。
3.3.1 lpush
在key对应的list的头部添加字符串元素。
flushdb lpush mylist a lpush mylist b lrange mylist 0 -1 flushdb lpush mylist a b c lrange mylist 0 -1 |
3.3.2 rpush
在key对应的list的尾部添加字符串元素。
flushdb rpush mylist world rpush mylist hello lrange mylist 0 -1 |
3.3.3 linsert
在key对应list的特定位置前或后添加字符串
flushdb rpush mylist world linsert mylist before world hello lrange mylist 0 -1 flushdb rpush mylist world linsert mylist after world hello lrange mylist 0 -1 |
3.3.4 lset
设置list中指定下标的元素值。
flushdb rpush mylist hello lset mylist 0 world lrange mylist 0 -1 |
3.3.5 lrem
从key对应的list中删除n个和value相同的元素。(n<0从尾删除,n=0全部删除)
flushdb rpush mylist1 a b c c d c c f lrem mylist1 2 c lrange mylist1 0 -1 rpush mylist2 a b c c d c c f lrem mylist1 -2 c lrange mylist2 0 -1 |
3.3.6 ltrim
保留指定key的值范围内的数据。
flushdb rpush mylist a b c d e ltrim mylist 1 -1 lrange mylist 0 -1 |
3.3.7 lpop
从list的头部删除元素,并返回删除的元素。
lrange mylist 0 -1 lpop mylist lrange mylist 0 -1 |
3.3.8 rpoplpush
从第一个list的尾部移除元素并添加到第二个list的头部。
rpush mylist1 one two three rpush mylist2 four five six rpoplpush mylist1 mylist2 lrange mylist1 0 -1 lrange mylist2 0 -1 |
3.3.9 lindex
返回名称为key的list中index位置的元素。
lrange mylist2 0 -1 lindex mylist2 0 lindex mylist2 1 |
3.3.10 llen
返回key对应list的长度
llen mylist2 |
3.4 Sets类型
set是集合,它是string类型的无序集合。set是通过hash table实现的,添加、删除和查找的复杂度都是O(1)。对集合我们可以取并集、交集、差集。通过这些操作我们可以实现sns中好友推荐和blog的tag功能。
3.4.1 sadd
向名称为key的set中添加元素。
sadd myset hello sadd myset world sadd myset world smembers myset |
3.4.2 srem
删除名称为key的set中的元素。
flushdb sadd myset one sadd myset two srem myset one smembers myset |
3.4.3 spop
随机返回并删除名称为key的set中的一个或多个元素。ver 3.2开始支持多个。
flushdb sadd myset one two three four five spop myset 2 |
3.4.4 sdiff
返回所有给定key与第一个key的差集。
flushdb sadd myset1 one two three four five sadd myset2 two five six sdiff myset1 myset2 |
3.4.5 sdiffstore
返回所有给定key与第一个key的差集,并将结果存为key3。
sdiffstore myset3 myset1 myset2 smembers myset3 |
3.4.6 sinter
返回所有给定key的交集。
sinter myset3 myset1 |
3.4.7 sinterstore
返回所有给定key的交集,并将结果存为另一个key。
sinterstore myset4 myset3 myset1 smembers myset4 |
3.4.8 sunion
返回所有给定key的并集。
sunion myset2 myset3 |
3.4.9 sunionstore
返回所有给定key的并集,并将结果存为另一个key。
sunionstore myset5 myset2 myset3 smembers myset5 |
3.4.10 smove
从第一个key对应的set中移除member并添加到第二个对应的set中。
flushdb sadd myset1 one two three sadd myset2 four five six smove myset1 myset2 three smembers myset2 |
3.4.11 scard
返回名称为key的set的元素个数。
scard myset2 smembers myset2 |
3.4.12 sismember
测试member是否是名称为key的set的元素。
smembers myset2 sismember myset2 two sismember myset2 three |
3.4.13 srandmember
随机返回名称为key的set的一个元素,但不删除元素。
sadd myset one two three four smembers myset srandmember myset |
3.5 Sorted sets类型
sorted set是set的一个升级版本,它在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解为有两列的mysql表,一列存value,一列存顺序。操作中key理解为zset的名字。
3.5.1 zadd
向名称为key的zset中添加元素member,score用于排序。如果该元素存在,则更新其顺序。
flushdb zadd myzset 1 one zadd myzset 2 uno zadd myzset 2 two 3 three zrange myzset 0 -1 withscores |
3.5.2 zrem
删除名称为key的zset中的元素。
zrange myzset 0 -1 withscores zrem myzset two zrange myzset 0 -1 withscores |
3.5.3 zincrby
如果在名称为key的zset中已经存在元素member,则该元素的score增加increment,否则向该集合中添加该元素,其score的值为increment。
zrange myzset 0 -1 withscores zincrby myzset 2 one zrange myzset 0 -1 withscores |
3.5.4 zrank
返回名称为key的zset中memeber元素的排名(按score从小到打排序)即下标。
flushdb zadd myzset 1 one 2 two 3 three 5 five zrange myzset 0 -1 withscores zrank myzset two |
3.5.5 zrevrank
返回名称为key的zset中member元素的排名(按score从大到小排序)及下标。
zrange myzset 0 -1 withscores zrevrank myzset five |
3.5.6 zrevrange
返回名称为key的zset(按score从大到小顺序)中的index从start到end的所有元素。
zrange myzset 0 -1 withscores zrevrange myzset 0 -1 withscores |
3.5.7 zrangebyscore
返回集合中score在给定区间内的元素。
zrange myzset 0 -1 withscores zrangebyscore myzset 2 3 zrangebyscore myzset 2 3 withscores |
3.5.8 zcount
返回集合中score在给定区间内的数量。
zrange myzset 0 -1 withscores zcount myzset 0 2 |
3.5.9 zcard
返回集合中元素个数。
zcard myzset |
3.5.10 zremrangebyrank
删除集合中排名在给定区间的元素。
flushdb zadd myzset 1 one 2 two 3 three 5 five zremrangebyrank myzset 0 1 zrange myzset 0 -1 withscores zremrangebyrank myzset 0 0 zrange myzset 0 -1 withscores |
3.5.11 zremrangebyscore
删除集合中score在给定区间的元素。
flushdb zadd myzset 1 one 2 two 3 three 5 five zremrangebyscore myzset 2 3 zrange myzset 0 -1 withscores |
4 常用命令
Redis提供了丰富的命令对数据库和各种数据库类型进行操作,这些命令可以在Linux终端使用。
4.1 键值相关命令
4.1.1 keys
返回满足给定条件的所有键。支持glob风格的匹配模式,如h?ll、h*llo、h[ab]llo、h[e]llo,h[a-b]llo等。
keys myzs* |
4.1.2 exists
确认一个key是否存在。存在返回1,不存在返回0。
exists name exists myzset |
4.1.3 del
删除一个key。成功返回1,失败返回0。
del name del myzset |
4.1.4 expire
设置一个key的过期时间。
hmset myhash name wing age 30 expire myhash 10 ttl myhash |
4.1.5 move
将当前数据库中的key转移到其他的数据库中。
select 0 set age 30 get age move age 1 get age select 1 get age |
4.1.6 persist
移除给定key的过期时间。
set age 30 expire age 300 ttl age persist age ttl age |
4.1.7 randomkey
随机返回key空间的一个key。
randomkey |
4.1.8 rename
重命名key。
rename age name get name |
4.1.9 type
返回值的类型。
set name wing hset myhash name wing rpush mylist name age weight sadd myset one tow zadd myzset 1 one 2 tow 3 three type age type name type myhash type mylist type myset type myzset |
4.2 服务器相关命令
4.2.1 ping
测试连接是否存活。
redis-cli ping |
4.2.2 echo
在命令行打印一些内容。
echo wing |
4.2.3 select
选择数据库。Redis数据库编号从0~15,我们可以选择任意一个数据库来进行数据的存取。
select 1 select 16 |
4.2.4 dbsize
返回当前数据库中key的数目。
dbsize |
4.2.5 info
获取服务器的信息和统计。
info |
4.2.6 config get
实时传输收到的请求。
config get * config get dir |
4.2.7 flushdb
删除当前数据库中的所有key。
dbsize flushdb dbsize |
4.2.8 flushall
删除所有数据库中的所有key。
dbsize select 1 dbsize flushall dbsize |
4.2.9 quit
退出连接。
quit |
5 Redis高级使用特性
5.1 安全性
设置客户端连接后进行任何其他指定操作前需要使用的密码。 警告:因为Redis速度相当快,所有一台比较好的服务器下,一个外部的用户可以在一秒钟进行150K次的密码尝试,这意味这你需要指定一个非常非常强大的密码来防止暴力破解。
#sudo sed '/requirepass foobared/a\requirepass wing' -i /etc/redis/redis.conf |
redis-cli keys \* |
5.2 主从复制
Redis主从复制配置是使用都非常简单,通过主从复制可以允许多个slave server拥有和master server相同的数据库副本。
5.2.1 Redis主从复制特点
- Master可以拥有多个Slave。
- 多个Slave可以连接到同一个Master外,还可以连接到其他的Slave。
- 主从复制不会阻塞Master,在同步数据时,Master可以继续处理client请求。
- 提高系统的伸缩性。
5.2.2 Redis主从复制过程
- Slave与Master建立连接,发送gsync同步命令
- Master会启动一个后台进程,将数据库快照保存到文件中,同时Master主进程会开始收集新的写命令并缓存。
- 后台完成保存后,就将此文件发送给Slave
- Slave将此文件保存到硬盘上。
5.2.3 配置主从服务器
配置Slave服务器很简单,只需要在Slave的配置文件中加入一下配置:
#指定master的ip和端口 |
5.2.4 测试
我们在master数据库上设置一对键值对
set name master |
在slave数据库上取这个键
get name |
怎么判断哪个是主哪个是从呢?我们只需调用info就可以得到主从的信息。
5.3 事务处理
Redis对事务的支持目前还比较简单。Redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。当一个client在一个连接中发出multi命令时,这个连接会进入一个事务上下文,该连接后续的命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令。
5.3.1 开启一个事务
auth wing get age multi set age 10 set age 20 exec get age |
5.3.2 取消一个事务
auth wing set age 10 get age multi set age 10 set age 20 discard get age |
5.3.3 乐观锁复杂事务控制
乐观锁:大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过数据库表添加一个version字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
Redis乐观锁实例:假设有一个age的key,我们开2个session来对age进行复制操作,我们来看一下结果如何。 第一步开启一个session:
get age watch age multi #开启第二个session exec #返回nil get age |
第二部开启另外一个session:
set age 30 get age |
watch命令监视给定的key,当exec执行的时候如果监视的key从调用watch之后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁了。注意watch的key是对这个连接有效的,事务也一样。如果连接断开了,监视和事务都会被自动清除,当然了exec,discard,unwatch命令都会清除连接中的所有监视。
redis的事务实现是如此简单,当然会存在一些问题。第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。
auth wing set age 30 set name wing multi incr age incr name exec get age get name |
从这个例子中可以看到,age由于是个数字,那么它可以有自增运算,但是name是个字符串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务都会回滚,但是我们看到redis却是可以执行的命令提交了,所以这个现象对于习惯于关系型数据库操作的朋友来说是很别扭的,这一点也是redis今天需要改进的地方。
5.4 持久化机制
Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保持持久化。 Redis支持两种持久化方式:
- snapshotting(快照)也是默认方式。
- Append-only file(缩写aof)的方式。
5.4.1 snapshotting方式
快照是默认的持久化方式。这种方式是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果m个key被修改就自动做快照。
save 900 1 #900秒内如超过1个key被修改,则发起快照保存 |
5.4.2 aof方式
由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。aof比快照方式有更好的持久化姓,是由于使用aof时,redis会将每一个收到的写命令都通过write函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
当然由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能丢失部分修改。可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机
appendonly yes #启用aof持久化方式 |
5.5 发布订阅信息
发布订阅(pub/sub)是一种消息通信模式,主要的目的是解除消息发布者和订阅者之间的耦合,Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subcrible和psubscrible命令想redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该消息类型的全部client都会收到此消息。 开一个session订阅频道
subscribe channel1 channel2 |
再开启一个session订阅频道
subscribe channel1 |
在开启一个session发布订阅
publish channel1 hello publish channel2 world |
用例:基于web的消息推送。
订阅 sub.php
<?php |
刘帅翻译的《HTML5数据推送应用开发》详细叙述了SSE、WebSocket或者数据拉取方案的区别和数据推送的兼容性实现方案。
测试页面 index.html
<script type="text/javascript"> |
发布 pub.php
<?php |
5.6 虚拟内存的使用
Redis的虚拟内存与操作系统的虚拟内存不是一回事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于redis这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个redis server外。另外能够提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换到磁盘上。 下面是vm相关配置
vm-enable yes #开启vm功能 |
6 为web应用添加页面缓存功能
许多网站建设初期只考虑功能实现,SEO优化,带来了巨大的爬虫的流量,为了SEO保持效果,不能阻止爬虫的访问,更要欢迎爬虫的到来。那如何才能应对爬虫带来的巨大流量。最简单有效的方法就是使用缓存。 网站代码是混合php,sql和html的原始写法,只考虑功能实现,完全不考虑代码的可读性的。不可能像Spring那样在接口层面去考虑缓存数据,所以选择粒度较大的页面缓存方式。
<?php |