看源码学编程系列之Deque在Kafka的应用(二)

  通过看kafka 源码发现,在生产段发送消息的时候,使用了Deque 双队列这种数据结构作为消息队列。双队列(Deque)与我们平时所理解的队列(Queue)有什么区别呢?

我们都知道队列(Queue)是一种新进先出的数据结构,我们就假设是从尾部进去,然后头部出去。双队列(Deque)就比较灵活了,头部和尾部都可以进去或者出去。额,这种

双队列(Deque)有什么好处呢?拿我们公司来说吧,我们公司是一家大数据公司,每天都会产生很多数据进入到Kafka 来排队等待处理,有些数据甚至要排队好几天才轮到。好了

等了好几天消息终于可以处理了,这个时候万一客户端那边消费不成功,那这条数据怎么办?重新排队么,又要等好几天才处理?不现实的!所以只能重新插入到头部来重新消费。

  这个时候有人也许会有疑问了,这个我用普通的队列(Queue)也一样处理,每次我都从队列的头部 get 一条数据,进行不断的处理等最终成功后再remove 这条数据,这种方案

好像也行但是不够给力,假如这条消息要处理得比较久,会不会影响接下来的第二、第三条数据处理效率呢,如果我们一开始就用poll 的方式,取出第一条进行处理,另外的线程接着

poll 第二、第三条数据进行处理,如果第一条数据处理不成功,就重新放回队列的头部进行重新处理,这种的效率会不会更高呢?

  队列的get 与 poll 有什么区别呢?最大的区别就是poll 的时候,该数据是真的从队列移除了,这样别的线程才能取接下来的数据,get 则不同,get只是单纯取出数据,不改变

原有的数据集合,也就是别的线程无法获取接下来的任务。关于Deque 双队列请看如下代码:

  

 1 public static void showDequeData() {
 2         Deque<String> deque =new ArrayDeque<>();
 3         
 4         // 以下从头部添加
 5         deque.offerFirst("111");//此时队列[111],如果头部数据插入成功则返回true,否则抛出异常
 6         deque.addFirst("222");//此时队列[222,111],,如果头部数据插入失败则抛出异常
 7         
 8         // 以下从尾部插入
 9         deque.offerLast("999");//此时队列[222,111,999]
10         deque.addLast("888");//此时队列[222,111,999,888]
11         deque.add("333");//此时队列[222,111,999,888,333],实际实现还是调用 addLast()
12         deque.offer("777");//此时队列[222,111,999,888,333,777],实际实现还是调用 addLast()
13         
14         // 头部取出
15         deque.pollFirst();////从头部移除第一个元素222,此时队列为[111, 999, 888, 333, 777]
16         deque.removeFirst();//从头部移除第一个元素111,此时队列为[ 999, 888, 333, 777],该函数实际调用了pollFirst()
17         deque.peekFirst();// 从头部取出第一个元素 999,此时队列为[999, 888, 333, 777]
18         deque.getFirst();// 从头部取出第一个元素 999,此时队列为[999, 888, 333, 777]
19         
20         // 尾部取出
21         deque.pollLast();// 从尾部移除第一个元素777,此时队列为[999, 888, 333]
22         deque.removeLast();// 从尾部移除第一个元素333,此时队列为[999, 888],实际调用了pollLast
23         deque.peekLast();// 从尾部取出第一个元素888,此时队列为[999, 888]
24         deque.getLast();// 从尾部取出第一个元素888,此时队列为[999, 888]
25         System.out.println(deque.toString());
26     }

  其实Deque 这个类,只是定义一个接口规范,真正实现比较典型的有ArrayDeque,ConcurrentLinkedDeque,其中ArrayDeque 是非线程安全, ConcurrentLinkedDeque 则是线程安全。

ConcurrentLinkedDeque 线程安全实现方式,其实是一种叫做CAS 的实现方式,以下就是具体实现的一个片段。从compareAndSwapObject 方法名称中就可以窥探出,其实所谓的CAS就是

一种乐观锁的实现哲学,也就是在修改之前先看一下原值是否有改动过,如果没有改动过就更新成功,否则就不成功。

 private boolean casHead(Node<E> paramNode1, Node<E> paramNode2) { return UNSAFE.compareAndSwapObject(this, headOffset, paramNode1, paramNode2); 

  以下代码就是参照Kafka 使用Deque 来实现消息发送,逻辑步骤如下:

    第一、从尾部生成需要发送的消息数据

    第二、从开头开始发送数据,如果失败则重新插回队列头部。

  以下是模拟的代码程序:

 1 public static void retrySendMessage() {
 2         Deque<String> dequeMsg =new ArrayDeque<>();
 3         
 4         // 从尾部开始插入10条消息,进行后续的发送
 5         for(int i=0;i<10;i++) {
 6             dequeMsg.addLast("Msg="+i);
 7         }
 8         
 9         while(true) {
10             if(dequeMsg.size()>0) {
11                 String msg = dequeMsg.pollFirst();// 从消息队列中取出第一条进行发送操作
12                 System.out.println("开始发送消息:"+msg);
13                 
14                 if(Utils.getRandomNum() >6) {// 使用随机数判断消息是否发送成功,如果大于6 则代表发送成功,否则发送失败
15                     System.out.println(msg+" 已经发送成功!");
16                 }else {
17                     System.out.println(msg+" 发送失败!,需要从头部重新进入队列");
18                     dequeMsg.addFirst(msg);//重新把数据返回队列头部
19                 }
20             }else {
21                 System.out.println("消息发送完成,退出程序");
22                 break;
23             }
24         }
25         
26     }

 

 

 

以上实现可能也许比较简单,实际Kafka实现的时候,是使用多个线程来实现的,最起码一个线程用来生成数据,另一个线程用来发送数据,但中心思想是一样的。
posted @ 2019-11-23 12:18  软件改变未来  阅读(331)  评论(0)    收藏  举报