ACE Developer

 找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 7377|回复: 0

Redis基础学习笔记

[复制链接]
发表于 2012-5-3 10:52:48 | 显示全部楼层 |阅读模式
最近这些日子对redis进行了学习,整理了一些学习笔记.发现redis还是一个非常不错的东西。
一.Redis介绍
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。
二.Redis安装及配置1.下载源码,编译安装
    # tar xf redis-tar.gz
    # cd redis
    # make  
    # 网上说不能makeinstall,可我这就是可以,奇怪,省去了手动copy redis命令的步骤
    # cd src && makeinstall  

直接makeinstall如果显示会显示下面的错误,用cd src && make install即可
   make[: Entering directory`/usr/local/src/redis-src'
   cd ../deps/hiredis&& make static ARCH=""
   make[: Entering directory`/usr/local/src/redis-deps/hiredis'
   make[: Nothing to be donefor `static'.
   make[: Leaving directory`/usr/local/src/redis-deps/hiredis'
   cd ../deps/linenoise&& make ARCH=""
   make[: Entering directory`/usr/local/src/redis-deps/linenoise'
   make[: `linenoise_example'is up to date.
   make[: Leaving directory`/usr/local/src/redis-deps/linenoise'
   cd ../deps/hiredis&& make static
   make[: Entering directory`/usr/local/src/redis-deps/hiredis'
   make[: Nothing to be donefor `static'.
   make[: Leaving directory`/usr/local/src/redis-deps/hiredis'
   cc -o redis-benchmark-std=c-pedantic -O-Wall -W   -lm-pthread   -g -rdynamic -ggdb  ae.o anet.o redis-benchmark.o sds.o adlist.ozmalloc.o ../deps/hiredis/libhiredis.a
   cc -o redis-cli-std=c-pedantic -O-Wall -W   -lm -pthread   -g -rdynamic -ggdb  anet.o sds.o adlist.o redis-cli.o zmalloc.orelease.o ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o

   Hint: To run 'make test' isa good idea ;)

   mkdir -p /usr/local/bin
   cp -p redis-server/usr/local/bin
   cp -p redis-benchmark/usr/local/bin
   cp -p redis-cli/usr/local/bin
   cp -p redis-check-dump/usr/local/bin
   cp -p redis-check-aof/usr/local/bin
   make[: Leaving directory`/usr/local/src/redis-src'

2.修改配置
a.修改配置之前,请将redis.confcopy一份到/etc/目录下

    daemonize no    是否以后台进程方式运行

改成

    daemonize yes

b.这两个参数

    loglevel warning  
    logfile/var/log/redis.log  

c.取消注释

    syslog-enabled no #这个改成syslog-enabledyes
    syslog-facility local0

d.数据文件目录

    # The working directory.
    #
    # The DB will be writteninside this directory, with the filename specified
    # above using the'dbfilename' configuration directive.
    #
    # Also the Append OnlyFile will be created inside this directory.
    #
    # Note that you mustspecify a directory here, not a file name.
    dir /var/db/redis

e.内存,连接数设置

    maxmemory
    maxclients

f.在Master服务中的配置文件修改:
         bind 192.168.3.82

在Slave服务中的配置文件修改:
port 6381(服务端口号要分开)
bind 192.168.3.82
slaveof 192.168.3.82 6379 (设置master的Host以及Port)

3.启动
在正式启动redis之前,先创建数据目录

    # mkdir /var/db/redis

否则会出现下面的错误

    [ May # Can't chdir to'/var/db/redis': No such file or directory

同时配置内核参数

    sysctlvm.overcommit_memory=

否则提示错误

    # WARNINGovercommit_memory is set to  Backgroundsave may fail under low memory condition.
    #To fix this issue
    #add 'vm.overcommit_memory=  to /etc/sysctl.conf and then reboot orrun the command
    #'sysctlvm.overcommit_memory= for this to take effect.

最后,启动

    [root@web ~]#/etc/init.d/redis start
    Starting RedisServer:                                    [  OK  ]

关闭redis的命令

    启动
    # redis-server/etc/redis.conf

    关闭
    # redis-cli shutdown

    关闭某个端口上的redis
    # redis-cli -p port shutdown
三.Redis常用的命令
Redis 指令手册中文版

连接控制
QUIT :退出,关闭连接
代码实例:连接 退出
telnet localhost 6379
QUIT
• AUTH : 密码验证
举例说明
1、首先需要在 redis 的配置文件 redis.conf 中 requirepass 注释掉的内容,设置需
要密码连接,否则 auth 任何密码都通过
2、重新启动 redis
3、验证 auth testpassword,testpassword 是我在配置文件中设置的
requirepass testpassword
4、redis 服务器的速度众所周知,因此官方文件中提醒设置比较复杂的密码,防止机器破
telnet localhost 6379
Escape character is '^]'.
auth dsddsd
-ERR invalid password
keys global*
-ERR operation not premitted
auth ddddd
-ERR invalid password
auth testpassword
+OK
管理数据操作
• EXISTS :判断一个键是否存在;存在返回 1;否则返回 0;
举例:
EXISTS burce
:0
SET bruce 10
paitoubing
+OK
SET test 5
paitoubing
+OK
-ERR unknown command 'ing'
EXISTS bruce
:1
上面的程序
EXISTS bruce 是否存在,结果是不存在,然后 set 一个 key 为 bruce 数据长度为 10
的数据,如果数据长度操作设置的值,多余的字节会当作redis 命令来处理
DEL :删除某个 key,或是一系列 key;DEL key1 key2 key3 key4
TYPE: 返回某个 key 元素的数据类型 ( none:不存在,string:字
符,list,set,zset,hash)
KEYS: 返回匹配的 key 列表 (KEYS foo*:查找 foo 开头的 keys)
RANDOMKEY: 随机获得已经存在的 key
RENAME : 更改 key 的名字,如果名字存在则更改失败
DBSIZE: 返回当前数据库的 key 的总数
EXPIRE: 设置某个 key 的过期时间(秒),(EXPIRE bruce 1000:设置 bruce 这
个 key1000 秒后系统自动删除)
TTL: 查找某个 key 还有多长时间过期,返回时间秒
SELECT: 选择数据库
MOVE: 把 key 从一个数据库转移到另外一个库
FLUSHDB: 清空当前数据库数据
FLUSHALL:清空所有数据库数据
字符串类型的数据操作
SET 存一个数据到数据库 SET keyname datalength data (SET bruce 10
paitoubing:保存 key 为 burce,字符串长度为 10 的一个字符串 paitoubing 到数据
库)
GET:获取某个 key 的 value 值
GETSET GETSET 可以理解成获得的 key 的值然后 SET 这个值,更加方便的操作
(SET bruce 10 paitoubing,这个时候需要修改 bruce 变成 1234567890并获取
这个以前的数据paitoubing,GETSET bruce 10 1234567890)
MGET 一次性获得多个 key 的数据 (MGET uid:1:name uid:1:email
uid:1:ciy)
SETNX SETNX 与 SET 的区别是 SET 可以创建与更新 key 的 value,而 SETNX
是如果 key 不存在,则创建 key 与 value 数据
SETEX SETEX = SET + EXPIRE,貌似我的这个版本没有办法测试
MSET 一次性设置多个参数的值(MSET uid:1:name shjuto uid:1:email
shjuto@gmail.com uid:1:city 8 回车 nanchang)最后一个值需要回车输入,和
SET 一样,不知为啥。
MSETNX 如果设置的 key 不存在的话,或是叫做新 key 的话;一次性设置多个参数的值
(MSET uid:1:name shjuto uid:1:email shjuto@gmail.com uid:1:city 8
回车 nanchang)最后一个值需要回车输入,和 SET 一样,不知为啥。
INCR 自增,有点类是 mysql incr.(INCR global:uid)
INCRBY 自增 +length ,(INCRBY uid 5)原来的基础+5=result
DECR 自减
* DECRBY 自减 -lenght
APPEND 一个例子足以说明
redis exists mykey
(integer) 0
redis append mykey "Hello "
(integer) 6
redis append mykey "World"
(integer) 11
redis get mykey
"Hello World"
SUBSTR 一个例子足以说明一切,LIKE PHP ‘S STYLE
redis set s "This is a string"
OK
redis substr s 0 3
"This"
redis substr s -3 -1
"ing"
redis substr s 0 -1
"This is a string"
redis substr s 9 100000
" string"
LISTS (无索引序列,head 位置是 0,......)
# RPUSH 追加数据到系列的尾部 (RPUSH listtest 10 \n 1111111122)
# LPUSH 追加数据到序列的头部 (LPUSH listtest 10 \n 2222222222)
# LLEN 一个序列的长度;(LLEN listtest)
# LRANGE 从自定的范围内返回序列的元素 (LRANGE testlist 0 2;返回序列
testlist 前 0 1 2 元素)
# LTRIM 修剪某个范围之外的数据 (LTRIM testlist 0 2;保留 0 1 2 元素,其余的删
除)
# LINDEX 返回某个位置的序列值(LINDEX testlist 0;返回序列 testlist 位置为零
的元素)
# LSET 更新某个位置元素的值 (LSET testlist 0 5 \n 55555;)
# LPOP key Return and remove (atomically) the first element of the
List at key
# RPOP key Return and remove (atomically) the last element of the
List at key
# LREM 根据值删除序列元素 (LREM testlist 0 5 \n 33333;删除序列中所有的等
于 33333 的元素,为何不是 REMOVEBY KEY?不知道何故,可能对删除重复数据有用
吧)
# BLPOP key1 key2 ... keyN timeout Blocking LPOP >1.31,后续更新
# BRPOP key1 key2 ... keyN timeout Blocking RPOP >1.31
# RPOPLPUSH srckey dstkey Return and remove (atomically) the
last element of the source List stored at _srckey_ and push the same
element to the destination List stored at _dstkey_
SETS (有索引无序序列)
# SADD 增加元素到 SETS 序列,如果元素不存在则添加成功 1,否则失败 0;(SADD
testlist 3 \n one)
# SREM 删除 SETS 序列的某个元素,如果元素不存则失败 0,否则成功 1(SREM
testlist 3 \N one)
# SPOP 随机删除某个元素 (SPOP testlist)
# SMOVE 把一个 SETS 序列的某个元素移动到 另外一个 SETS 序列 (SMOVE
testlist test 3\n two;从序列 testlist 移动元素 two 到 test 中,—testlist 中将不存
在 two 元素)
# SCARD 统计某个 SETS 的序列的元素数量 (SCARD testlist)
# SISMEMBER 产看某个数据是否在序列中,(SISMEMBER testlist 3 \n two)
# SINTER 几个 SETS 序列的交集 SINTER key1 key2 ... keyN (SINTER
test testlist),牛 B 呀
# SINTERSTORE 把计算出来的交集记录到一个新的序列 SINTERSTORE
dstkey key1 key2 ... keyN (SINTERSTORE resultlist testlist test;把
testlist test 的交集记录到 resultlist)
# SUNION 几个 SETS 序列的并集 SUNION key1 key2 ... keyN (SUNION
test testlist)
# SUNIONSTORE 把计算出来的并集记录到一个新的序列 SUNIONSTORE
dstkey key1 key2 ... keyN (SUNIONSTORE resultlist testlist test;把
testlist test 的交集记录到 resultlist)
# SDIFF key1 key2 ... keyN,求出某几个序列的并集 与 某个序列 求出差集 ,请看
官方例子:
key1 = x,a,b,c
key2 = c
key3 = a,d
SDIFF key1,key2,key3 => x,b
# SDIFFSTORE dstkey key1 key2 ... keyN ,和前面的SINTERSTORE
SUNIONSTORE 差不多,对比
# SMEMBERS KEY 返回某个序列的所有元素
# SRANDMEMBER KEY 随机返回某个序列的元素
Commands operating on sorted sets (zsets,
Redis version >= 1.1)
• ZADD keyscore member Add the specified member to the Sorted
Set value at key or update the score if it already exist
• ZREM keymember Remove the specified member from the Sorted
Set value at key
• ZINCRBY keyincrement member If the member already exists
increment its score by _increment_, otherwise add the member
setting _increment_ as score
• ZRANK keymember Return the rank (or index) or _member_ in
the sorted set at _key_, with scores being ordered from low
to high
• ZREVRANKkey member Return the rank (or index) or _member_
in the sorted set at _key_, with scores being ordered from
high to low
• ZRANGE keystart end Return a range of elements from the sorted
set at key
• ZREVRANGEkey start end Return a range of elements from the
sorted set at key, exactly like ZRANGE, but the sorted set is
ordered in traversed in reverse order, from the greatest to
the smallest score
•ZRANGEBYSCORE key min max Return all the elements with
score >= min and score <= max (a range query) from the sorted
set
• ZCARD keyReturn the cardinality (number of elements) of the
sorted set at key
• ZSCORE keyelement Return the score associated with the
specified element of the sorted set at key
• ZREMRANGEBYRANKkey min max Remove all the elements with
rank >= min and rank <= max from the sorted set
•ZREMRANGEBYSCORE key min max Remove all the elements with
score >= min and score <= max from the sorted set
• ZUNIONSTORE/ ZINTERSTORE dstkey N key1 ... keyN WEIGHTS w1
... wN AGGREGATE SUM|MIN|MAX Perform a union or
intersection over a number of sorted sets with optional
weight and aggregate
Commands operating on hashes
• HSET keyfield value Set the hash field to the specified value.
Creates the hash if needed.
• HGET keyfield Retrieve the value of the specified hash field.
• HMSET keyfield1 value1 ... fieldN valueN Set the hash fields to
their respective values.
• HINCRBY keyfield integer Increment the integer value of the
hash at _key_ on _field_ with _integer_.
• HEXISTS keyfield Test for existence of a specified field in a
hash
• HDEL keyfield Remove the specified field from a hash
• HLEN keyReturn the number of items in a hash.
• HKEYS keyReturn all the fields in a hash.
• HVALS keyReturn all the values in a hash.
• HGETALL keyReturn all the fields and associated values in a
hash.
Sorting
• SORT key BYpattern LIMIT start end GET pattern ASC|DESC ALPHA
Sort a Set or a List accordingly to the specified parameters
Transactions
•MULTI/EXEC/DISCARD Redis atomic transactions
Publish/Subscribe
•SUBSCRIBE/UNSUBSCRIBE/PUBLISH Redis Public/Subscribe
messaging paradigm implementation
Persistence control commands
• SAVESynchronously save the DB on disk
• BGSAVEAsynchronously save the DB on disk
• LASTSAVEReturn the UNIX time stamp of the last successfully
saving of the dataset on disk
• SHUTDOWNSynchronously save the DB on disk, then shutdown
the server
•BGREWRITEAOF Rewrite the append only file in background
when it gets too big
Remote server control commands

INFO Provide information and statistics about the server
MONITOR Dump all the received requests in real time
SLAVEOF Change the replication settings
CONFIG Configure a Redis server at runtime
update:2010-05-21,myredis version 1.26
四.Redis 在客户端程序中的使用 1.Java客户端jar包
Jedis-2.0.0.jar
2.测试程序:
import java.util.ArrayList;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

public class test {

/**
* @param args
*/
public static voidmain(String[] args) {
           // TODOAuto-generated method stub
           JedisPoolConfig jpc= new JedisPoolConfig();

           //简单连接池
           JedisPool jp=newJedisPool(jpc,"172.16.33.151");
           Jedis jd =jp.getResource();
           jd.set("key","value");
           jp.returnResource(jd);
           jp.destroy();

           //通过shardinfo操作对操作集群,客户端会通过特定算法将键分配到某一台server上
           JedisShardInfo ji1=new JedisShardInfo("172.16.33.151");
           JedisShardInfo ji2=new JedisShardInfo("172.16.33.152");
           ArrayList<JedisShardInfo>l= new ArrayList<JedisShardInfo>();
           l.add(ji1);
           l.add(ji2);

           ShardedJedisPool sjp= new ShardedJedisPool(jpc,l);
           ShardedJedis sj =sjp.getResource();
           sj.set("key","value");
           sjp.returnResource(sj);
           System.out.println(sj.get("key"));
           sjp.destroy();
}

}

//集群方式上如何使用全表搜索
public class Client_Keys {

    public static void main(String[] args) {
       JedisPoolConfig jpc = new JedisPoolConfig();

       //通过shardinfo操作对操作集群,客户端会通过特定算法将键分配到某一台server
       JedisShardInfo ji1= new JedisShardInfo("10.20.150.70");
//     JedisShardInfo ji2= newJedisShardInfo("10.20.150.69");
       ArrayList<JedisShardInfo> l= new ArrayList<JedisShardInfo>();
       l.add(ji1);
//     l.add(ji2);

       ShardedJedisPool sjp = new ShardedJedisPool(jpc,l);
       ShardedJedis sj = sjp.getResource();
       sj.set("wzucxdtest", "Client_Keys");

       System.out.println(sj.get("wzucxdtest"));


       //keys搜索
       Collection<Jedis> jedisList = sj.getAllShards();
       Iterator<Jedis> ite = jedisList.iterator();
       while(ite.hasNext()){

           Jedis jedis = ite.next();
           System.out.println("jedis result:" + jedis.keys("9*3"));

       }


       sjp.returnResource(sj);
       sjp.destroy();
    }
}
3.Spring中的配置
    <bean id="jedisPool"class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig"/>
        <constructor-arg index="1" value="172.16.33.156"/>
    </bean>

    <bean id="shardInfo"class="redis.clients.jedis.JedisShardInfo">
        <constructor-arg index="0" value="172.16.33.151"/>
        <constructor-arg index="1" value="6379"/>
    </bean>

    <bean id="shardInfo1" class="redis.clients.jedis.JedisShardInfo">
        <constructor-arg index="0" value="172.16.33.152"/>
        <constructor-arg index="1" value="6379"/>
    </bean>

    <!-- jedisshard pool配置 -->
    <bean id="shardedJedisPool"class="redis.clients.jedis.ShardedJedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig"/>
        <constructor-arg index="1">
            <list>
                <ref bean="shardInfo" />
                <ref bean="shardInfo1" />
            </list>
        </constructor-arg>
</bean>
4.pipeline
Redis作为一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。基本的通信过程如下
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
基本上四个命令需要8个tcp报文才能完成。由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒。那么上面的四个命 令8个报文至少会需要1秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令。这显示没有充分利用 redis的处理能力。除了可以利用mget,mset 之类的单条命令处理多个key的命令外
我们还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。通信过程如下

Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4

假设不会因为tcp报文过长而被拆分。可能两个tcp报文就能完成四条命令,client可以将四个incr命令放到一个tcp报文一起发送,server则可以将四条命令的处理结果放到一个tcp报文返回。通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。下面是个jedis客户端使用pipeline的测试
package com.wzucxd;

import redis.clients.jedis.Jedis;

public class PipeLineTest {

    public static void main(String[] args) {
       long start = System.currentTimeMillis();
       usePipeline();
       long end = System.currentTimeMillis();
       System.out.println(end - start);

       start = System.currentTimeMillis();
       withoutPipeline();
       end = System.currentTimeMillis();
       System.out.println(end - start);

    }

    private static void withoutPipeline() {
       try {
           Jedis jedis = new Jedis("10.20.150.70", 6379);
           for (int i = 0; i < 100000; i++) {
               jedis.incr("test2");
           }
           jedis.quit();
       } catch (Exception e) {
       }
    }

    private static void usePipeline() {
       try {
           Jedis jedis = new Jedis("10.20.150.70",6379);
           jedis.pipelined();
           for (int i = 0; i < 100000; i++) {
              jedis.incr("test2");
           }
           jedis.quit();
       } catch (Exception e) {
       }
    }

}
输出
53682 //使用了pipeline
54556 //没有使用

测试结果不是很明显,这应该是跟我的测试环境有关。我是在自己win连接虚拟机的linux。网络延迟比较小。所以pipeline
优势不明显。如果网络延迟小的话,最好还是不用pipeline。除了增加复杂外,带来的性能提升不明显。

五.测试服务器
Master:   172.16.33.156          port:6379
Slaver:      172.16.33.151          port:6379
Slaver:      172.16.33.152         port:6379
六.密码验证服务器     主库redis.conf 添加:requirepass BJXooPY3!(密码)
     从库 redis.conf添加  masterauth  BJXooPY3! (主库设置的秘密)
     登陆redis>auth BJXooPY3!   通过验证才能操作

七.Log      1.Loglevel    verbose    日志级别
      2.Logfile    /usr/local/redis  输出日志文件路径
      3.Dbfilename  dump.db    本地数据库库文件名
      4.Dir  /var/lib/redis        本地数据库库文件存放路径
      5.Appendonly  yes         数据更新日志启用
      6.  appendfilename   xxx.aof   更新日志文件名

八.DB数      1.默认数据库为0,一般为16
      2.数据库选择用 select 0 或select 1
      3. 不同库间移动key。Move <key> <db>  例子:movesry  1

九. Redis原理1.数据结构redis目前提供四种数据类型:string,list,set及zset(sortedset)。
string(字符串)
  string是最简单的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value,其上支持的操作与Memcached的操作类似。但它的功能更丰富。
  redis采用结构sdshdr和sds封装了字符串,字符串相关的操作实现在源文件sds.h/sds.c中。sdshdr
  数据结构定义如下:
  typedef char *sds;
      struct sdshdr {
      long len;
      long free;
      char buf[];
  };
list(双向链表)
  list是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等。操作中key理解为链表的名字。
  对list的定义和实现在源文件adlist.h/adlist.c,相关的数据结构定义如下:
  // list迭代器
  typedef struct listIter {
      listNode *next;
      int direction;
  } listIter;
  // list数据结构
  typedef struct list {
      listNode *head;
      listNode *tail;
      void *(*dup)(void *ptr);
      void (*free)(void *ptr);
      int (*match)(void *ptr,void *key);
      unsigned int len;
      listIter iter;
  } list;
dict(hash表)
  set是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作。操作中key理解为集合的名字。
  在源文件dict.h/dict.c中实现了hashtable的操作,数据结构的定义如下:
  // dict中的元素项
  typedef struct dictEntry {
      void *key;
      void *val;
      struct dictEntry *next;
  } dictEntry;
  // dict相关配置函数
  typedef struct dictType {
      unsigned int(*hashFunction)(const void *key);
      void *(*keyDup)(void*privdata, const void *key);
      void *(*valDup)(void*privdata, const void *obj);
      int (*keyCompare)(void*privdata, const void *key1, const void *key2);
      void (*keyDestructor)(void*privdata, void *key);
      void (*valDestructor)(void*privdata, void *obj);
  } dictType;
  // dict定义
  typedef struct dict {
      dictEntry **table;
      dictType *type;
      unsigned long size;
      unsigned long sizemask;
      unsigned long used;
      void *privdata;
  } dict;
  // dict迭代器
  typedef struct dictIterator {
      dict *ht;
      int index;
      dictEntry *entry,*nextEntry;
  } dictIterator;
  dict中table为dictEntry指针的数组,数组中每个成员为hash值相同元素的单向链表。set是在dict的基础上实现的,指定了key的比较函数为dictEncObjKeyCompare,若key相等则不再插入。
zset(排序set)
  zset是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解了有两列的mysql表,一列存value,一列存顺序。操作中key理解为zset的名字。
  typedef struct zskiplistNode {
      struct zskiplistNode**forward;
      struct zskiplistNode*backward;
      double score;
      robj *obj;
  } zskiplistNode;
  typedef struct zskiplist {
      struct zskiplistNode*header, *tail;
      unsigned long length;
      int level;
  } zskiplist;
  typedef struct zset {
      dict *dict;
      zskiplist *zsl;
  } zset;
  zset利用dict维护key -> value的映射关系,用zsl(zskiplist)保存value的有序关系。zsl实际是叉数
  不稳定的多叉树,每条链上的元素从根节点到叶子节点保持升序排序。
2.存储结构和存储格式  redis使用了两种文件格式:全量数据和增量请求。全量数据格式是把内存中的数据写入磁盘,
  便于下次读取文件进行加载;增量请求文件则是把内存中的数据序列化为操作请求,用于读取文件进行replay得到数据,序列化的操作包括SET、RPUSH、SADD、ZADD。
  redis的存储分为内存存储、磁盘存储和log文件三部分,配置文件中有三个参数对其进行配置。
  save seconds updates,save配置,指出在多长时间内,有多少次更新操作,就将数据同步到数据文件。这个可以多个条件配合,比如默认配置文件中的设置,就设置了三个条件。
  appendonly yes/no ,appendonly配置,指出是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面的save条件来同步的,所以有的数据会在一段时间内只存在于内存中。
  appendfsync no/always/everysec ,appendfsync配置,no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次。


3.性能下面是官方的bench-mark数据:
  The testwas done with 50 simultaneous clients performing 100000 requests.
  The valueSET and GET is a 256 bytes string.
  The Linuxbox is running Linux 2.6, it’s Xeon X3320 2.5Ghz.
  Textexecuted using the loopback interface (127.0.0.1).
Results:about 110000 SETs per second, about 81000 GETs per second


4.网络IO模型
Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。
5.内存管理方面
Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。
在Redis2.0之后数据容量突破了物理内存限制,并实现冷热分离,相当于重新实现了OS中的虚拟内存概念。
Redis的VM依照之前的epoll实现思路实现。但是在前面操作系统的介绍提到OS也可以自动帮程序实现冷热数据分离,Redis只需要OS申请一块大内存,OS会自动将热数据放入物理内存,冷数据交换到硬盘,另外一个知名的Varnish就是这样实现,也取得了非常成功的效果。
作者antirez在解释为什么要自己实现VM中提到几个原因:主要OS的VM换入换出是基于Page概念,比如OS VM1个Page是4K, 4K中只要还有一个元素即使只有1个字节被访问,这个页也不会被SWAP, 换入也同样道理,读到一个字节可能会换入4K无用的内存。而Redis自己实现则可以达到控制换入的粒度。另外访问操作系统SWAP内存区域时block 进程,也是导致Redis要自己实现VM原因之一。
6.数据一致性问题
Redis没有提供cas 命令(Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题),并不能保证这点,不过Redis提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断。
7.存储方式及其它方面
Redis除key/value之外,还支持list,set,sortedset,hash等众多数据结构,提供了KEYS进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis提供了工具可以直接扫描其dump文件,枚举出所有数据,Redis还同时提供了持久化和复制等功能。
8.关于不同语言的客户端支持
Redis由于其协议本身就比Memcached复杂,加上作者不断增加新的功能等,对应第三方客户端跟进速度可能会赶不上,有时可能需要自己在第三方客户端基础上做些修改才能更好的使用。
当我们不希望数据被踢出,或者需要除key/value之外的更多数据类型时,或者需要落地功能时,使用Redis比使用Memcached更合适。
9.关于Redis的一些周边功能
Redis除了作为存储之外还提供了一些其它方面的功能,比如聚合计算、pubsub、scripting等,对于此类功能需要了解其实现原理,清楚地了解到它的局限性后,才能正确的使用,比如pubsub功能,这个实际是没有任何持久化支持的,消费方连接闪断或重连之间过来的消息是会全部丢失的,又比如聚合计算和scripting等功能受Redis单线程模型所限,是不可能达到很高的吞吐量的,需要谨慎使用。
  总的来说Redis作者是一位非常勤奋的开发者,可以经常看到作者在尝试着各种不同的新鲜想法和思路,针对这些方面的功能就要求我们需要深入了解后再使用。
总结:
 1.Redis使用最佳方式是全部数据in-memory。
 2.Redis更多场景是作为Memcached的替代者来使用。
 3.当需要除key/value之外的更多数据类型支持时,使用Redis更合适。
    4.当存储的数据不能被剔除时,使用Redis更合适。

10.Redis通信协议规范
当安装完程序后我们知道可以用redis-cli连接redis-server进行通讯,进行各种操作。
那么如今我们用的高级语言 如:C、Java、Python、C#等 如何来发送命令(request)来操作数据、从数据库取到相关数据(replies)呢?

官网已经推荐了多种语言client的实现程序http://redis.io/clients 有兴趣的可以分析源代码

如何发送命令?
Socket! 通过TCP协议形式的Socket我们可以连接到redis-server,然后发送一些特定格式的命令及相关数据就可以了。其中命令都是以 \r\n (CR LF) 结尾的

特殊命令?
上面提到的特殊格式就是所谓的redis通讯协议,保证发送的命令、数据及返回数据格式的规范。这样我们才能够正确的发送命令到redis-server,然后根据协议的格式解析返回的数据(reply data)得到自己想要的形式。

协议规范内容介绍:
标准协议:
该协议已经在redis1.2版本中介绍过了,但是在2.0版本中才作为与redis-server进行通讯的标准方式

在这个新的协议中,所有的发送到redis-server的参数都是二进制安全的(保证二进制的安全),其基本形式如下:
*<参数的个数> CRLF
$<参数1字节数> CR LF
<参数1> CR LF
...
$<参数n字节数> CR LF
<参数n> CR LF

例如命令:setmykey myvalue 相对应的形式应该如下,
*3CR LF  //三个参数
$3CR LF  //第一个参数set有三个字节
SETCR LF //参数内容set
$5CR LF  //第二个参数mykey有五个字节
mykeyCR LF //参数二内容 mykey
$7CR LF  //第三个参数有7个字节
myvalueCR LF //参数三内容 myvalue

字符串形式的结果就是 :"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

实际上返回的数据也符合这个协议格式。如:$6\r\nmydata\r\n 是一个从redis-server返回的数据,被称为块响应数据,通常是一个字符串(还有其他几个类型,稍后介绍)。另外redis也可以返回一个数据的列表,可以返回多个 块数据(就被称作多块数据)。它返回的结果中总是以 "*参数个数\r\n"开头的,参数个数表示了共有几个块数据(可以理解为返回多个字符串)

响应的类型:
判断一个响应的类型都是由返回数据的第一个字节决定的,有如下几种类型:

"+"代表一个状态信息如 +ok
"-"代表发送了错误  (如:操作运算操作了错误的类型)
":"返回的是一个整数  格式如:":11\r\n。
一些命令返回一些没有任何意义的整数,如LastSave返回一个时间戳的整数值, INCR返回一个加1后的数值;一些命令如exists将返回0或者1代表是否true or false;其他一些命令如SADD, SREM 在确实执行了操作时返回1 ,否则返回0

"$"返回一个块数据
被用来返回一个二进制安全的字符串,如Get mykey 返回: "$6\r\nsongzh\r\n";
如果请求的值不存在那么将返回一个$-1。当实现客户端程序时,你真正的应该提示用户值不存在,返回null
"*"返回多个块数据(用来返回多个值,总是第一个字节为"*",后面写着包含多少个相应值,如:
  1. C:LRANGEmylist 0 3
  2. S:*4
  3. S:$3
  4. S:foo
  5. S:$3
  6. S:bar
  7. S:$5
  8. $:world
复制代码

如果指定的值不存在,那么返回*0

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2023-2-9 07:32 , Processed in 0.021706 second(s), 7 queries , Redis On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表