redis笔记
备注:该笔记是在观看爱酷学习网的 《redis视频教程》 所做的,大部分理论的东西还是基于观看过程中的PPT内容。以供自己以后参考。版权归爱哭学习网所有。谢谢李老师。
视频地址:http://www.icoolxue.com/album/show/84
redis在线操作的工具,免费的,http://try.redis.io/
1.noSql介绍
非关系型数据库,noSQL是key-value形式存储的,不一定遵循关系型数据库的一些基本要求,比如说遵循SQL标准,ACID属性,表结构等等。这类数据库的主要特点是:非关系型的,分布式的,开源的,水平扩展的。redis也支持事务,但是它的事务比较简单。
NOSQL特点:1.处理超大量的数据
2.运行在便宜的PC服务器集群上(在PC服务器集群上很便宜的)
3.击碎了性能瓶颈(性能高)
NOSQL适用场景:
1.对数据高并发读写
2.对海量数据的高效率存储和访问
3.对数据的高可扩展性和高可用性(nosql可以做分布式,提供服务器数据节点,在系统升级的时候,停机维护和数据迁移对MySQL是很麻烦的,但是nosql可以做)
2.redis介绍
redis是开源的,key-value存储的,通常被称为数据结构服务器。因为键可以包含字符串,哈希,链表,集合和有序集合。链表可以做成栈(先进后出)和队列(先进先出)。redis支持不同方式的排序(它的value类型中有一个是有序集合)。为了保证效率,数据都是缓存在内存中(存在内存中有一个风险就是一旦机器挂掉,那么内存中的数据就会丢失)。它也可以周期性的把数据写入磁盘或者是把修改操作写入追加的记录文件。
3.redis适用场合
成功的一个案例就是在新浪微博中的使用。新浪微博中redis的部署场景很多,大概分为以下两种:1.应用程序直接访问redis服务器,执行读写操作 2.应用程序直接访问redis(redis同步MySQL集群),当访问redis失败的时候,会去访问MySQL。
redis的一个巨大优势就是灵活数据结构和数据操作
具体的应用场景:
1.取最新n个数据的操作
2.排行版的应用,取top n操作
3.需要精确设定过期时间的应用
4.计数器的应用
5.uniq操作,获取某段时间所有数据排重值
6.实时系统,反垃圾系统
7.Pub/sub构建实时消息系统(发布与订阅)
8.构建队列系统(栈)
9.缓冲
4.redis的安装和部署
5.redis的数据类型
redis目前提供四种数据类型:string,list,set及zset(sorted set)和Hash。
Spring 是最简单的类型,一个key对应一个value,string类型是二进制安全的。redis的string可以包含任何数据,比如JPG图片或者是序列化的对象。String数据类型及操作如下:
set ==> 设置key对应的string类型的value
redis 127.0.0.1:6379> set name evan OK redis 127.0.0.1:6379> get name "evan"redis里面同一个键只能对应一个值,重复设值,会覆盖前面的值。
setnx ==> 设置key对应的string类型的value,如果key已经存在,返回0,nx是not exist的意思。
</pre><pre name="code" class="java">redis 127.0.0.1:6379> setnx name kevin (integer) 0 redis 127.0.0.1:6379> get name "evan" redis 127.0.0.1:6379>setex ==> 设置key对应的string类型的value,并指定此键值对应的有效期。没有设置有效期,代表永久有效
redis 127.0.0.1:6379> setex color 10 green //设置color为绿色,有效期是10秒 OK redis 127.0.0.1:6379> get color "green" redis 127.0.0.1:6379> get color (nil) //nil表示空
setrange ==> 设置指定key的value值的子字符串
redis 127.0.0.1:6379> get email "[email protected]" redis 127.0.0.1:6379> setrange email 4 163.com //4表示的是字符串的下标 (integer) 11 //11表示替换后的字符串的总长度 redis 127.0.0.1:6379> get email "[email protected]" redis 127.0.0.1:6379>
mset ==> 一次设置多个key的值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置
redis 127.0.0.1:6379> mset key1 kevin key2 evan OK redis 127.0.0.1:6379> get key1 "kevin" redis 127.0.0.1:6379> get key2 "evan" redis 127.0.0.1:6379>msetnx和setnx差不多,只是它是批量设置,一次设置多个key的值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置。但是不会覆盖已经存在的key。
get ==> 获取key对应的string值,如果key不存在,返回nil
getset ==> 设置key的值,并返回key的旧值
redis 127.0.0.1:6379> get key1 "kevin" redis 127.0.0.1:6379> getset key1 oven "kevin" redis 127.0.0.1:6379>
getrange ==> 获取key的value值得子字符串。getrange name 0 5 返回第0到第4个字符
mget ==> 一次获取多个key的值,如果对应key不存在,则对应返回nil
incr ==> 对key的值做加加操作。并返回新的值
incrby ==> 同incr类似,加指定值,key不存在时会设置key,并认为原来的value是0.
<pre name="code" class="java">redis 127.0.0.1:6379> set key3 2 OK redis 127.0.0.1:6379> incrby key3 5 //给key3的值加上5.如果加上一个负数就相当于减了 (integer) 7 redis 127.0.0.1:6379>decr对key的值做减减操作
decrby同incrby一样,减指定值(减去负数也就相当于加了)
append ==> 给指定key的字符串追加value,返回新字符串值的长度strlen
取指定key的value长度
hash数据类型及操作
redis hash是一个string类型的field和value的映射表,它的添加,删除操作都是0(1)(平均操作)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个string类型(string可以对象序列化)。将一个对象存储在hash类型中会占用更少的内存。并且可以更方便的存取整个对象。
hset ==> 设置hash field 为指定值,如果key不存在,则先创建。
> hset field1 field hello //field1是hash表,字段名为field,值为name (integer) 1
> hset user:001 name wvan //设置user对象,id为001的记录,设置字段name的只是"wvan" (integer) 1 > hget user:001 name //取值操作 "wvan"hsetnx ==> 设置hash field为指定值,如果key不存在,则先创建,如果存在,则返回0
> hsetnx user:001 name zhangsan (integer) 0
hmset ==> 同时设置hash的多个field
> hmset user:002 name kevin age 20 sex male OK > hget user:002 age "20" > hget user:002 sex "male" > hget user:002 name "kevin"
hget ==> 获取hash field的值
hmget ==> 获取全部指定的hash field
> hmget user:002 name age sex 1) "kevin" 2) "20" 3) "male"
hincrby ==> 指定的hash field,加上指定的值
> hincrby user:002 age 2 (integer) 22 > hget user:002 age "22"hexists ==> 测试指定的field是否存在
> hexists user:002 password (integer) 0 > hexists user:002 name (integer) 1
hlen ==> 返回指定hash的field数量
> hlen user:002 3
hdel ==> 删除指定hash的field
> hget user:001 name "wvan" > hdel user:001 name (integer) 1 > hlen user:001 0hkeys ==> 返回hash的所有field
> hkeys user:002 1) "name" 2) "age" 3) "sex"hvals ==> 返回hash所有的value
> hvals user:002 1) "kevin" 2) "22" 3) "male"
hgetall ==> 获取某个hash中全部的field和value
> hgetall user:002 1) "name" 2) "kevin" 3) "age" 4) "22" 5) "sex" 6) "male"list数据类型及操作
> lpush myworld hello (integer) 1 > lpush myworld world (integer) 2 > lpop myworld "world" > lpop myworld "hello"
> lpush myworld world (integer) 1 > lpush myworld hello (integer) 2 //2表示的是myworld里面的元素个数 > lrange myworld 0 -1 //从头部第0个元素取(第一个元素),-1表示尾部开始的第一个元素。所以 <span style="font-family: Arial, Helvetica, sans-serif;">lrange myworld 0 -1 表示从第一个元素取到最后一个元素,从栈顶到栈底 </span> 1) "hello" 2) "world"
rpush ==> 在key对应list底部添加字符串元素
> rpush myworld1 hello (integer) 1 > rpush myworld1 world (integer) 2 > lrange myworld1 0 -1 1) "hello" 2) "world"linsert ==> 在key对应list的特定位置的前或后添加字符串
> rpush myworld3 world (integer) 1 > linsert myworld3 before world hello 2 > lrange myworld3 0 -1 1) "hello" 2) "world"从尾向头为前。before的意思就是在...之前,栈底到栈顶的位置为前。lpush或者是rpush都是从栈顶往进添加元素,不同的是lpop元素从栈顶出去,rpop的元素是从栈顶出去。这样就好理解了。
> rpush mylist hello (integer) 1 > lset mylist 0 world OK > lrange mylist 0 -1 1) "world" > lset mylist 1 hello (error) ERR index out of range这个指定下标需要存在
lrem ==> 从key对应list中删除n个和value相同的元素(n<0,从尾部开始删除,n=0,表示删除所有)
> rpush mylist1 hello (integer) 1 > rpush mylist1 hello (integer) 2 > rpush mylist1 hello (integer) 3 > lrange mylist1 0 -1 1) "hello" 2) "hello" 3) "hello" > lrem mylist1 1 hello //1代表删除的个数 (integer) 1 > lrange mylist1 0 -1 1) "hello" 2) "hello"
ltrim ==> 保留指定key的值范围内的数据
> lrange mylist1 0 -1 1) "hello" 2) "world" 3) "test" > ltrim mylist1 2 -1 //保留从下标为2的元素到结尾,也就是说保留第三个元素之后的所有元素,其他的删除 OK > lrange mylist1 0 -1 1) "test"
lpop ==> 从list头部删除元素,并返回删除元素
> lrange mylist2 0 -1 1) "hello3" 2) "hello2" 3) "hello1" 4) "hello" > lpop mylist2 "hello3" > lrange mylist2 0 -1 1) "hello2" 2) "hello1" 3) "hello"
rpop|push ==> 从第一个list的尾部移除元素并添加到第二个list的头部
> lrange mylist1 0 -1 1) "world" 2) "hello" 3) "world1" 4) "world2" > lrange mylist2 0 -1 1) "hello2" 2) "hello1" 3) "hello" > rpoplpush mylist1 mylist2 "world2" > lrange mylist1 0 -1 1) "world" 2) "hello" 3) "world1" > lrange mylist2 0 -1 1) "world2" 2) "hello2" 3) "hello1" 4) "hello"lindex ==> 返回名称为key的list中index位置的元素
> lrange mylist2 0 -1 1) "world2" 2) "hello2" 3) "hello1" 4) "hello" > lindex mylist2 1 "hello2"len ==> 返回key对应list的长度
> llen mylist2 (integer) 4set数据类型及操作
set是集合,它是string类型的无序集合,set是通过hash table实现的,添加,删除和查找的复杂度都是0(1)。对集合我们可以取并集,差集,交集。通过这些操作我们可以实现sns中好友推荐和blog中的tag功能。
sadd ==> 向名称为key的set中添加一个元素.集合中不允许有重复的值
> sadd myset set1 (integer) 1 > sadd myset set2 (integer) 1 > sadd myset set3 (integer) 1 > sadd myset set3 (integer) 0 > smembers myset 1) "set1" 2) "set3" 3) "set2srem ==> 删除名称为key的set中的元素
> srem myset set3 1 > smembers myset 1) "set1" 2) "set2"spop ==> 随机返回并删除名称为key的set中的一个元素
> smembers myset 1) "set2" 2) "set4" 3) "set1" 4) "set3" 5) "set5" > spop myset "set5" > smembers myset 1) "set2" 2) "set4" 3) "set1" 4) "set3"sdiff ==> 返回所有给定key与第一个key的差集(两个集合的差集)
> smembers myset 1) "set2" 2) "set4" 3) "set1" 4) "set3" > smembers myset1 1) "set1" 2) "set6" 3) "set2" > sdiff myset1 myset //以myset1为标准 1) "set6" > sdiff myset myset1 1) "set4" 2) "set3"sdiffstore ==> 返回所有给定key与第一个key的差集,并将结果存为另一个key(将两个集合的差集存到另一个集合中)
> sdiff myset myset1 1) "set4" 2) "set3" > sdiffstore myset2 myset myset1 2 > smembers myset2 1) "set4" 2) "set3"sinter ==> 返回所有给定key的交集(返回所有集合的交集)
> sinter myset myset1 1) "set1" 2) "set2" > sinter myset1 myset 1) "set1" 2) "set2"sinterstore ==> 返回所有给定key的交集(返回所有集合的交集),并将结果存到另一个集合中
> sinterstore myset3 myset myset1 2sunion ==> 返回所有给定key的并集(返回所有集合的并集)
> sunion myset myset1 1) "set2" 2) "set4" 3) "set1" 4) "set3" 5) "set6"sunionstore ==> 返回所有给定key的并集(返回所有集合的并集),并将结果存到另一个集合中
> sunionstore myset4 myset myset1 5 > smembers myset4 1) "set2" 2) "set4" 3) "set1" 4) "set3" 5) "set6"smove ==> 从第一个key对应的set中移除member并添加到第二个key对应的set中
> smembers myset1 1) "set1" 2) "set6" 3) "set2" > smembers myset 1) "set4" 2) "set1" 3) "set3" 4) "set2" > smove myset myset1 set3 (integer) 1 > smembers myset 1) "set4" 2) "set1" 3) "set2" > smembers myset1 1) "set1" 2) "set3" 3) "set6" 4) "set2"scard ==> 返回名称为key的set的元素个数
> scard myset1 4sismember ==> 测试member 是否是名称为key的set的元素
> smembers myset 1) "set4" 2) "set1" 3) "set2" > sismember myset set1 (integer) 1 > sismember myset set5 (integer) 0srandmember ==> 随机返回名称为key的set的一个元素,但不删除元素
> srandmember myset "set2" > smembers myset 1) "set4" 2) "set1" 3) "set2"sorted set类型及操作
> zadd myset5 1 "ome" (integer) 1 > zadd myset5 2 "two" (integer) 1 > zadd myset5 3 "three" (integer) 1 > zadd myset5 4 "four" (integer) 1 > zadd myset5 0 "zero" (integer) 1 > zrange myset5 0 -1 1) "zero" 2) "ome" 3) "two" 4) "three" 5) "four" > zadd myset5 0 "zero" (integer) 0 > zrange myset5 0 -1 1) "zero" 2) "ome" 3) "two" 4) "three" 5) "four"
> zrange myset5 0 -1 withscores //输出元素的顺序号 1) "zero" 2) 0.0 3) "ome" 4) 1.0 5) "two" 6) 2.0 7) "three" 8) 3.0 9) "four" 10) 4.0zrem ==> 删除名称为key的zset中的元素member
> zrem myset5 "four" 1 > zrange myset5 0 -1 1) "zero" 2) "ome" 3) "two" 4) "three"zincrby ==> 如果在名称为key的zset中已经存在元素member,则该元素的score(顺序)增加increment,否则向该集合中添加该元素,其score的值为increment
> zrange myset5 0 -1 withscores 1) "zero" 2) 0.0 3) "hello" 4) 1.0 5) "ome" 6) 1.0 7) "two" 8) 2.0 9) "three" 10) 3.0 > zincrby myset5 2 zero 2.0 > zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "two" 6) 2.0 7) "zero" 8) 2.0 9) "three" 10) 3.0 > zincrby myset5 2 five 2.0 > zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "five" 6) 2.0 7) "two" 8) 2.0 9) "zero" 10) 2.0 11) "three" 12) 3.0zrank ==> 返回名称为key的zset中member元素的排名(按score从小到大排序)即下标。
> zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "five" 6) 2.0 7) "two" 8) 2.0 9) "zero" 10) 2.0 11) "three" 12) 3.0 > zrank myset5 hello 0 > zrank myset5 ome 1zrevrank ==> 返回名称为key的zset中member元素的排名(按score从大到小排序)即下标。
> zrevrank myset5 ome 4 > zrevrank myset5 hello 5zrevrange ==> 返回名称为key的zset(按score从大到小的顺序)的index从start到end的所有元素 (降序排序)
> zrevrange myset5 0 -1 withscores 1) "three" 2) 3.0 3) "zero" 4) 2.0 5) "two" 6) 2.0 7) "five" 8) 2.0 9) "ome" 10) 1.0 11) "hello" 12) 1.0
zrange ==> 返回名称为key的zset(按score从小到大的顺序)的index从end到start的所有元素 (升序排序)
> zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "five" 6) 2.0 7) "two" 8) 2.0 9) "zero" 10) 2.0 11) "three" 12) 3.0zrangebyscore ==> 返回集合中score在给定区间的元素
> zrangebyscore myset5 2 3 withscores 1) "five" 2) 2.0 3) "two" 4) 2.0 5) "zero" 6) 2.0 7) "three" 8) 3.0
> zrangebyscore myset5 2 2 withscores 1) "five" 2) 2.0 3) "two" 4) 2.0 5) "zero" 6) 2.0zcount ==> 返回集合中score在给定区间的数量
> zcount myset5 2 3 4zcard ==> 返回集合中的元素个数
> zcard myset5 6zremrangbyrank ==> 删除集合中排名在给定区间的元素(按索引删除)
> zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "five" 6) 2.0 7) "two" 8) 2.0 9) "zero" 10) 2.0 11) "three" 12) 3.0 > zremrangebyrank myset5 2 4 //2和4表示索引,不是顺序 (integer) 3 > zrange myset5 0 -1 withscores 1) "hello" 2) 1.0 3) "ome" 4) 1.0 5) "three" 6) 3.0
6.redis的常用命令
redis提供了丰富的命令对数据库和各种数据库类型进行操作,这些命令可以再linux终端使用。redis的数据库编号从0-15,共16个数据库,可以选择任意一个数据库进行存取,选择数据库使用的命令是select db_name,默认我们选择的数据库是 select 0
1>键值相关命令
keys ==> 返回满足给定pattern的所有key
> keys myset* <span style="font-family: Arial, Helvetica, sans-serif;">//表达式myset*代表取出以myset开头的满足条件的key. 如果只写*,那么就会返回所有的key.</span> 1) "myset2" 2) "myset3" 3) "myset4" 4) "myset" 5) "myset1" 6) "myset5"exists ==> 确认一个key是否存在
> exists name (integer) 1 //表示该key存在 > exists age (integer) 0del ==> 删除一个key
> del field1 (integer) 1 > exists field1 (integer) 0expire
设置一个键的过期时间
设置addr的过期时间是10秒,不断地用ttl来获取这个key的有效时长,直至-1,说明此值已过期。
> set name 5 OK > expire name 5 (integer) 1 > ttl name (integer) 1 > ttl name (integer) -2
> get name (nil)move
将当前数据库中的key转移到其他数据库中
选择数据库使用select 命令,之后将数据移到数据库中,使用的命令是 move key_name db_name 。
redis 127.0.0.1:6379[1]> select 0 OK redis 127.0.0.1:6379> set name 10 OK redis 127.0.0.1:6379> move name 1 (integer) 1 redis 127.0.0.1:6379> get name (nil) redis 127.0.0.1:6379> select 1 OK redis 127.0.0.1:6379[1]> get name "10"persist ==> 移除给定key的过期时间
> expire name 100 (integer) 1 > ttl name (integer) 96 > persist name (integer) 1 > get name "10" > ttl name (integer) -1randomkey ==> 随机返回key空间的一个key
redis 127.0.0.1:6379[1]> randomkey "world"
rename ==> 重命名key
redis 127.0.0.1:6379[1]> get name "10" redis 127.0.0.1:6379[1]> rename name name1 OK redis 127.0.0.1:6379[1]> get name (nil) redis 127.0.0.1:6379[1]> get name1 "10"type ==> 返回值得类型
redis 127.0.0.1:6379[1]> type name1 string
2.服务器相关命令
ping ==> 测试链接是否存活 (如果在执行ping命令之前,关闭了redis服务器,那么ping就会被拒绝,也就是会报一个error错误)
redis 127.0.0.1:6379[1]> ping PONG
quit ==> 退出连接
dbsize ==> 返回当前数据库中key的数目
redis 127.0.0.1:6379[1]> dbsize (integer) 7info ==> 获取服务器的信息和统计
redis 127.0.0.1:6379[1]> info # Server redis_version:2.6.17 redis_git_sha1:00000000 redis_git_dirty:0 redis_mode:standalone os:Linux 3.13.0-24-generic x86_64 arch_bits:64 multiplexing_api:epoll gcc_version:4.8.2 process_id:3772 run_id:15f63ed39ce7d39a50e29232393f9cdb446080f1 tcp_port:6379 uptime_in_seconds:38270 uptime_in_days:0 hz:10 lru_clock:1130521 ......config get ==> 实时转储收到的请求
redis 127.0.0.1:6379[1]> config get dir 1) "dir" 2) "/home/user"上例中我们获取了dir这个参数配置的值,如果想获取全部参数配置的值,只需要执行 config get * 即可。
删除当前选择数据库中的所有的key
flushall
删除所有数据库中的所有的key
7.redis的高级应用
1>安全性
设置客户端连接后进行任何其它指定前需要使用的密码。进入redis.conf配置文件,找到# requirepass foobared,进行如下的配置即可。
# requirepass foobared requirepass ******
温馨提示:
因为redis的速度非常快,所以在一台比较好的服务器下,一个外部的用户可以再1秒钟进行150K次的密码尝试,这意味着你需要指定非常强大的密码来防止暴力破解。
设置密码之后,重启服务器,再次登录redis-cli,执行一些基本的命令,提示没有权限操作,这是我们只需要输入
redis 127.0.0.1:6379[1]> auth ****** //返回OK,表示授权成功然后就可以直接操作了
还可以在连接到服务器期间就指定一个口令,在登陆的时候直接输入密码,方法如下:
user@user-xubuntu:~$ redis-cli -a ******之后不需要授权,直接操作。
2>主从复制
redis的主从配置和使用都比较简单,通过主从复制,可以允许多个slave server 拥有和master server相同的数据库副本。
redis的主从复制的特点:
slaveof 192.168.1.1 6379 #指定master的 ip和端口号 masterauth ****** #这是主机的密码输入info命令可以查看许多服务器相关的信息,其中有一个信息是role,可以查看该机器的角色。
3>事务处理
redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入任何其它client端的的命令,当一个client在一个连接着发出multi命令时,这个连接会进入一个事务上下文,该连接后续的命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有的命令。
redis 127.0.0.1:6379[1]> get name1 "10" redis 127.0.0.1:6379[1]> multi OK redis 127.0.0.1:6379[1]> set name1 20 QUEUED redis 127.0.0.1:6379[1]> get name1 QUEUED redis 127.0.0.1:6379[1]> set name1 30 QUEUED redis 127.0.0.1:6379[1]> exec 1) OK 2) "20" 3) OK redis 127.0.0.1:6379[1]> get name1 "30"如果将一些命令放在一个事务中,而这些命令中有一个命令是错误的,不会影响其它命令的执行
redis 127.0.0.1:6379[1]> set name "hello" OK redis 127.0.0.1:6379[1]> set age 10 OK redis 127.0.0.1:6379[1]> incr name (error) ERR value is not an integer or out of range redis 127.0.0.1:6379[1]> multi OK redis 127.0.0.1:6379[1]> incr age QUEUED redis 127.0.0.1:6379[1]> incr name QUEUED redis 127.0.0.1:6379[1]> exec 1) (integer) 11 2) (error) ERR value is not an integer or out of range redis 127.0.0.1:6379[1]> get age "11" redis 127.0.0.1:6379[1]> get name "hello"
取消一个事务.。discard命令其实就是清空事务的命令队列并退出事务上下文,也就是我们常说的事务回滚。
redis 127.0.0.1:6379[1]> get name1 "30" redis 127.0.0.1:6379[1]> multi OK redis 127.0.0.1:6379[1]> set name1 40 QUEUED redis 127.0.0.1:6379[1]> set name1 50 QUEUED redis 127.0.0.1:6379[1]> discard OK redis 127.0.0.1:6379[1]> get name1 "30"乐观锁复杂的事务控制
大多数是基于数据库版本(version)的记录机制实现的,即为数据增加一个版本标示,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个"version"字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号+1,此时,将提交数据的版本号与数据库表对应记录的当前版本号进行对比,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
redis的乐观锁实例:
假设有一个age的key,我们开两个session来对age进行赋值操作,看一下结果
redis 127.0.0.1:6379[1]> get name "hello" redis 127.0.0.1:6379[1]> watch name //监控键name OK redis 127.0.0.1:6379[1]> multi //开启一个事务上下文 OKwatch命令会监视给定的key,当exec的时候,如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁,注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec, discard, unwatch命令都会清除连接中的所有监视。
redis的事务很简单,当然会存在一些问题,第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配
4>持久化机制
redis的所有键和数据都存在内存中,redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保证持久化。redis支持两种持久化方式:
4.1 snapshopping(快照)也是默认方式(将数据存在文件中)
snapshopping 是默认的持久化方式,这种方式是将内存中的数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb,可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照。
save 900 1 #表示900秒内有一个key被修改了,则发起快照保存。 save 300 10 #表示300秒内如果超过10个key被修改,则发起快照保存dump.rdb的文件位置一般在 redis -> 32bit -> dump.rdb。( 它和redis-server在一个目录下,Windows环境下)
4.2 Append-only file(缩写为aof)的方式(将写、更改和删除操作存到一个文件中)
由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过writer函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
当然由于OS会在内核中缓存write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。
可以通过配置文件redis.conf告诉redis我们想要通过fsync同步函数强制os写入到磁盘的时机。(redis.conf所在的位置和redis-server在一个目录下,windows环境下)
appendonly yes //启用aof持久化方式,以下是三种方式: # appendfsync always //收到写命令就立即写入磁盘,效率最慢,但是保证完全的持久化 appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中 # appendfsync.no //完全依赖os,性能最好,持久化没保证
dump.rdb文件存的就是数据,appendonly.aof文件存的是操作
5>发布订阅消息
发布订阅(pub/sub)是一种消息通信模式,主要的目的是解除消息发布者和消息订阅者之间的耦合,redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subscribe和psubscribe命令向redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该消息类型的全部client都会收到此消息。
6>虚拟内存的使用
redis的虚拟内存和操作系统的虚拟内存不是一回事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于redis这样的内存数据库,内存总是不够用的。除了可以将数据分割为多个redis server外。另外能够提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换到磁盘上。
vm相关配置:
vm-enable yes #开启vm功能
vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径
vm-max-memory 1000000 #redis使用的最大内存上限
vm-page-size 32 #每个页面的大小32字节
vm-pages 134217728 #最多使用多少页面
vm-max-threads 4 #用于执行value对象放入缓存的工作线程的数量
8.redis的学习链接整理
一个redis爱好者创建的相关问题讨论网站:http://www.rediscookbook.org/
为什么使用 Redis及其产品定位:http://www.infoq.com/cn/articles/tq-why-choose-redis
Redis内存使用优化与存储:http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage