精华内容
下载资源
问答
  • OSAL之消息管理

    千次阅读 2015-05-14 10:22:40
    OSAL实现对消息管理的功能实现是在osal.c 与 osal.h 文件,对于操作系统来说,不可缺少的就是任务之间信息的传递,消息包括:信号量,互斥量,消息邮箱、消息队列等。在OSAL仅仅实现了消息队列的功能,系统...

    转载请注明出处,尊重原创;

    本文基于蓝牙1.3.2版本


    总述:

    消息处理机制流程
    消息处理机制流程
    OSAL中实现对消息管理的功能实现是在osal.cosal.h 文件中,对于操作系统来说,不可缺少的就是任务之间信息的传递,消息包括:信号量,互斥量,消息邮箱、消息队列等。在OSAL中仅仅实现了消息队列的功能,系统可以发送或接收消息,并对消息进行管理。

    背景知识:在OSAL.H文件中

    1、定义消息管理的消息首部,所有的消息构成都有它这部分;

    typedef struct
    {   //用于消息首部
      void   *next;
      uint16 len;
      uint8  dest_id;
    } osal_msg_hdr_t;

    其中next指针将消息组织成有序的链表,len为消息的长度,dest_id是目标任务id。
    2、定义管理消息队列的指针类型,从这里也可以看出管理消息队列指针类型是空指针,也说明了消息结构的类型是变化的,不是固定的。
    typedef void * osal_msg_q_t;
    3、定义几个消息操作的宏

    获得下一个消息
    #define OSAL_MSG_NEXT(msg_ptr)      ((osal_msg_hdr_t *) (msg_ptr) - 1)->next
    
    获得一个消息的消息长度
    #define OSAL_MSG_LEN(msg_ptr)      ((osal_msg_hdr_t *) (msg_ptr) - 1)->len
    获得消息发送的目标任务
    #define OSAL_MSG_ID(msg_ptr)      ((osal_msg_hdr_t *) (msg_ptr) - 1)->dest_id
    
    
    下面几个 *(q_ptr)计算,说明必须传过来的是消息链表头指针的地址,
    typedef void *osal_msg_q_t;
    osal_msg_q_t osal_qHead;//定义的消息头指针
    //调用入队列函数osal_msg_enqueue( &osal_qHead, msg_ptr );
    //它的定义处是void osal_msg_enqueue( osal_msg_q_t *q_ptr, void *msg_ptr ),这里说明q_ptr是一个二级指针。
    初始化一个消息队列,将消息头指针置空
    #define OSAL_MSG_Q_INIT(q_ptr)      *(q_ptr) = NULL
    判断消息队列是否为空
    #define OSAL_MSG_Q_EMPTY(q_ptr)     (*(q_ptr) == NULL)
    获得消息队列头指针
    #define OSAL_MSG_Q_HEAD(q_ptr)      (*(q_ptr))

    4、定义一个管理消息队列的变量用于管理消息队列
    osal_msg_q_t osal_qHead;
    最后消息结构如图
    从这个图可以看出osa_qHead是指向消息空间的,但是因为消息空间不能给出一个固定的结构模式,所以他是一个空指针的形式。

    API函数

    1、创建消息

    osal_msg_allocate() 给消息分配内存空间,该函数由发送消息的任务调用以创建一个消息
    参数:
    len-消息的长度
    1、分配内存空间(消息管理首部大小+消息的长度)
    hdr=(osal_msg_hdr_t*)osal_mem_alloc((short)(len+sizeof( osal_msg_hdr_t )) );
    2、填充消息首部

    {
        hdr->next = NULL;
        hdr->len = len;
        hdr->dest_id = TASK_NO_TASK;
        return ( (uint8 *) (hdr + 1) );//这里返回的是消息信息空间的地址
      }

    创建一个消息缓冲区后,下一步是在调用函数里填充消息信息空间了。然后调用osal_msg_send()函数将一个消息发送到消息队列队末尾。

    2、发送消息

    osal_msg_send()函数是向消息队列发送一个消息,同时还会向目标任务的事件列表里置位消息事件。
    参数:
    destination_task:目标任务id
    *msg_ptr: 指向消息空间

    uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )
    {
      return ( osal_msg_enqueue_push( destination_task, msg_ptr, FALSE ) );
    }
    
    //他是调用了一个入队列函数,最后参赛FALSE是指明入队列末尾。也就是说发送消息只是入队列而已

    3、将消息入队列

    osal_msg_enqueue_push( uint8 destination_task, uint8 *msg_ptr, uint8 push )函数功能是将一则消息入消息队列,同时设置目的任务的消息事件。
    参数:
    destination_task:目标任务id
    *msg_ptr: 指向消息空间
    push 指定是入队头还是队尾;TRUE是对头,FALSE是队尾
    主要函数代码:

    OSAL_MSG_ID( msg_ptr ) = destination_task;//将目标id复制给消息头dest_id
    
      if ( push == TRUE )
      {
        // prepend the message如果push为TRUE则将消息插入到队列头部
        //这样说明osal_msg_push函数是仅仅将消息插入队列头部
        osal_msg_push( &osal_qHead, msg_ptr );
      }
      else
      {
        // append the message这里就是将消息插入队列尾部
        osal_msg_enqueue( &osal_qHead, msg_ptr );
      }
    
      // Signal the task that a message is waiting置位相应任务的消息事件
      osal_set_event( destination_task, SYS_EVENT_MSG );

    对入队列调用的API讲解

    osal_msg_push( &osal_qHead, msg_ptr );
    将消息插入到队列对头,当push为TRUE时候执行。&osal_qHead是队列头指针的地址,msg_ptr是指向消息空间的指针
    osal_msg_enqueue( &osal_qHead, msg_ptr );
    将消息插入到队列末尾,&osal_qHead是队列头指针的地址,msg_ptr是指向消息空间的指针。

    4发送消息的另外一种方式

    osal_msg_push_front方式

    uint8 osal_msg_push_front( uint8 destination_task, uint8 *msg_ptr )
    {
      return ( osal_msg_enqueue_push( destination_task, msg_ptr, TRUE ) );//最后一个参数TRUE表明插入到队列头
    }

    它与osal_msg_send唯一不同的是它将消息插入到队列头

    5以上完成了一个消息的创建,发送,入队列的过程。下面就是任务消息事件的处理了。


    6目标任务执行处理系统消息事件

    1、获取该任务的消息uint8 *osal_msg_receive( uint8 task_id )

    osal_msg_hdr_t *listHdr;          //定义用来遍历消息队列,指向当前遍历的元素   
      osal_msg_hdr_t *prevHdr = NULL;     //用来遍历时候使用,指向foundHdr指针前一个元素
      osal_msg_hdr_t *foundHdr = NULL;    //指向发现符合该任务的消息
      halIntState_t   intState;
    
      // Hold off interrupts
      HAL_ENTER_CRITICAL_SECTION(intState);
    
      // Point to the top of the queue      osal_qHead指向的是消息信息结构体,不是消息头结构体
      listHdr = osal_qHead;
    
      // Look through the queue for a message that belongs to the asking task
      while ( listHdr != NULL )
      {
        if ( (listHdr - 1)->dest_id == task_id )
        {//找到了该任务的消息
          if ( foundHdr == NULL )
          {
            // Save the first one
            foundHdr = listHdr;
          }
          else
          {//如果有两个,则调出循环下次在执行
            // Second msg found, stop looking
            break;
          }
        }
        //若没找到符合的消息,那么prevHdr向下遍历,如果找到了,foundHdr不为0,prevHdr不再往下遍历了,指向foundHdr前一个元素
         if ( foundHdr == NULL )
        {                      
          prevHdr = listHdr;
        }
        listHdr = OSAL_MSG_NEXT( listHdr );
      }

    该段代码主要是在消息队列中寻找目标任务的消息;跳出这个while循环有两种情况,1、找到了消息,同时listHdr也到了队列末尾,那么是因为while条件不满足listHdr = NULL而结束循环;2、找到了消息但是是通过break结束while循环,那么说明该任务不止一个消息,OSAL在这里的处理方式是先处理完这个事件,然后在过来处理下一个该任务的消息事件。基于这两种情况,马上就有下面的处理代码了。

     // Is there more than one?
      if ( listHdr != NULL )
      {
        // Yes, Signal the task that a message is waiting如果该任务不止一个消息事件
        osal_set_event( task_id, SYS_EVENT_MSG );
      }
      else
      {
        // No more,listHdr遍历到链表末尾了那么就只有一个这个任务的事件
        osal_clear_event( task_id, SYS_EVENT_MSG );
      }

    基于上面第一段代码,找到了该任务的消息,那么下一步就是提取这个消息,本质上在数据结构里处理方式来说就是在单链表里删除一个元素,同时返回指向该消息的指针。

    if ( foundHdr != NULL )
      {
        // Take out of the link list
        osal_msg_extract( &osal_qHead, foundHdr, prevHdr );
      }
    
      // Release interrupts
      HAL_EXIT_CRITICAL_SECTION(intState);
    
      return ( (uint8*) foundHdr );

    osal_msg_extract( osal_msg_q_t *q_ptr, void *msg_ptr, void *prev_ptr )接口

    主要完成的功能是将一个消息从消息队列中删除,所以函数里面主要处理的是对后继指针的操作,
    参数:
    q_ptr指向队列头指针
    msg_ptr指向待提取的消息
    prev_ptr 指向待提取消息前一个元素

     if ( msg_ptr == *q_ptr )
      {
        // remove from first待取的消息是队列第一个元素
        *q_ptr = OSAL_MSG_NEXT( msg_ptr );
      }
      else
      {
        // remove from middle
        OSAL_MSG_NEXT( prev_ptr ) = OSAL_MSG_NEXT( msg_ptr );
      }
      //下面语句的意思是将提取的消息设为无效
      OSAL_MSG_NEXT( msg_ptr ) = NULL;
      OSAL_MSG_ID( msg_ptr ) = TASK_NO_TASK;//将抽取的消息设为不为任务占用,等待取消消息空间

    从队列中间删除一个元素的过程如图
    这里写图片描述

    2提取消息空间后就是调用消息处理函数

    3处理完之后就是删除消息空间

    osal_msg_deallocate( uint8 *msg_ptr )

    //这里就是将消息空间以及消息头都包括在内
    x = (uint8 *)((uint8 *)msg_ptr - sizeof( osal_msg_hdr_t ));
    //释放消息空间+消息头部空间
      osal_mem_free( (void *)x );

    上面这些完成了一个消息从创建到发送,目标任务获取消息,处理消息,释放消息空间整个详细介绍过程。

    下面介绍消息处理的其他API函数

    1 osal_msg_dequeue( osal_msg_q_t *q_ptr )

    函数功能是将一个消息出列。
    参数:q_ptr指向一个消息结构体的指针

     if ( *q_ptr != NULL )
      {
        // Dequeue message
        msg_ptr = *q_ptr;
        *q_ptr = OSAL_MSG_NEXT( msg_ptr );
        OSAL_MSG_NEXT( msg_ptr ) = NULL;
        OSAL_MSG_ID( msg_ptr ) = TASK_NO_TASK;
      }
    return msg_ptr;//返回的指向该消息结构体的指针

    2 osal_msg_find(uint8 task_id, uint8 event)

    函数实现的功能是根据task_id, event寻找满足这两个条件的消息

    pHdr = osal_qHead;  // Point to the top of the queue.
    
      // Look through the queue for a message that matches the task_id and event parameters.
    while (pHdr != NULL)
      {
        if (((pHdr-1)->dest_id == task_id) && (((osal_event_hdr_t *)pHdr)->event == event))
        {
          break;
        }
    
        pHdr = OSAL_MSG_NEXT(pHdr);
      }
    return (osal_event_hdr_t *)pHdr;
    展开全文
  • 消息队列的使用场景

    万次阅读 多人点赞 2016-03-04 10:26:20
    是大型分布式系统不可缺少的中间件。 目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。 二、消息队列应用场景 以下介绍消息队列在实际应用常用的使用

    一、消息队列概述

    消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。

    目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。

    二、消息队列应用场景

    以下介绍消息队列在实际应用中常用的使用场景。异步处理,应用解耦,流量削锋和消息通讯四个场景。

    2.1异步处理

    场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种1.串行的方式;2.并行方式。

    (1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。(架构KKQ:466097527,欢迎加入)

     

    (2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

     

    假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。

    因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)。

     

    小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?

    引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:

     

    按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍。

    2.2应用解耦

    场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图:(架构KKQ:466097527,欢迎加入)

     

    传统模式的缺点:

    1)  假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;

    2)  订单系统与库存系统耦合;

    如何解决以上问题呢?引入应用消息队列后的方案,如下图:

     

    • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
    • 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
    • 假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。

    2.3流量削锋

    流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。

    应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

    1. 可以控制活动的人数;
    2. 可以缓解短时间内高流量压垮应用;

     

    1. 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面;
    2. 秒杀业务根据消息队列中的请求信息,再做后续处理。

    2.4日志处理

    日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下:(架构KKQ:466097527,欢迎加入)

     

    • 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列;
    • Kafka消息队列,负责日志数据的接收,存储和转发;
    • 日志处理应用:订阅并消费kafka队列中的日志数据;

    以下是新浪kafka日志处理应用案例:

    转自(http://cloud.51cto.com/art/201507/484338.htm)

     

    (1)Kafka:接收用户日志的消息队列。

    (2)Logstash:做日志解析,统一成JSON输出给Elasticsearch。

    (3)Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能。

    (4)Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK stack的重要原因。

    2.5消息通讯

    消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

    点对点通讯:

     

    客户端A和客户端B使用同一队列,进行消息通讯。

    聊天室通讯:

     

    客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。

    以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。

    三、消息中间件示例

    3.1电商系统

     

    消息队列采用高可用,可持久化的消息中间件。比如Active MQ,Rabbit MQ,Rocket Mq。(1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功可以开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性)

    (2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。

    (3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。

    3.2日志收集系统

     

    分为Zookeeper注册中心,日志收集客户端,Kafka集群和Storm集群(OtherApp)四部分组成。

    • Zookeeper注册中心,提出负载均衡和地址查找服务;
    • 日志收集客户端,用于采集应用系统的日志,并将数据推送到kafka队列;
    • Kafka集群:接收,路由,存储,转发等消息处理;

    Storm集群:与OtherApp处于同一级别,采用拉的方式消费队列中的数据;

    四、JMS消息服务

    讲消息队列就不得不提JMS 。JMS(Java Message Service,Java消息服务)API是一个消息服务的标准/规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。

    在EJB架构中,有消息bean可以无缝的与JM消息服务集成。在J2EE架构模式中,有消息服务者模式,用于实现消息与应用直接的解耦。

    4.1消息模型

    在JMS标准中,有两种消息模型P2P(Point to Point),Publish/Subscribe(Pub/Sub)。

    4.1.1 P2P模式

     

    P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。

     

    P2P的特点

    • 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
    • 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列
    • 接收者在成功接收消息之后需向队列应答成功

     

    如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式。(架构KKQ:466097527,欢迎加入)

    4.1.2 Pub/sub模式

     

    包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。

    Pub/Sub的特点

    • 每个消息可以有多个消费者
    • 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
    • 为了消费消息,订阅者必须保持运行的状态。

     

    为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。

    如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型。

    4.2消息消费

    在JMS中,消息的产生和消费都是异步的。对于消费来说,JMS的消息者可以通过两种方式来消费消息。

    (1)同步

    订阅者或接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞;

    (2)异步

    订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage方法。

     

    JNDI:Java命名和目录接口,是一种标准的Java命名系统接口。可以在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回资源连接建立所必须的信息。

    JNDI在JMS中起到查找和访问发送目标或消息来源的作用。(架构KKQ:466097527,欢迎加入)

    4.3JMS编程模型

    (1) ConnectionFactory

    创建Connection对象的工厂,针对两种不同的jms消息模型,分别有QueueConnectionFactory和TopicConnectionFactory两种。可以通过JNDI来查找ConnectionFactory对象。

    (2) Destination

    Destination的意思是消息生产者的消息发送目标或者说消息消费者的消息来源。对于消息生产者来说,它的Destination是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,它的Destination也是某个队列或主题(即消息来源)。

    所以,Destination实际上就是两种类型的对象:Queue、Topic可以通过JNDI来查找Destination。

    (3) Connection

    Connection表示在客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection可以产生一个或多个Session。跟ConnectionFactory一样,Connection也有两种类型:QueueConnection和TopicConnection。

    (4) Session

    Session是操作消息的接口。可以通过session创建生产者、消费者、消息等。Session提供了事务的功能。当需要使用session发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。同样,也分QueueSession和TopicSession。

    (5) 消息的生产者

    消息生产者由Session创建,并用于将消息发送到Destination。同样,消息生产者分两种类型:QueueSender和TopicPublisher。可以调用消息生产者的方法(send或publish方法)发送消息。

    (6) 消息消费者

    消息消费者由Session创建,用于接收被发送到Destination的消息。两种类型:QueueReceiver和TopicSubscriber。可分别通过session的createReceiver(Queue)或createSubscriber(Topic)来创建。当然,也可以session的creatDurableSubscriber方法来创建持久化的订阅者。

    (7) MessageListener

    消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的onMessage方法。EJB中的MDB(Message-Driven Bean)就是一种MessageListener。

     

    深入学习JMS对掌握JAVA架构,EJB架构有很好的帮助,消息中间件也是大型分布式系统必须的组件。本次分享主要做全局性介绍,具体的深入需要大家学习,实践,总结,领会。

    五、常用消息队列

    一般商用的容器,比如WebLogic,JBoss,都支持JMS标准,开发上很方便。但免费的比如Tomcat,Jetty等则需要使用第三方的消息中间件。本部分内容介绍常用的消息中间件(Active MQ,Rabbit MQ,Zero MQ,Kafka)以及他们的特点。

    5.1 ActiveMQ

    ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。

    ActiveMQ特性如下:

    ⒈ 多种语言和协议编写客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP

    ⒉ 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)

    ⒊ 对spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性

    ⒋ 通过了常见J2EE服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上

    ⒌ 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA

    ⒍ 支持通过JDBC和journal提供高速的消息持久化

    ⒎ 从设计上保证了高性能的集群,客户端-服务器,点对点

    ⒏ 支持Ajax

    ⒐ 支持与Axis的整合

    ⒑ 可以很容易得调用内嵌JMS provider,进行测试

    5.2 RabbitMQ

    RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    结构图如下:(架构KKQ:466097527,欢迎加入)

    几个重要概念:

    Broker:简单来说就是消息队列服务器实体。

      Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

      Queue:消息队列载体,每个消息都会被投入到一个或多个队列。

      Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。

      Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

      vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。

      producer:消息生产者,就是投递消息的程序。

      consumer:消息消费者,就是接受消息的程序。

      channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

    消息队列的使用过程,如下:

    (1)客户端连接到消息队列服务器,打开一个channel。

    (2)客户端声明一个exchange,并设置相关属性。

    (3)客户端声明一个queue,并设置相关属性。

    (4)客户端使用routing key,在exchange和queue之间建立好绑定关系。

    (5)客户端投递消息到exchange。

    exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。

    5.3 ZeroMQ

    号称史上最快的消息队列,它实际类似于Socket的一系列接口,他跟Socket的区别是:普通的socket是端到端的(1:1的关系),而ZMQ却是可以N:M 的关系,人们对BSD套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程。

    引用官方的说法: “ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,像框架一样的一个socket library,他使得Socket编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一 层封装。ZMQ让编写高性能网络应用程序极为简单和有趣。”

    特点是:

    • 高性能,非持久化;
    • 跨平台:支持Linux、Windows、OS X等。
    • 多语言支持; C、C++、Java、.NET、Python等30多种开发语言。
    • 可单独部署或集成到应用中使用;
    • 可作为Socket通信库使用。

    与RabbitMQ相比,ZMQ并不像是一个传统意义上的消息队列服务器,事实上,它也根本不是一个服务器,更像一个底层的网络通讯库,在Socket API之上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的API接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三种基本模型和扩展模型。

     

    ZeroMQ高性能设计要点:

    1、无锁的队列模型

       对于跨线程间的交互(用户端和session)之间的数据交换通道pipe,采用无锁的队列算法CAS;在pipe两端注册有异步事件,在读或者写消息到pipe的时,会自动触发读写事件。

    2、批量处理的算法

       对于传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统的开销比较大,zeroMQ对于批量的消息,进行了适应性的优化,可以批量的接收和发送消息。

    3、多核下的线程绑定,无须CPU切换

       区别于传统的多线程并发模式,信号量或者临界区, zeroMQ充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的CPU切换开销。

    5.4 Kafka

    Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。

    Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性:

    • 通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除)
    • 高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息。
    • 支持通过Kafka服务器和消费机集群来分区消息。
    • 支持Hadoop并行数据加载。

     

    Kafka相关概念

    • Broker

    Kafka集群包含一个或多个服务器,这种服务器被称为broker[5]

    • Topic

    每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

    • Partition

    Parition是物理上的概念,每个Topic包含一个或多个Partition.

    • Producer

    负责发布消息到Kafka broker

    • Consumer

    消息消费者,向Kafka broker读取消息的客户端。

    • Consumer Group

    每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。

     

    一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。

    六、参考资料

    以下是本次分享参考的资料和推荐大家参考的资料。

     

    参考资料(可参考资料):

    (1)Jms

    http://blog.sina.com.cn/s/blog_3fba24680100r777.html

    http://blog.csdn.net/jiuqiyuliang/article/details/46701559(深入浅出JMS(一)--JMS基本概念)

    (2)RabbitMQ

    http://baike.baidu.com/link?url=s2cU-QgOsXan7j0AM5qxxlmruz6WEeBQXX-Bbk0O3F5jt9Qts2uYQARxQxl7CBT2SO2NF2VkzX_XZLqU-CTaPa

    http://blog.csdn.net/sun305355024sun/article/details/41913105

    (3)Zero MQ

    http://www.searchtb.com/2012/08/zeromq-primer.html

    http://blog.csdn.net/yangbutao/article/details/8498790

    http://wenku.baidu.com/link?url=yYoiZ_pYPCuUxEsGQvMMleY08bcptZvwF3IMHo2W1i-ti66YXXPpLLJBGXboddwgGBnOehHiUdslFhtz7RGZYkrtMQQ02DV5sv9JFF4LZnK

    (4)Kafka

    http://baike.baidu.com/link?url=qQXyqvPQ1MVrw9WkOGSGEfSX1NHy4unsgc4ezzJwU94SrPuVnrKf2tbm4SllVaN3ArGGxV_N5hw8JTT2-lw4QK

    http://www.infoq.com/cn/articles/apache-kafka/

    http://www.mincoder.com/article/3942.shtml

    已分享的电子资料(在群文件中)

    (1)Active MQ

     

    (2)Kafka

     

    (3)Notify

     

    七、本次分享总结

    以上是本周的分享,主要讲解了消息队列概述,常用消息队列应用场景(异步处理,应用解耦,流量削锋,日志处理和消息通讯),JMS Java消息服务,以及目前流行的几款消息队列介绍。最后演示了两个使用消息中间件的架构。

    因为时间关系,有些讲解的不细致,大家可以问下度娘/Google,希望本次分享对大家有帮助。

    本次是春节前最后一次分享,我们的分享年后会继续,明年会继续大型网站架构系列》,并会增加《一步一步学架构系列》。具体时间和分享内容会以QQ群公告的方式通知大家。感谢大家的关注。

    分享是快乐的,也是个人成长的过程。文章一般是自己的学习总结,工作经验,不足之处在所难免,请大家指正,共同进步。建立了一个以架构为中心的KK群466097527 ,欢迎大家加入。专注大型分布式网站架构,大数据,架构模式,设计模式。

     

    原文:http://www.cnblogs.com/itfly8/p/5155983.html

    六、参考资料

    以下是本次分享参考的资料和推荐大家参考的资料。

    参考资料(可参考资料):

    (1)Jms

    http://blog.sina.com.cn/s/blog_3fba24680100r777.html

    http://blog.csdn.net/jiuqiyuliang/article/details/46701559(深入浅出JMS(一)–JMS基本概念)

    (2)RabbitMQ

    http://baike.baidu.com/link?url=s2cU-QgOsXan7j0AM5qxxlmruz6WEeBQXX-Bbk0O3F5jt9Qts2uYQARxQxl7CBT2SO2NF2VkzX_XZLqU-CTaPa

    http://blog.csdn.net/sun305355024sun/article/details/41913105

    (3)Zero MQ

    http://www.searchtb.com/2012/08/zeromq-primer.html

    http://blog.csdn.net/yangbutao/article/details/8498790

    http://wenku.baidu.com/link?url=yYoiZ_pYPCuUxEsGQvMMleY08bcptZvwF3IMHo2W1i-ti66YXXPpLLJBGXboddwgGBnOehHiUdslFhtz7RGZYkrtMQQ02DV5sv9JFF4LZnK

    (4)Kafka

    http://baike.baidu.com/link?url=qQXyqvPQ1MVrw9WkOGSGEfSX1NHy4unsgc4ezzJwU94SrPuVnrKf2tbm4SllVaN3ArGGxV_N5hw8JTT2-lw4QK

    http://www.infoq.com/cn/articles/apache-kafka/

    http://www.mincoder.com/article/3942.shtml

    • Kafka集群:接收,路由,存储,转发等消息处理;

    Storm集群:与OtherApp处于同一级别,采用拉的方式消费队列中的数据;

    展开全文
  • 实现高性能,高可用,可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。 消息队列在电商系统、消息通讯、日志收集等应用扮演着关键作用,以阿里为例,其研发的消息队列(MQ)服务于阿里集团超过11年,在...

    消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。

    消息队列在电商系统、消息通讯、日志收集等应用中扮演着关键作用,以阿里为例,其研发的消息队列(MQ)服务于阿里集团超过11年,在历次天猫双十一活动中支撑了万亿级的数据洪峰,为大规模交易提供了有力保障。

    目前在生产环境,使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ 等。本场 Chat 将介绍基于 Kafka 和 ZooKeeper 的分布式消息队列。

    本场 Chat,您将清楚以下问题:

    1. Kafka,Zookeeper 是什么?
    2. 基于 Kafka 和 ZooKeeper 的分布式消息队列架构是怎样的?
    3. Kafka 为什么要将 Topic 进行分区?
    4. 分布式消息队列中 Zookeeper 以怎样的形式存在,起什么作用?
    5. 消息队列发布-订阅全流程是怎样的?

    特别说明:本场Chat仅仅作为分享,不足之处,还请读者包容,谢谢

    消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。

    本场 Chat 主要内容:

    • Kafka 的架构解读;
    • Kafka 为什么要将 Topic 进行分区;
    • Kafka 高可靠性实现基础解读;
    • Kafka 复制原理和同步方式;
    • Leader 选举机制,及如何确保新选举出的 Leader 是优选;
    • 同步副本 ISR;
    • Kafka 数据可靠性和持久性保证;
    • 深入解读 HW 机制;
    • Kafka 架构中 ZooKeeper 以怎样的形式存在;
    • 全程解析:Producer -> kafka -> consumer。

    相关内容链接:

    1. 《分布式中间件实践之路》
    2. 《Python 快速入门实战教程》
    3. 《分布式锁的最佳实践之:基于 Etcd 的分布式锁》
    4. 《基于 Redis 的分布式锁实现及踩坑案例》
    5. 《一个高可靠性商用 Redis 集群方案介绍》

    1. Kafka 总体架构

    基于 Kafka-ZooKeeper 的分布式消息队列系统总体架构如下:

    enter image description here

    如上图所示,一个典型的 Kafka 体系架构包括若干 Producer(消息生产者),若干 broker(作为 Kafka 节点的服务器),若干 Consumer(Group),以及一个 ZooKeeper 集群。Kafka通过 ZooKeeper 管理集群配置、选举 Leader 以及在 consumer group 发生变化时进行 Rebalance(即消费者负载均衡,在下一课介绍)。Producer 使用 push(推)模式将消息发布到 broker,Consumer 使用 pull(拉)模式从 broker 订阅并消费消息。

    上图仅描摹了一个总体架构,并没有对作为 Kafka 节点的 broker 进行深入刻画,事实上,它的内部细节相当复杂,如下图所示,Kafka 节点涉及 Topic、Partition 两个重要概念。

    enter image description here

    在 Kafka 架构中,有几个术语:

    • Producer:生产者,即消息发送者,push 消息到 Kafka 集群中的 broker(就是 server)中;
    • Broker:Kafka 集群由多个 Kafka 实例(server) 组成,每个实例构成一个 broker,说白了就是服务器;
    • Topic:producer 向 kafka 集群 push 的消息会被归于某一类别,即Topic,这本质上只是一个逻辑概念,面向的对象是 producer 和 consumer,producer 只需要关注将消息 push 到哪一个 Topic 中,而 consumer 只需要关心自己订阅了哪个 Topic;
    • Partition:每一个 Topic 又被分为多个 Partitions,即物理分区;出于负载均衡的考虑,同一个 Topic 的 Partitions 分别存储于 Kafka 集群的多个 broker 上;而为了提高可靠性,这些 Partitions 可以由 Kafka 机制中的 replicas 来设置备份的数量;如上面的框架图所示,每个 partition 都存在两个备份;
    • Consumer:消费者,从 Kafka 集群的 broker 中 pull 消息、消费消息;
    • Consumer group:high-level consumer API 中,每个 consumer 都属于一个 consumer-group,每条消息只能被 consumer-group 中的一个 Consumer 消费,但可以被多个 consumer-group 消费;
    • replicas:partition 的副本,保障 partition 的高可用;
    • leader:replicas 中的一个角色, producer 和 consumer 只跟 leader 交互;
    • follower:replicas 中的一个角色,从 leader 中复制数据,作为副本,一旦 leader 挂掉,会从它的 followers 中选举出一个新的 leader 继续提供服务;
    • controller:Kafka 集群中的其中一个服务器,用来进行 leader election 以及 各种 failover;
    • ZooKeeper:Kafka 通过 ZooKeeper 来存储集群的 meta 信息等,文中将详述。

    1.1 Topic & Partition

    一个 topic 可以认为是一类消息,每个 topic 将被分成多个 partition,每个 partition 在存储层面是 append log 文件。任何发布到此 partition 的消息都会被追加到log文件的尾部,每条消息在文件中的位置称为 offset(偏移量),offset 为一个 long 型的数字,它唯一标记一条消息。 Kafka 机制中,producer push 来的消息是追加(append)到 partition 中的,这是一种顺序写磁盘的机制,效率远高于随机写内存,如下示意图:

    enter image description here

    1.2 Kafka 为什么要将 Topic 进行分区?

    简而言之:负载均衡 + 水平扩展。

    前已述及,Topic 只是逻辑概念,面向的是 producer 和 consumer;而 Partition 则是物理概念。可以想象,如果 Topic 不进行分区,而将 Topic 内的消息存储于一个 broker,那么关于该 Topic 的所有读写请求都将由这一个 broker 处理,吞吐量很容易陷入瓶颈,这显然是不符合高吞吐量应用场景的。有了 Partition 概念以后,假设一个 Topic 被分为 10 个 Partitions,Kafka 会根据一定的算法将 10 个 Partition 尽可能均匀的分布到不同的 broker(服务器)上,当 producer 发布消息时,producer 客户端可以采用 randomkey-hash轮询 等算法选定目标 partition,若不指定,Kafka 也将根据一定算法将其置于某一分区上。Partiton 机制可以极大的提高吞吐量,并且使得系统具备良好的水平扩展能力。

    在创建 topic 时可以在 $KAFKA_HOME/config/server.properties 中指定这个 partition 的数量(如下所示),当然可以在 topic 创建之后去修改 partition 的数量。

    # The default number of log partitions per topic. More partitions allow greater# parallelism for consumption, but this will also result in more files across# the brokers.num.partitions=3

    在发送一条消息时,可以指定这个消息的 key,producer 根据这个 key 和 partition 机制来判断这个消息发送到哪个partition。partition 机制可以通过指定 producer 的 partition.class 这一参数来指定(即支持自定义),该 class 必须实现 kafka.producer.Partitioner 接口。

    有关 topic 与 partition 的更多细节,可以参考下面的“Kafka 文件存储机制”这一节。

    2. Kafka 高可靠性实现基础解读

    谈及可靠性,最常规、最有效的策略就是 “副本(replication)机制” ,Kafka 实现高可靠性同样采用了该策略。通过调节副本相关参数,可使 Kafka 在性能和可靠性之间取得平衡。本节先从 Kafka 文件存储机制入手,从最底层了解 Kafka 的存储细节,进而对消息的存储有个微观的认知。之后通过介绍 Kafka 的复制原理和同步方式来阐述宏观层面的概念。最后介绍 ISR,HW 和 leader 选举。

    2.1 Kafka 文件存储机制

    Kafka 中消息是以 topic 进行分类的,生产者通过 topic 向 Kafka broker 发送消息,消费者通过 topic 读取数据。然而 topic 在物理层面又能以 partition 为分组,一个 topic 可以分成若干个 partition。事实上,partition 并不是最终的存储粒度,partition 还可以细分为 segment,一个 partition 物理上由多个 segment 组成,那么这些 segment 又是什么呢?

    为了便于说明问题,假设这里只有一个 Kafka 集群,且这个集群只有一个 Kafka broker,即只有一台物理机。在这个 Kafka broker 中配置 log.dirs=/tmp/kafka-logs,以此来设置 Kafka 消息文件存储目录;与此同时,通过命令创建一个 topic:mytopic_test,partition 的数量配置为 4(创建 topic 的命令请见上一课)。之后,可以在 /tmp/kafka-logs 目录中可以看到生成了 4 个目录:

    drwxr-xr-x 2 root root 4096 Apr 15 13:21 mytopic_test-0drwxr-xr-x 2 root root 4096 Apr 15 13:21 mytopic_test-1drwxr-xr-x 2 root root 4096 Apr 15 13:21 mytopic_test-2drwxr-xr-x 2 root root 4096 Apr 15 13:21 mytopic_test-3

    在 Kafka 文件存储中,同一个 topic 下有多个不同的 partition,每个 partiton 为一个目录,partition 的名称规则为:topic 名称 + 有序序号,第一个序号从 0 开始计,最大的序号为 partition 数量减 1,partition 是实际物理上的概念,而 topic 是逻辑上的概念。

    问题 1:为什么不能以 partition 作为存储单位?

    上面提到 partition 还可以细分为 segment,这个 segment 又是什么?如果就以 partition 为最小存储单位,可以想象,当 Kafka producer 不断发送消息,必然会引起 partition 文件的无限扩张,将对消息文件的维护以及已消费的消息的清理带来严重的影响,因此,需以 segment 为单位将 partition 进一步细分。每个 partition(目录)相当于一个巨型文件被平均分配到多个大小相等的 segment(段)数据文件中(每个 segment 文件中消息数量不一定相等)这种特性也方便 old segment 的删除,即方便已被消费的消息的清理,提高磁盘的利用率。每个 partition 只需要支持顺序读写就行,segment 的文件生命周期由服务端配置参数(log.segment.bytes,log.roll.{ms,hours} 等若干参数)决定。

    问题 2:segment 的工作原理是怎样的?

    segment 文件由两部分组成,分别为 “.index” 文件和 “.log” 文件,分别表示为 segment 索引文件和数据文件。这两个文件的命令规则为:partition 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset 值,数值大小为 64 位,20 位数字字符长度,没有数字用 0 填充,如下:

    00000000000000000000.index00000000000000000000.log00000000000000170410.index00000000000000170410.log00000000000000239430.index00000000000000239430.log

    以上面的 segment 文件为例,展示出 segment:00000000000000170410 的 “.index” 文件和 “.log” 文件的对应的关系,如下图:

    enter image description here

    如上图,“.index” 索引文件存储大量的元数据,“.log” 数据文件存储大量的消息,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。其中以 “.index” 索引文件中的元数据 [3, 348] 为例,在 “.log” 数据文件表示第 3 个消息,即在全局 partition 中表示 170410+3=170413 个消息,该消息的物理偏移地址为 348。

    问题 3:如何从 partition 中通过 offset 查找 message 呢?

    以上图为例,读取 offset=170418 的消息,首先查找 segment 文件,其中 00000000000000000000.index 为最开始的文件,第二个文件为 00000000000000170410.index(起始偏移为 170410+1=170411),而第三个文件为 00000000000000239430.index(起始偏移为 239430+1=239431),所以这个 offset=170418 就落到了第二个文件之中。其它后续文件可以依次类推,以其偏移量命名并排列这些文件,然后根据二分查找法就可以快速定位到具体文件位置。其次根据 00000000000000170410.index 文件中的 [8,1325] 定位到 00000000000000170410.log 文件中的 1325 的位置进行读取。

    要是读取 offset=170418 的消息,从 00000000000000170410.log 文件中的 1325 的位置进行读取,那么,如何确定何时读完本条消息呢?(否则就读到下一条消息的内容了)

    这个问题由消息的物理结构解决,消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段,可以确定一条消息的大小,即读取到哪里截止。

    2.2 复制原理和同步方式

    Kafka 中 topic 的每个 partition 有一个预写式的日志文件,虽然 partition 可以继续细分为若干个 segment 文件,但是对于上层应用来说,仍然可以将 partition 看成最小的存储单元(一个有多个 segment 文件拼接的 “巨型” 文件),每个 partition 都由一些列有序的、不可变的消息组成,这些消息被连续的追加到 partition 中。

    enter image description here

    上图中有两个新名词:HW 和 LEO。这里先介绍下 LEO,LogEndOffset 的缩写,表示每个 partition 的 log 最后一条 Message 的位置。HW 是 HighWatermark 的缩写,是指 consumer 能够看到的此 partition 的位置,这个涉及到多副本的概念,这里先提及一下,下文再详述。

    言归正传,为了提高消息的可靠性,Kafka 每个 topic 的 partition 有 N 个副本(replicas),其中 N(大于等于 1)是 topic 的复制因子(replica fator)的个数。Kafka 通过多副本机制实现故障自动转移,当 Kafka 集群中出现 broker 失效时,副本机制可保证服务可用。对于任何一个 partition,它的 N 个 replicas 中,其中一个 replica 为 leader,其他都为 follower,leader 负责处理 partition 的所有读写请求,follower 则负责被动地去复制 leader 上的数据。如下图所示,Kafka 集群中有 4 个 broker,某 topic 有 3 个 partition,且复制因子即副本个数也为 3:

    enter image description here

    如果 leader 所在的 broker 发生故障或宕机,对应 partition 将因无 leader 而不能处理客户端请求,这时副本的作用就体现出来了:一个新 leader 将从 follower 中被选举出来并继续处理客户端的请求。

    如何确保新选举出的 leader 是优选呢?

    一个 partition 有多个副本(replicas),为了提高可靠性,这些副本分散在不同的 broker 上,由于带宽、读写性能、网络延迟等因素,同一时刻,这些副本的状态通常是不一致的:即 followers 与 leader 的状态不一致。那么,如何保证新选举出的 leader 是优选呢? Kafka 机制中,leader 将负责维护和跟踪一个 ISR(In-Sync Replicas)列表,即同步副本队列,这个列表里面的副本与 leader 保持同步,状态一致。如果新的 leader 从 ISR 列表中的副本中选出,那么就可以保证新 leader 为优选。当然,这不是唯一的策略,下文将继续解读。

    2.3 同步副本 ISR

    上一节中讲到了同步副本队列 ISR(In-Sync Replicas)。虽然副本极大的增强了可用性,但是副本数量对 Kafka 的吞吐率有一定影响。默认情况下 Kafka 的 replica 数量为 1,即每个 partition 都只有唯一的 leader,无 follower,没有容灾能力。为了确保消息的可靠性,生产环境中,通常将其值(由 broker 的参数 offsets.topic.replication.factor 指定)大小设置为大于 1,比如 3。 所有的副本(replicas)统称为 Assigned Replicas,即 AR。ISR 是 AR 中的一个子集,由 leader 维护 ISR 列表,follower 从 leader 同步数据有一些延迟(由参数 replica.lag.time.max.ms 设置超时阈值),超过阈值的 follower 将被剔除出 ISR, 存入 OSR(Outof-Sync Replicas)列表,新加入的 follower 也会先存放在 OSR 中。AR=ISR+OSR。

    注:ISR中包括:leader + 与leader保持同步的followers。

    上面一节还涉及到一个概念,即 HW。HW 俗称高水位,HighWatermark 的缩写,取一个 partition 对应的 ISR 中最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置。另外每个 replica 都有 HW,leader 和 follower 各自负责更新自己的 HW 的状态。对于 leader 新写入的消息,consumer 不能立刻消费,leader 会等待该消息被所有 ISR 中的 replicas 同步后更新 HW,此时消息才能被 consumer 消费。这样就保证了如果 leader 所在的 broker 失效,该消息仍然可以从新选举的 leader 中获取。对于来自内部 broker 的读取请求,没有 HW 的限制。

    下图详细的说明了当 producer 生产消息至 broker 后,ISR 以及 HW 和 LEO 的流转过程:

    enter image description here

    由此可见,Kafka 的复制机制既不是完全的同步复制,也不是单纯的异步复制。事实上,同步复制要求所有能工作的 follower 都复制完,这条消息才会被 commit,这种复制方式受限于复制最慢的 follower,会极大的影响吞吐率。而异步复制方式下,follower 异步的从 leader 复制数据,数据只要被 leader 写入 log 就被认为已经 commit,这种情况下如果 follower 都还没有复制完,落后于 leader 时,突然 leader 宕机,则会丢失数据,降低可靠性。而 Kafka 使用 ISR 的策略则在可靠性和吞吐率方面取得了较好的平衡。

    Kafka 的 ISR 的管理最终都会反馈到 ZooKeeper 节点上,具体位置为:

    /brokers/topics/[topic]/partitions/[partition]/state

    目前,有两个地方会对这个 ZooKeeper 的节点进行维护。

    1. Controller 来维护:Kafka 集群中的其中一个 Broker 会被选举为 Controller,主要负责 Partition 管理和副本状态管理,也会执行类似于重分配 partition 之类的管理任务。在符合某些特定条件下,Controller 下的 LeaderSelector 会选举新的 leader,ISR 和新的 leader_epochcontroller_epoch 写入 ZooKeeper 的相关节点中。同时发起 LeaderAndIsrRequest 通知所有的 replicas。

    2. leader 来维护:leader 有单独的线程定期检测 ISR 中 follower 是否脱离 ISR,如果发现 ISR 变化,则会将新的 ISR 的信息返回到 ZooKeeper 的相关节点中。

    2.4 数据可靠性和持久性保证

    当 producer 向 leader 发送数据时,可以通过 request.required.acks 参数来设置数据可靠性的级别:

    1. request.required.acks = 1

    这是默认情况,即:producer 发送数据到 leader,leader 写本地日志成功,返回客户端成功;此时 ISR 中的其它副本还没有来得及拉取该消息,如果此时 leader 宕机了,那么此次发送的消息就会丢失。

    2. request.required.acks = 0

    producer 不停向leader发送数据,而不需要 leader 反馈成功消息,这种情况下数据传输效率最高,但是数据可靠性确是最低的。可能在发送过程中丢失数据,可能在 leader 宕机时丢失数据。

    3. request.required.acks = -1(all)

    producer 发送数据给 leader,leader 收到数据后要等到 ISR 列表中的所有副本都同步数据完成后(强一致性),才向生产者返回成功消息,如果一直收不到成功消息,则认为发送数据失败会自动重发数据。这是可靠性最高的方案,当然,性能也会受到一定影响。

    **注意:参数 min.insync.replicas **

    如果要提高数据的可靠性,在设置 request.required.acks=-1 的同时,还需参数 min.insync.replicas 配合,如此才能发挥最大的功效。min.insync.replicas 这个参数用于设定 ISR 中的最小副本数,默认值为1,当且仅当 request.required.acks 参数设置为-1时,此参数才生效。当 ISR 中的副本数少于 min.insync.replicas 配置的数量时,客户端会返回异常:org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。不难理解,如果 min.insync.replicas 设置为 2,当 ISR 中实际副本数为 1 时(只有leader),将无法保证可靠性,此时拒绝客户端的写请求以防止消息丢失。

    2.5 深入解读 HW 机制

    考虑这样一种场景:acks=-1,部分 ISR 副本完成同步,此时leader挂掉,如下图所示:follower1 同步了消息 4、5,follower2 同步了消息 4,与此同时 follower2 被选举为 leader,那么此时 follower1 中的多出的消息 5 该做如何处理呢?

    enter image description here

    这里就需要 HW 的协同配合了。如前所述,一个 partition 中的 ISR 列表中,leader 的 HW 是所有 ISR 列表里副本中最小的那个的 LEO。类似于木桶原理,水位取决于最低那块短板。

    enter image description here

    如上图,某个 topic 的某 partition 有三个副本,分别为 A、B、C。A 作为 leader 肯定是 LEO 最高,B 紧随其后,C 机器由于配置比较低,网络比较差,故而同步最慢。这个时候 A 机器宕机,这时候如果 B 成为 leader,假如没有 HW,在 A 重新恢复之后会做同步(makeFollower) 操作,在宕机时 log 文件之后直接做追加操作,而假如 B 的 LEO 已经达到了 A 的 LEO,会产生数据不一致的情况,所以使用 HW 来避免这种情况。 A 在做同步操作的时候,先将 log 文件截断到之前自己的 HW 的位置,即 3,之后再从 B 中拉取消息进行同步。

    如果失败的 follower 恢复过来,它首先将自己的 log 文件截断到上次 checkpointed 时刻的 HW 的位置,之后再从 leader 中同步消息。leader 挂掉会重新选举,新的 leader 会发送 “指令” 让其余的 follower 截断至自身的 HW 的位置然后再拉取新的消息。

    当 ISR 中的个副本的 LEO 不一致时,如果此时 leader 挂掉,选举新的 leader 时并不是按照 LEO 的高低进行选举,而是按照 ISR 中的顺序选举。

    2.6 Leader 选举

    为了保证可靠性,对于任意一条消息,只有它被 ISR 中的所有 follower 都从 leader 复制过去才会被认为已提交,并返回信息给 producer。如此,可以避免因部分数据被写进 leader,而尚未被任何 follower 复制就宕机的情况下而造成数据丢失。对于 producer 而言,它可以选择是否等待消息 commit,这可以通过参数 request.required.acks 来设置。这种机制可以确保:只要 ISR 中有一个或者以上的 follower,一条被 commit 的消息就不会丢失。

    问题 1:如何在保证可靠性的前提下避免吞吐量下降?

    有一个很重要的问题是当 leader 宕机了,怎样在 follower 中选举出新的 leader,因为 follower 可能落后很多或者直接 crash 了,所以必须确保选择 “最新” 的 follower 作为新的 leader。一个基本的原则就是,如果 leader 挂掉,新的 leader 必须拥有原来的 leader 已经 commit 的所有消息,这不就是 ISR 中副本的特征吗?

    但是,存在一个问题,ISR 列表维持多大的规模合适呢?换言之,leader 在一个消息被 commit 前需要等待多少个 follower 确认呢?等待 follower 的数量越多,与 leader 保持同步的 follower 就越多,可靠性就越高,但这也会造成吞吐率的下降。

    少数服从多数的选举原则

    一种常用的选举 leader 的策略是 “少数服从多数” ,不过,Kafka 并不是采用这种方式。这种模式下,如果有 2f+1 个副本,那么在 commit 之前必须保证有 f+1 个 replica 复制完消息,同时为了保证能正确选举出新的 leader,失败的副本数不能超过 f 个。这种方式有个很大的优势,系统的延迟取决于最快的几台机器,也就是说比如副本数为 3,那么延迟就取决于最快的那个 follower 而不是最慢的那个。

    “少数服从多数” 的策略也有一些劣势,为了保证 leader 选举的正常进行,它所能容忍的失败的 follower 数比较少,如果要容忍 1 个 follower 挂掉,那么至少要 3 个以上的副本,如果要容忍 2 个 follower 挂掉,必须要有 5 个以上的副本。也就是说,在生产环境下为了保证较高的容错率,必须要有大量的副本,而大量的副本又会在大数据量下导致性能的急剧下降。这种算法更多用在 ZooKeeper 这种共享集群配置的系统中,而很少在需要大量数据的系统中使用。

    Kafka 选举 leader 的策略是怎样的?

    实际上,leader 选举的算法非常多,比如 ZooKeeper 的 Zab、Raft 以及 Viewstamped Replication。而 Kafka 所使用的 leader 选举算法更像是微软的 PacificA 算法。

    Kafka 在 ZooKeeper 中为每一个 partition 动态的维护了一个 ISR,这个 ISR 里的所有 replica 都与 leader 保持同步,只有 ISR 里的成员才能有被选为 leader 的可能(通过参数配置:unclean.leader.election.enable=false)。在这种模式下,对于 f+1 个副本,一个 Kafka topic 能在保证不丢失已经 commit 消息的前提下容忍 f 个副本的失败,在大多数使用场景下,这种模式是十分有利的。事实上,对于任意一条消息,只有它被 ISR 中的所有 follower 都从 leader 复制过去才会被认为已提交,并返回信息给 producer,从而保证可靠性。但与 “少数服从多数” 策略不同的是,Kafka ISR 列表中副本的数量不需要超过副本总数的一半,即不需要满足 “多数派” 原则,通常,ISR 列表副本数大于等于 2 即可,如此,便在可靠性和吞吐量方面取得平衡。

    极端情况下的 leader 选举策略

    前已述及,当 ISR 中至少有一个 follower 时(ISR 包括 leader),Kafka 可以确保已经 commit 的消息不丢失,但如果某一个 partition 的所有 replica 都挂了,自然就无法保证数据不丢失了。这种情况下如何进行 leader 选举呢?通常有两种方案:

    1. 等待 ISR 中任意一个 replica 恢复过来,并且选它作为 leader;
    2. 选择第一个恢复过来的 replica(并不一定是在 ISR 中)作为leader。

    如何选择呢?这就需要在可用性和一致性当中作出抉择。如果一定要等待 ISR 中的 replica 恢复过来,不可用的时间就可能会相对较长。而且如果 ISR 中所有的 replica 都无法恢复了,或者数据丢失了,这个 partition 将永远不可用。

    选择第一个恢复过来的 replica 作为 leader,如果这个 replica 不是 ISR 中的 replica,那么,它可能并不具备所有已经 commit 的消息,从而造成消息丢失。默认情况下,Kafka 采用第二种策略,即 unclean.leader.election.enable=true,也可以将此参数设置为 false 来启用第一种策略。

    unclean.leader.election.enable 这个参数对于 leader 的选举、系统的可用性以及数据的可靠性都有至关重要的影响。生产环境中应慎重权衡。

    3. Kafka 架构中 ZooKeeper 以怎样的形式存在?

    ZooKeeper 是一个分布式的、开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现。分布式应用程序可以基于它实现统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。在基于 Kafka 的分布式消息队列中,ZooKeeper 的作用有:broker 注册、topic 注册、producer 和 consumer 负载均衡、维护 partition 与 consumer 的关系、记录消息消费的进度以及 consumer 注册等。

    3.1 broker 在 ZooKeeper 中的注册

    • 为了记录 broker 的注册信息,在 ZooKeeper 上,专门创建了属于 Kafka 的一个节点,其路径为 /brokers;
    • Kafka 的每个 broker 启动时,都会到 ZooKeeper 中进行注册,告诉 ZooKeeper 其 broker.id,在整个集群中,broker.id 应该全局唯一,并在 ZooKeeper 上创建其属于自己的节点,其节点路径为 /brokers/ids/{broker.id}
    • 创建完节点后,Kafka 会将该 broker 的 broker.name 及端口号记录到该节点;
    • 另外,该 broker 节点属性为临时节点,当 broker 会话失效时,ZooKeeper 会删除该节点,这样,我们就可以很方便的监控到broker 节点的变化,及时调整负载均衡等。

    3.2 Topic 在 ZooKeeper 中的注册

    在 Kafka 中,所有 topic 与 broker 的对应关系都由 ZooKeeper 进行维护,在 ZooKeeper 中,建立专门的节点来记录这些信息,其节点路径为 /brokers/topics/{topic_name}。 前面说过,为了保障数据的可靠性,每个 Topic 的 Partitions 实际上是存在备份的,并且备份的数量由 Kafka 机制中的 replicas 来控制。那么问题来了:如下图所示,假设某个 TopicA 被分为 2 个 Partitions,并且存在两个备份,由于这 2 个 Partitions(1-2)被分布在不同的 broker 上,同一个 partiton 与其备份不能(也不应该)存储于同一个 broker 上。以 Partition1 为例,假设它被存储于 broker2,其对应的备份分别存储于 broker1 和 broker4,有了备份,可靠性得到保障,但数据一致性却是个问题。

    enter image description here

    为了保障数据的一致性,ZooKeeper 机制得以引入。基于 ZooKeeper,Kafka 为每一个 partition 找一个节点作为 leader,其余备份作为 follower;接续上图的例子,就 TopicA 的 partition1 而言,如果位于 broker2(Kafka 节点)上的 partition1 为 leader,那么位于 broker1 和 broker4 上面的 partition1 就充当 follower,则有下图:

    enter image description here

    基于上图的架构,当 producer push 的消息写入 partition(分区) 时,作为 leader 的 broker(Kafka 节点) 会将消息写入自己的分区,同时还会将此消息复制到各个 follower,实现同步。如果,某个follower 挂掉,leader 会再找一个替代并同步消息;如果 leader 挂了,follower 们会选举出一个新的 leader 替代,继续业务,这些都是由 ZooKeeper 完成的。

    3.3 consumer 在 ZooKeeper 中的注册

    注册新的消费者分组

    当新的消费者组注册到 ZooKeeper 中时,ZooKeeper 会创建专用的节点来保存相关信息,其节点路径为 ls/consumers/{group_id},其节点下有三个子节点,分别为 [ids, owners, offsets]

    • ids 节点:记录该消费组中当前正在消费的消费者;
    • owners 节点:记录该消费组消费的 topic 信息;
    • offsets 节点:记录每个 topic 的每个分区的 offset。

    注册新的消费者

    当新的消费者注册到 Kafka 中时,会在 /consumers/{group_id}/ids 节点下创建临时子节点,并记录相关信息。

    监听消费者分组中消费者的变化

    每个消费者都要关注其所属消费者组中消费者数目的变化,即监听 /consumers/{group_id}/ids 下子节点的变化。一单发现消费者新增或减少,就会触发消费者的负载均衡。

    3.4 Producers 负载均衡

    对于同一个 topic 的不同 partition,Kafka会尽力将这些 partition 分布到不同的 broker 服务器上,这种均衡策略实际上是基于 ZooKeeper 实现的。在一个 broker 启动时,会首先完成 broker 的注册过程,并注册一些诸如 “有哪些可订阅的 topic” 之类的元数据信息。producers 启动后也要到 ZooKeeper 下注册,创建一个临时节点来监听 broker 服务器列表的变化。由于在 ZooKeeper 下 broker 创建的也是临时节点,当 brokers 发生变化时,producers 可以得到相关的通知,从改变自己的 broker list。其它的诸如 topic 的变化以及broker 和 topic 的关系变化,也是通过 ZooKeeper 的这种 Watcher 监听实现的。

    在生产中,必须指定 topic;但是对于 partition,有两种指定方式:

    • 明确指定 partition(0-N),则数据被发送到指定 partition;
    • 设置为 RD_KAFKA_PARTITION_UA,则 Kafka 会回调 partitioner 进行均衡选取,partitioner 方法需要自己实现。可以轮询或者传入 key 进行 hash。未实现则采用默认的随机方法 rd_kafka_msg_partitioner_random 随机选择。

    3.5 Consumer 负载均衡

    Kafka 保证同一 consumer group 中只有一个 consumer 可消费某条消息,实际上,Kafka 保证的是稳定状态下每一个 consumer 实例只会消费某一个或多个特定的数据,而某个 partition 的数据只会被某一个特定的 consumer 实例所消费。这样设计的劣势是无法让同一个 consumer group 里的 consumer 均匀消费数据,优势是每个 consumer 不用都跟大量的 broker 通信,减少通信开销,同时也降低了分配难度,实现也更简单。另外,因为同一个 partition 里的数据是有序的,这种设计可以保证每个 partition 里的数据也是有序被消费。

    consumer 数量不等于 partition 数量

    如果某 consumer group 中 consumer 数量少于 partition 数量,则至少有一个 consumer 会消费多个 partition 的数据;如果 consumer 的数量与 partition 数量相同,则正好一个 consumer 消费一个 partition 的数据,而如果 consumer 的数量多于 partition 的数量时,会有部分 consumer 无法消费该 topic 下任何一条消息。

    借助 ZooKeeper 实现负载均衡

    关于负载均衡,对于某些低级别的 API,consumer 消费时必须指定 topic 和 partition,这显然不是一种友好的均衡策略。基于高级别的 API,consumer 消费时只需制定 topic,借助 ZooKeeper 可以根据 partition 的数量和 consumer 的数量做到均衡的动态配置。

    consumers 在启动时会到 ZooKeeper 下以自己的 conusmer-id 创建临时节点 /consumer/[group-id]/ids/[conusmer-id],并对 /consumer/[group-id]/ids 注册监听事件,当消费者发生变化时,同一 group 的其余消费者会得到通知。当然,消费者还要监听 broker 列表的变化。librdkafka 通常会将 partition 进行排序后,根据消费者列表,进行轮流的分配。

    3.6 记录消费进度 Offset

    在 consumer 对指定消息 partition 的消息进行消费的过程中,需要定时地将 partition 消息的消费进度 Offset 记录到 ZooKeeper上,以便在该 consumer 进行重启或者其它 consumer 重新接管该消息分区的消息消费权后,能够从之前的进度开始继续进行消息消费。Offset 在 ZooKeeper 中由一个专门节点进行记录,其节点路径为:

    #节点内容就是Offset的值。/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]

    PS:Kafka 已推荐将 consumer 的 Offset 信息保存在 Kafka 内部的 topic 中,即:

    __consumer_offsets(/brokers/topics/__consumer_offsets)

    并且默认提供了 kafka_consumer_groups.sh 脚本供用户查看consumer 信息(命令:sh kafka-consumer-groups.sh –bootstrap-server * –describe –group *)。在当前版本中,offset 存储方式要么存储在本地文件中,要么存储在 broker 端,具体的存储方式取决 offset.store.method 的配置,默认是存储在 broker 端。

    3.7 记录 Partition 与 Consumer 的关系

    consumer group 下有多个 consumer(消费者),对于每个消费者组(consumer group),Kafka都会为其分配一个全局唯一的 group ID,group 内部的所有消费者共享该 ID。订阅的 topic 下的每个分区只能分配给某个 group 下的一个consumer(当然该分区还可以被分配给其它 group)。同时,Kafka 为每个消费者分配一个 consumer ID,通常采用 hostname:UUID 形式表示。

    在Kafka中,规定了每个 partition 只能被同组的一个消费者进行消费,因此,需要在 ZooKeeper 上记录下 partition 与 consumer 之间的关系,每个 consumer 一旦确定了对一个 partition 的消费权力,需要将其 consumer ID 写入到 ZooKeeper 对应消息分区的临时节点上,例如:

    /consumers/[group_id]/owners/[topic]/[broker_id-partition_id]

    其中,[broker_id-partition_id] 就是一个消息分区的标识,节点内容就是该消息分区 消费者的 consumer ID。

    4. 全程解析(Producer-kafka-consumer)

    4.1 producer 发布消息

    producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。

    其路由机制为:

    1. 指定了 patition,则直接使用;
    2. 未指定 patition 但指定 key,通过对 key 进行 hash 选出一个 patition;
    3. patition 和 key 都未指定,使用轮询选出一个 patition。

    写入流程:

    1. producer 先从 ZooKeeper 的 "/brokers/.../state" 节点找到该 partition 的leader;
    2. producer 将消息发送给该 leader;
    3. leader 将消息写入本地 log;
    4. followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK;
    5. leader 收到所有 ISR 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK;

    4.2 Broker 存储消息

    物理上把 topic 分成一个或多个 patition,每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件)

    4.3 Consumer 消费消息

    high-level consumer API 提供了 consumer group 的语义,一个消息只能被 group 内的一个 consumer 所消费,且 consumer 消费消息时不关注 offset,最后一个 offset 由 ZooKeeper 保存(下次消费时,该group 中的consumer将从offset记录的位置开始消费)。

    注意:

    1. 如果消费线程大于 patition 数量,则有些线程将收不到消息;
    2. 如果 patition 数量大于消费线程数,则有些线程多收到多个 patition 的消息;
    3. 如果一个线程消费多个 patition,则无法保证你收到的消息的顺序,而一个 patition 内的消息是有序的。

    consumer 采用 pull 模式从 broker 中读取数据。

    push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。

    对于 Kafka 而言,pull 模式更合适,它可简化 broker 的设计,consumer 可自主控制消费消息的速率,同时 consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。


    参考文献与致谢

    本文的一些图片和文字引用了一些博客和论文,尊重原创是每一个写作者应坚守的底线,在此,将本文引用过的文章一一列出,以表敬意:

    1. Kafka 官方文档
    2. Kafka 数据可靠性深度解读

    本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

    阅读全文: http://gitbook.cn/gitchat/activity/5ad5634e1165247fd990c306

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • Java架构之消息队列 ():消息队列的概述

    万次阅读 多人点赞 2018-10-08 16:09:38
     消息队列的概述 二、消息队列之RabbitMQ的使用 三、消息队列之Kafka的使用 四、消息队列之RabbitMQ的原理详解 五、消息队列之Kafka的原理详解 六、消息队列之面试集锦 1.消息队列的概述 消息队列...

    消息队列系列分享大纲:

     一、消息队列的概述

    二、消息队列之RabbitMQ的使用

    三、消息队列之Kafka的使用

    四、消息队列之RabbitMQ的原理详解

    五、消息队列之Kafka的原理详解

    六、消息队列之面试集锦


    1.消息队列的概述

    消息队列(Message Queue)中间件是分布式系统中重要的组件;

    主要解决应用耦合,异步消息,流量削锋等问题;

    实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件;

    目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等;

    部分数据库如Redis、Mysql以及phxsql也可实现消息队列的功能;

    具有 低耦合、可靠投递、广播、流量控制、最终一致性 等一系列功能。


    2.消息队列使用场景

    消息队列在实际应用中有四个场景:

    • 应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
    • 异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
    • 限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
    • 消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;

    2.1异步处理

    具体场景:用户为了使用某个应用,进行注册,系统需要发送注册邮件并验证短信。对这两个操作的处理方式有两种:串行及并行

    (1)串行方式:新注册信息生成后,先发送注册邮件,再发送验证短信;

    在这种方式下,需要最终发送验证短信后再返回给客户端。

    (2)并行处理:新注册信息写入后,由发短信和发邮件并行处理;

    在这种方式下,发短信和发邮件 需处理完成后再返回给客户端。

    假设以上三个子系统处理的时间均为50ms,且不考虑网络延迟,则总的处理时间:

    串行:50+50+50=150ms 并行:50+50 = 100ms

    若使用消息队列:

    并在写入消息队列后立即返回成功给客户端,则总的响应时间依赖于写入消息队列的时间,而写入消息队列的时间本身是可以很快的,基本可以忽略不计,因此总的处理时间相比串行提高了2倍,相比并行提高了一倍;

    2.2 应用耦合

    具体场景:用户使用QQ相册上传一张图片,人脸识别系统会对该图片进行人脸识别,一般的做法是,服务器接收到图片后,图片上传系统立即调用人脸识别系统,调用完成后再返回成功,如下图所示:

    该方法有如下缺点:

    • 人脸识别系统被调失败,导致图片上传失败;
    • 延迟高,需要人脸识别系统处理完成后,再返回给客户端,即使用户并不需要立即知道结果;
    • 图片上传系统与人脸识别系统之间互相调用,需要做耦合;

    若使用消息队列:

    客户端上传图片后,图片上传系统将图片信息写入消息队列,直接返回成功;而人脸识别系统则定时从消息队列中取数据,完成对新增图片的识别。

    人脸识别系统可以选择不同的调度策略,按照闲时、忙时、正常时间,对队列中的图片信息进行处理。

    2.3 限流削峰

    具体场景:购物网站开展秒杀活动,一般由于瞬时访问量过大,服务器接收过大,会导致流量暴增,相关系统无法处理请求甚至崩溃。而加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲。

    该方法有如下优点:

    • 请求先入消息队列,而不是由业务处理系统直接处理,做了一次缓冲,极大地减少了业务处理系统的压力;
    • 队列长度可以做限制,事实上,秒杀时,后入队列的用户无法秒杀到商品,这些请求可以直接被抛弃,返回活动已结束或商品已售完信息;

    2.4 日志处理

    日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下:

    消息队列应用于日志处理的架构

    • 日志采集客户端:负责日志数据采集,定时写受写入Kafka队列;
    • Kafka消息队列:负责日志数据的接收,存储和转发;
    • 日志处理应用:订阅并消费kafka队列中的日志数据;

    • Kafka:接收用户日志的消息队列。
    • Logstash:做日志解析,统一成JSON输出给Elasticsearch。
    • Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能。
    • Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK stack的重要原因。

    2.5消息通讯

    消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

    点对点通讯:

     

    客户端A和客户端B使用同一队列,进行消息通讯。

    聊天室通讯:

     

    客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。

    以上实际是消息队列的两种消息模式,点对点或发布订阅模式,下面会介绍这两种模式。


    3.消息队列的两种模式

    消息队列包括两种模式,点对点模式(point to point, queue)和发布/订阅模式(publish/subscribe,topic)。

    JMS(JAVA Message Service,java消息服务)API是一个消息服务的标准/规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。

    在JMS标准中,有两种消息模型P2P(Point to Point),Publish/Subscribe(Pub/Sub)。

    3.1 点对点模式

    点对点模式下包括三个角色:

    • 消息队列
    • 发送者 (生产者)
    • 接收者(消费者)

    消息发送者生产消息发送到queue中,然后消息接收者从queue中取出并且消费消息。消息被消费以后,queue中不再有存储,所以消息接收者不可能消费到已经被消费的消息。

    点对点模式特点:

    • 每个消息只有一个接收者(Consumer)(即一旦被消费,消息就不再在消息队列中);
    • 发送者和接收者间没有依赖性,发送者发送消息之后,不管有没有接收者在运行,都不会影响到发送者下次发送消息;
    • 接收者在成功接收消息之后需向队列应答成功,以便消息队列删除当前接收的消息;

    3.2 发布/订阅模式

    发布/订阅模式下包括三个角色:

    • 角色主题(Topic)
    • 发布者(Publisher)
    • 订阅者(Subscriber)

    发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。

    发布/订阅模式特点:

    • 每个消息可以有多个订阅者;
    • 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
    • 为了消费消息,订阅者需要提前订阅该角色主题,并保持在线运行;

    4.消息中间件案例

    3.1电商系统

     

    消息队列采用高可用,可持久化的消息中间件。比如Active MQ,Rabbit MQ,Rocket Mq。

    (1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功可以开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性)

    (2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。

    (3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。

    3.2日志收集系统

     

    分为Zookeeper注册中心,日志收集客户端,Kafka集群和Storm集群(OtherApp)四部分组成。

    • Zookeeper注册中心,提出负载均衡和地址查找服务;
    • 日志收集客户端,用于采集应用系统的日志,并将数据推送到kafka队列;
    • Kafka集群:接收,路由,存储,转发等消息处理;

    Storm集群:与OtherApp处于同一级别,采用拉的方式消费队列中的数据;


    5.常用消息队列介绍

    5.1.ZeroMQ


    ZeroMQ号称是“史上最快的消息队列”,基于c语言开发的,可以在任何平台通过任何代码连接,通过inproc、IPC、TCP、TIPC、多播传送消息,支持发布-订阅、推-拉、共享队列等模式,高速异步I/O引擎。

    根据官方的说法,ZeroMQ是一个简单好用的传输层,像框架一样的可嵌入的socket类库,使Socket编程更加简单、简洁、性能更高,是专门为高吞吐量/低延迟的场景开发的。ZeroMQ与其他MQ有着本质的区别,它根本不是消息队列服务器,更类似与一个底层网络通讯库,对原有Socket API进行封装,在使用的使用引入对应的jar包即可,可谓是相当灵活。

    同时,因为它的简单灵活,如果我们想作为消息队列使用的话,需要开发大量代码。而且,ZeroMQ不支持消息持久化,其定位并不是安全可靠的消息传输,所以还需要自己编码保证可靠性。简而言之一句话,ZeroMQ很强大,但是想用好需要自己实现。

    特点是:

    • 高性能,非持久化;
    • 跨平台:支持Linux、Windows、OS X等。
    • 多语言支持; C、C++、Java、.NET、Python等30多种开发语言。
    • 可单独部署或集成到应用中使用;
    • 可作为Socket通信库使用。
    • 与RabbitMQ相比,ZMQ并不像是一个传统意义上的消息队列服务器,它像一个底层的网络通讯库,在Socket API之上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的API接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三种基本模型和扩展模型。

    ZeroMQ高性能设计要点:

    1、无锁的队列模型

       对于跨线程间的交互(用户端和session)之间的数据交换通道pipe,采用无锁的队列算法CAS;在pipe两端注册有异步事件,在读或者写消息到pipe的时,会自动触发读写事件。

    2、批量处理的算法

       对于传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统的开销比较大,zeroMQ对于批量的消息,进行了适应性的优化,可以批量的接收和发送消息。

    3、多核下的线程绑定,无须CPU切换

       区别于传统的多线程并发模式,信号量或者临界区, zeroMQ充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的CPU切换开销。

    5.2 ActiveMQ


     

    ActiveMQ是由Apache出品,ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。

    主要特性:

    1. 服从 JMS 规范:JMS 规范提供了良好的标准和保证,包括:同步或异步的消息分发,一次和仅一次的消息分发,消息接收和订阅等等。遵从 JMS 规范的好处在于,不论使用什么 JMS 实现提供者,这些基础特性都是可用的;
    2. 连接性:ActiveMQ 提供了广泛的连接选项,支持的协议有:HTTP/S,IP 多播,SSL,STOMP,TCP,UDP,XMPP等等。对众多协议的支持让 ActiveMQ 拥有了很好的灵活性。
    3. 支持的协议种类多:OpenWire、STOMP、REST、XMPP、AMQP ;
    4. 持久化插件和安全插件:ActiveMQ 提供了多种持久化选择。而且,ActiveMQ 的安全性也可以完全依据用户需求进行自定义鉴权和授权;
    5. 支持的客户端语言种类多:除了 Java 之外,还有:C/C++,.NET,Perl,PHP,Python,Ruby;
    6. 代理集群:多个 ActiveMQ 代理可以组成一个集群来提供服务;
    7. 异常简单的管理:ActiveMQ 是以开发者思维被设计的。所以,它并不需要专门的管理员,因为它提供了简单又使用的管理特性。有很多中方法可以监控 ActiveMQ 不同层面的数据,包括使用在 JConsole 或者 ActiveMQ 的Web Console 中使用 JMX,通过处理 JMX 的告警消息,通过使用命令行脚本,甚至可以通过监控各种类型的日志。

    使用ActiveMQ需要:

    • Java JDK
    • ActiveMQ安装包

    ActiveMQ可以运行在Java语言所支持的平台之上。

    优点:

    1. 跨平台(JAVA编写与平台无关有,ActiveMQ几乎可以运行在任何的JVM上)
    2. 可以用JDBC:可以将数据持久化到数据库。虽然使用JDBC会降低ActiveMQ的性能,但是数据库一直都是开发人员最熟悉的存储介质。将消息存到数据库,看得见摸得着。而且公司有专门的DBA去对数据库进行调优,主从分离;
    3. 支持JMS :支持JMS的统一接口;
    4. 支持自动重连;
    5. 有安全机制:支持基于shiro,jaas等多种安全配置机制,可以对Queue/Topic进行认证和授权。
    6. 监控完善:拥有完善的监控,包括Web Console,JMX,Shell命令行,Jolokia的REST API;
    7. 界面友善:提供的Web Console可以满足大部分情况,还有很多第三方的组件可以使用,如hawtio;

    缺点:

    1. 社区活跃度不及RabbitMQ高;
    2. 根据其他用户反馈,会出莫名其妙的问题,会丢失消息;
    3. 目前重心放到activemq6.0产品-apollo,对5.x的维护较少;
    4. 不适合用于上千个队列的应用场景;

    5.3.RabbitMQ


    概述:

    RabbitMQ是流行的开源消息队列系统,用erlang语言开发。

    RabbitMQ是AMQP(高级消息队列协议)的标准实现。

    支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化。

    用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    主要特性:

    • 可靠性:提供了多种技术可以让你在 性能 和 可靠性 之间进行 权衡。这些技术包括 持久性机制、投递确认、发布者证实 和 高可用性机制;

    • 灵活的路由:消息在到达队列前是通过 交换机 进行 路由 的。RabbitMQ 为典型的路由逻辑提供了 多种内置交换机 类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做 RabbitMQ 的 插件 来使用;

    • 消息集群:在相同局域网中的多个 RabbitMQ 服务器可以 聚合 在一起,作为一个独立的逻辑代理来使用;

    • 队列高可用:队列可以在集群中的机器上 进行镜像,以确保在硬件问题下还保证 消息安全;

    • 支持多种协议:支持 多种消息队列协议;

    • 支持多种语言:用 Erlang 语言编写,支持只要是你能想到的 所有编程语言;

    • 管理界面: RabbitMQ 有一个易用的 用户界面,使得用户可以 监控 和 管理 消息 Broker 的许多方面;

    • 跟踪机制:如果 消息异常,RabbitMQ 提供消息跟踪机制,使用者可以找出发生了什么;

    • 插件机制:提供了许多 插件,来从多方面进行扩展,也可以编写自己的插件。

    优点:

    1. 由于erlang语言的特性,mq 性能较好,高并发;
    2. 健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
    3. 有消息确认机制和持久化机制,可靠性高;
    4. 高度可定制的路由;
    5. 管理界面较丰富,在互联网公司也有较大规模的应用;
    6. 社区活跃度高;

    缺点:

    1. 尽管结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
    2. 实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
    3. 需要学习比较复杂的接口和协议,学习和维护成本较高;

    重要概念:

    • Broker:简单来说就是消息队列服务器实体。
    • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
    • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
    • Binding:绑定,它的作用就是把Exchange和Queue按照路由规则绑定起来。
    • Routing Key:路由关键字,Exchange根据这个关键字进行消息投递。
    • vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
    • producer:消息生产者,就是投递消息的程序。
    • consumer:消息消费者,就是接受消息的程序。
    • channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

    消息队列的使用过程,如下:

    1. 客户端连接到消息队列服务器,打开一个channel。
    2. 客户端声明一个exchange,并设置相关属性。
    3. 客户端声明一个queue,并设置相关属性。
    4. 客户端使用routing key,在exchange和queue之间建立好绑定关系。
    5. 客户端投递消息到exchange。

    5.4.RocketMQ


    RocketMQ出自 阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,消息可靠性上比 Kafka 更好。RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。

    主要特性:

    1. 是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点;
    2. Producer、Consumer、队列都可以分布式;
    3. Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合;
    4. 能够保证严格的消息顺序;
    5. 提供丰富的消息拉取模式;
    6. 高效的订阅者水平扩展能力;
    7. 实时的消息订阅机制;
    8. 亿级消息堆积能力;
    9. 较少的依赖;

    使用RocketMQ需要:

    • Java JDK
    • 安装git、Maven
    • RocketMQ安装包

    RocketMQ可以运行在Java语言所支持的平台之上。

    优点:

    1. 单机支持 1 万以上持久化队列
    2. RocketMQ 的所有消息都是持久化的,先写入系统 PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,直接从内存读取。
    3. 模型简单,接口易用(JMS 的接口很多场合并不太实用);
    4. 性能非常好,可以大量堆积消息在broker中;
    5. 支持多种消费,包括集群消费、广播消费等。
    6. 各个环节分布式扩展设计,主从HA;
    7. 开发度较活跃,版本更新很快。

    缺点:

    1. 支持的客户端语言不多,目前是java及c++,其中c++不成熟;
    2. RocketMQ社区关注度及成熟度也不及前两者;
    3. 没有web管理界面,提供了一个CLI(命令行界面)管理工具带来查询、管理和诊断各种问题;
    4. 没有在 mq 核心中去实现JMS等接口;

    5.5 Kafka


    Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),,之后成为Apache项目的一部分。Kafka系统快速、可扩展并且可持久化。它的分区特性,可复制和可容错都是其不错的特性。

    主要特性:

    1. 快速持久化,可以在O(1)的系统开销下进行消息持久化;
    2. 高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;
    3. .完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
    4. 支持同步和异步复制两种HA;
    5. 支持数据批量发送和拉取;
    6. zero-copy:减少IO操作步骤;
    7. 数据迁移、扩容对用户透明;
    8. 无需停机即可扩展机器;
    9. 其他特性:严格的消息顺序、丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制;

    使用Kafka需要:

    • Java JDK
    • Kafka安装包

    优点:

    1. 客户端语言丰富,支持java、.net、php、ruby、python、go等多种语言;
    2. 性能卓越,单机写入TPS约在百万条/秒,消息大小10个字节;
    3. 提供完全分布式架构, 并有replica机制, 拥有较高的可用性和可靠性, 理论上支持消息无限堆积;
    4. 支持批量操作;
    5. 消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
    6. 有优秀的第三方Kafka Web管理界面Kafka-Manager;
    7. 在日志领域比较成熟,被多家公司和多个开源项目使用;

    缺点:

    1. Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
    2. 使用短轮询方式,实时性取决于轮询间隔时间;
    3. 消费失败不支持重试;
    4. 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
    5. 社区更新较慢;

    5.6 Apollo


    Apache称Apollo为最快、最强健的STOMP服务器。支持STOMP、AMQP、MQTT、OpenWire协议,支持Topic、Queue、持久订阅等消费形式,支持对消息的多种处理,支持安全性处理,支持REST管理API。。。功能列表很长,最大的弊病就是目前市场接收度不够,所以使用的并不广泛。

    6. RabbitMQ/ActiveMQ/RocketMQ/Kafka对比

    结论:

    Kafka在于分布式架构,RabbitMQ基于AMQP协议来实现,RocketMQ/思路来源于kafka,改成了主从结构,在事务性可靠性方面做了优化。广泛来说,电商、金融等对事务性要求很高的,可以考虑RabbitMQ和RocketMQ,对性能要求高的可考虑Kafka。

    7.消息队列MQ选型


    综合上面的材料得出以下两点:
    (1)中小型软件公司:

    建议选RabbitMQ

    erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。

    不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。
    (2)大型软件公司:

    根据具体使用在rocketMq和kafka之间二选一。

    大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。

    至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。

     

    展开全文
  • 分布式-消息中间件介绍

    万次阅读 2018-07-26 16:01:46
    是大型分布式系统不可缺少的中间件。 目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等 二、消息队列作用 主要解决应用耦合,异步消息,流量削...
  • 在Windows世界,有无数块活动的大陆,它们都有个共同的名字——动态链接库。现在就让我们走进这些神奇的活动大陆,找出它们隐藏已久的秘密吧!   初窥门径:Windows的基石   随便打开个系统目录,眼望...
  • Kafka消息中间件(

    千次阅读 2018-12-14 09:58:01
    Kafka可以说是现在所有开源消息组件之性能最高的产品,但是同时也需要认识到个问题:Kafka是项不断继续发展的技术,所以来说对于其的稳定性永远无法评估。Kafka官网地址: http://kafka.apache.org/ Kafka...
  • Team Foundation 的带编号的错误和事件消息
  • 使用ASP.NET MVC4的出现个问题,S0234: 命名空间“System.Web”中不存在类型或命名空间名称“Optimization”(是否缺少程序集引用?),具体信息如下图所示: 经查询,将Areas/Views下的Web.config文件...
  • 文章目录、事务(基于AOP)二、@Transactional介绍三、@Transactional失效场景 说明:当我准备写我知道...事务管理在系统开发不可缺少一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两
  • 一部分:TCL基本知识

    千次阅读 2015-02-12 21:14:36
    .简介 Tcl 表示工具命令语言(Tool Command Language),它是种流行的脚本编制语言,最初由加州大学伯克利分校的 John Ousterhout 教授开发。John Ousterhout 打算将 Tcl 作为种将其它程序组件粘合在一起的...
  • 消息队列”是在消息的传输过程保存消息的容器。 “消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。 消息被发送到队列。“消息队列”是在...
  • c++STL库 简介 及 使用说明

    万次阅读 多人点赞 2017-10-27 09:03:48
    作为C++标准不可缺少一部分,STL应该是渗透在C++程序的角角落落里的。STL不是实验室里的宠儿,也不是程序员桌上的摆设,她的激动人心并非昙花一现。本教程旨在传播和普及STL的基础知识,若能借此机会为STL的推广做...
  •  http://blog.csdn.net/zhmxy555/article/details/7364697作者:毛星云 邮箱: happylifemxy@qq.com 欢迎邮件交流编程心得地图是游戏元素里面不可缺少一部分,要产生游戏地图,除了可以直接使用已经绘制好的...
  • 本文节选自苏震巍撰写的《微信开发深度解析...MessageHandler 是个微信消息的处理模块,也是整个微信开发过程中不可缺少一部分。在 MessageHandler ,开发者可以非常轻松地处理所有类型的微信消息。本文将介绍...
  • MQ消息队列

    千次阅读 2013-04-29 11:26:55
    AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的个开放标准,为面向消息的中间件设计。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 AMQP在...
  • MFC消息处理学习总结

    千次阅读 2017-04-08 12:24:44
    Windows消息机制概述 ... 消息是指什么?  消息系统对于个win32程序来说十分...消息,是系统定义的个32位的值,他唯一的定义了个事件,向 Windows发出个通知,告诉应用程序某个事情发生了。例如,单击鼠标
  • 《第行代码——Android》封面诞生记

    万次阅读 多人点赞 2014-08-28 09:04:28
    《第行代码——Android》已经上市快个月了,目前销售情况还算良好,也是特别感谢众多朋友的支持。其实本书如果想要卖的好,除了内容必须要给力之外,封面的设计也是至关重要的,而本书的封面无疑是在充实的...
  • javaScript学习笔记()js基础

    万次阅读 多人点赞 2018-09-21 10:07:18
    JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码。 诞生于1995年,当时的主要目的是验证表单的数据是否合法。 Java...
  • 在利用swarmctl部署应用的过程当中,我快速分析了SwarmKit的优点,并汇总了其中尚缺少的生产环境必要工具选项——部分可利用其它现有工具替代,部分可能需要自行构建。另外,在撰写本文的过程,README文档又新增...
  • 众所周知,GUI是游戏中不可缺少的元素,这篇文章,我们首先了解了游戏GUI界面的知识与相关概念,然后一起设计了个封装好GUI图形界面的C++类。这个类有着非常强的扩展性,使用也是极其方便,很适合二次开发。 ...
  • Spring Cloud 从入门到精通

    万次阅读 多人点赞 2018-07-03 02:45:08
    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是个框架,而是个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。 本课程由浅入深带领大家步步...
  • HTTP Header消息头详解

    万次阅读 2018-09-01 00:25:43
    HTTP消息头是指,在超文本传输协议( Hypertext Transfer Protocol ,HTTP)的请求和响应消息中,协议头部分的那些组件。HTTP消息头用来准确描述正在获取的资源、服务器或者客户端的行为,定义了HTTP事务的具体...
  • VS2015程序出现缺少dll文件解决方法

    万次阅读 2017-03-09 15:48:21
    动态编译的执行文件需要附带个的动态链接库,在执行时,需要调用其对应动态链接库的命令。所以其优点方面是缩小了执行文件本身的体积,另方面是加快了编译速度,节省了系统资源。缺点是哪怕是很简单的...
  • MQ消息

    万次阅读 2015-03-30 23:40:48
    AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的个开放标准,为面向消息的中间件设计。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 AMQP在...
  • 【转】用 Go 构建个区块链

    千次阅读 2019-05-11 17:34:43
    不过,使它独一无二的是,区块链是个公开的数据库,而不是个私人数据库,也就是说,每个使用它的人都有个完整或部分的副本。 只有经过其他数据库管理员的同意,才能向数据库添加新的记录。 此外,也正是...
  • C#:昨天,今天和明天:和 Anders Hejlsberg 座谈,第二部分源贴地址: C#: Yesterday, today, and tomorrow作者:John Osborn10/17/2005翻译:我要去桂林Osborn:回到和语言相关的问题,我现在还是想说说LINQ。...
  • Log4j详细使用教程

    万次阅读 多人点赞 2015-05-18 11:07:38
    日志是应用软件中不可缺少部分,Apache的开源项目Log4j是个功能强大的日志组件,提供方便的日志记录。在apache网站:jakarta.apache.org/log4j 可以免费下载到Log4j最新版本的软件包。
  • 毕业后回首,却很少有人能说,自己从来没有迷茫过。迷茫,仿佛就是团乌云,笼罩在每个心中怀有抱负的人的头上。每当夜深人静,思绪归于对自己人生未来的严肃思考,不知去往何处的苦闷,再加之不断迫近的升学/...
  • 关于HPE收购Niuble storage的消息已经...关于HPE最近动态请参阅HPE牵手CSC打造DXC延承HP百年家规,HPE最近的次有关存储的收购就是Simplivty,至此,HPE缺少的存储产品就是Original AFA产品和SDS controller方案了。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 142,453
精华内容 56,981
关键字:

一则消息中不可缺少的部分