引言:
nosql 的兴起和革命,在我看来已经开始逐渐影响到了传统的sql的地位,但是仅仅是影响而已,取代是不太可能的。
正文:
两年前,一个偶然的机会开始接触到 nosql ( mongodb )。用来作数据挖掘的存储容器,第一次接触到nosql,真的被它惊艳到了。鄙人受到传统的SQL的思维定势,甚至一时间难以接受。
mongodb是一个非关系型文档数据库,非常适合文档类型的数据的存储,查询也十分方便,支持动态的横向和纵向的数据扩展。爱不释手。下个用几行shell来展示一下mongodb的魅力
show dbs; //无则会创建 use mydb; show collections; //新建集合,相当于mysql中的表 db.createCollection('mycollection'); //插入数据. db.mycollection.insert({'username':'chenqimiao','age':23}) db.mycollection.insert({'username':'cqm','age':23,'sex':'男'}) //查询 db.mycollection.find({"age":23});
类比mysql的话,最大的区别可能在于表结构或者说集合结构的定义了,mysql的列是预定义的,而mongodb是在插入数据的时候才确定这条数据的列。
所以mongodb可以支持动态扩展列,在mongo中可能不叫列,要叫作域(field)吧。
后来花了一点时间了解了一下 HBase , Habase 诞生于 Hadoop的子项目,受大数据的遗传。单表可以非常的大,同样Habase也是基于列的。
印象中HBase 中有一个非常特别的特性,HBase 的数据覆盖,并不是实际的覆盖。HBase 有一个时间维度的概念,所有的数据都是基于这个维度进行存储的,
简单点说,同样的key 可能在HBase 中存在两个value。它是怎么做到的?因为它给每一条记录都偷偷记录了一个timestamp,每次你去覆盖键值对的时候,你以为你已经删除了旧值,替换成了新
值,而实际上只是再添加了一条记录而已,两条记录共存在一个时间维度上。每次get(key)默认取最新的一个value,仅此而已。
你还可以手动设置失效时间TTL,这样每一个值就会有一个有效期,过了有效期都值是不能get出来了,但是值还是存储在HBase中并未丢失。
再后来接触到了 redis ,相信大家对这个都相当的熟悉,基于内存的缓存数据库,简单的set() get()就可以了缓存一些经常使用到的值,之前我也专门介绍过 redis的安装,shell命令,多实例部署,读写分离,主从复制,哨兵 等等问题。
当数据超过一定量 , redis会把数据swap到文件中去,如果使用到swap中的文件的值,redis会把文件在swap到内存中,进行读写,十分智能。支持的数据类型也比较多 ,除了k-v 还有hset ,hash ,zset等等
最近在做在线课堂,用到了 Memcache ,这个东西基本和redis的使用场景相似,基于内存的nosql。但是支持的数据类型只有k-v的形式。这是不同于redis的一点。
其二的话, Memcache 不支持文件持久化。
其三, memcache 的多实例,是基于客户端的,这个比较有意思要好好聊一聊了,象我们平常接触的mysql,redis,多实例同步基本是读写分离,主从复制,主机写,从机读,这样的模式。可以说是基于服务端的多实例方案。但是 memcache 有点好玩了,它的多实例之间不进行同步,那它是怎么做到负载均衡的时候保证数据的完整性呢?
说到这里,我想先介绍一下memcache的主流的客户端程序(JAVA)
- 官方提供的基于传统阻塞io由Greg Whalin维护的客户端
较早推出的客户端,稳定,持久运行。
- Dustin Sallings实现的基于java nio的Spymemcached
A simple, asynchronous, single-threaded memcached client written in java. 支持异步,单线程的memcached客户端,用到了java1.5版本的concurrent和nio,存取速度会高于前者,但是稳定性不好,测试中常 报timeOut等相关异常。
- XMemcache
Memcached同样是基于java nio的客户端,java nio相比于传统阻塞io模型来说,有效率高(特别在高并发下)和资源耗费相对较少的优点。传统阻塞IO为了提高效率,需要创建一定数量的连接形成连接 池,而nio仅需要一个连接即可(当然,nio也是可以做池化处理),相对来说减少了线程创建和切换的开销,这一点在高并发下特别明显。因此 XMemcached与Spymemcached在性能都非常优秀,在某些方面(存储的数据比较小的情况下)Xmemcached比 Spymemcached的表现更为优秀,具体可以看这个Java Memcached Clients Benchmark。
根据上面的介绍,大概可以了解到,官方提供的是阻塞的客户端,要利用线程池来实现并发,但是官方提供的包还有一个非常致命的问题,不提供 CAS 的同步功能。
什么是 CAS ?
了解过java下面的 java.util.concurrent.atomic; 下面的类的同学应该知道, CAS (check and swap),这是一种乐观锁的实现,程序陷入一个循环,得到旧值,执行CAS方法,传入旧值,新值,若旧值未发生变化 ,则用旧值,换出新值。
while(true){ Object oldValue = atomicObject.get("key1") ; Object newValue = new Object(); Object value = checkAndSwap("key1",oldValue,newValue) ; if(value!=null&&oldValue.equals(value)) break; }
解释完 CAS ,回到刚才的问题,为什么没有实现 CAS ,是官方包一个致命的弱点,明白了 CAS 原理的同学,应该发现其实它就是一个同步的手段,那为什么不能使用 syncronized 的。那是因为数据并发发生在不同项目里面,没有办法给多个项目之前共用一个 syncronized 。这个时候只能利用memcache提供的锁,利用 CAS 作为同步手段。
庆幸spyMemcache和xmemcache都提供了 CAS 的操作。
那么问题来了,刚刚最早提出的问题如何解答?( CAS 多实例同步问题。)其实啊memcache根本不需要进行多实例同步,它的多实例是依赖于客户端程序实现的。
比如spymemcache:
para.memcache.server=192.168.202.121:11211,192.168.202.121:11210 配置两个server即可。其余的操作和操作一个memcache是一摸一样的,客户端会根据Hash散列算法( HashMap 的实现算法)将键值对放到对应的 memcache 中,由于算法一致性,所以存取双方都能得知键值在哪一个 memcache 中。这样就实现了多实例了。
另外spymemcache客户端还有一个比较厉害的地方,它能直接将java对象序列化,作为k-v中的v,get(k)的时候自动反序列化成对象,无需直接操作JSONObject,当然前提是对象实现
Serializable 接口,并给定一个 serialVersionUID 。redis也可以直接存储二进制文件,但是官方提供的客户端程序,并没有封装好序列化和反序列化,需要自己实现。
说了这么多nosql,好像传统sql无用武之地一般。其实传统的sql才是最稳定的,适用面积最广,最安全,并且提供了事务回滚,保证数据一致性,这是nosql为了提高速度,增强扩展性,所要面临的一部分舍弃。