Cassandra学习笔记

1). cassandra任何一个节点都可以被客户端访问。

2). 对cassandra某个节点的访问是通过调用org.apache.cassandra.service.Cassandra的内部类Client的相应接口实现的。

3). 2)中的Cassandra这个类包含了很多内部类和一个接口(Iface)。其中的Client和Processor两个内部类都是对Iface的实现,这保证了他们的内部的所有方法是一一对应的。

4). 当Client这个内部类中的某个方法被调用的时候,该方法会用他内部的send_开头的方法发送消息,并且用recv_开头的方法接收返回的内容,容,返回的内容可能是我们想要的数据,也可能是个异常的消息,如果是异常的消息,则会在客户端生成一个相应的异常并抛出,

5). Client端send_和recv_方法同目标节点的交互是分别通过oprot和iprot的实例完成的,这两个实例是负责输入输出的,具体的功能的实现在libthrift.jar中。

6). 节点和客户端通信的连接是由libthrift.jar中的TThreadPoolServer的实例实现的,这个实例在该节点最初启动的时候被生成,并且该实例内部还保有一个2)中提到的Processor实例。TThreadPoolServer实例给Processor实例提供了输入输出实例iprot和oprot,并且通过调用Processor的processprocess(TProtocol iprot, TProtocol oprot)接口来进一步的向内传递消息。

7). 节点最初启动的初始话过程是在org.apache.cassandra.service.CassandraDaemon的setup()中完成的。

. 在Processor的processprocess(TProtocol iprot, TProtocol oprot)会解析iprot中传入的客户端的请求,并首先解析出要调用函数的函数名字,然后通过查询processMap_来决定究竟由那个ProcessFunction实例来接收处理消息,相应的ProcessFunction实例的process(int seqid, TProtocol iprot, TProtocol oprot)被激活并开始全权负责消息的处理和反馈。

9). 相应的的ProcessFunction的实例主要负责三件事:i,进一步处理iprot传入的消息 ii,将详细的信息转发给iface的相应方法处理 iii,将得到的反馈通过oprot返回给客户端。这里的iface实例实际上是org.apache.cassandra.service.CassandraServer的一个实例,在Processor的实例创建的时候(节点启动的时候)被装入了Processor实例,但是由于ProcessFunction类是Processor的内部类,所以ProcessFunction的实例也能直接访问。

10)以上可知,最终客户端的信息是交给CassandraServer的相应方法来处理的,而thrift的相关功能只是负责了客户端和节点间的交互(9160端口),而节点之间的交互并没有使用thrift的资源。

 

 

源码中对节点的如下称呼应该是等价的: end point , node ,  machine , datacenter , host。

    cassandra节点的启动main()在类org.apache.cassandra.service.CassandraDaemon中,细节在 setup()中。过程中会start一个CassandraServer的实例peerStorageServer。 peerStorageServer在建立的时候,内部会实例化一个 StorageService实例,在该StorageService实例初始化的过程中,该节点的所有功能服务会被配置激活,这些操作是在 StorageService的默认构造器中完成的。

    StorageService的构造器中大致做了如下几件事情:

    1)生成一个storageLoadBalancer_s实例负责负载均衡,现在还没有明白原理。

    2)生成一个endPointSnitch_实例,这个提供了对两个end_point进行比较的一个途径,基本上是判断两个end_point是不是同一个ip等

    3)启动了MessagingService,并且注册了一些handler实例。MessagingService是负责该end_point与其他end_point进行通信的。两个节点间通信的内容被封装在一个Message的实例里面。

       比如,如果节点A想向节点B获得一定的数据,那么A需要通过自己的MessageService向节点B发送一个Message实例,这个实例里面包含了如下信息:这个请求的类型(属于什么stage) ,这个请求要调用的B的哪个handler来处理,以及这个请求的其他具体内容。当节点B接收到节点A发送过来的Message实例后,会将根据这个 Message实例内部指定的handler信息,将该实例转发相应的handler去处理。当然这样做的前提是这个指定的handler已经在B节点注册了,而这个注册过程就是在StorageService启动的时候完成的。

    4)consistencyManager_:还没明白什么意思。

    5)StageManager的配置:

       这个stage的概念来自于“SEDA“( staged event driven architecture)中的”S”,参考http://www.eecs.harvard.edu/~mdw/papers/seda-sosp01.pdf。

       大致意思好像是可以将一个工作流程分为若干个阶段(stage),然后给各个stage动态的分配线程资源来处理stage内定义的逻辑。stage和 stage之间的通信是通过任务队列完成的,当一个stage的逻辑执行完后,如果需要调用下一个stage来继续执行,那么就往下一个stage保有的任务队列中写入必要的任务信息。

       这样的SEDA结构的好处是:在实际的运行当中各个stage的忙闲程度是不一样的,可以通过将比较闲的stage上的线程资源分配给同期比较忙的stage来实现效率上的提高。

       在cassandra中,还是以3)中例子说明,在A节点发往B节点的Message实例中有一个“这个请求的类型”的信息,这个信息保存在 Message实例内header实例的type_上,是一个字符串。这个字符串标明了当MessageService获取了Message实例后究竟由那个 stage来负责执行指定的handler对象。因为handler对象只是定义了对Message的处理逻辑,所以需要stage里面的线程来对其进行执行。

       当前的stage只有4种,依次在StroageService的 /* All stage identifiers */标注下被定义,分别负责不同的任务,“ROW-READ-STAGE”是负责读取的,其他的含义还没有完全搞清楚,大概是负责修改,数据压缩和 http后台页面信息接收的。

       StageManager部分的源码还没哟看到,可能是负责各个stage间线程调度的吧。

    6)设置nodepicker_定义了当前节点对周围节点的查询策略,具体的还不清楚

 

当某个end point拿到一个key(比如”王老六”)并想取出他的相关信息的时候,这个节点是怎么知道这个key的相关信息是存放在哪些节点中的呢? 以下将用从客户端拿到的”get_clomun”请求为例,进行说明:

      “get_column”的相关信息会在CassandraServer的get_column(String tablename, String key, String columnPath)方法中被封装成一个readCommand实例,该对象简单包含了请求信息,另外也提供了一些别的方法。

      然后该command实例会最终被交给 StorageProxy.readProtocol(command,StorageService.ConsistencyLevel.WEAK)来处理,并期待这个方法能返回一个和key关联的columnFamily的数据,这些数据被装在一个row对象里返回的。

      在上述的StorageProxy.readProtocol(…)这个方法中,首先要做的事情就是根据提交的key(“王老六”)确定他的相关信息内容是存放在那些end point中的。而这个寻找end point的工作由StorageService.instance().getNStorageEndPoint(command.key)来完成。

      实际上,上述的StorageService的getNStorageEndPoint(String key)方法调用了他持有的实例”nodePicker_”的getStorageEndPoints(Token token) 来完成寻找end point这件事情。

      题外话:StorageService这个实例提供了本地节点数据存储和与其他节点交互的服务,这个实例是在节点启动之初被建立的,在建立的时候该实例会初始化一个nodePicker_放在其内部,这个nodePicker主要负责本地节点同其他节点的交互规则,也就是如何选择其他节点的策略。当前的策略有两种:RackAwareStrategy–机架敏感 和 RackUnawareStrategy–非机架敏感,默认策略是RackUnawareStrategy。

      所以默认情况下,nodePicker_就是一个RackUnawareStrategy实例,而他的 getStorageEndPoints(Token token)将被用来查找合适我们给出的key(“王老六”)的end point,这里合适的意思是“存有关于这个key的信息”。而这里提到的token是由key封装得来的,他对key进行了hash,并且继承了 Comparable接口。也就是说,这个token实例是可以和其他同类token比较大小的,而这样做的目的是为了“可以在Token组成的list 上进行二分查询Collection.binarySort,并定位查找目标的在list中的相对位置”–这种查找操作将被经常使用。默认情况下 token是一个BigIntegerToken的实例。

      nodePicker_最终会将查找的工作交给getStorageEndPoints(Token token, Map tokenToEndPointMap)来做,这个方法会返回一个查找到的end_point的数组。以下是对RackUnawareStrategy中的相应方法的说明(因为默认是使用这个方法): //此处的tokenToEndPointMap保存了该节点知道的所有其他节点,并且可以根据他们的token查找到

//此处的token是我们要查找的key(“王老五”)生成的token,他跟上面end point的token生成的算法是相同的,也都是BigIntegerToken

  public EndPoint[] getStorageEndPoints(Token token, Map tokenToEndPointMap)     {         //根据key寻址的切入点         int startIndex;         List list = new ArrayList();         int foundCount = 0;         //将key的set装入一个ArrayList中,并完成排序,(一位token是comparable的)         List [...]

(一)依赖:cassandra.jar

             libthrift.jar

(二)连接:

    //该方法将返回一个Cassandra.Client实例,该实例包含和server端指定节点会话的API

       public Cassandra.Client getClient()     {         //192.168.0.169为想连接到的某个节点的ip,9160为端口           TSocket socket = new TSocket("192.168.0.169", 9160);           TTransport transport_ = socket;           TBinaryProtocol binaryProtocol = new TBinaryProtocol(transport_, false, false);         Cassandra.Client cassandraClient = new Cassandra.Client(binaryProtocol);           try         {             transport_.open();         }         catch(Exception e)         {             // Should move this to Log4J as well probably…             System.err.println("Exception " + e.getMessage());                       e.printStackTrace();         }                return cassandraClient;           [...]

1)columnFamily下一个column和多个column的读取区别 2)columnfamily 和superColumnFamily的读取区别

测试机数量:两台,jvm最大使用内存都开到1.3G。

起始key: 1356278962 ;

改变组: product_name1    :    “是一个非常可靠的大规模分布式存储系统” product_name2    :    “中国惨败伊朗丢亚锦赛冠军创34年参赛最耻辱一败”

控制组: product_value1    :    “第一次在主场丢冠军” product_value2    :    “胡雪峰顶替受伤的刘炜” product_value3    :    “朱芳雨都有外线出手机会” product_value4    :    “张庆鹏传球意图太过明显” product_value5    :    “最后一节比赛” product_value6    :    “中国队首发:易建联、王治郅、朱芳雨、王仕鹏、胡雪峰” product_value7    :    “中国队本次亚锦赛首次尝到失利的滋味,在家门口把冠军拱手相让” product_value8    :    “下半场易边再战,中国队仍然如同梦游,球员之间没有形成整体”

//下面这个是一个要存入的“摘要” product_value9    :    “下半场易边再战,中国队仍然如同梦游,球员之间没有形成整体,             单打独斗的进攻模式成功率相当低。伊朗队内外结合,多点得分,             继续扩大分差。朱芳雨传球被断,对手长传快攻,14号球员扣篮得分,             28-53。王治郅强攻得手,造成哈达迪犯规,加罚命中。             王治郅再次溜到篮下,反身投篮得分。王治郅成为中国队的唯一亮点,             持球接连晃过三名球员的防守,投篮得分,加罚再中,             36-53。靠着王治郅的出色发挥,中国队留住翻盘的一线希望。             第三节结束,39-56,分差仍为17分。”

    我们有的ColumnFamily: Standard1 Standard2 Super1 Super2

测试1:    向Standard1中写入10万条记录,此时Standard1中只有”product_name”一个column,key是从1356278962开始往后10万个

    insert()进行插入,速度很慢,不可能在一小时内完成     CQL 用时256秒,关掉log后用时55秒

测试2:    然后在product_name1 和 product_name2之间反复修改key-1356279962对应的”produck_name”这个column的内容10万次     CQL:

    当config中的ReplicationFactor为1的时候,用时120秒     当config中的ReplicationFactor为2的时候,用时183秒;关掉log后用时42秒

    测试3:    将存在的各个key的product_name读取一遍(此时相应columnFamily中只有1个column)     当config中的ReplicationFactor为1的时候:速度相当慢     当config中的ReplicationFactor为2的时候:

        CQL:

        用时267秒;关掉log后用时145秒

        API:         用get_column用时203秒;关掉log后用时135秒

以下均以ReplicationFactor=2来进行测试

测试4:    依照已经存在的各个key,向Standard1中写入10万条控制组里面的column数据     [...]

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值