Redis学习10–Java Drivers

Java驱动实践
  1. Redis的客户端有主要有三种:JDBC-Redis, JRedis和Jedis,推荐Jedis的方式;
  2. Jedis API Online Help:http://www.jarvana.com/jarvana/view/redis/clients/jedis/2.0.0/jedis-2.0.0-javadoc.jar!/index.html;
  3. Jredis使用总结:
    1. pipeline:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them;即pipeline适用于批处理,当有大量的操作需要一次性执行的时候,可以用管道;
    2. 分布式的id生成器:因为redis-server是单线程处理client端的请求的,所以可以使用jedis.incr(“id_key”)来生成序列;
    3. 分布式锁watch/multi:可以用来实现跨jvm的同步问题;
      1. 方法1:Jedis.setnx(key, value),推荐的方法;
      2. 方法2:事务multi;
      3. 方法3:事务+监听;
    4. redis分布式:jedis里面通过MD5,MURMUR Hash(默认)两种方式实现了分布式,也可以自己实现redis.clients.util.Hashing接口;
  4. Jedis中Pool的问题:It seems like server has closed the connection;
    1. 原因:redis-server关闭了此客户端的连接,server端设置了maxidletime(默认是5分钟),服务端会不断循环检测clinet的最后一次通信时间(lastinteraction),如果大于maxidletime,则关闭连接,并回收相关资源,client在向该连接中写数据后就会由于server端已经关闭而出现broken pipe的问题;
    2. 错误的配置:在spring初始化时获取一次实例化jedisCommands,而后每次的redis的调用时并未从pool中获取;
    3. 解决办法;

———————– Pipeline ———————–

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
 
public class Redis
{
     public static void main(String[] args)
     {
           // 连接Redis 服务器;
          Jedis jedis = new Jedis( “192.168.10.112”, 6379);
           // 生成Pipeline;
          Pipeline pipeline = jedis.pipelined();
           // 管道操作,会把所有的操作发送给服务器端,然后一次性执行;
           // ……
          pipeline.incr( “key”);
           // 获得所有的结果;
          pipeline.sync();
     }
}
———————– Pipeline ———————–
———————– Jedis.setnx(key, value) ———————–
import java.util.Random;
import org.apache.commons.pool.impl.GenericObjectPool.Config;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
 
public class RedisLock
{
     // 加锁标志
     public static final String    LOCKED                  = “TRUE”;
     public static final long      ONE_MILLI_NANOS         = 1000000L;
     
     // 默认超时时间(ms )
     public static final long      DEFAULT_TIME_OUT   = 3000;
     public static JedisPool       pool;
     public static final Random    r                       = new Random();
     
     // 锁的超时时间(s),过期删除
     public static final int       EXPIRE                  = 5 * 60;
     static
     {
           pool = new JedisPool( new Config(), “host”, 6379);
     }
     
     private Jedis                 jedis;
     private String                key;
     
     // 锁状态标志
     private boolean                    locked                  = false;
     
     public RedisLock(String key)
     {
           this. key = key;
           this. jedis = pool.getResource();
     }
     
     public boolean lock( long timeout)
     {
           long nano = System. nanoTime();
          timeout *= ONE_MILLI_NANOS;
           try
          {
               while ((System. nanoTime() – nano) < timeout)
              {
                    if ( jedis.setnx( key, LOCKED) == 1)
                   {
                         jedis.expire( key, EXPIRE);
                         this. locked = true ;
                         return locked;
                   }
                    // 短暂休眠,nano避免出现活锁
                   Thread. sleep(3, r.nextInt(500));
              }
          }
           catch (Exception e)
          {
              e.printStackTrace();
          }
           return false;
     }
     
     public boolean lock()
     {
           return lock( DEFAULT_TIME_OUT);
     }
     
     // 无论是否加锁成功,必须调用
     public void unlock()
     {
           try
          {
               if ( locked)
              {
                    jedis.del( key);
              }
          }
           finally
          {
               pool.returnResource( jedis);
          }
     }
}
———————– Jedis.setnx(key, value) ———————–
———————– 事务multi ———————–
     public boolean lock_2( long timeout)
     {
           long nano = System. nanoTime();
          timeout *= ONE_MILLI_NANOS;
           try
          {
               while ((System. nanoTime() – nano) < timeout)
              {
                   Transaction t = jedis.multi();
                    // 开启事务,当server端收到 multi指令;
                    // 会将该client的命令放入一个队列,然后依次执行,直到收到 exec指令;
                   t.getSet( key, LOCKED);
                   t.expire( key, EXPIRE);
                   String ret = (String) t.exec().get(0);
                    if (ret == null || ret.equals(“UNLOCK”))
                   {
                         return true;
                   }
                    // 短暂休眠,nano避免出现活锁;
                   Thread. sleep(3, r.nextInt(500));
              }
          }
           catch (Exception e)
          {
          }
           return false;
     }
———————– 事务multi ———————–
———————– 事务+监听 ———————–
     public boolean lock_3( long timeout)
     {
           long nano = System. nanoTime();
          timeout *= ONE_MILLI_NANOS;
           try
          {
               while ((System. nanoTime() – nano) < timeout)
              {
                    jedis.watch( key);
                    // 开启watch之后,如果key的值被修改,则事务失败, exec方法返回null;
                   String value = jedis.get( key);
                    if (value == null || value.equals(“UNLOCK”))
                   {
                        Transaction t = jedis.multi();
                        t.setex( key, EXPIRE, LOCKED);
                         if (t.exec() != null)
                        {
                              return true;
                        }
                   }
                    jedis.unwatch();
                    // 短暂休眠,nano避免出现活锁;
                   Thread. sleep(3, r.nextInt(500));
              }
          }
           catch (Exception e)
          {
          }
           return false;
     }
———————– 事务+监听 ———————–
———————– redis分布式 ———————–
List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>();
// server1
JedisShardInfo host1 = new JedisShardInfo( “”, 6380, 2000);
// server2
JedisShardInfo host2 = new JedisShardInfo( “”, 6381, 2000);
hosts.add(host1);
hosts.add(host2);
 
ShardedJedis jedis = new ShardedJedis(hosts);
jedis.set(“key”, “”);
———————– redis分布式 ———————–
———————– 错误的配置 ———————–
<bean id=”jedisPoolConfig” class=”redis.clients.jedis.JedisPoolConfig”>
<property name=”maxActive”  value=”20″ />
<property name=”maxIdle” value=”10″ />
<property name=”maxWait” value=”1000″ />
</bean>

<!– jedis shard信息配置 –>
<bean id=”jedis.shardInfo” class=”redis.clients.jedis.JedisShardInfo”>
<constructor-arg index=”0″ value=”*.*.*.*” />
<constructor-arg index=”1″ value=”6379″ />
</bean>

<!– jedis shard pool配置 –>
<bean id=”shardedJedisPool” class=”redis.clients.jedis.ShardedJedisPool”>
<constructor-arg index=”0″ ref=”jedisPoolConfig” />
<constructor-arg index=”1″>
<list>
<ref bean=”jedis.shardInfo” />
</list>
</constructor-arg>
</bean>

<bean id=”jedisCommands” factory-bean=”shardedJedisPool” factory-method=”getResource” />

———————– 错误的配置 ———————–
———————– 解决办法 ———————–
<!– POOL配置 –>
<bean id=”jedisPoolConfig” class=”redis.clients.jedis.JedisPoolConfig”>
<property name=”maxActive”  value=”20″ />
<property name=”maxIdle” value=”10″ />
<property name=”maxWait” value=”1000″ />
<property name=”testOnBorrow”  value=”true”/>
</bean>

<!– jedis shard信息配置 –>
<bean id=”jedis.shardInfo” class=”redis.clients.jedis.JedisShardInfo”>
<constructor-arg index=”0″ value=”*.*.*.*” />
<constructor-arg index=”1″ value=”6379″ />
</bean>

<!– jedis shard pool配置 –>
<bean id=”shardedJedisPool” class=”redis.clients.jedis.ShardedJedisPool”>
<constructor-arg index=”0″ ref=”jedisPoolConfig” />
<constructor-arg index=”1″>
<list>
<ref bean=”jedis.shardInfo” />
</list>
</constructor-arg>
</bean>

———————– 解决办法 ———————–

Redis学习09–虚拟内存

Redis中的虚拟内存
  1. Redis中虚拟内存的概念:
    1. redis的虚拟内存与os的虚拟内存不是同一个概念,但是实现的方法和目的是相同的,就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据;
    2. 对于redis这样的内存数据库,内存总是不够用的,除了可以将数据分割到多个redis server外,另外的能够提高数据库容量的办法就是使用vm把那些不经常访问的数据交换的磁盘上;
    3. 总是有少部分数据被经常访问,大部分数据很少被访问,对于网站来说确实总是只有少量用户经常活跃,当少量数据被经常访问时,使用vm不但能提高单台redis server数据库的容量,而且也不会对性能造成太多影响;
  2. Redis没有使用os提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制,主要原因为:
    1. os的虚拟内存是以4k页面为最小单位进行交换的,而redis的大多数对象都远小于4k,所以一个os页面上可能有多个redis对象;
    2. 另外redis的集合对象类型如list,set可能存在与多个os页面上,最终可能造成只有10%的key被经常访问,但是所有os页面都会被os认为是活跃的,这样只有内存真正耗尽时os才会交换页面;
    3. 相比于os的交换方式,redis可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息,一般压缩后的对象会比内存中的对象小10倍,这样redis的vm会比osvm能少做很多io操作;
  3. VM的配置:
    1. vm-enabled yes                     # 开启vm功能;
    2. vm-swap-file /tmp/redis.swap       # 交换出来的value保存的文件路径/tmp/redis.swap;
    3. vm-max-memory 1000000              # redis使用的最大内存上限,超过上限后redis开始交换value到磁盘文件中;
    4. vm-page-size 32                    # 每个页面的大小32个字节;
    5. vm-pages 134217728                 # 最多使用在文件中使用多少页面,交换文件的大小 = vm-page-size * vm-pages
    6. vm-max-threads 4                   # 用于执行value对象换入换出的工作线程数量,推荐设置为cpu的核心数,0表示不使用工作线程;
  4. 参数的解释:
    1. redis的vm在设计上为了保证key的查找速度,只会将value交换到swap文件中,所以如果是内存问题是由于太多value很小的key造成的,那么vm并不能解决;
    2. 和os一样redis也是按页面来交换对象的,redis规定同一个页面只能保存一个对象,但是一个对象可以保存在多个页面中;
    3. 在redis使用的内存没超过vm-max-memory之前是不会交换任何value的,当超过最大内存限制后,redis会选择较老的对象,如果两个,对象一样老会优先交换比较大的对象,精确的公式swappability = age log(size_in_memory);
    4. 对于vm-page-size的设置应该根据自己的应用将页面的大小设置为可以容纳大多数对象的大小,太大了会浪费磁盘空间,太小了会造成交换文件出现碎片;
    5. 对于交换文件中的每个页面,redis会在内存中对应一个1bit值来记录页面的空闲状态,所以像上面配置中页面数量(vm-pages 134217728)会占用16M(134217728/8/1024/1024)内存用来记录页面空闲状态;
    6. vm-max-threads表示用做交换任务的线程数量,如果大于0推荐设为服务器的cpu core的数量,如果是0则交换过程在主线程进行;
  5. vm的工作原理:
    1. 当vm-max-threads设为0时(Blocking VM):
      1. 换出:主线程定期检查发现内存超出最大上限后,会直接已阻塞的方式,将选中的对象保存到swap文件中,并释放对象占用的内存,此过程会一直重复直到下面条件满足
        1. 内存使用降到最大限制以下;
        2. swap文件满了;
        3. 几乎全部的对象都被交换到磁盘了;
      2. 换入:当有client请求value被换出的key时,主线程会以阻塞的方式从文件中加载对应的value对象,加载时此时会阻塞所有的client,然后处理client的请求;
    2. 当vm-max-threads大于0(Threaded VM):
      1. 换出:当主线程检测到使用内存超过最大上限,会将选中的要交换的对象信息放到一个队列中交由工作线程后台处理,主线程会继续处理client请求;
      2. 换入:如果有client请求的key被换出了,主线程先阻塞发出命令的client,然后将加载对象的信息放到一个队列中,让工作线程去加载,加载完毕后工作线程通知主线程,主线程再执行client的命令,这种方式只阻塞请求value被换出key的client;
    3. 总的来说blocking vm的方式总的性能会好一些,因为不需要线程同步,创建线程和恢复被阻塞的client等开销,但是也相应的牺牲了响应性;threaded vm的方式主线程不会阻塞在磁盘io上,所以响应性更好;如果应用不太经常发生换入换出,而且也不太在意有点延迟的话则推荐使用blocking vm的方式;
  6. 相关链接:
    1. http://antirez.com/post/redis-virtual-memory-story.html;
    2. http://redis.io/topics/internals-vm;

Redis学习08–Master-Slave架构

Redis的主从架构
  1. redis主从复制的配置和使用都非常简单,通过主从复制可以允许多个slave servers和master server具有相同的数据库副本;
  2. Master-Salve的特点:
    1. master可以有多个slaves;
    2. 除了多个slave连到相同的master外,slave也可以连接其他slave形成图状结构;
    3. 主从复制不会阻塞master,也就是说当一个或多个slave与master进行初次同步数据时,master可以继续处理client发来的请求,相反slave在初次同步数据时则会阻塞不能处理client的请求;
    4. 从复制可以用来提高系统的可伸缩性,我们可以用多个slave专门用于client的读请求,比如sort操作可以使用slave来处理,也可以用来做简单的数据冗余;
    5. 可以在master禁用数据持久化,只需要注释掉master配置文件中的所有save配置,然后只在slave上配置数据持久化;
  3. Master-Slave的过程:
    1. 当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令;
    2. 无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来;
    3. 后台进程完成写文件后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上;
    4. 接着master就会把缓存的命令转发给slave,而且后续master收到的写命令都会通过开始建立的连接发送给slave;
    5. 从master到slave的同步数据的命令和从client发送的命令使用相同的协议格式;
    6. 当master和slave的连接断开时slave可以自动重新建立连接,如果master同时收到多个slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave;
  4. 配置slave服务器很简单,只需要在配置文件中加入如下配置:slaveof 192.168.1.1 6379  #指定master的ip和端口;

Redis学习07–持久化

Redis的数据持久化
  1. redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化,redis支持两种持久化方式:
    1. Snapshotting(快照)也是默认方式;
    2. Append-only file(缩写aof)的方式;
  2. Snapshotting:
    1. 快照是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb;
    2. 可以通过配置设置自动做快照持久化的方式,我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置:
      1. save 900 1  #900s内如果超过1个key被修改,则发起快照保存;
      2. save 300 10 #300s内如果超过10个key被修改,则发起快照保存;
      3. save 60 10000 # 10s内,如果超过10000个key被修改,则发起快照保存;
    3. 快照保存过程:
      1. redis调用fork,现在有了子进程和父进程;
      2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件,由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面,所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照;
      3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出;
    4. 快照持久化的注意事项:
      1. client也可以使用save或者bgsave命令通知redis做一次快照持久化,save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有client的请求,这种方式会阻塞所有client请求,所以不推荐使用;
      2. 需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据,如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能;
      3. 另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改,如果应用要求不能丢失任何修改的话,可以采用aof持久化方式;
  3. Append-only file:
    1. aof比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof),当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容,当然由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上,这样aof方式的持久化也还是有可能会丢失部分修改,不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机:
      1. appendonly yes:启用aof持久化方式;
      2. appendfsync always:每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用;
      3. appendfsync everysec:每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐,也是默认方式;
      4. appendfsync no:完全依赖os,性能最好,持久化没保证;
    2. aof的方式也同时带来了另一个问题,持久化文件会变的越来越大;例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的,因为要恢复数据库的状态其实文件中保存一条set test 100就够了;为了压缩aof的持久化文件,redis提供了bgrewriteaof命令,收到此命令redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件;
    3. bgrewriteaof命令原理:
      1. redis调用fork,现在有父子两个进程;
      2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令;
      3. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中,同时把收到的写命令缓存起来,这样就能保证如果子进程重写失败的话并不会出问题;
      4. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程,然后父进程把缓存的写命令也写入到临时文件;
      5. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加;
    4. 需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似;

Redis学习06–发布及订阅

Redis的发布及订阅

  1. 发布订阅(pub/sub)是一种消息通信模式,主要的目的是解决消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似;pub/sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合;
  2. redis作为一个pub/sub server,在订阅者和发布者之间起到了消息路由的功能,订阅者可以通过subscribe和psubscribe命令向redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel);
  3. 当发布者通过publish命令向redis server发送特定类型的消息时,订阅该消息类型的全部client都会收到此消息;这里消息的传递是多对多的,一个client可以订阅多个channel,也可以向多个channel发送消息;
  4. 发布/订阅的命令:
    1. SUBSCRIBE channel [channel …]:Subscribes the client to the specified channels,Once the client enters the subscribed state it is not supposed to issue any other commands, except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands;
    2. PSUBSCRIBE pattern [pattern …]:Subscribes the client to the given patterns;
    3. UNSUBSCRIBE [channel [channel …]]:Unsubscribes the client from the given channels, or from all of them if none is given,When no channels are specified, the client is unsubscribed from all the previously subscribed channels. In this case, a message for every unsubscribed channel will be sent to the client;
    4. PUNSUBSCRIBE [pattern [pattern …]]:Unsubscribes the client from the given patterns;
    5. PUBLISH channel message:Posts a message to the given channel; Integer reply:the number of clients that received the message;
  5. 开启三个redis-cli测试发布订阅的例子;
  6. redis的协议是文本类型的,具体链接为:http://redis.io/topics/protocol;
  7. 使用java客户端来订阅消息;
———————— 发布/订阅测试 ————————
— 1.打开第一个redis-cli的客户端订阅news:snda和news:taobao两个channels,收到redis-server返回订阅成功的消息;
redis 127.0.0.1:6379> subscribe news:snda news:taobao
Reading messages… (press Ctrl-C to quit)
1) “subscribe”
2) “news:snda”
3) (integer) 1
1) “subscribe”
2) “news:taobao”
3) (integer) 2
— 2.打开第二个redis-cli客户端,使用psubscribe订阅news:*模式(*表示任意字符串)的channels;
redis 127.0.0.1:6379> psubscribe news:*
Reading messages… (press Ctrl-C to quit)
1) “psubscribe”
2) “news:*”
3) (integer) 1
— 3.打开第三个redis-cli客户端,通过publish命令发布消息,返回值表示接收到此订阅的个数,通过订阅的窗口查看消息;
redis 127.0.0.1:6379> publish news:snda “www.snda.com”
(integer) 2
redis 127.0.0.1:6379> publish news:taobao “www.taobao.com”
(integer) 2
———————— 发布/订阅测试 ————————
———————— 使用java客户端来订阅消息 ————————
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
 
public class PubSubTest
{
     private static Socket    socket;
     
     public static void main(String[] args)
     {
           // String cmd = args[0] + “\r\n”;
          String cmd = “subscribe news:snda news:taobao \r\n”;
           try
          {
               socket = new Socket( “192.168.10.112”, 6379);
              InputStream in = socket.getInputStream();
              OutputStream out = socket.getOutputStream();
              out.write(cmd.getBytes());
               // 发送订阅命令         
               byte[] buffer = new byte[1024];
               while ( true)
              {
                    int readCount = in.read(buffer);
                   System. out.write(buffer, 0, readCount);
                   System. out.println( “————————————–“ );
              }
          }
           catch (Exception e)
          {
              e.printStackTrace();
          }
     }
}
 
———————— 使用java客户端来订阅消息 ————————

Redis学习05–Pipeline

Redis中的Pipeline
  1. Pipeline是打包多条命令发送给服务端,服务端处理完多条命令后将结果打包一起返回的方式;
  2. redis是一个cs模式的tcp server,使用和http类似的请求响应协议,一个client可以通过一个socket连接发起多个请求命令;每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client;
  3. 模拟的通信过程;                                                
  4. 基本上四个命令需要8个tcp报文才能完成,由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒,那么上面的四个命令8个报文至少会需要1秒才能完成,这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令,这显示没有充分利用redis的处理能力;
  5. 可以利用mget,mset之类的单条命令处理多个key的命令外,还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端;
  6. 假设不会因为tcp报文过长而被拆分,可能两个tcp报文就能完成四条命令,client可以将四个incr命令放到一个tcp报文一起发送,server则可以将四条命令的处理结果放到一个tcp报文返回;
    通过pipeline方式当有大批量的操作时候,我们可以节省很多原来浪费在网络延迟的时间,需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果,打包的命令越多,缓存消耗内存也越多;所以并是不是打包的命令越多越好;
  7. 使用Jedis测试Pipeline的过程,发现使用pipeline节省很多时间;                        
package com.snda.study.redis;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
 
public class PipeLineTest
{
     public static void main(String[] args)
     {
           // 不使用pipeline的时间消耗;
           long start = System. currentTimeMillis();
           withoutPipeline();
           long end = System. currentTimeMillis();
          System. out.println( “WithoutPipeline:” + (end – start));
          
           // 使用pipeline的时间消耗;
          start = System. currentTimeMillis();
           usePipeline();
          end = System. currentTimeMillis();
          System. out.println( “UsePipeline:” + (end – start));
     }
     
     /**
      * 不使用pipeline的方式,循环N次,需要与服务器端交互2N次;
      */
     private static void withoutPipeline()
     {
           try
          {
              Jedis jedis = new Jedis( “192.168.10.112”, 6379);
               for ( int i = 0; i < 100000; i++)
              {
                   jedis.incr( “withoutPipeline”);
              }
              jedis.quit();
          }
           catch (Exception e)
          {
              e.printStackTrace();
          }
     }
     
     /**
      * 使用pipeline,循环N次,会把操作打包发送给服务端,然后一次性返回;
      */
     private static void usePipeline()
     {
           try
          {
              Jedis jedis = new Jedis( “192.168.10.112”, 6379);
              Pipeline pipeline = jedis.pipelined();
               for ( int i = 0; i < 100000; i++)
              {
                   pipeline.incr( “usePipeline”);
              }
              pipeline.sync();
              jedis.quit();
          }
           catch (Exception e)
          {
              e.printStackTrace();
          }
     }
}

Redis学习04–对事务的处理

Redis中的事务
  1. redis对事务的支持目前还比较简单,redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令,由于redis是单线程来处理所有client的请求的所以做到这点是很容易的;
  2. 一般情况下redis在接受到一个client发来的命令后会立即处理并返回处理结果,但是当一个client在一个连接中发出multi命令时,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中,当此连接收到exec命令后,redis会顺序的执行队列中的所有命令,并将所有命令的运行结果打包到一起返回给client,然后此连接就结束事务上下文;
  3. 命令解释:
    1. MULTI:Marks the start of a transaction block. Subsequent commands will be queued for atomic execution using EXEC,开启一个事务,相当于begin transaction;
    2. EXEC:Executes all previously queued commands in a transaction and restores the connection state to normal,When using WATCH, EXEC will execute commands only if the watched keys were not modified,执行事务队列里面的命令,相当于commit;
    3. DISCARD:Flushes all previously queued commands in a transaction and restores the connection state to normal,If WATCH was used, DISCARD unwatches all keys,取消事务队列里面的命令,相当于rollback;
    4. WATCH:Marks the given keys to be watched for conditional execution of a transaction,命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败;也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁了;
    5. 注意watch的key是对整个连接有效的,事务也一样,如果连接断开,监视和事务都会被自动清除,当然了exec, discard, unwatch命令都会清除连接中的所有监视;
  4. 使用multi命令的例子;
  5. 使用discard命令的例子;
  6. 使用watch命令的例子;
  7. Redis使用事务的bug:
    1. redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,命令类型不匹配的例子;
    2. 当事务的执行过程中,如果redis服务宕机了,只有部分命令执行了,后面的也就被丢弃了;当然如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容,即使是这种方式还是有可能只部分写入了事务到磁盘;发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出,可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容,修复完后就能够重新启动了;

————————- multi命令 ————————-

— 1.可以看到incr a ,incr b命令发出后并没执行而是被放到了队列中;调用exec后两个命令被连续的执行,最后返回的是两条命令执行后的结果;
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1

————————- multi命令 ————————-
————————- discard命令 ————————-
— 1.可以发现incr a incr b都没被执行;discard命令其实就是清空事务的命令队列并退出事务上下文;
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> discard
OK
redis 127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
redis 127.0.0.1:6379> get a
“1”
redis 127.0.0.1:6379> get b
“1”
————————- discard命令 ————————-
————————- watch命令 ————————-
— 1.常规情况,设置a的值;如果是一个incr a的操作,使用以下命令去实现,由于get a和set a两个命令并不能保证是连续执行的(get操作不在事务上下文中),很可能有两个client同时做这个操作,结果期望是加两次a从原来的1变成3,但是很有可能两个client的get a,取到都是1,造成最终加两次结果却是2,主要问题我们没有对共享资源a的访问进行任何的同步;
也就是说redis没提供任何的加锁机制来同步对a的访问;
redis 127.0.0.1:6379> get a
“1”
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a 2
QUEUED
redis 127.0.0.1:6379> exec
1) OK
redis 127.0.0.1:6379> get a
“2”
— 2.使用watch命令对变量加锁;
redis 127.0.0.1:6379> watch a
OK
redis 127.0.0.1:6379> get a
“2”
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a 3
QUEUED
redis 127.0.0.1:6379> exec
1) OK
redis 127.0.0.1:6379> get a
“3”
— 3.被watch的key如果在事务在exec之前被修改了,则返回错误;
————————- watch命令 ————————-
————————- Redis中事务的bugs ————————-
— 1.数据类型不一致导致事务失败;
redis 127.0.0.1:6379> set a 1
OK
redis 127.0.0.1:6379> lpush list1 5
(integer) 1
redis 127.0.0.1:6379> set c 1
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr list1
QUEUED
redis 127.0.0.1:6379> incr c
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 2
2) (error) ERR Operation against a key holding the wrong kind of value
3) (integer) 2
— 2.当事务的执行过程中,如果redis服务宕机了,只有部分命令执行了,后面的也就被丢弃了;
————————- Redis中事务的bugs ————————-

Redis学习03–排序操作

Redis的排序操作

  1. redis支持对list, set和sorted set元素的排序;
  2. 排序命令SORT的语法:SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC|DESC] [ALPHA] [STORE destination];
  3. sort key [ASC|DESC] [ALPHA]:
    1. sort key:不加任何选项时就是简单的对列表自身元素排序,并返回排序结果,列表中的值只能是数字;
    2. ASC:sort默认的排序方式,即升序排列;
    3. DESC:是逆序排列;
    4. ALPHA:如果列表中是字符串或者数字的话,需要使用alpha选项按照字母顺序排列;ASC|DESC可以和ALPHA连用;
  4. [LIMIT offset count]:用于分页显示,表示从offset的位置(从0开始),显示count个记录;
  5. [BY nosort]:不排序,直接返回list, set或者sorted set中元素的物理顺序;
  6. [BY pattern]:使用外部的keys作为权重来对list, set, sorted set中的元素排序,但是权重的后缀必须与几何中的元素一一对应;
  7. [GET pattern …]:以通过get选项去获取指定pattern作为新key对应的值;
  8. [STORE destination]:如果对集合经常按照固定的模式去排序,那么把排序结果缓存起来会减少cpu的开销,使用store选项可以将排序内容保存到指定key中,保存的类型是list;
  9. 关于排序的两个问题及解决方案:
    1. 如果我们有多个redis server的话,不同的key可能存在于不同的server上,比如weight_3,weight_9, weight_12, weight_22很有可能分别在四个不同的server上存放着,这种情况会对排序性能造成很大的影响;
      1. 可以通过key tag将需要排序的key都放到同一个server上,由于具体决定哪个key存在哪个服务器上一般都是在client端hash的办法来做的,我们可以通过只对key的部分进行hash;
      2. 举个例子假如我们的client如果发现key中包含[],那么只对key中[]包含的内容进行hash,我们将weight_四个相关的key,[weight_]3,[weight_]9, [weight_]12, [weight_]22都这样命名,于是client程序就会把他们都放到同一server上;
    2. 如果要sort的集合非常大的话排序就会消耗很长时间,由于redis单线程的,所以长时间的排序操作会阻塞其他client的请求;
      1. 通过主从复制机制将数据复制到多个slave上,然后我们只在slave上做排序操作,并进可能的对排序结果缓存;
      2. 或者是采用sorted set对需要按某个顺序访问的集合建立索引;
  10. 实际使用的场景及例子;
——————————— sort key [ASC|DESC] [ALPHA] ———————————
— 1.sort key;
redis 127.0.0.1:6379> lpush l1 12
(integer) 1
redis 127.0.0.1:6379> lpush l1 3
(integer) 2
redis 127.0.0.1:6379> lpush l1 22
(integer) 3
redis 127.0.0.1:6379> lpush l1 9
(integer) 4
redis 127.0.0.1:6379> sort l1
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 desc
1) “22”
2) “12”
3) “9”
4) “3”
— 2.alpha;
1.对数字按照字母表顺序排序;
2.字符串按照字母表顺序排序;
redis 127.0.0.1:6379> lpush l2 snda
(integer) 1
redis 127.0.0.1:6379> lpush l2 tencent
(integer) 2
redis 127.0.0.1:6379> lpush l2 baidu
(integer) 3
redis 127.0.0.1:6379> lpush l2 taobao
(integer) 4
redis 127.0.0.1:6379> sort l2 alpha
1) “baidu”
2) “snda”
3) “taobao”
4) “tencent”
redis 127.0.0.1:6379> sort l2 desc alpha
1) “tencent”
2) “taobao”
3) “snda”
4) “baidu”
redis 127.0.0.1:6379> sort l2
(error) ERR One or more scores can’t be converted into double
——————————— sort key [ASC|DESC] [ALPHA] ———————————
——————————— [LIMIT offset count] ———————————
获得l1集合中第二个字符开始的后两个值;
redis 127.0.0.1:6379> sort l1
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 limit 1 2
1) “9”
2) “12”
redis 127.0.0.1:6379> sort l1 limit 1 2 desc
1) “12”
2) “9”
——————————— [LIMIT offset count] ———————————
——————————— [BY nosort] ———————————
li列表插入的时候是按照12, 3, 22, 9的顺序插入的,但是实际的物理顺序是反序的,即后插入的元素在前面;
redis 127.0.0.1:6379> sort l1 by nosort
1) “9”
2) “22”
3) “3”
4) “12”
——————————— [BY nosort] ———————————
——————————— [BY pattern] ———————————
redis 127.0.0.1:6379> sort l1 by nosort
1) “9”
2) “22”
3) “3”
4) “12”
redis 127.0.0.1:6379> set weight_3 1
OK
redis 127.0.0.1:6379> set weight_12 2
OK
redis 127.0.0.1:6379> set weight_22 3
OK
redis 127.0.0.1:6379> set weight_9 4
OK
redis 127.0.0.1:6379> sort l1 by weight_*
1) “3”
2) “12”
3) “22”
4) “9”
redis 127.0.0.1:6379> sort l1 by weight_* desc
1) “9”
2) “22”
3) “12”
4) “3”
redis 127.0.0.1:6379> set weight_22 5
OK
redis 127.0.0.1:6379> sort l1 by weight_*
1) “3”
2) “12”
3) “9”
4) “22”
——————————— [BY pattern] ———————————
——————————— [GET pattern …] ———————————
— 1.返回pattern的值,按照pattern的顺序;
redis 127.0.0.1:6379> sort l1 by weight_*  get weight_*
1) “1”
2) “2”
3) “4”
4) “5”
— 2.通过#(#特殊符号引用的是原始集合)返回原来列表中的值;
redis 127.0.0.1:6379> sort l1 by weight_* get weight_* get #
1) “1”
2) “3”
3) “2”
4) “12”
5) “4”
6) “9”
7) “5”
8) “22”
— 3.引用hash类型字段的话需要使用->符号,当值不存在就返回nil;
redis 127.0.0.1:6379> hset h_3 id 1
(integer) 1
redis 127.0.0.1:6379> hset h_22 id 2
(integer) 1
redis 127.0.0.1:6379> hset h_9 id 3
(integer) 1
redis 127.0.0.1:6379> hset h_10 id 4
(integer) 1
redis 127.0.0.1:6379> sort l1 get h_*->id
1) “1”
2) “3”
3) (nil)
4) “2”
——————————— [GET pattern …] ———————————
——————————— [STORE destination] ———————————
把排序的结果保存在一个list中;
redis 127.0.0.1:6379> sort l1
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 limit 1 2 store sl1
(integer) 2
redis 127.0.0.1:6379> type sl1
list
redis 127.0.0.1:6379> lrange sl1 0 -1
1) “9”
2) “12”
——————————— [STORE destination] ———————————
——————————— 实际使用的场景及例子 ———————————
— 1.kobe的队友列表,存放队友的号码uid;
redis 127.0.0.1:6379> sadd kobe:team:list 10
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 12
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 15
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 16
(integer) 1
— 2.球员的排序规则,存放球员的得分;
redis 127.0.0.1:6379> set uid:sort:10 1000
OK
redis 127.0.0.1:6379> set uid:sort:12 2000
OK
redis 127.0.0.1:6379> set uid:sort:15 300
OK
redis 127.0.0.1:6379> set uid:sort:16 2500
OK
— 3.队友的信息;
redis 127.0.0.1:6379> set uid:10 “{‘uid’:10,’name’:’Steve Nash’}”
OK
redis 127.0.0.1:6379> set uid:12 “{‘uid’:12,’name’:’Dwight Howard’}”
OK
redis 127.0.0.1:6379> set uid:15 “{‘uid’:15,’name’:’Ron Artest’}”
OK
redis 127.0.0.1:6379> set uid:16 “{‘uid’:16,’name’:’Pau Gasol’}”
OK
— 4.按照得分对球员排序;
redis 127.0.0.1:6379> sort kobe:team:list by uid:sort:* get uid:*
1) “{‘uid’:15,’name’:’Ron Artest’}”
2) “{‘uid’:10,’name’:’Steve Nash’}”
3) “{‘uid’:12,’name’:’Dwight Howard’}”
4) “{‘uid’:16,’name’:’Pau Gasol’}”
redis 127.0.0.1:6379> sort kobe:team:list by uid:sort:* get uid:* get uid:sort:*
1) “{‘uid’:15,’name’:’Ron Artest’}”
2) “300”
3) “{‘uid’:10,’name’:’Steve Nash’}”
4) “1000”
5) “{‘uid’:12,’name’:’Dwight Howard’}”
6) “2000”
7) “{‘uid’:16,’name’:’Pau Gasol’}”
8) “2500”
——————————— 实际使用的场景及例子 ———————————

Redis学习02–数据类型介绍

Redis中的数据类型

  1. Redis中支持的数据类型:
    1. string;
    2. list;
    3. set;
    4. sorted set;
    5. hash;
  2. Redis中的Key:
    1. redis本质上一个key-value数据库,它的key是字符串类型,但是key中不能包括边界字符,由于key不是binary safe的字符串,所以像”my key”和”mykey\n”这样包含空格和换行的key是不允许的(之后的版本是可以包含任字符的);
    2. 在redis内部并不限制使用binary字符,这是redis协议限制的,”\r\n”在协议格式中会作为特殊字符;redis 1.2以后的协议中部分命令已经开始使用新的协议格式了(比如MSET),推荐把包含边界字符当成非法的key,免得被bug纠缠;
    3. key的一个格式约定,object-type:id:field;比如user:1000:password,blog:xxidxx:title;key的长度最好不要太长,首先占内存啊,而且查找时候相对短key也更慢,不过也不推荐过短的key,可读性不好;
    4. 与key相关的命令:
      1. exists key:测试指定key是否存在,返回1表示存在,0不存在;
      2. del key1 key2 ….keyN:删除给定key,返回删除key的数目,0表示给定key都不存在;
      3. type key:返回给定key的value类型;返回none表示不存在key,string字符类型,list链表类型,set无序集合类型;
      4. keys pattern:返回匹配指定模式的所有key;
        1. keys *:获得所有的keys;
        2. key t*:获得t开头的keys;
        3. key t[ab]x:获得以t开头x结尾中间是a或者b的keys;
        4. key t?x:获得以t开头x结尾中间之后一个字符的keys;
      5. randomkey:返回从当前数据库中随机选择的一个key,如果当前数据库是空的,返回空串;
      6. rename oldkey newkey:原子的重命名一个key,如果newkey存在,将会被覆盖,返回1表示成功,0失败;可能是oldkey不存在或者和newkey相同;
      7. renamenx oldkey newkey:同上,但是如果newkey存在返回失败;
      8. dbsize:返回当前数据库的key数量;
      9. expire key seconds:为key指定过期时间,单位是秒;返回1成功,0表示key已经设置过过期时间或者不存在;
      10. ttl key:返回设置过过期时间的key的剩余过期秒数,-1表示key不存在或者没有设置过过期时间;
      11. select db-index:通过索引选择数据库,默认连接的数据库所有是0,默认数据库数是16个;返回1表示成功,0失败;
      12. move key db-index:将key从当前数据库移动到指定数据库,返回1成功,0如果key不存在,或者已经在指定数据库中;
      13. flushdb:删除当前数据库中所有key,此方法不会失败,慎用;
      14. flushall:删除所有数据库中的所有key,此方法不会失败,更加慎用;
  3. string类型:
    1. string是redis最基本的类型,而且string类型是二进制安全的,即redis的string可以包含任何数据;比如jpg图片或者序列化的对象;
    2. 从内部实现来看其实string可以看作byte数组,最大上限是1G字节,string类型的定义为:struct sdshdr {long len; long free; char buf[]; };
      1. buf是个char数组用于存贮实际的字符串内容,其实char和高级语言中的byte是等价的,都是一个字节;
      2. len是buf数组的长度;
      3. free是数组中剩余可用字节数;
    3. string类型可以被部分命令按int处理,比如incr等命令;
    4. redis的其它类型像list, set, sorted set, hash它们包含的元素与都只能是string类型;
    5. 如果只用string类型,redis就可以被看作加上持久化特性的memcached,当然redis对string类型的操作比memcached多很多;
    6. 与string相关的操作:
      1. set key value:设置key对应的值为string类型的value,返回1表示成功,0失败;
      2. setnx key value:同上,如果key已经存在,返回0;nx是not exist的意思;
      3. get key:获取key对应的string值,如果key不存在返回nil;
      4. getset key value:原子的设置key的值,并返回key的旧值;如果key不存在返回nil;
      5. mget key1 key2 … keyN:一次获取多个key的值,如果对应key不存在,则对应返回nil;
      6. mset key1 value1 … keyN valueN:一次设置多个key的值,成功返回1表示所有的值都设置了,失败返回0表示没有任何值被设置;
      7. msetnx key1 value1 … keyN valueN:同上,但是不会覆盖已经存在的key;
      8. incr key:对key的值做加加操作,并返回新的值;注意incr一个不是int的value会返回错误,incr一个不存在的key,则设置key为1;
      9. decr key:同上,但是做的是减减操作,decr一个不存在key,则设置key为-1;
      10. incrby key integer:同incr,加指定值,key不存在时候会设置key,并认为原来的value是0;
      11. decrby key integer:同decr,减指定值;decrby完全是为了可读性,我们完全可以通过incrby一个负值来实现同样效果,反之一样;
      12. substr key start end:返回截取过的key的字符串值,注意并不修改key的值,下标是从0开始的(redis在2.0版本以后不包括2.0,使用的方法是getrange参数相同);
      13. append key value:给指定key的字符串值追加value,返回新字符串值的长度;
  4. list类型:
    1. redis的list类型其实就是一个每个子元素都是string类型的双向链表,所以[lr]push和[lr]pop命令的算法时间复杂度都是O(1),另外list会记录链表的长度,所以llen操作也是O(1);
    2. list插入的元素,默认是按照时间的逆序排列的,最新的数据都是插入到头部,可以通过lrange list 0 -1/sort list by nosort查看;
    3. 链表的最大长度是(2的32次方-1),我们可以通过push,pop操作从链表的头部或者尾部添加删除元素;这使得list既可以用作栈,也可以用作队列;
    4. list的pop操作还有阻塞版本的,当我们[lr]pop一个list对象时,如果list是空,或者不存在,会立即返回nil,但是阻塞版本的b[lr]pop则可以阻塞,当然可以加超时时间,超时后也会返回nil;
    5. 为什么要阻塞版本的pop呢:主要是为了避免轮询;举个简单的例子如果我们用list来实现一个工作队列,执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在,当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟;
    6. 与list相关的操作:
      1. lpush key string:在key对应list的头部添加字符串元素,返回1表示成功,0表示key存在且不是list类型;
      2. rpush key string:同上,在尾部添加;
      3. llen key:返回key对应list的长度,key不存在返回0,如果key对应类型不是list返回错误;
      4. lrange key start end:返回指定区间内的元素,下标从0开始,负值表示从后面计算,-1表示倒数第一个元素,key不存在返回空列表;
      5. ltrim key start end:截取list,保留指定区间内元素,成功返回1,key不存在返回错误;
      6. lset key index value:设置list中指定下标的元素值,成功返回1,key或者下标不存在返回错误;
      7. lrem key count value:从key对应list中删除count个和value相同的元素,count为0时候删除全部;
      8. lpop key:从list的头部删除元素,并返回删除元素,如果key对应list不存在或者是空返回nil,如果key对应值不是list返回错误;
      9. rpop:同上,但是从尾部删除;
      10. blpop key1…keyN timeout:从左到右扫描返回对第一个非空list进行lpop操作并返回;
        1. 比如blpop list1 list2 list3 0,如果list1不存在list2,list3都是非空则对list2做lpop并返回从list2中删除的元素;如果所有的list都是空或不存在,则会阻塞timeout秒,timeout为0表示一直阻塞;
        2. 当阻塞时,如果有client对key1…keyN中的任意key进行push操作,则第一在这个key上被阻塞的client会立即返回;如果超时发生,则返回nil;有点像unix的select或者poll
      11. brpop:同blpop,一个是从头部删除一个是从尾部删除;
      12. rpoplpush srckey destkey:从srckey对应list的尾部移除元素并添加到destkey对应list的头部,最后返回被移除的元素值,整个操作是原子的.如果srckey是空或者不存在返回nil;
  5. set类型:
    1. redis的set是string类型的无序集合,set元素最大可以包含(2的32次方-1)个元素;
    2. set的是通过hash table实现的,所以添加,删除,查找的复杂度都是O(1);hash table会随着添加或者删除自动的调整大小,需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现;
    3. 跳表已经在sorted set中使用了,关于set集合类型除了基本的添加删除操作,其它有用的操作还包含集合的取并集(union),交集(intersection),差集(difference);通过这些操作可以很容易的实现sns中的好友推荐和blog的tag功能;
    4. 与set相关的操作:
      1. sadd key member:添加一个string元素到key对应的set集合中,成功返回1,如果元素以及在集合中返回0,key对应的set不存在返回错误;
      2. srem key member:从key对应set中移除给定元素,成功返回1,如果member在集合中不存在或者key不存在返回0,如果key对应的不是set类型的值返回错误;
      3. spop key:删除并返回key对应set中随机的一个元素,如果set是空或者key不存在返回nil;
      4. srandmember key:同spop,随机取set中的一个元素,但是不删除元素;
      5. smove srckey dstkey member:从srckey对应set中移除member并添加到dstkey对应set中,整个操作是原子的;成功返回1,如果member在srckey中不存在返回0,如果key不是set类型返回错误;
      6. scard key:返回set的元素个数,如果set是空或者key不存在返回0;
      7. sismember key member:判断member是否在set中,存在返回1,0表示不存在或者key不存在;
      8. sinter key1 key2…keyN:返回所有给定key的交集;
      9. sinterstore dstkey key1…keyN:同sinter,但是会同时将交集存到dstkey下;
      10. sunion key1 key2…keyN:返回所有给定key的并集;
      11. sunionstore dstkey key1…keyN:同sunion,并同时保存并集到dstkey下;
      12. sdiff key1 key2…keyN:返回所有给定key的差集;
      13. sdiffstore dstkey key1…keyN:同sdiff,并同时保存差集到dstkey下;
      14. smembers key:返回key对应set的所有元素,结果是无序的;
  6. sorted set类型:
    1. 和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个double类型的score;
    2. sorted set的实现是skip list和hash table的混合体当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,所以给定一个元素获取score的开销是O(1),另一个score到元素的映射被添加到skip list并按照score排序,所以就可以有序的获取集合中的元素;添加,删除操作开销都是O(log(N))和skip list的开销一致,redis的skip list实现用的是双向链表,这样就可以逆序从尾部取元素;
    3. sorted set最经常的使用方式应该是作为索引来使用,我们可以把要排序的字段作为score存储,对象的id当元素存储;
    4. 与sorted set相关的操作:
      1. zadd key score member:添加元素到集合,元素在集合中存在则更新对应score;
      2. zrem key member:删除指定元素,1表示成功,如果元素不存在返回0;
      3. zincrby key incr member:增加对应member的score值,然后移动元素并保持skip list保持有序,返回更新后的score值;
      4. zrank key member:返回指定元素在集合中的排名(下标),集合中元素是按score从小到大排序的;
      5. zrevrank key member:同上,但是集合中元素是按score从大到小排序;
      6. zrange key start end:类似lrange操作从集合中去指定区间的元素;返回的是有序结果;
      7. zrevrange key start end:同上,返回结果是按score逆序的;
      8. zrangebyscore key min max:返回集合中score在给定区间的元素;
      9. zcount key min max:返回集合中score在给定区间的数量;
      10. zcard key:返回集合中元素个数;
      11. zscore key element:返回给定元素对应的score;
      12. zremrangebyrank key min max:删除集合中排名在给定区间的元素;
      13. zremrangebyscore key min max:删除集合中score在给定区间的元素;
  7. hash类型:
    1. redis的hash是一个string类型的field和value的映射表,它的添加,删除操作平均都是O(1),hash特别适合用于存储对象;
    2. 相较于将对象的每个字段存成单个string类型,将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象,省内存的原因是新建一个hash对象时开始是用zipmap(又称为small hash)来存储的;
    3. zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销,尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多,所以使用zipmap也是很快的,也就是说添加删除平均还是O(1);
    4. 如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现,这个限制可以在配置文件中指定;
      1. hash-max-zipmap-entries 64 #配置字段最多64个
      2. hash-max-zipmap-value 512 #配置value最大为512字节
    5. 与hash相关的操作:
      1. hset key field value:设置hash field为指定值,如果key不存在,则先创建;
      2. hget key field:获取指定的hash field;
      3. hmget key filed1….fieldN:获取全部指定的hash filed;
      4. hmset key filed1 value1 … filedN valueN:同时设置hash的多个field;
      5. hincrby key field integer:将指定的hash filed 加上给定值;
      6. hexists key field:测试指定field是否存在;
      7. hdel key field:删除指定的hash field;
      8. hlen key:返回指定hash的field数量;
      9. hkeys key:返回hash的所有field;
      10. hvals key:返回hash的所有value;
      11. hgetall:返回hash的所有filed和value;

Redis学习01–在Linux下安装Redis v2.6.13

安装Redis
  1. Redis的介绍:
    1. redis是一个开源的key-value数据库,它又经常被认为是一个数据结构服务器,因为它的value不仅包括基本的string类型还有list,set,sorted set和hash类型,当然这些类型的元素也都是string类型,也就是说list,set这些集合类型也只能包含string类型;
    2. 可以在这些类型上做很多原子性的操作,比如对一个字符value追加字符串(APPEND命令),加加或者减减一个数字字符串(INCR命令,当然是按整数处理的),可以对list类型进行push,或者pop元素操作(可以模拟栈和队列),对于set类型可以进行一些集合相关操作(intersection,union,difference);
    3. memcache也有类似与++,–的命令,不过memcache的value只包括string类型,远没有redis的value类型丰富;
    4. 和memcahe一样为了性能,redis的数据通常都是放到内存中的,当然redis可以每间隔一定时间将内存中数据写入到磁盘以防止数据丢失;
    5. redis也支持主从复制机制(master-slave replication),redis的其它特性包括简单的事务支持和发布订阅(pub/sub)通道功能,而且redis配置管理非常简单,还有各种语言版本的开源客户端类库;
  2. key-list类型内存数据引擎:
    1. 互联网数据目前基本使用两种方式来存储,关系数据库或者key value,但是这些互联网业务本身并不属于这两种数据类型,比如用户在社会化平台中的关系,它是一个list,如果要用关系数据库存储就需要转换成一种多行记录的形式,这种形式存在很多冗余数据,每一行需要存储一些重复信息;如果用key value存储则修改和删除比较麻烦,需要将全部数据读出再写入; — by timyang@weibo.com
    2. 如果用key-value中的value存储list,只能实现最简单的列表功能(按照id或时间先后排序,例如使用memcache的append或prepend协议),其他list操作只能靠客户端操作,性能很差,如果数据量较大,操作时间是无法接受的,并发也会遇到巨大挑战);
    3. key-list系统key对应的”value”是一个list(eg.set list),可以对list中的单个item进行操作,理想的key-list需要如下特点:
      1. list可以是海量的,且操作性能高效;
      2. list是可以是有序的,且可动态调整顺序;
    4. 使用场景:
      1. 论坛中的主题列表,回复列表;
      2. 微博中的用户关注列表,用户feed列表,用户关注feed列表;
      3. 最近访问列表;
      4. 集合操作:求交集,并集,差集(sdiff/sinter/sunion);
      5. 好友推荐;
      6. 排行榜;
  3. 下载:
    1. 官方地址: http://redis.io/;
    2. 下载地址: http://redis.io/download;
    3. 文档地址: http://redis.io/documentation;
    4. 作者blog: http://oldblog.antirez.com/;
  4. 安装:
    1. 解压缩到/usr/local目录下:tar -zxvf /tools/redis-2.6.13.tar.gz -C /usr/local/;                            
    2. 编译:cd /usr/local/redis-2.6.13/;make;                 
    3. 安装时报错:undefined reference to `__sync_add_and_fetch_4′;  
    4. 是因为CPU信息判断失误引起的,所以需要确定CPU的信息,然后把CPU的信息设置到环境变量中,清除旧的编译文件,最后重新编译即可:uname -a(uname -m);export CFLAGS=-march=i686;cd /usr/local/redis-2.6.13/;make distclean;make;              
    5. 如果需要运行make-test的话需要安装8.5版本以上的tcl;
    6. 此时就会在src目录下出现redis-server的服务端和redis-cli客户端,为了方便使用可以分别创建软连接:ln -s /usr/local/redis-2.6.13/src/redis-server /usr/local/redis-2.6.13/src/redisd;ln -s /usr/local/redis-2.6.13/src/redis-cli /usr/local/redis-2.6.13/src/redis;                                 
    7. 添加PATH变量:PATH=$PATH:/usr/local/redis-2.6.13/src;                                  
    8. 启动服务端:
      1. 默认配置启动:redisd;
      2. 使用自己的配置文件启动:redisd /etc/redis.conf;
      3. 在后台运行:redisd /etc/redis.conf &;                                     
    9. 启动客户端:redis;                                        
  5. 驱动下载:
    1. 官网提供的支持语言的下载地址: http://redis.io/clients;
    2. Jedis的github地址:https://github.com/xetorthio/jedis;
    3. jar包下载地址:https://github.com/xetorthio/jedis/downloads;
    4. 提供的方法:
      1. Sorting
      2. Connection handling
      3. Commands operating on any kind of values
      4. Commands operating on string values
      5. Commands operating on hashes
      6. Commands operating on lists
      7. Commands operating on sets
      8. Commands operating on sorted sets
      9. Transactions
      10. Pipelining
      11. Publish/Subscribe
      12. Persistence control commands
      13. Remote server control commands
      14. Connection pooling
      15. Sharding (MD5, MurmureHash)
      16. Key-tags for sharding
      17. Sharding with pipelining
  6. 配置文件;
# Redis configuration file example
 
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
 
# By default Redis does not run as a daemon. Use ‘yes’ if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes
 
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
# default. You can specify a custom pid file location here.
pidfile /var/run/redis.pid
 
# Accept connections on the specified port, default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
 
# If you want you can bind a single interface, if the bind option is not
# specified all the interfaces will listen for incoming connections.
#
# bind 127.0.0.1
 
# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 755
 
# Close the connection after a client is idle for N seconds (0 to disable)
timeout 0
 
# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
#    equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 60 seconds.
tcp-keepalive 0
 
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
 
# Specify the log file name. Also ‘stdout’ can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile stdout
 
# To enable logging to the system logger, just set ‘syslog-enabled’ to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
 
# Specify the syslog identity.
# syslog-ident redis
 
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
 
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and ‘databases’-1
databases 16
 
################################ SNAPSHOTTING  #################################
#
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving at all commenting all the “save” lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save “”
 
#save 900 1
#save 300 10
#save 60 10000
 
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in an hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# distater will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usually even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
 
# Compress string objects using LZF when dump .rdb databases?
# For default that’s set to ‘yes’ as it’s almost always a win.
# If you want to save some CPU in the saving child set it to ‘no’ but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes
 
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes
 
# The filename where to dump the DB
dbfilename dump.rdb
 
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the ‘dbfilename’ configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./
 
################################# REPLICATION #################################
 
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
# another Redis server. Note that the configuration is local to the slave
# so for example it is possible to configure the slave to save the DB with a
# different interval, or to listen to another port, and so on.
#
# slaveof <masterip> <masterport>
 
# If the master is password protected (using the “requirepass” configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.
#
# masterauth <master-password>
 
# When a slave loses its connection with the master, or when the replication
# is still in progress, the slave can act in two different ways:
#
# 1) if slave-serve-stale-data is set to ‘yes’ (the default) the slave will
#    still reply to client requests, possibly with out of date data, or the
#    data set may just be empty if this is the first synchronization.
#
# 2) if slave-serve-stale-data is set to ‘no’ the slave will reply with
#    an error “SYNC with master in progress” to all the kind of commands
#    but to INFO and SLAVEOF.
#
slave-serve-stale-data yes
 
# You can configure a slave instance to accept writes or not. Writing against
# a slave instance may be useful to store some ephemeral data (because data
# written on a slave will be easily deleted after resync with the master) but
# may also cause problems if clients are writing to it because of a
# misconfiguration.
#
# Since Redis 2.6 by default slaves are read-only.
#
# Note: read only slaves are not designed to be exposed to untrusted clients
# on the internet. It’s just a protection layer against misuse of the instance.
# Still a read only slave exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
# security of read only slaves using ‘rename-command’ to shadow all the
# administrative / dangerous commands.
slave-read-only yes
 
# Slaves send PINGs to server in a predefined interval. It’s possible to change
# this interval with the repl_ping_slave_period option. The default value is 10
# seconds.
#
# repl-ping-slave-period 10
 
# The following option sets a timeout for both Bulk transfer I/O timeout and
# master data or ping response timeout. The default value is 60 seconds.
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-slave-period otherwise a timeout will be detected
# every time there is low traffic between the master and the slave.
#
# repl-timeout 60
 
# Disable TCP_NODELAY on the slave socket after SYNC?
#
# If you select “yes” Redis will use a smaller number of TCP packets and
# less bandwidth to send data to slaves. But this can add a delay for
# the data to appear on the slave side, up to 40 milliseconds with
# Linux kernels using a default configuration.
#
# If you select “no” the delay for data to appear on the slave side will
# be reduced but more bandwidth will be used for replication.
#
# By default we optimize for low latency, but in very high traffic conditions
# or when the master and slaves are many hops away, turning this to “yes” may
# be a good idea.
repl-disable-tcp-nodelay no
 
# The slave priority is an integer number published by Redis in the INFO output.
# It is used by Redis Sentinel in order to select a slave to promote into a
# master if the master is no longer working correctly.
#
# A slave with a low priority number is considered better for promotion, so
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
# pick the one wtih priority 10, that is the lowest.
#
# However a special priority of 0 marks the slave as not able to perform the
# role of master, so a slave with priority of 0 will never be selected by
# Redis Sentinel for promotion.
#
# By default the priority is 100.
slave-priority 100
 
################################## SECURITY ###################################
 
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
# requirepass foobared
 
# Command renaming.
#
# It is possible to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
# hard to guess so that it will still be available for internal-use tools
# but not available for general clients.
#
# Example:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# It is also possible to completely kill a command by renaming it into
# an empty string:
#
# rename-command CONFIG “”
#
# Please note that changing the name of commands that are logged into the
# AOF file or transmitted to slaves may cause problems.
 
################################### LIMITS ####################################
 
# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error ‘max number of clients reached’.
#
# maxclients 10000
 
# Don’t use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# accordingly to the eviction policy selected (see maxmemmory-policy).
#
# If Redis can’t remove keys according to the policy, or if the policy is
# set to ‘noeviction’, Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU cache, or to set
# an hard memory limit for an instance (using the ‘noeviction’ policy).
#
# WARNING: If you have slaves attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the slaves are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of slaves is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short… if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is ‘noeviction’).
#
# maxmemory <bytes>
 
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don’t expire at all, just return an error on write operations
#
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are not suitable keys for eviction.
#
#       At the date of writing this commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy volatile-lru
 
# LRU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can select as well the sample
# size to check. For instance for default Redis will check three keys and
# pick the one that was used less recently, you can change the sample size
# using the following configuration directive.
#
# maxmemory-samples 3
 
############################## APPEND ONLY MODE ###############################
 
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
 
appendonly yes
 
# The name of the append only file (default: “appendonly.aof”)
# appendfilename appendonly.aof
 
# The fsync() call tells the Operating System to actually write data on disk
# instead to wait for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don’t fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log . Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is “everysec”, as that’s usually the right compromise between
# speed and data safety. It’s up to you to understand if you can relax this to
# “no” that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that’s snapshotting),
# or on the contrary, use “always” that’s very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use “everysec”.
 
# appendfsync always
appendfsync everysec
# appendfsync no
 
# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it’s possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving, the durability of Redis is
# the same as “appendfsync none”. In practical terms, this means that it is
# possible to lose up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to “yes”. Otherwise leave it as
# “no” that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no
 
# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.
 
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
 
################################ LUA SCRIPTING  ###############################
 
# Max execution time of a Lua script in milliseconds.
#
# If the maximum execution time is reached Redis will log that a script is
# still in execution after the maximum allowed time and will start to
# reply to queries with an error.
#
# When a long running script exceed the maximum execution time only the
# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
# used to stop a script that did not yet called write commands. The second
# is the only way to shut down the server in the case a write commands was
# already issue by the script but the user don’t want to wait for the natural
# termination of the script.
#
# Set it to 0 or a negative value for unlimited execution without warnings.
lua-time-limit 5000
 
################################## SLOW LOG ###################################
 
# The Redis Slow Log is a system to log queries that exceeded a specified
# execution time. The execution time does not include the I/O operations
# like talking with the client, sending the reply and so forth,
# but just the time needed to actually execute the command (this is the only
# stage of command execution where the thread is blocked and can not serve
# other requests in the meantime).
#
# You can configure the slow log with two parameters: one tells Redis
# what is the execution time, in microseconds, to exceed in order for the
# command to get logged, and the other parameter is the length of the
# slow log. When a new command is logged the oldest one is removed from the
# queue of logged commands.
 
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000
 
# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128
 
############################### ADVANCED CONFIG ###############################
 
# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
 
# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64
 
# Sets have a special encoding in just one case: when a set is composed
# of just strings that happens to be integers in radix 10 in the range
# of 64 bit signed integers.
# The following configuration setting sets the limit in the size of the
# set in order to use this special memory saving encoding.
set-max-intset-entries 512
 
# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
 
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)
# performs a lazy rehashing: the more operation you run into an hash table
# that is rehashing, the more rehashing “steps” are performed, so if the
# server is idle the rehashing is never complete and some more memory is used
# by the hash table.
#
# The default is to use this millisecond 10 times every second in order to
# active rehashing the main dictionaries, freeing memory when possible.
#
# If unsure:
# use “activerehashing no” if you have hard latency requirements and it is
# not a good thing in your environment that Redis can reply form time to time
# to queries with 2 milliseconds delay.
#
# use “activerehashing yes” if you don’t have such hard requirements but
# want to free memory asap when possible.
activerehashing yes
 
# The client output buffer limits can be used to force disconnection of clients
# that are not reading data from the server fast enough for some reason (a
# common reason is that a Pub/Sub client can’t consume messages as fast as the
# publisher can produce them).
#
# The limit can be set differently for the three different classes of clients:
#
# normal -> normal clients
# slave  -> slave clients and MONITOR clients
# pubsub -> clients subcribed to at least one pubsub channel or pattern
#
# The syntax of every client-output-buffer-limit directive is the following:
#
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
#
# A client is immediately disconnected once the hard limit is reached, or if
# the soft limit is reached and remains reached for the specified number of
# seconds (continuously).
# So for instance if the hard limit is 32 megabytes and the soft limit is
# 16 megabytes / 10 seconds, the client will get disconnected immediately
# if the size of the output buffers reach 32 megabytes, but will also get
# disconnected if the client reaches 16 megabytes and continuously overcomes
# the limit for 10 seconds.
#
# By default normal clients are not limited because they don’t receive data
# without asking (in a push way), but just after a request, so only
# asynchronous clients may create a scenario where data is requested faster
# than it can read.
#
# Instead there is a default limit for pubsub and slave clients, since
# subscribers and slaves receive data in a push fashion.
#
# Both the hard or the soft limit can be disabled by setting them to zero.
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
 
# Redis calls an internal function to perform many background tasks, like
# closing connections of clients in timeot, purging expired keys that are
# never requested, and so forth.
#
# Not all tasks are perforemd with the same frequency, but Redis checks for
# tasks to perform accordingly to the specified “hz” value.
#
# By default “hz” is set to 10. Raising the value will use more CPU when
# Redis is idle, but at the same time will make Redis more responsive when
# there are many keys expiring at the same time, and timeouts may be
# handled with more precision.
#
# The range is between 1 and 500, however a value over 100 is usually not
# a good idea. Most users should use the default of 10 and raise this up to
# 100 only in environments where very low latency is required.
hz 10
 
# When a child rewrites the AOF file, if the following option is enabled
# the file will be fsync-ed every 32 MB of data generated. This is useful
# in order to commit the file to the disk more incrementally and avoid
# big latency spikes.
aof-rewrite-incremental-fsync yes
 
################################## INCLUDES ###################################
 
# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis server but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# include /path/to/local.conf
# include /path/to/other.conf

MongoDB学习08–驱动实践

驱动实践

  1. 目前驱动有两种:
    1. 官方驱动:
      1. 官方页面:https://github.com/mongodb;
      2. java驱动下载:https://github.com/mongodb/mongo-java-driver/downloads;
      3. mongodb主页:http://docs.mongodb.org/ecosystem/drivers/;
    2. samus驱动:
      1. 官方页面:https://github.com/samus;
      2. c#驱动下载: https://github.com/samus/mongodb-csharp/downloads;
  2. 有时间使用java写一下CRUD的例子;

MongoDB学习07–运维技术

运维技术

  1. 常见的运维技术:
    1. 安装部署;
    2. 状态监控;
    3. 安装认证;
    4. 备份与恢复;
  2. 安装部署:
    1. mongod进程总是停留在命令行窗口下,很容易误操作给结束掉;而且日志信息总是在命令行下打印,不便于之后的查看,可以把mongod作为后台进程运行,并把日志输出到文件中;
    2. Linux中:mongod –dbpath=/mongo/data –logpath=/mongo/logs/mongod.log –logappend –port=27017 –fork;                         
    3. Windows中:mongod –dbpath=D:\mongo\data –logpath=D:\mongo\logs\mongod.log –logappend –port=27017 –install;之后就可以通过[net start/stop MongoDB]命令来开启和关闭mongodb数据库了;
    4. Linux下开机启动,可以编辑/etc/rc.d/rc.local文件,把mongod启动的脚本添加进去即可;
    5. Linux下关闭mongod服务器:
      1. 如果没有使用–fork命令,则直接退出终端即可,mongodb会自动清理;
      2. 如果使用了–fork命令,在admin数据库下执行:db.shutdownServer();
      3. 如果在Master-Slave集群中,如果备机和主机时间相差超过10s的话不会关闭mongod,可以通过配置timeoutSecs来完成mongod的数据更新,时间差在10s以内的话就可以关闭了:db.shutdownServer({force : true, timeoutsec : 5});
  3. 监控状态:
    1. http监控器,通过页面打开,网页使用端口是mongod服务端口加1000:http://mongod-host:port;
    2. 打开客户端调用db.serverStatus()获得服务器的状态,包括服务器基本信息,锁,索引,内存,连接,游标,网络和用户操作行为等信息;
    3. 使用客户端工具mongostat来实时查看统计信息(具体的字段含义可以通过mongostat -h查看),每秒刷新一次:mongostat –port=27017 –all;                           
  4. 安全认证:
    1. mongodb身份验证的机制:
      1. 当启动mongod服务的时候不加–auth参数,默认是没有权限验证的,可以直接登陆数据库做任何操作而且可以远程访问数据库,相当于都是超级用户的权限;
      2. 当启动mongod服务的时候添加–auth参数,如果没有在admin数据库中添加过用户(即admin.system.users不存在或者为空),那么还是可以做任何操作,直到在admin.system.users中添加了一个用户(如果没有在admin.system.users中添加用户,而在其它数据库下添加了用户,它们依然是超级用户的权限,因为只有admin.system.users中有用户后mongod的认证授权服务才生效);
      3. admin.system.users中保存的用户都是超级用户,可以对其它的数据库进行操作,而其它数据库中的用户只能对自己数据库中的集合(不论是哪个用户创建的集合)进行操作;
      4. mongodb中,数据库是由超级用户来创建的,一个数据库可以包含多个用户,但是一个用户只能在一个数据库中操作,不同的数据库中的用户可以同名;
    2. 创建用户的语法:db.addUser(username, password[, readOnly=false]);
      1. username:用户名;
      2. password:访问的密码;
      3. readOnly:默认是false,即有读写的权限,如果设置为true的话,则只有读的权限;
    3. 创建超级用户:mongo –port=27017 admin;db.addUser(“root”, “mongo”);                                           
    4. 验证用户:
      1. 可以在登陆时指定用户名密码:mongo 127.0.0.1:27017/admin -uroot -pmongo;                                
      2. 登陆之后使用db.auth()函数验证用户:db.auth(“root”, “mongo”),返回1表示成功,返回0表示失败;                      
    5. 查看用户:db.getCollection(‘system.users’).find();                               
    6. 删除用户:db.removeUser(“root”),也可以使用db.system.users.remove()操作,如果删除所有admin.system.users中的用户,则就没有安全验证了;                                    
    7. 其它数据库中创建用户的做法相同;
  5. 备份与恢复:
    1. 冷备份:在关闭服务器的情况下直接拷贝mongodb的数据目录,不推荐;
    2. mongodump和mongorestore:mongodb提供的内置工具,提供了热备功能,备份的父目录为/mongo/backup;
      1. mongodump:mongodump -h 127.0.0.1 –port 27017 -d test -o /mongo/backup;                      
      2. mongorestore:mongorestore -h 127.0.0.1 –port 27017 -d test -drop /mongo/backup/test;(–drop选项是指定导入数据之前先删除原来的数据)                             
      3. 在导出的时候可能数据的实时性不能保证,因为导出的时候可能数据还在内存中,可以使用mongodb提供的fsync+lock机制把缓冲区数据暴力刷入硬盘,然后给数据库一个写入锁,所有的写入都会被阻塞,直到fsync+lock释放锁为止;
        1. 加锁:db.runCommand({“fsync”:1, “lock”:1});
        2. 释放锁:db.$cmd.unlock.findOne();
    3. Master-Slave架构;

MongoDB学习06–分片技术

分片技术

  1. 当数据量达到T级别的时候,对CPU,内存和磁盘的压力都很大,这个时候就需要采用分片技术,将MongoDB中的集合进行拆分,分担到多个片(即多台MongoDB服务器)上;
  2. 跨服务器的数据拆分中,Sharding是一个有效的方法;MongoDB中支持自动化Sharding,但是对数据库性能会造成很大影响;因此建议用户尽早进行Sharding,使用MMS:Munin (+ Mongo plugin)和CloudWatch等工具对MongoDB进行监控,确保系统资源使用达到80%之前就完成Sharding工作;
  3. 分片技术中各个组件的职能及拓扑图:
    1. clients:客户端,即发出数据请求,并得到返回结果;
    2. config:保存数据和片的对应关系以及相应的配置信息,配置节点推荐使用使用主备,防止单点故障;
    3. mongos:即路由服务器,根据设置的片键(即集合拆分的依据)将数据分摊到不同的mongod集群;
    4. mongod:普通的MongoDB服务器,就是所谓的片;
    5. 拓扑图;                                                                                                 
  4. 使用不同的目录和端口来模拟不同的服务器:
    1. config:mongod –dbpath=/mongo/config –port=20001;
    2. mongos:mongos –port=20002 –configdb=127.0.0.1:20001;
    3. mongod1:mongod –dbpath=/mongo/mongod1 –port=30001;
    4. mongod2:mongod –dbpath=/mongo/mongod2 –port=30002;
    5. mongod3:mongod –dbpath=/mongo/mongod3 –port=30003;
  5. 开启config服务器:mongod –dbpath=/mongo/config –port=20001;                             
  6. 启动mongos服务器(不需要指定数据目录,因为它只是起到路由器的功能),同时要指定config服务器,因为要写入配置信息:mongos –port=20002 –configdb=127.0.0.1:20001;                               
  7. 分别启动三台mongod服务器:mongod –dbpath=/mongo/mongodX –port=3000X;
  8. 配置mongos服务:
    1. 连接mongos服务并添加分片节点:mongo 127.0.0.1:20002/admin;                                
    2. 开启数据库的分片功能,以test数据库为例:db.runCommand({“enablesharding”:”test”});                                  
    3. 指定集合的分片键,以user集合的name列为例:                                             
  9. 查看效果:
    1. 通过mongos插入10w条测试数据;                                         
    2. 查看数据的分布情况:db.printShardingStatus();                                
    3. 结果分析:
      1. shards:查看到已经分成了shard0000,shard0001,shard0002三个片;
      2. databases:看到test的partitioned属性为true;
      3. chundks:被分成了$minKey(无穷小)->user0, user0->user9999, user9999->$maxKey(无穷大)三段;
———————— 配置mongos服务器 ————————
— 1.连接mongos服务器;
mongo 127.0.0.1:20002/admin
— 2.添加分片;
db.runCommand({addshard:”127.0.0.1:30001″, allowLocal:true})
db.runCommand({addshard:”127.0.0.1:30002″, allowLocal:true})
db.runCommand({addshard:”127.0.0.1:30003″, allowLocal:true})
— 3.开启数据库的分片功能;
db.runCommand({“enablesharding”:”test”})
— 4.指定片键;
db.runCommand({“shardcollection”:”test.user”, “key”:{“name”:1}})
———————— 配置mongos服务器 ————————
———————— 插入测试数据并查看结果 ————————
— 1.连接mongos服务器的test数据库;
mongo 127.0.0.1:20002/test
— 2.插入10w条测试数据;
for(var i = 0; i< 100000; i++){ db.user.insert({name:”user”+i, age:i}) }
— 3.查看数据分布情况;
db.printShardingStatus()
———————— 插入测试数据并查看结果 ————————

MongoDB学习05–Master-Slave架构及副本集

Master-Slave架构及副本集

  1. Master-Slave架构的优点及拓扑图:
    1. 主要的优点:
      1. 解决单点故障问题;
      2. 实现数据的备份和恢复;
      3. 实现数据的读写分离;
    2. 拓扑图;                                                                                                              
    3. 使用不同的目录和端口来模拟不同的服务器:
      1. master:mongod –dbpath=/mongo/master –port=27017 –master;
      2. slave1:mongod –dbpath=/mongo/slave1 –port=30001 –slave –source=127.0.0.1:27017;
      3. slave2:mongod –dbpath=/mongo/slave2 –port=30002 –slave;
  2. 启动Master数据库:mongod –dbpath=/mongo/master –port=27017 –master,在master和slave的服务器上都会有一个local的集合,用来存放内部的复制信息;                      
  3. 配置Salve1数据库,并在启动时就与Master数据库保持同步:mongod –dbpath=/mongo/slave1 –port=30001 –slave –source=127.0.0.1:27017,会看到有一个rep1的进程从Master服务器同步数据;           
  4. 把原来的Slave2服务器添加到Master-Slave架构中:
    1. 首先启动一台Slave2服务器,并不指定Master源:mongod –dbpath=/mongo/slave2 –port=30002 –slave;                               
    2. 连接到Slave2服务器,在local数据库下添加Master服务器信息,然后查看,同步完成后会出现同步的时间戳;                                    
    3. 可以通过观察系统日志或者查看集合的记录数来查看同步的状况;
  5. 查看Master, Slave的状态:
    1. 查看master服务器的状态:db.printReplicationInfo();                     
    2. 查看slave服务器的状态:db.printReplicationInfo();                           
  6. 读写分离:
    1. 默认情况下Salve服务器不支持数据的读取,可以通过驱动中的slaveOkay来显示读取Slave数据库,从而减轻Master服务器的压力;
    2. 也可以让前端程序直接连接到Slave服务器读取数据;
  7. 副本集:
    1. 与Master-Slave不同的是,它没有特定的主数据库,如果哪个主数据库宕机了,集群中就会推选出一个从属的数据库作为主数据库,具有自动故障恢复功能,所以一定要保证副本集为奇数个,否则会发生主节点宕机,其它节点为只读的情况;
    2. 创建一个名为snda的集群;
    3. 使用不同的服务器和端口来模拟不同的服务器:
      1. rep1:mongod –dbpath=/mongo/rep1 –port=30001 –replSet=snda/127.0.0.1:30002,127.0.0.1:30003;
      2. rep2:mongod –dbpath=/mongo/rep2 –port=30002 –replSet=snda/127.0.0.1:30001,127.0.0.1:30003;
      3. rep3:mongod –dbpath=/mongo/rep3 –port=30003 –replSet=snda/127.0.0.1:30001,127.0.0.1:30002;
      4. arbriter:mongod –dbpath=/mongo/arbiter –port=30000 –replSet=snda/127.0.0.1:30001;
  8. 打开第一个副本服务器rep1:mongod –dbpath=/mongo/rep1 –port=30001 –replSet=snda/127.0.0.1:30002,127.0.0.1:30003,replSet参数表示让服务器知道副本集snda下还有其它的服务器;     
  9. 打开第二个副本服务器rep2:mongod –dbpath=/mongo/rep2 –port=30002 –replSet=snda/127.0.0.1:30001,127.0.0.1:30003,会与其它的副本进行通信,并读取副本集的配置;             
  10. 同样的方法打开第三个副本集rep3:mongod –dbpath=/mongo/rep3 –port=30003 –replSet=snda/127.0.0.1:30001,127.0.0.1:30002;
  11. 添加副本集的配置信息,在任意一个副本上的admin数据库进行配置,这里选择rep1:db.runCommand({replSetInitiate:{_id:”snda”, members:[{_id:1, host:”127.0.0.1:30001″}, {_id:2, host:”127.0.0.1:30002″}, {_id:3, host:”127.0.0.1:30003″}]}});                      
  12. 此时可以从日志看出哪一个副本已经成为了Primary服务器,哪一个副本是Secondary服务器,可以看到rep2和rep3都投票给rep1作为primary服务器;当然也可以通过查看状态查询:rs.status();               
  13. 启动仲裁服务器:mongod –dbpath=/mongo/arbiter –port=30000 –replSet=snda/127.0.0.1:30001;                            
  14. 添加仲裁服务器配置,可以通过snda副本集中任意一个副本的admin数据库添加,这里以rep1为例:rs.addArb(“127.0.0.1:30000”);                         
  15. 查看副本集群中各个服务器的状态:rs.status();                                          
  16. 测试自动故障恢复功能,关掉当前的主服务器,之后通过rs.status()查看状态,发现自动投票选择了新的主服务器;                                        
  17. 当rep1重新修复后加入集群,自动成为了Secondary服务器;
—————————– 已开启的Slave服务器加入到Master-Slave架构中 —————————–
mongo 127.0.0.1:30002/local
db.sources.insert({host:”127.0.0.1:27017″})
db.sources.find()
db.sources.find()
—————————– 已开启的Slave服务器加入到Master-Slave架构中 —————————–
—————————– MongoDB副本集的配置 —————————–
— 1.添加三个副本,并互相告知剩余的成员;
mongod –dbpath=/mongo/rep1 –port=30001 –replSet=snda/127.0.0.1:30002,127.0.0.1:30003;
mongod –dbpath=/mongo/rep2 –port=30002 –replSet=snda/127.0.0.1:30001,127.0.0.1:30003;
mongod –dbpath=/mongo/rep3 –port=30003 –replSet=snda/127.0.0.1:30001,127.0.0.1:30002;
— 2.添加副本集的配置信息;
mongo 127.0.0.1:30001/admin
db.runCommand({replSetInitiate:{_id:”snda”, members:[{_id:1, host:”127.0.0.1:30001″}, {_id:2, host:”127.0.0.1:30002″}, {_id:3, host:”127.0.0.1:30003″}]}})
— 3.启动仲裁服务器;
mongod –dbpath=/mongo/arbiter –port=30000 –replSet=snda/127.0.0.1:30001;
— 4.添加仲裁服务器的配置信息;
mongo 127.0.0.1:30001/admin
rs.addArb(“127.0.0.1:30000”)
— 5.查看副本集各个成员的状态;
rs.status()
—————————– MongoDB副本集的配置 —————————–

MongoDB学习04–性能分析及索引操作

MongoDB中的性能分析及索引操作

  1. 性能分析函数explain();
    1. 语法:cursor.explain(verbose),verbose为true或者1的时候会输出所有的执行计划和旧的执行计划;
    2. 一般是跟在find()操作后使用(eg:db.mycoll.find().explain()),它的执行速度决定于实际查询的速度,尽管explain()会额外生成一系列候选的执行计划,但是跟实际查询的性能相差不大;
    3. explain函数的输出结果;                                                          
    4. explain()函数输出结果含义:
      1. cursor:查找使用的cursor类型和索引名称;
      2. isMultiKey:是否是联合索引;
      3. n:返回记录的个数;
      4. nscannedObjects:遍历的文档对象的个数;
      5. nscanned:遍历的文档的个数;
      6. nscannedObjectsAllPlans: <num>;
      7. nscannedAllPlans: <num>;
      8. scanAndOrder: <boolean>;
      9. indexOnly: <boolean>;
      10. nYields: <num>;
      11. nChunkSkips: <num>;
      12. millis:消耗的时间,单位是ms;
      13. indexBounds:索引的范围,如果为空表示没有用到索引;
      14. llPlans:产生的所有的候选的执行计划;
      15. oldPlan:旧的执行计划;
      16. server:主机名和端口号<host:port>;
  2. 创建/删除索引,及前后效率的对比;
    1. 创建索引语法:db.mycoll.ensureIndex(keypattern[,options]);
      1. keypattern:是索引的键;
      2. options:可以取name,unique,dropDups,sparse等值;
      3. 只有在某个列上没有索引才会创建,如果存在索引的话就不再创建;
      4. 默认创建的是b-tree索引;
      5. 可以在子文档上创建索引:db.mycoll.ensureIndex({“object.attritude”:1});
    2. 没有创建索引时,查找name=index10000的效率:db.index.find({“name”:”index10000″}).explain();使用的是BasicCursor即没有索引,扫描了10w条记录后返回1条记录,消耗了192ms;              
    3. 在name列创建一个名为idx_index_name的索引:db.index.ensureIndex({name:1}, {name:”idx_index_name”}),如果不指定第二个参数中的name的话,则系统会自动生成一个索引的名称;          
    4. 查看创建索引后的效率,发现使用了Btree索引,一共扫描一条记录,返回1条记录,并且瞬间返回;                                            
    5. 删除索引语法:db.mycoll.dropIndex(index),可以根据名称删除索引,或者根据创建时指定的键值;
      1. db.mycoll.dropIndex(“indexName”):根据索引名称删除;
      2. db.mycoll.dropIndex({ “indexKey” : 1 }):根据key值删除索引;
      3. 如果要删除某个集合下所有的索引,则使用db.mycoll.dropIndexes(),但是无法删除系统自动创建的基于_id列的索引,它由数据库自己维护;
    6. 重建索引:db.mycoll.reIndex(),是把原来的索引删掉后重建;                                                        
  3. 唯一索引:
    1. 语法:db.mycoll.ensureIndex(keypattern, {unique:true});
    2. 创建一个基于name列的唯一索引unq_index_name:db.index.ensureIndex({name:1}, {name:”unq_index_name”, unique:true});
    3. 如果某一个列上已经存在的重复值,在这样的列上创建唯一索引的话可以使用dropDups选项,这样系统会保留第一条记录,删除剩下重复的记录:db.mycoll.ensureIndex(keypattern, {unique:true, dropDups:true});
    4. 如果某一个列是唯一列,插入数据时没有插入此列的话会保存一个null值,一个唯一列中只能有一个null值;(这点与关系型数据库不同,关系型数据库中唯一列中可以保存null值,但是在mongodb中null值作为了一个值来比较)
  4. 组合索引:
    1. 语法与普通索引相同,只是需要多指定几个列:db.mycoll.ensureIndex({key1:value1, key2:value2, …});
    2. 键后面的数字表示索引的存储顺序,其中1表示升序,-1表示降序.在随机访问的时候意义不大,但是在排序或者是范围查询时很重要;
    3. 如果在a,b,c三列上创建了这索引,则以a,ab,abc开头的条件都可以使用到此索引;
  5. 稀疏索引:
    1. Sparse indexes only contain entries for documents that have the indexes field, any document that is missing the field is not indexes;
    2. By contrast, non-sparse indexes contrain all documents in collection, and store null values for documents that do not contrain the indexes field;
    3. 语法:db.mycoll.ensureIndex(keypattern, {sparse:true});
    4. 稀疏索引与非稀疏索引的对比;
  6. hint:
    1. 查询优化器会选择最优的执行计划,而且第一次执行后的执行计划将会被保存;如果需要使用自己指定的查询方案的话可以使用hint;
    2. 语法:cursor.hint(index);
      1. db.mycoll.find().hint(“index_name”):通过指定索引名称来实现hint;
      2. db.mycoll.find().hint({key:value}):通过指定创建索引时的key的顺序来实现hint,可以之前通过db.mycoll.getIndexes()查看;
    3. 查看hint的性能:db.mycoll.find().hint(index).explain();
  7. 注意事项:
    1. MongoDB中的索引是大小写敏感的;
    2. 当更新对象时,只有在索引上的这些key发生变化才更新,所以提高了性能;当对象增长了或者移动时,所有的索引都必须更新,会很慢;
    3. 索引的信息会保存在system.indexes集合中,运行db.system.indexes.find()可以查看这些数据;
    4. 索引的字段的大小限制目前是800bytes,大于之后可以在这个字段上创建索引,但是该字段不会被索引;
    5. 如果数据集合比较小(4M),可以联合使用limit()和sort()函数而不需要创建索引来查询数据;
———————- 10w条测试数据 ———————-
db.index.remove()
for(var i = 0; i < 100000; i++){
     db.index.insert({name:”index”+i, age:i})
}
———————- 10w条测试数据 ———————-
———————- 稀疏索引与非稀疏索引的对比 ———————-
— 1.测试数据;
db.sparse.insert({name:”sparse1″, age:20})
db.sparse.insert({name:”sparse2″})
db.sparse.find()
— 2.在age列创建一个稀疏索引;
db.sparse.ensureIndex({age:1}, {name:”idx_sparse_age”, sparse:true})
db.sparse.getIndexes()
— 3.查看数据,稀疏索引中不包含列为null的文档,只有稀疏索引中的文档才会被返回;
db.sparse.find()
db.sparse.find().sort({age:1})
db.sparse.find({name:{$ne:0}})
— 4.删除稀疏索引,并创建一般的索引;
db.sparse.dropIndex(“idx_sparse_age”)
db.sparse.ensureIndex({age:1}, {name:”idx_sparse_age”})
db.sparse.getIndexes()
— 查看数据,非稀疏索引中包含了列为null的文档;
———————- 稀疏索引与非稀疏索引的对比 ———————-

MongoDB学习03–聚合操作,游标的使用及排序分页操作

聚合操作,游标的使用及排序分页操作

  1. 聚合操作:
    1. count:查看符合某些条件的集合中记录的个数;
      1. 查看集合user的记录数:db.user.count();                                        
      2. 查看集合user中年龄大于20的记录的个数:db.user.count({“age”:{$gt:20}});                                  
    2. distinct:查看集合中某个属性的独立存在的个数;
      1. 获得不同年龄的数组:db.user.distinct(“age”);                               
      2. 获得不同年龄值的个数:db.user.distinct(“age”).length;                           
    3. group:对集合按照某一属性分组,形成一个K-V模型;
      1. 语法:db.mycoll.group( { key : …, initial: …, reduce : … [, finalize: …] [, condition: …] } );
        1. key:分组的键;
        2. initial:每个分组使用的初始化函数;
        3. reduce:此函数的第一个参数是当前的文档对象,第二个参数是上次函数(即initial函数)操作的累计对象,有多少个文档,$reduce就会调用多少次;
        4. finalize:每一组文档执行完之后,会触发执行的一个函数,参数是上次函数(即initial函数)操作的累计对象;
        5. condtion:过滤条件;
      2. 按照年龄分组的例子:db.user.group({key:{age:true}, initial:{user:[]}, reduce:function(cur, prev){prev.user.push(cur.name);}}),push函数是把一个值加入到数组中;       
      3. 按照年龄分组,过滤掉年龄大于25的记录,并且添加一个用户数量的属性count:db.user.group({key:{age:true}, initial:{user:[]}, reduce:function(cur, prev){prev.user.push(cur.name);}, finalize:function(prev){prev.count=prev.user.length;}, condition:{age:{$lte:25}}});                               
    4. mapReduce:其实是一种编程模型,用在分布式计算中.可以把问题划分为多个不同的部分并分发到不同的服务器并行处理,每台服务器都把分配给自己的一部分处理完成后把结果集返回给主服务器,主服务器汇总结果后完成问题的处理;
      1. 语法:db.mycoll.mapReduce( mapFunction , reduceFunction , <optional params> );
        1. mapFunction:映射函数,里面会调用emit(key, value),集合会按照指定的key进行映射分组;
        2. reduceFunction:;简化函数,会对map分组后的数据进行分组简化.其中reduce(key, value)中的key就是emit中的key,value为emit分组后的emit(value)的集合,它有一个或者多个对应于键的文档组成;
        3. 可选参数;
      2. 原理:map首先将文档映射到集合并操作文档,这一步可能产生多个键和多个值或者什么也没有(文档中要处理的值为空).而后按照键分组,并将产生的值组成列表放到对应的键中,reduce则把列表中的值化简为一个值,这个值被返回,而后继续按键分组,进行化简,直到每个键在列表中只有一个值为止,这个值也就是最终结果;
      3. 例子;
  2. 游标的使用:
    1. 使用while循环遍历输出:var cursor=db.user.find(); while(cursor.hasNext()) { printjson(cursor.next()); };(其中hasNext()函数判断是否有记录,next()函数返回下一条记录)             
    2. pringjson()函数是内置函数,能够将结果输出为json格式;
    3. 得到数据集集合,遍历完之后游标便销毁;
    4. 使用forEach()函数遍历:db.user.find().forEach(printjson);                          
    5. 像访问数组一样使用游标:var cursor=db.user.find();printjson(cursor[0]);(会把访问的数据都加载ram中,如果数据量很大的话非常消耗内存)                
    6. 直接转换成数组访问:var array=db.user.find().toArray();array[0];                            
  3. 排序操作:
    1. 语法:db.mycoll.find().sort({col:value, …});
      1. col:表示按照排序的列;
      2. value:可以取1和-1,1表示升序,-1表示降序;
      3. 查找所有记录,并按照年龄升序,名称降序显示:db.user.find().sort({“age”:1, “name”:-1}),select name, age from user order by age desc, name asc;              
    2. 还可以通过其它方法实现,db.mycoll.find([query], [fields])操作是根据query过滤记录,先后显示fields里面指定的列,如果query给默认的话({}就是默认的参数),只需要指定列名及排序方式即可:select name, age from user order by age desc, name asc;                                 
    3. 先按年龄升序,然后按照名称升序排序:select name from user order by age asc, name asc;                      
  4. 分页操作:
    1. 使用db.mycoll.find().skip(n)操作和db.mycoll.find().limit(n)实现,前者是跳过之前多少条记录,后者是显示多少条记录:skip((pageIndex-1) * pageSize).limit(pageSize);
    2. 排序后的结果,每页两条记录,显示第二页:db.user.find().sort({“age”:1, “name”:-1}).skip(2).limit(2);                 
— 测试数据;
db.user.drop()
db.user.insert({“name”:”jack”, “age”:20})
db.user.insert({“name”:”joe”, “age”:22})
db.user.insert({“name”:”mary”, “age”:26})
db.user.insert({“name”:”kobe”, “age”:20})
db.user.insert({“name”:”wade”, “age”:22})
db.user.insert({“name”:”yi”, “age”:22})
db.user.find()
— mapReduce操作;
1.创建map函数;
function map(){ emit(this.age, {count:1}); }
2.创建reduce函数;
function reduce(key, value){
     var result = {count:0};
     for (var i = 0; i < value.length; i++){
          result.count += value[i].count;
     }
     return result;
}
3.执行mapReduce操作,并把结果集存放在集合collection中;
db.user.mapReduce(map, reduce, {out:”collection”})
result:存放集合名;
timeMillis:执行的时间,单位是毫秒;
input:传入文档的个数;
emit:emit函数被调用的次数;
reduce:reduce函数被调用的次数;
output:最后返回文档的个数;
4.查看collection中的结果;
db.collection.find()

MongoDB学习02–增删改查操作

MongoDB的增删改查操作

  1. INSERT操作:
    1. 单条插入(可以使用js的语法);                                          
    2. 批量插入:mongodb中并没有提供批量插入的语法,但是高级语言中提供了与mongodb批量插入的接口,也可以通过for循环来模拟;
    3. insert和save的区别:
      1. 如果不使用_id列,两个函数没有区别都是插入数据;
      2. 如果使用了_id列,insert插入_id重复的话会报错;save插入_id重复的话会调用upsert,即存在就更新,不存在就插入;
  2. FIND操作:
    1. 根据条件查询:[>, >=, <, <=, !=, =]分别对应[$gt, $gte, $lt, $lte, $ne, 无]关键字;                    
    2. 多条件查询:[and, or, in, notin]分别对应[无, $or, $in, $nin]关键字;                      
    3. 正则表达式查询;                                                       
    4. 使用$where子句查询;                                                   
    5. findOne()函数返回满足条件的第一条记录或者是null:printjson(db.user.findOne({name:”joe”}));                                 
  3. UPDATE操作:
    1. 整体更新;                                                            
    2. 局部更新,使用修改器$inc和$set来更新某一个字段;
      1. $inc:increase的缩写,即在原来值的基础上增加$inc指定的值,可以用于自增的主键;                                
      2. $set:即设置某个列的值为$set指定的值;                                         
    3. upsert操作:update操作的第一个参数是查询条件,第二个参数是要更新的值,如果设置第三个参数为true的话,就表示如果存在记录就更新,如果不存在就插入;                           
    4. multi操作:批量更新,如果update操作匹配到了多条记录,默认情况下只更新第一条,如果需要全部更新的话,需要把update操作中第四个参数设置为true;                           
  4. REMOVE操作:
    1. 根据条件删除:db.mycoll.remove(query);                                       
    2. 全部删除:db.mycoll.remove();                                                        
— insert操作;
var user={“name”:”jack”, “password”:”12345″, “age”:20, “address”:{“province”:”shanghai”, “city”:”shanghai”}, “favourite”:[“apple”, “banana”]}
db.user.insert(user);
user.name=”joe”
user.age=21
user.address={“province”:”beijing”, “city”:”beijing”}
user.favourite=[“sanguosha”, “dota”]
db.user.insert(user)
db.user.find()
— find操作;
1.查找年龄大于等于21岁的集合;
db.user.find({“age”:{$gte:21}})
2.查找年龄等于20岁的集合;
db.user.find({“age”:20})
3.查找名称为jack而且省份为shanghai的集合:name=’jack’ and province=’shanghai’;
db.user.find({“name”:”jack”, “address.province”:”shanghai”})
4.查找省份为上海或者北京的集合:province=’shanghai’ or province=’beijing’;
db.user.find({$or:[{“address.province”:”shanghai”}, {“address.province”: “beijing”}]})
5.查找省份在上海或者北京的集合:province in (‘shanghai’, ‘beijing’);
db.user.find({“address.province”:{$in:[“shanghai”, “beijing”]}})
6.查找省份不在上海的集合:province not in (‘shanghai’);
db.user.find({“address.province”:{$nin:[“shanghai”]}})
7.查找姓名以’j’开头并且以’e’结尾的集合:name like ‘j%e’;
db.user.find({“name”:/^j/, “name”:/e$/})
8.条件复杂的时候,可以使用$where子句,其实就是js的函数,查找名称为’jack’的集合:name=’jack’;
db.user.find({$where:function(){return this.name == ‘jack’}})
— update操作;
1.整体更新一条记录;
db.user.find({“name”:”jack”})
var model=db.user.findOne({“name”:”jack”})
model.age=30
db.user.update({“name”:”jack”}, model)
db.user.find({“name”:”jack”})
2.使用$inc修改器更新一个字段;
db.user.find({“name”:”jack”})
db.user.update({“name”:”jack”}, {$inc:{“age”:-5}})
db.user.find({“name”:”jack”})
3.使用$set修改器更新一个字段;
db.user.find({“name”:”jack”})
db.user.update({“name”:”jack”}, {$set:{“age”:20}})
db.user.find({“name”:”jack”})
4.upsert操作:查找到就更新,没有就插入;
db.user.find({“name”:”kobe”})
db.user.update({“name”:”kobe”}, {$set:{“age”:20}}, true)
db.user.find({“name”:”kobe”})
5.multi操作:批量更新记录;
db.user.find()
db.user.update({“age”:{$gte:20}}, {$inc:{“age”:5}}, false, true)
db.user.find()
— remove操作;
1.根据条件删除;
db.user.find()
db.user.remove({“name”:”kobe”})
db.user.find()
2.全部删除;
db.user.remove()
db.user.find()
db.user.count()

MongoDB学习01–Linux下安装MongoDB v2.2

Linux下安装MangoDB

  1. 官网地址:http://www.mongodb.org;
  2. 下载文件:从http://www.mongodb.org/downloads页面下载相应的版本,这里选择linux下32bit的2.2.4版本;
  3. 解压文件:tar -zxvf /tools/mongodb-linux-i686-2.2.4.tgz;
  4. 修改文件名,并移动到/usr/local目录下:mv mongodb-linux-i686-2.2.4 mongodb; cp -R  /tools/mongodb /usr/local/;
  5. 把mangodb的路径添加到path中:PATH=$PATH:/usr/local/mongodb/bin;
  6. 启动mongod服务并修改默认数据目录:
    1. mongodb默认的数据文件的路径是/data/db,需要预先创建,如果使用的是mongo用户的话,需要修改此目录的权限:chown mongo /data/db;
    2. 修改数据文件的路径:mkdir -p /mongo/data/journal;mongod –journal –dbpath /mongo/data/;(默认是不开启journal功能的,可以指定参数打开)                         
    3. 访问相应的端口查看信息:http://192.168.10.112:28017/;                                
  7. 使用配置文件方式启动mongod服务,每次启动都需要输入很长的参数比较麻烦,可以把参数写在/etc/mongod.conf文件中,然后采用:mongod -f /etc/mongod.conf的方式启动;             
  8. 登陆客户端,使用mongo命令,同时也是js的编辑器(可以使用一切js的语法),默认连接test数据库;(使用SecureCRT的话,命令总是会重复一次,推荐使用xshell)                                                   
  9. 获得帮助,使用help命令;                                                   
  10. 关闭mongod服务:mongod –dbpath /mongo/data –shutdown;                                           
  11. 如果非法关闭后再次打开时报错:mongod –journal –dbpath=/mongo/data,此时需要删除rm -rf /mongo/data/mongod.lock文件,然后再打开;                                        
  12. 对MongoDB的基本CRUD操作:
    1. 插入一个集合,是以bson(json的扩展)的形式插入的;                              
    2. find操作,_id字段是数据库默认加的guid,目的是保证数据的唯一性;                              
    3. update操作,第一个参数为查找的条件,第二个参数为更新的值;                                 
    4. remove操作,如果不加参数就会删除所有的数据,而且不能回滚;                                
  13. 官方给定的使用mongodb的一些建议:
    1. MongoDB分成32位版本和64位版本,由于MongoDB使用内存映射文件,所以32位版本只能存储2GB左右的数据;建议存储更多数据的用户使用64位版本;
    2. MongoDB是文档型数据库,数据以BSON形式存储在文档中;最新版本的MongoDB能够支持最大16MB的文档大小;建议用户尽量不要存储大型对象,将文档控制在1 MB以内;
    3. MongoDB的写入和更新速度非常快,所以错误提示并不明确;要确保写入正确,建议用户使用getLastError或者使用安全写入;
    4. 关系型数据库往往会有预定义的schema,你想添加额外的列就需要在整个表上添加;MongoDB没有这个约束,这使得开发和管理变得更简单;但这并不意味着你就可以完全忽视MongoDB的schema设计,一个设计良好的schema能够让MongoDB的性能达到最佳;
    5. MongoDB的更新在默认情况下会使用类似于传统数据库的LIMIT语句,即LIMIT 1.因此更新不会影响到所有的文档,如果你想要一次更新许多文档,那么请把multi设为true;
    6. MongoDB默认情况下是区分大小写的,例如db.people.find({name: ‘Russell’}) 和db.people.find({name: ‘russell’})就是不一样的;所以用户需要知道MongoDB的大小写限制;
    7. 传统数据库中,如果插入错误的数据类型,通常会提示错误或者强制转换成预定义的数据值;MongoDB中没有这种限制,所以输入错误数据类型不会出现提示;建议用户确保输入正确的数据类型;
    8. 全局锁是一直被MongoDB用户诟病的特性,MongoDB 2.2中增加了数据库级锁,这是一个很大的改进;建议用户使用稳定版的MongoDB 2.2数据库,避免全局锁限制;
    9. 过期版本MongoDB用户在下载程序包时会出问题,建议用户使用10gen最新版本的官方程序包;
    10. Replica Set是MongoDB中受关注最多的功能,它能为MongoDB集群增加冗余并提供良好的读性能;但由于Replica Set的选举机制,必须保证Replica Set成员数目为奇数;如果是偶数的话,主节点宕机就会导致其他节点变为只读;解决方法也可以使用一个仲裁节点(arbiter),它也是一个Replica Set的成员,但并不存储用户数据;所以请记住设置Replica Set成员时要定为奇数;
    11. MongoDB中不存在join,你要针对多个集合进行数据检索的时候,必须使用多个查询;所以当你遇到这个问题时,可以考虑重新设计MongoDB的schema;
    12. Journaling日志是MongoDB中非常好的功能,能够增强节点的可用性;在2.0版本之后,MongoDB默认是开启Journaling日志功能的;虽然Journaling日志会对数据库性能造成一定的影响,但这部分影响是可以忽略的;因此建议用户开启Journaling功能,特别是对于可用性要求较高的用户;
    13. MongoDB默认情况下是没有认证功能的,因此建议用户使用防火墙对MongoDB进行保护;
    14. Replica Set的工作是通过传送oplog来完成的,主节点发生故障后,新的数据将会存放在数据目录下的一个特定文件夹内,即rollback文件夹;你可以用来手动完成数据恢复;所以在每次故障发生之后,你一定要看看这个文件夹,MongoDB自带的工具就能够帮助你轻松地完成手动数据恢复;
    15. 跨服务器的数据拆分中,Sharding是一个有效的方法;MongoDB中支持自动化Sharding,但是对数据库性能会造成很大影响;因此建议用户尽早进行Sharding,使用MMS:Munin (+ Mongo plugin)和CloudWatch等工具对MongoDB进行监控,确保系统资源使用达到80%之前就完成Sharding工作;
    16. MongoDB使用shard key来决定特定的文档在哪个分片上,当插入一个文档之后,你是无法更新shard key的;这里建议用户删除文档并重新插入,这样就能够将其分配到合适的分片上;
    17. MongoDB对分片的限制还包括集合的大小,当超过256 GB的时候,MongoDB将不允许进行分片;相信10gen公司会在未来放弃这一限制,但在此之前用户需要留意;
    18. MongoDB中跨分片并没有强制要求唯一性,MongoDB只针对独立的分片进行强制而非全局性;当然除shard key之外;
    19. 进行拆分的时候,MongoDB会要求你选择一个键;用户需要注意选择正确的键,否则会造成不必要的麻烦;如何进行选择并无定式,主要取决于你的应用,比如针对news feed使用时间戳就是错的;在下一版本中,MongoDB将对此进行改进;
    20. MongoDB连接默认情况下是不加密的,也就是说你的数据是能够被第三方记录和使用的;所以你在公共网中访问MongoDB的话,就一定要进行加密;
    21. MongoDB只支持单一文档的原子性,这一点与传统的数据库有所不同,如MySQL.因此MongoDB中跨多个文档是不提供内置的transaction支持的;
    22. 当MongoDB显示ready的时候,其实内部还在进行journal的配置;因此针对速度较慢的文件系统,MongoDB的journal配置也会很慢;
    23. 不建议尝试NUMA + Linux + MongoDB的组合,如果你的MongoDB跑在NUMA服务器上,建议将它关掉;
    24. 在Linux上运行MongoDB遭遇segfault错误时,这主要是因为open files / process限制过低;建议用户将限制设定为4K+;
————————– 基本的增删改查操作 ————————–
— insert操作;
db.person.insert({“name”:”kobe”,”age”:32})
db.person.insert({“name”:”james”,”age”:28})
— find()操作;
db.person.find()
— update()操作;
db.person.update({“name”:”kobe”},{“name”:”kobe”,”age”:33})
db.person.find({“name”:”kobe”})
— delete()操作;
db.person.remove({“name”:”james”})
db.person.find()
db.person.count()
————————– 基本的增删改查操作 ————————–
————————– mongodb的配置文件/etc/mongod.conf ————————–
vi /etc/mongod.conf
port=27017
dbpath=/mongo/data
directoryperdb=true
logpath=/mongo/logs/mongod.log
logappend=true
auth=true
journal=true
fork=true
————————– mongodb的配置文件/etc/mongod.conf ————————–

分布式文档存储数据库–MongoDB

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:

  • 面向集合存储,易存储对象类型的数据。
  • 模式自由。
  • 支持动态查询。
  • 支持完全索引,包含内部对象。
  • 支持查询。
  • 支持复制和故障恢复。
  • 使用高效的二进制数据存储,包括大型对象(如视频等)。
  • 自动处理碎片,以支持云计算层次的扩展性
  • 支持RUBY,PYTHON,JAVA,C++,PHP等多种语言。
  • 文件存储格式为BSON(一种JSON的扩展)
  • 可通过网络访问

所谓“面向集合”(Collenction-Orented),意思是数据被分组存储在数据集中,被称为一个集合(Collenction)。每个 集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定 义任何模式(schema)。
模式自由(schema-free),意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义。如果需要的话,你完全可以把不同结构的文件存储在同一个数据库里。
存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各中复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized dOcument Format)。

MongoDB服务端可运行在Linux、Windows或OS X平台,支持32位和64位应用,默认端口为27017。推荐运行在64位平台,因为MongoDB在32位模式运行时支持的最大文件尺寸为2GB。

MongoDB把数据存储在文件中(默认路径为:/data/db),为提高效率使用内存映射文件进行管理。

NoSQL的现状

经过了至少4年的激烈争论,现在是对NoSQL的现状做一个阶段性结论的时候了。围绕着NoSQL发生了如此之多的事情,以至于很难对其作出一个简单概括,也很难判断它达到了什么目标以及在什么方面没有达到预期。

在很多领域,NoSQL不仅在行业内也在学术领域中取得了成功。大学开始认识到NoSQL必须要加入到课程中。只是反复讲解标准数据库已经不够了。当然,这不意味着深入学习关系型数据库是错误的。相反,NoSQL是很好的很重要的补充。

发生了什么?

NoSQL领域在短短的4到5年的时间里,爆炸性地产生了50到150个新的数据库。nosql-database.org列出了150个这样的数据库,包括一些像对象数据库这样很古老但很强大的。当然,一些有意思的合并正在发生,如CouchDB和Membase交易产生的CouchBase。但是我们稍后会在本文中讨论每一个主要的系统。

很多人都曾经假设在NoSQL领域会有一个巨大地整合。但是这并没有发生。NoSQL过去是爆炸性地增长,现在依旧如此。就像计算机科学中的所有领域一样——如编程语言——现在有越来越多的空白领域需要大量的数据库。这是与互联网、大数据、传感器以及将来很多技术的爆炸性增长同步的,这导致了更多的数据以及对它们进行处理的不同需求。在过去的四年中,我们只看到了一个重要的系统离开了舞台:德国的Graph数据库Sones。为数众多的NoSQL依然快乐地生存着,要么在开源社区,不用考虑任何的金钱回报,要么在商业领域。

可见性与金钱?

另外一个重要的方面就是可见性与行业采用的情况。在这个方面,我们可以看到在传统的行业中——要保护投资——与新兴的行业(主要是初创公司)之间有很大的差别。几乎所有热门的基于Web的创业公司如Pinterest和Instagram 都在使用混合式(SQL + NoSQL)的架构,而传统的行业依然纠结于是否采用NoSQL。但是观察显示,越来越多这样的公司正在试图将它们的一部分数据流用NoSQL方案进行处理并在以后进行分析,这样的方案包括Hadoop、MongoDB以及Cassandra等。

这同时导致了对具备NoSQL知识的架构师和开发人员的需求持续增长。最近的调查显示行业中最需要的开发人员技能如下:

  1. HTML5
  2. MongoDB
  3. iOS
  4. Android
  5. Mobile Apps
  6. Puppet
  7. Hadoop
  8. jQuery
  9. PaaS
  10. Social Media

在前十名的技术需求中,有两个NoSQL数据库。有一个甚至排在了iOS前面。如果这不是对它的赞扬,那是什么呢?!

但是,跟最初预计相比,对NoSQL的采用变得越来越快,越来越深入。在2011年夏天,Oracle曾经发布过一个著名白皮书,它提到NoSQL数据库感觉就像是冰淇淋的风味,但是你不应该过于依附它,因为它不会持续太长时间。但是仅仅在几个月之后,Oracle就展现了它们将Hadoop集成到大数据设备的方案。甚至,他们建立了自己的NoSQL数据库,那是对BerkeleyDB的修改。从此之后,所有的厂商在集成Hadoop方面展开了竞赛。Microsoft、Sybase、IBM、Greenplum、Pervasive以及很多的公司都已经对它有了紧密的集成。有一个模式随处可见:不能击败它,就拥抱它。

但是,关于NoSQL被广泛采用的另一个很重要但不被大家关注的重要信号就是NoSQL成为了一个PaaS标准。借助于众多NoSQL数据库的易安装和管理,像Redis和MongoDB这样的数据库可以在很多的PaaS服务中看到,如Cloud Foundry、OPENSHIFT、dotCloud、Jelastic等。随着所有的事情都在往云上迁移,NoSQL会对传统的关系型数据库产生很大的压力。例如当面临选择MySQL/PostGres或MongoDB/Redis时,将会强制人们再三考虑他们的模型、需求以及随之而来的其他重要问题。

另外一个很有意思的技术指示器就是ThoughtWorks的技术雷达,即便你可能不完全同意它所包含的所有事情,但它总会包含一些有意思的事情。让我们看一下他们2012年10月份的技术雷达,如图1:

图1:ThoughtWorks技术雷达,2012年10月——平台

在他们的平台象限中,列出了5个数据库:

  1. Neo4j (采用)
  2. MongoDB(试用阶段但是采用)
  3. Riak(试用)
  4. CouchBase(试用)
  5. Datomic(评估)

你会发现它们中至少有四个获得了很多的风险投资。如果你将NoSQL领域的所有风险投资加起来,结果肯定是在一亿和十亿美元之间!Neo4j就是一个例子,它在一系列的B类资助中得到了一千一百万美元。其他得到一千万到三千万之间资助的公司是Aerospike、Cloudera、DataStax、MongoDB以及CouchBase等。但是,让我们再看一下这个列表:Neo4j、MongoDB、Riak以及CouchBase已经在这个领域超过四年了并且在不断地证明它们是特定需求的市场领导者。第五名的数据库——Datomic——是一个令人惊讶的全新数据库,它是由一个小团队按照全新的范式编写的。这一定是很热门的东西,在后面简要讨论所有数据库的时候,我们更更深入地了解它们。

标准

已经有很多人要求NoSQL标准了,但他们没有看到NoSQL涵盖了一个范围如此之大的模型和需求。所以,适用于所有主要领域的统一语言如Wide Column、Key/Value、Document和Graph数据库肯定不会持续很长时间,因为它不可能涵盖所有的领域。有一些方式,如Spring Data,试图建立一个统一层,但这取决于读者来测试这一层在构建多持久化环境时是不是一个飞跃。

大多数的Graph和Document数据库在它们的领域中已经提出了标准。在Graph数据库世界,因为它的tinkerpop blueprints、Gremlin、Sparql以及Cypher使得它更为成功一些。在Document数据库领域,UnQL和jaql填补了一些位置,尽管前者缺少现实世界NoSQL数据库的支持。但是借助Hadoop的力量,很多项目正在将著名的ETL语言如Pig和Hive使用到其他NoSQL数据库中。所以标准世界是高度分裂的,但这只是因为NoSQL是一个范围很广的领域。

格局

作为最好的数据库格局图之一,是由451 Group的Matt Aslett在一个报告中给出的。最近,他更新了该图片从而能够让我们可以更好得深入理解他所提到的分类。你可以在下面的图片中看到,这个格局是高度碎片化和重叠的:

(点击图片放大)

图2:Matt Aslett(451 Group)给出的数据库格局

你可以看到在这个图片中有多个维度。关系型的以及非关系型的、分析型的以及操作型的、NoSQL类型的以及NewSQL类型的。最后的两个分类中,对于NoSQL有著名的子分类Key-Value、Document、Graph以及Big Tables,而对于NewSQL有子分类Storage-Engine、Clustering-Sharding、New Database、Cloud Service Solution。这个图有趣的地方在于,将一个数据放在一个精确的位置变得越来越难。每一个都在拼命地集成其他范围数据库中的特性。NewSQL系统实现NoSQL的核心特性,而NoSQL越来越多地试图实现“传统”数据库的特性如支持SQL或ACID,至少是可配置的持久化机制。

这一切都始于众多的数据库都提供与Hadoop进行集成。但是,也有很多其他的例子,如MarkLogic开始参与JSON浪潮,所以也很难对其进行定位。另外,更多的多模型数据库开始出现,如ArangoDB、OrientDB和AlechemyDB(现在它是很有前途的Aerospike DB的一部分)。它们允许在起始的时候只有一个数据库模型(如document/JSON模型)并在新需求出现的时候添加新的模型(Graph或key-value)。

图书

另外一个证明它开始变得成熟的标志就是图书市场。在2010年和2011年两本德语书出版之后,我们看到Wiley出版了Shashank Tiwari的书。它的结构很棒并且饱含了深刻伟大的见解。在2012年,这个竞赛围绕着两本书展开。“七周七数据库”(Seven Databases in Seven Weeks)当然是一本杰作。它的特点在于新颖的编写以及实用的基于亲身体验的见解:它选取了6种著名的NoSQL数据库以及PostGreSQL。这些都使得它成为一本高度推荐的图书。另一方面,P.J. Sandalage以及Martin Fowler采取了一种更为全面的方法,涵盖了所有的特征并帮助你评估采用NoSQL的路径和决策。

但是,会有更多的书出现。Manning的书出现在市场上只是个时间问题:Dan McCreary和Ann Kelly正在编写一本名为“Making Sense of NoSQL”的书,首期的MEAP(指的是Manning Early Access Program——译者注)章节已经可以看到了。

在介绍完理念和模式后,他们的第三章看起来保证很有吸引力:

  • 构建NoSQL大数据解决方案
  • 构建NoSQL搜索解决方案
  • 构建NoSQL高可用性解决方案
  • 使用NoSQL来提高敏捷性

只是一个全新的方式,绝对值得一读。

领导者的现状

让我们快速了解一下各个NoSQL的领导者。作为市场上很明显的领导者之一,Hadoop是一个很奇怪的动物(作者使用这个词,可能是因为Hadoop的标识是一只大象——译者注)。一方面,它拥有巨大的发展势头。正如前面所说,每个传统的数据库提供商都急切地声明支持Hadoop。像Cloudera和MapR这样的公司会持续增长并且新的Hadoop扩展和继承者每周都在出现。
即便是Hive和Pig也在更好地得到接受。不过,有一个美中不足之处:公司们依然在抱怨非结构化的混乱(读取和解析文件本应该更快一些),MapReduce在批处理上做的还不够(甚至Google已经舍弃了它),管理依旧很困难,稳定性问题以及在本地很难找到培训/咨询。即便你可以解决一些上面的问题,如果Hadoop继续像现在这样发展或发生重大变化的话,它依然会是热点问题。

第二位领导者,MongoDB,同样面临激烈的争论。处于领导地位的数据库会获得更多的批评,这可能是很自然的事情。不过,MongoDB经历了快速的增长,它受到的批评主要如下:

a)就老版本而言或者
b)缺少怎样正确使用它的知识。尽管MongoDB在下载区域清楚地表明32位版本不能处理2GB的数据并建议使用64位版本,但这依然受到了很多近乎荒谬的抱怨。

不管怎样,MongoDB合作者和资助者推动了雄心勃勃的发展路线,包含了很多热门的东西:

  • 行业需要的一些安全性/LDAP特性,目前正在开发
  • 全文本搜索很快会推出
  • 针对MapReduce的V8将会推出
  • 将会出现比集合级别更好的锁级别
  • Hash分片键正在开发中

尤其是最后一点吸引了很多架构师的兴趣。MongoDB经常被抱怨(同时也被竞争对手)没有实现简洁一致的哈希,因为key很容易定义所以不能保证完全正确。但在将来,将会有一个对hash分片键的配置。这意味着用户可以决定使用hash key来分片,还是需要使用自己选择分片key所带来的优势(可能很少)。

Cassandra是这个领域中的另一个产品,它做的很好并且添加了更多更好的特性,如更好的查询。但是不断有传言说运行Cassandra集群并不容易,需要一些很艰难的工作。但这里最吸引人的肯定是DataStax。Cassandra的新公司——获得了两千五百万美元的C类资助——很可能要处理分析和一些操作方面的问题。尤其是分析能力使得很多人感到惊讶,因为早期的Cassandra并没有被视为强大的查询机器。但是这种现状在最近的几个版本中发生了变化,查询功能对一些现代分析来讲已经足够了。

Redis的开发进度也值得关注。尽管Salvatore声明如果没有社区和Pieter Noordhuis的帮助,他做不成任何的事情,但是它依旧是相当棒的一个产品。对故障恢复的良好支持以及使用Lua的服务器端脚本语言是其最近的成就。使用Lua的决策对社区带来了一些震动,因为每个人都在集成JavaScript作为服务器端的语言。但是,Lua是一个整洁的语言并为Redis开启新的潘多拉盒子带来了可能性。

CouchBase在可扩展性和其他潜在因素方面看起来也是一个很好的选择,尽管Facebook以及Zynga面临着巨大的风波。它确实不是很热门的查询机器,但如果他们能够在将来提高查询能力,那它的功能就会相当完整了。与CouchDB创立者的合并毫无疑问是很重要的一个步骤,CouchDB在CouchBase里面的影响值得关注。在每个关于数据库的会议上,听到这样的讨论也是很有意思的,那就是在Damien、Chris和Jan离开后,CouchDB会变得更好呢还是更坏呢?大家在这里只能听到极端的观点。但是,只要数据库做得好谁关心这个呢。现在看起来,它确实做的很好。

最后一个需要提及的NoSQL数据库当然是Riak,在功能性和监控方面它也有了巨大的提升。在稳定性方面,它继续得到巨大的声誉:“像巨石一般稳定可靠且不显眼,并对你的睡眠有好处”。Riak CS fork在这种技术的模块化方面看起来也很有趣。

有意思的新加入者

除了市场领导者,评估新的加入者通常是很有意思的。让我们深入了解它们中的一部分。

毫无疑问,Elastic Search是最热门的新NoSQL产品,在一系列的A轮资助中它刚刚获得了一千万美元,这是它热门的一个明证。作为构建在Lucene之上的高扩展性搜索引擎,它有很多的优势:a)它有一个公司提供服务并且b)利用了Lucene在过去的多年中已被充分证明的成就。它肯定会比以往更加深入得渗透到整个行业中,并在半结构化信息领域给重要的参与者带来冲击。

Google在这个领域也推出了小巧但是迅速的LevelDB。在很多特殊的需求下,如压缩集成方面,它作为基础得到了很多的应用。即使是Riak都集成了LevelDB。考虑到Google的新数据库如Dremel和Spanner都有了对应的开源项目(如Apache Drill或Cloudera Impala),它依然被视为会继续存在的。

另外一个技术变化当然就是在2012年初的DynamoDB。自从部署在Amazon中,他们将其视为增长最快的服务。它的可扩展性很强。新特性开发地比较慢但它关注于SSD,其潜力是很令人振奋的。

多模块数据库也是值得关注的一个领域。最著名的代表者是OrientDB,它现在并不是新的加入者但它在很迅速地提高功能。可能它变化得太快了,很多使用者也许会很开心地看到OrientDB已经到达了1.0版本,希望它能更稳定一些。对Graph、Document、Key-Value的支持以及对事务和SQL的支持,使得我们有理由给它第二次表现的机会。尤其是对SQL的良好支持使得它对诸如Penthao这样的分析解决方案方面很有吸引力。这个领域另一个新的加入者是ArangoDB,它的进展很快,并不畏惧将自己与已确定地位的参与者进行比较。
但是,如果有新的需求必须要实现并且具有不同类型的新数据模型要进行持久化的话,对原生JSON和Graph的支持会省去很多的努力。

到目前位置,2012年的最大惊喜来自于Datomic。它由一些摇滚明星采用Clojure语言以难以令人置信的速度开发的,它发布了一些新的范式。另外,它还进入了ThoughtWorks的技术雷达,占据了推荐关注的位置。尽管它“只是”已有数据库中一个参与者,但是它有很多的优势,如:

  • 事务
  • 时间机器
  • 新颖且强大的查询方式
  • 新的模式方式
  • 缓存以及可扩展性的特性

目前,支持将DynamoDB、Riak、CouchBase、Infinispan以及SQL作为底层的存储引擎。它甚至允许你同时混合和查询不同的数据库。很多有经验的人都很惊讶于这种颠覆性的范式转变是如何可能实现的。但幸运的是它就是这样。

总结

作为总结,我们做出三点结论:

  1. 关于CAP理论,Eric Brewer的一些新文章应该几年前就发表。在这篇文章中这篇佳文的中文版地址——译者注),他指出“三选二”具有误导性,并指出了它的原因,世界为何远比简单的CP/AP更为复杂,如在ACID/BASE之间做出选择。虽然如此,近些年来有成千上万的对话和文章继续赞扬CAP理论而没有任何批评性的反思。Michael Stonebraker是NoSQL最强有力的审查者之一(NoSQL领域也对他颇多感激),他在多年前就指出了这些问题!遗憾的是,没有多少人在听。但是,既然Eric Brewer更新了他的理论,简单的CAP叙述时代肯定要结束了。在指出CAP理论的真实和多样性的观点上,请站在时代的前列。
  2. 正如我们所了解的那样,传统关系型数据库的不足导致了NoSQL领域的产生。但这也是传统帝国发起回击的时刻。在“NewSQL”这个术语之下,我们可以看到许多新的引擎(如database.com、VoltDB、GenieDB等,见图2),它们提高了传统的解决方案、分片以及云计算方案的能力。这要感谢NoSQL运动。

    但是随着众多的数据库尝试实现所有的特性,明确的边界消失了
    确定使用哪种数据库比以前更为复杂了。
    你必须要知道50个用例、50个数据库并要回答至少50个问题。关于后者,笔者在过去两年多的NoSQL咨询中进行了收集,可以在以下地址找到:选择正确的数据库在NoSQL和NewSQL间进行选择

  3. 一个通用的真理就是,每一项技术的变化——从客户端-服务端技术开始甚至更早——需要十倍的成本才能进行转移。例如,从大型机到客户端-服务端、客户端-服务端到SOA、SOA到WEB、RDBMS到混合型持久化之间的转换都是如此。所以可以推断出,在将NoSQL加入到他们的产品决策上,很多的公司在迟疑和纠结。但是,大家也都知道,最先采用的公司会从这个两个领域获益并且能够快速集成NoSQL,所以在将来会占据更有利的位置。就这一点而言,NoSQL解决方案会一直存在并且评估起来会是有利可图的领域。

关于作者

Prof. Dr. Stefan Edlich是德国柏林Beuth HS技术(University of App. Sc.)的高级讲师。他为诸多出版社如Apress、OReilly、Spektrum/Elsevier等编写了超过10本IT图书。他维护着NoSQL Archive网站, 从事NoSQL咨询并组织NoSQL技术会议,编写了世界上最早的两本NoSQL图书,现在他热衷于Clojure编程语言。