Redis

[b]1. 简介 [/b]
redis是一个开源的key-value数据库。它又经常被认为是一个数据结构服务器。因为它的value不仅包括基本的string类型还有 list,set ,sorted set和hash类型。当然这些类型的元素也都是string类型。也就是说list,set这些集合类型也只能包含string 类型。你可以在这些类型上做很多原子性的操作。比如对一个字符value追加字符串(APPEND命令)。加加或者减减一个数字字符串(INCR命令,当然是按整数处理的).可以对list类型进行push,或者pop元素操作(可以模拟栈和队列)。对于set类型可以进行一些集合相关操作 (intersection union difference)。memcache也有类似与++,--的命令。不过memcache的 value只包括string类型。远没有redis的value类型丰富。和memcache一样为了性能,redis的数据通常都是放到内存中的。当然 redis可以每间隔一定时间将内存中数据写入到磁盘以防止数据丢失。
redis也支持主从复制机制(master-slave replication)。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。从盘可以有意无意的对数 据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。redis的其他特性包括简单的事务支持和发布订阅(pub/sub)通道功能,而且redis配置管理非常简单。还有各种语言版本的开源客户端类库。
官方的bench-mark(基准)数据如下:
测试完成了50个并发执行100000个请求,设置和获取的值是一个256字节字符串,Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz,文本执行使用loopback接口(127.0.0.1),结果:读的速度是110000次/s,写的速度是81000次/s 。

[b]2. 安装[/b]
下载地址:http://www.redis.cn/download.html ,目前的最新稳定版是2.8.13。
可以在linux下运行如下命令进行安装:
$ tar xzf redis-2.8.13.tar.gz
$ cd redis-2.8.13
$ make
make完后 redis-2.8.13目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli等文件。下面启动redis服务:
$ ./redis-server // 注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动:
$ ./redis-server redis.conf // redis.conf是一个默认的配置文件。我们可以根据需要使用自己的配置文件。
启动redis服务进程后,就可以使用测试客户端程序redis-cli和redis服务交互了,如下:
$ ./redis-cli
> set name jiang // name是key ,jiang是个string类型的value
OK
> get name // 得到name的value
"jiang"

[b]3. keys[/b]
首先key也是字符串类型,但是key中不能包括边界字符。另外关于key的一个格式约定介绍下,object-type:id:field。比如user:1000:password,blog:xxidxx:title,还有key的长度最好不要太长。道理很明显占内存啊,而且查找时候相对短key也更慢。不过也不推荐过短的key,比如u:1000:pwd,这样的。显然没上面的user:1000:password可读性好。
下面介绍下key相关的公用命令:
> exists key //判断一个键是否存在
> keys pattern //返回指定模式的所有key,如>keys * 返回所有key
> type key //返回给定key的value类型不存在key时返回none
> dbsize //返回当前数据库的key数量
> del key //删除给定key,返回删除的数目
> randomkey //随机获得一个已经存在的key,如果当前数据库为空,则返回空字符串
> rename oldkey newkey //重命名key,如果newkey存在将会被覆盖
> renamenx oldkey newkey //重命名key,newkey存在将返回失败
> expire key seconds //为key指定过期时间,单位是秒,如果在还没有过期的时候,对值进行了改变,那么那个值会被清除。
> ttl key //返回设置过期时间的key的剩余过期秒数
> select db-index //通过索引选择数据库,默认连接的数据库所有是0,默认数据库有16个
> move key db-index //将key从当前数据库移动到指定数据库
> flushdb //删除当前数据库中所有key,慎用
> flushall //删除所有数据库中的所有key,更加慎用

[b]4. 数据类型[/b]
[b](1)string[/b]
string是redis最基本的类型,而且string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象。从内部实现来看其实string可以看作byte数组,最大上限是1G字节。下面是string类型的定义。
struct sdshdr {
long len;
long free;
char buf[];
};
buf是个char数组用于存贮实际的字符串内容。其实char和c#中的byte是等价的,都是一个字节,len是buf数组的长度,free是数组中剩余可用字节数。由此可以理解为什么string类型是二进制安全的了。因为它本质上就是个byte数组。当然可以包含任何数据了。另外string类型可以被部分命令按int处理,比如incr等命令。还有redis的其他类型像list,set,sorted set ,hash它们包含的元素与都只能是string类型。如果只用string类型,redis就可以被看作加上持久化特性的memcached,当然redis对string类型的操作比memcached多很多。如下:
> set key value //设置key对应的值为string类型的value,存在进覆盖
> setnx key value //同上,如果key已经存在,返回失败
> get key //获取key对应的string值,如果key不存在返回nil
> getset key value //原子的设置key的值,并返回key的旧值,如果key不存在返回nil
> mget key1 key2 ... keyN //一次获取多个key的值,如果对应key不存在,则对应返回nil
> mset key1 value1 ... keyN valueN //一次设置多个key的值,存在进覆盖
> msetnx key1 value1 ... keyN valueN //同上,但是不会覆盖已经存在的key
> incr key //对key的值做加加操作,并返回新值,incr一个不是int的value会返回错误,incr一个不存在的key设置key为1
> decr key //同上,减减操作,decr一个不存在key,则设置key为-1
> incrby key integer //同incr,加指定值 ,key不存在时候会设置key,并认为原来的value是 0
> decrby key integer //同decr,减指定值,也可以通过incrby一个负值来实现同样效果,反之一样
> append key value //给指定key的字符串值追加value,返回新字符串值的长度
> substr key start end //返回截取过的key的字符串值,注意并不修改key的值,下标是从0开始的。 下面给个例子

[b](2)list[/b]
redis的list类型其实就是一个每个子元素都是string类型的双向链表。所以[lr]push和[lr]pop命令的算法时间复杂度都是O(1)。另外list会记录链表的长度。所以llen操作也是O(1).链表的最大长度是(2的32次方-1)。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。有意思的是list的pop操作还有阻塞版本的。当我们[lr]pop一个 list对象是,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop可以则可以阻塞,当然可以加超时时间,超时后也会返回nil。
为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列。执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在。当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。下面介绍list相关命令:
> lpush key string //在key对应list的头部添加字符串元素,不存在进添加key,返回list的长度
> rpush key string //同上,在尾部添加
> llen key //返回key对应list的长度,key不存在返回0
> lrange key start end //返回指定区间内的元素,下标从0开始,负值表示从后面计算,-1表示倒数第一个元素
> ltrim key start end //截取list,保留指定区间内元素
> lset key index value //设置list中指定下标的元素值
> lrem key count value //从key对应list中删除count个和value相同的元素,count为0时候删除全部
> lpop key //从list的头部删除元素,并返回删除元素,如果key对应list不存在或者是空返回nil
> rpop key //同上,从尾部删除
> blpop key1...keyN timeout //从左到右扫描返回对第一个非空list进行lpop操作并返回,比如blpop list1 list2 list3 0 ,如果list不存在,list2,list3都是非空则对list2做lpop并返回从list2中删除的元素。如果所有的list都是空或不存在,则会阻塞timeout秒,timeout为0表示一直阻塞。当阻塞时,如果有client对key1...keyN中的任意key进行push操作,则第一在这个key上被阻塞的client会立即返回。如果超时发生,则返回nil,有点像unix的select或者poll
> brpop //同blpop,一个是从头部删除一个是从尾部删除。
> rpoplpush srckey destkey //从srckey对应list的尾部移除元素并添加到destkey对应list的头部,最后返回被移除的元素值,整个操作是原子的

[b](3)set[/b]
redis 的set是string类型的无序集合。set元素最大可以包含(2的32次方-1)个元素。set是通过hash table实现的,所以添加,删除,查找的复杂度都是O(1)。hash table会随着添加或者删除自动的调整大小。需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作。可能不久后就会改用跳表(skip list)来实现,跳表已经在sorted set中使用了。关于set集合类型除了基本的添加删除操作,其他有用的操作还包含集合的取并集(union),交集(intersection),差集(difference)。下面详细介绍set相关命令:
> sadd key val //添加一个string元素到key对应的set集合中,如果元素已经在集合中返回0
> srem key val //从key对应set中移除给定元素
> spop key //删除并返回key对应set中随机的一个元素,如果set是空或者key不存在返回nil
> srandmember key //同spop,随机取set中的一个元素,但是不删除元素
> smove srckey dstkey member //从srckey对应set中移除member并添加到dstkey对应set中,整个操作是原子的
> scard key //返回set的元素个数,如果set是空或者key不存在返回0
> sismember key member //判断member是否在set中,存在返回1,0表示不存在或者key不存在
> sinter key1 key2...keyN //返回所有给定key的交集
> sinterstore dstkey key1...keyN //同sinter,但是会同时将交集存到dstkey下
> sunion key1 key2...keyN //返回所有给定key的并集
> sunionstore dstkey key1...keyN //同sunion,并同时保存并集到dstkey下
> sdiff key1 key2...keyN //返回所有给定key的差集
> sdiffstore dstkey key1...keyN //同sdiff,并保存差集到dstkey下
> smembers key //返回key对应set的所有元素,结果是无序的

[b](4)sorted set[/b]
和set一样,sorted set也是string类型元素的集合,不同的是每个元素都会关联一个double类型的score(根据score排序,相同的score按照字母顺序排放)。sorted set的实现是skip list和hash table的混合体。当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,所以给定一个元素获取score的开销是O(1),另一个score到元素的映射被添加到skip list并按照score排序,所以就可以有序的获取集合中的元素。添加,删除操作开销都是O(log(N))和skip list的开销一致,redis的skip list实现用的是双向链表,这样就可以逆序从尾部取元素。sorted set最经常的使用方式应该是作为索引来使用,我们可以把要排序的字段作为score存储,对象的id当元素存储。下面是sorted set相关命令:
> zadd key score member //添加元素到集合,元素在集合中存在则更新对应score
> zrem key member //删除指定元素,1表示成功,如果元素不存在返回0
> zincrby key incr member //增加对应member的score值,然后移动元素并保持skip list保持有序。返回更新后的score值
> zrank key member //返回指定元素在集合中的排名(下标),集合中元素是按score从小到大排序的
> zrevrank key member //同上,但是集合中元素是按score从大到小排序
> zrange key start end //类似lrange操作从集合中去指定区间的元素。返回的是有序结果
> zrevrange key start end //同上,返回结果是按score逆序的
> zrangebyscore key min max //返回集合中score在给定区间的元素
> zcount key min max //返回集合中score在给定区间的数量
> zcard key //返回集合中元素个数
> zscore key element //返回给定元素对应的score
> zremrangebyrank key min max //删除集合中排名在给定区间的元素
> zremrangebyscore key min max //删除集合中score在给定区间的元素

[b](5)hash[/b]
redis hash是一个string类型的field和value的映射表。它的添加,删除操作都是O(1)(平均)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新建一个hash对象时开始是用 zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是 O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者 value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现. 这个限制可以在配置文件中指定,如下:
hash-max-zipmap-entries 64 #配置字段最多64个
hash-max-zipmap-value 512 #配置value最大为512字节
下面介绍hash相关命令:
> hset key field value //设置hash field为指定值,如果key不存在,则先创建
> hget key field //获取指定的hash field
> hmget key filed1....fieldN //获取全部指定的hash filed
> hmset key filed1 value1 ... filedN valueN //同时设置hash的多个field
> hincrby key field integer //将指定的hash filed 加上给定值
> hexists key field //测试指定field是否存在
> hdel key field //删除指定的hash field
> hlen key //返回指定hash的field数量
> hkeys key //返回hash的所有field
> hvals key //返回hash的所有value
> hgetall key //返回hash的所有filed和value

[b]5. redis事务[/b]
redis对事务的支持目前还比较简单。redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。 由于redis是单线程来处理所有client的请求的所以做到这点是很容易的。
一般情况下redis在接受到一个client发来的命令后会立即处理并返回处理结果,但是当一个client在一个连接中发出multi命令后,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到exec命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client.然后此连接就结束事务上下文。如下:
> multi
OK
> incr a
QUEUED
> incr b
QUEUED
> exec
我们可以调用discard命令来取消一个事务,如下:
> multi
OK
> incr a
QUEUED
> incr b
QUEUED
> discard
OK
redis的事务实现是如此简单,当然会存在一些问题。 第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令。另一个十分罕见的问题是,当事务的执行过程中,如果redis意外的挂了。很遗憾只有部分命令执行了,后面的也就被丢弃了。当然如果我们使用append-only file方式持久化,redis会用单个write操作写入整个事务内容。即是是这种方式还是有可能只部分写入了事务到磁盘。发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出。可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容。修复完后就能够重新启动了。

[b]6. java客户端使用(jedis-2.1.0.jar)[/b]
package test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import redis.clients.jedis.Jedis;

public class TestRedis {

public static void main(String[] args) {

Jedis jedis = new Jedis("192.168.214.184",6379); //连接redis服务
//jedis.auth("password"); //密码验证-如果你没有设置redis密码可不验证即可使用相关命令

// String类型
jedis.set("name_jiang", "111"); //设置
System.out.println(jedis.get("name_jiang"));
jedis.append("name_jiang", "222"); //追加
System.out.println(jedis.get("name_jiang"));
jedis.del("name_jiang"); //删除
System.out.println(jedis.get("name_jiang"));

jedis.mset("name_jiang1", "1","name_jiang2", "2","name_jiang3", "3"); //同时设置多个
System.out.println(jedis.mget("name_jiang1","name_jiang2","name_jiang3")); //同时获取多个,返回List
jedis.del("name_jiang1");
jedis.del("name_jiang2");
jedis.del("name_jiang3");

// list类型
jedis.del("list_jiang");
System.out.println(jedis.lrange("list_jiang", 0, -1));
jedis.lpush("list_jiang", "A");
jedis.lpush("list_jiang", "B");
jedis.lpush("list_jiang", "C");
System.out.println(jedis.lrange("list_jiang", 0, -1));
System.out.println(jedis.lrange("list_jiang", 0, 1));
jedis.del("list_jiang");

//set类型
jedis.sadd("set_jiang", "wobby");
jedis.sadd("set_jiang", "kings");
jedis.sadd("set_jiang", "demon");
System.out.println(jedis.scard("set_jiang")); //返回元素个数
System.out.println(jedis.smembers("set_jiang"));
System.out.println(jedis.sismember("set_jiang", "wobby"));
System.out.println(jedis.srandmember("set_jiang"));
jedis.srem("set_jiang", "demon");
System.out.println(jedis.smembers("set_jiang"));
jedis.del("set_jiang");

// sorted set类型
Map<Double,String> m = new HashMap<Double,String>();
m.put((double) 1, "111");
m.put((double) 2, "222");
jedis.zadd("ss_jiang", m);
System.out.println(jedis.zrange("ss_jiang", 0, 9));
jedis.del("ss_jiang");

// map类型
Map<String,String> user = new HashMap<String,String>();
user.put("name", "111");
user.put("password", "222");
jedis.hmset("user", user); //设置map
System.out.println(jedis.hlen("user")); //长度
System.out.println(jedis.hkeys("user")); //map中的所有键值
System.out.println(jedis.hvals("user")); //map中的所有value
List<String> rsmap = jedis.hmget("user", "name","password"); //取出map中的name字段值
System.out.println(rsmap);
jedis.hdel("user", "password"); //删除map中的某一个键值 password
System.out.println(jedis.hmget("user", "name", "password"));
jedis.del("user");
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值