Archive for the ‘nosql’ Category

30
Jun

Redis几个认识误区

   Posted by: zu

前几天微博发生了一起大的系统故障,很多技术的朋友都比较关心,其中的原因不会超出James Hamilton在On Designing and Deploying Internet-Scale Service(1)概括的那几个范围,James第一条经验“Design for failure”是所有互联网架构成功的一个关键。互联网系统的工程理论其实非常简单,James paper中内容几乎称不上理论,而是多条实践经验分享,每个公司对这些经验的理解及执行力决定了架构成败。

题外话说完,最近又研究了Redis。去年曾做过一个MemcacheDB, Tokyo Tyrant, Redis performance test,到目前为止,这个benchmark结果依然有效。这1年我们经历了很多眼花缭乱的key value存储产品的诱惑,从Cassandra的淡出(Twitter暂停在主业务使用)到HBase的兴起(Facebook新的邮箱业务选用HBase(2)),当再回头再去看Redis,发现这个只有1万多行源代码的程序充满了神奇及大量未经挖掘的特性。Redis性能惊人,国内前十大网站的子产品估计用1台Redis就可以满足存储及Cache的需求。除了性能印象之外,业界其实普遍对Redis的认识存在一定误区。本文提出一些观点供大家探讨。

1. Redis是什么

这个问题的结果影响了我们怎么用Redis。如果你认为Redis是一个key value store, 那可能会用它来代替MySQL;如果认为它是一个可以持久化的cache, 可能只是它保存一些频繁访问的临时数据。Redis是REmote DIctionary Server的缩写,在Redis在官方网站的的副标题是A persistent key-value database with built-in net interface written in ANSI-C for Posix systems,这个定义偏向key value store。还有一些看法则认为Redis是一个memory database,因为它的高性能都是基于内存操作的基础。另外一些人则认为Redis是一个data structure server,因为Redis支持复杂的数据特性,比如List, Set等。对Redis的作用的不同解读决定了你对Redis的使用方式。

互联网数据目前基本使用两种方式来存储,关系数据库或者key value。但是这些互联网业务本身并不属于这两种数据类型,比如用户在社会化平台中的关系,它是一个list,如果要用关系数据库存储就需要转换成一种多行记录的形式,这种形式存在很多冗余数据,每一行需要存储一些重复信息。如果用key value存储则修改和删除比较麻烦,需要将全部数据读出再写入。Redis在内存中设计了各种数据类型,让业务能够高速原子的访问这些数据结构,并且不需要关心持久存储的问题,从架构上解决了前面两种存储需要走一些弯路的问题。

2. Redis不可能比Memcache快

很多开发者都认为Redis不可能比Memcached快,Memcached完全基于内存,而Redis具有持久化保存特性,即使是异步的,Redis也不可能比Memcached快。但是测试结果基本是Redis占绝对优势。一直在思考这个原因,目前想到的原因有这几方面。

  • Libevent。和Memcached不同,Redis并没有选择libevent。Libevent为了迎合通用性造成代码庞大(目前Redis代码还不到libevent的1/3)及牺牲了在特定平台的不少性能。Redis用libevent中两个文件修改实现了自己的epoll event loop(4)。业界不少开发者也建议Redis使用另外一个libevent高性能替代libev,但是作者还是坚持Redis应该小巧并去依赖的思路。一个印象深刻的细节是编译Redis之前并不需要执行./configure。
  • CAS问题。CAS是Memcached中比较方便的一种防止竞争修改资源的方法。CAS实现需要为每个cache key设置一个隐藏的cas token,cas相当value版本号,每次set会token需要递增,因此带来CPU和内存的双重开销,虽然这些开销很小,但是到单机10G+ cache以及QPS上万之后这些开销就会给双方相对带来一些细微性能差别(5)。
3. 单台Redis的存放数据必须比物理内存小

Redis的数据全部放在内存带来了高速的性能,但是也带来一些不合理之处。比如一个中型网站有100万注册用户,如果这些资料要用Redis来存储,内存的容量必须能够容纳这100万用户。但是业务实际情况是100万用户只有5万活跃用户,1周来访问过1次的也只有15万用户,因此全部100万用户的数据都放在内存有不合理之处,RAM需要为冷数据买单。

这跟操作系统非常相似,操作系统所有应用访问的数据都在内存,但是如果物理内存容纳不下新的数据,操作系统会智能将部分长期没有访问的数据交换到磁盘,为新的应用留出空间。现代操作系统给应用提供的并不是物理内存,而是虚拟内存(Virtual Memory)的概念。

基于相同的考虑,Redis 2.0也增加了VM特性。让Redis数据容量突破了物理内存的限制。并实现了数据冷热分离。

4. Redis的VM实现是重复造轮子

Redis的VM依照之前的epoll实现思路依旧是自己实现。但是在前面操作系统的介绍提到OS也可以自动帮程序实现冷热数据分离,Redis只需要OS申请一块大内存,OS会自动将热数据放入物理内存,冷数据交换到硬盘,另外一个知名的“理解了现代操作系统(3)”的Varnish就是这样实现,也取得了非常成功的效果。

作者antirez在解释为什么要自己实现VM中提到几个原因(6)。主要OS的VM换入换出是基于Page概念,比如OS VM1个Page是4K, 4K中只要还有一个元素即使只有1个字节被访问,这个页也不会被SWAP, 换入也同样道理,读到一个字节可能会换入4K无用的内存。而Redis自己实现则可以达到控制换入的粒度。另外访问操作系统SWAP内存区域时block进程,也是导致Redis要自己实现VM原因之一。

5. 用get/set方式使用Redis

作为一个key value存在,很多开发者自然的使用set/get方式来使用Redis,实际上这并不是最优化的使用方法。尤其在未启用VM情况下,Redis全部数据需要放入内存,节约内存尤其重要。

假如一个key-value单元需要最小占用512字节,即使只存一个字节也占了512字节。这时候就有一个设计模式,可以把key复用,几个key-value放入一个key中,value再作为一个set存入,这样同样512字节就会存放10-100倍的容量。

这就是为了节约内存,建议使用hashset而不是set/get的方式来使用Redis,详细方法见参考文献(7)。

6. 使用aof代替snapshot

Redis有两种存储方式,默认是snapshot方式,实现方法是定时将内存的快照(snapshot)持久化到硬盘,这种方法缺点是持久化之后如果出现crash则会丢失一段数据。因此在完美主义者的推动下作者增加了aof方式。aof即append only mode,在写入内存数据的同时将操作命令保存到日志文件,在一个并发更改上万的系统中,命令日志是一个非常庞大的数据,管理维护成本非常高,恢复重建时间会非常长,这样导致失去aof高可用性本意。另外更重要的是Redis是一个内存数据结构模型,所有的优势都是建立在对内存复杂数据结构高效的原子操作上,这样就看出aof是一个非常不协调的部分。

其实aof目的主要是数据可靠性及高可用性,在Redis中有另外一种方法来达到目的:Replication。由于Redis的高性能,复制基本没有延迟。这样达到了防止单点故障及实现了高可用。

小结

要想成功使用一种产品,我们需要深入了解它的特性。Redis性能突出,如果能够熟练的驾驭,对国内很多大型应用具有很大帮助。希望更多同行加入到Redis使用及代码研究行列。

参考文献
  1. On Designing and Deploying Internet-Scale Service(PDF)
  2. Facebook’s New Real-Time Messaging System: HBase To Store 135+ Billion Messages A Month
  3. What’s wrong with 1975 programming
  4. Linux epoll is now supported(Google Groups)
  5. CAS and why I don’t want to add it to Redis(Google Groups)
  6. Plans for Virtual Memory(Google Groups)
  7. Full of keys(Salvatore antirez Sanfilippo)

30
Jun

redis java client

   Posted by: zu

redis主页上列出的java 客户端JDBC-Redis JRedis Jedis三种,下面分别介绍三种客户端的优缺点及其他相关的工具.

 

支持redis版本 性能 维护 推荐
JDBC-Redis   not good    
JRedis 1.2.n release 
2.0.0 尚未release版本
fast    
Jedis 2.0.0 release fast actively developed 推荐

 

JDBC-Redis

JDBC-Redis is just a JDBC wrapper for JRedis database. 
If you plan on using your code with different back-ends then JDBC is a good way to go. NOTE: It is not a complete JDBC implementation and the NOSQL will bleed through.

If you are going to stay with Redis then I would suggest using the API, which will give you more flexibility. Use a DAO layer pattern to encapsulate your DB Access and down the road that is all you will need to change.

Romain Hippeau

Redis syntax is completely different from standard SQL so using JDBC doesn’t help encapsulating different back-ends as you suggest: I would have to write new queries anyway… – muriloq Jun 16 ‘10 at 14:00

@muriloq – but the mechanical acquiring and releasing resources is standard. – Romain Hippeau

 

 

spring wrapper

Spring provides a wrapper around both implementations(Jredis Jedis) and they’re providing serialization/deserialization, amongst other things:

Person p = new Person("Joe", "Trader", 33);template.convertAndSet("trader:1", p);Person samePerson = template.getAndConvert("trader:1", Person.class);Assert.assertEquals(p, samePerson);     

放弃spring wrapper

项目中本来打算使用spring wrapper,出于以下原因最终还是放弃,直接使用Jedis,等有时间在把:

1.spring wrapper的版本是1.0.0.M2,里面有些bug (*)

2.对shard的支持没有jedis好

3.依赖spring3.0(主要是spring3.0 core中的convert及serializer),我们目前大多项目还是采用spring2.5.6(主要)

4.经过多层封装后性能还是会有损耗

spring nosql/cross-store

prototype implementation allowing entities to be stored in multiple types of data stores (i.e. JPA and Neo4j or JPA and Redis etc.)

 

JOhm

JOhm is a blazingly fast Object-Hash Mapping library for Java inspired by the awesome Ohm. The JOhm OHM is a modern-day avatar of the old ORM’s like Hibernate with the difference being that we are not dealing with an RDBMS here but with a NoSQL rockstar.

homepage:https://github.com/xetorthio/johm

 

jedis pool的问题

在使用jedis pool时遇到了这个问题:It seems like server has closed the connection

原因分析:

1.redis server 关闭了此客户端的连接:server端设置了maxidletime(默认是5分钟),服务端会不断循环检测clinet的最后一次通信时间(lastinteraction),如果大于maxidletime,则关闭连接,并回收相关资源。client在向该连接中写数据后就会由于server端已经关闭而出现 broken pipe的问题。

2.pool的设置错误:

<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" />

上面的这种配法在spring初始化时获取一次实例化jedisCommands,而后每次的redis的调用时并未从pool中获取

解决方案:

设置

<!-- 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>

参考: 
http://stackoverflow.com/questions/3047010/best-redis-library-for-java 
https://github.com/xetorthio/johm

https://github.com/xetorthio/jedis/issues/closed#issue/76

30
Jun

redis

   Posted by: zu

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

30
Jun

Redis作者谈Redis应用场景

   Posted by: zu

毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象构建不同的冰箱。希望你喜欢这个比喻。

下面是一篇新鲜出炉的文章,其作者是Redis作者@antirez,他描述了Redis比较适合的一些应用场景,NoSQLFan简单列举在这里,供大家一览:

1.取最新N个数据的操作

比如典型的取你网站的最新文章,通过下面方式,我们可以将最新的5000条评论的ID放在Redis的List集合中,并将超出集合部分从数据库获取

  • 使用LPUSH latest.comments<ID>命令,向list集合中插入数据
  • 插入完成后再用LTRIM latest.comments 0 5000命令使其永远只保存最近5000个ID
  • 然后我们在客户端获取某一页评论时可以用下面的逻辑(伪代码)
FUNCTION get_latest_comments(start,num_items):
    id_list = redis.lrange("latest.comments",start,start+num_items-1)
    IF id_list.length < num_items
        id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")
    END
    RETURN id_list
END

如果你还有不同的筛选维度,比如某个分类的最新N条,那么你可以再建一个按此分类的List,只存ID的话,Redis是非常高效的。

2.排行榜应用,取TOP N操作

这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。

3.需要精准设定过期时间的应用

比如你可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

4.计数器应用

Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。

5.Uniq操作,获取某段时间所有数据排重值

这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。

6.实时系统,反垃圾系统

通过上面说到的set功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。没有做不到,只有想不到。

7.Pub/Sub构建实时消息系统

Redis的Pub/Sub系统可以构建实时的消息系统,比如很多用Pub/Sub构建的实时聊天系统的例子。

8.构建队列系统

使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。

9.缓存

这个不必说了,性能优于Memcached,数据结构更多样化。