使用Executor Service将Java多线程迁移到Akka

时间:2017-04-03 10:24:17

标签: java multithreading akka

我只是想知道是否可以将用Java的Executor服务编写的旧多线程代码替换为Akka。我对此几乎没有疑问。

Is akka actor runs in their own thread? 

How Threads will be assigned for the Actors ?

What are the pros and cons of migration of it is possible?

目前我使用固定线程池进行多线程处理,并提交一个可调用的。

示例代码,

public class KafkaConsumerFactory {

    private static Map<String,KafkaConsumer> registry = new HashMap<>();

    private static ThreadLocal<KafkaConsumer> consumers = new ThreadLocal<KafkaConsumer>(){
        @Override
        protected KafkaConsumer initialValue() {
            return new KafkaConsumer(createConsumerConfig());
        }
    };

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                registry.forEach((tid,con) -> {
                    try{
                        con.close();
                    } finally {
                        System.out.println("Yes!! Consumer for " + tid + " is closed.");
                    }
                });
            }
        });
    }

    private static Properties createConsumerConfig() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "newcon-grp5");
        props.put("key.deserializer", StringDeserializer.class.getName());
        props.put("value.deserializer", KafkaKryoSerde.class.getName());
        return props;
    }


    public static <K,V> KafkaConsumer<K,V> createConsumer(){
        registry.put(Thread.currentThread().getName(),consumers.get());
        return consumers.get();
    }
}

/////////////////////////////////////////////// //////////

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class KafkaNewConsumer {
    public static int MAX_THREADS = 10;
    private ExecutorService es = null;
    private boolean stopRequest = false;




    public static void main(String[] args){
        KafkaNewConsumer knc = new KafkaNewConsumer();
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run(){
                knc.es.shutdown();
                try {
                    knc.es.awaitTermination(500, TimeUnit.MILLISECONDS);
                } catch (InterruptedException ignored) {

                }finally {
                    System.out.println("Finished");
                }
            }
        });

        knc.consumeTopic("rtest3",knc::recordConsuemer);

    }

    public void recordConsuemer(ConsumerRecord<?,?> record){
        String result = new StringJoiner(": ")
                .add(Thread.currentThread().getName())
                .add("ts").add(String.valueOf(record.timestamp()))
                .add("offset").add(String.valueOf(record.offset()))
                .add("data").add(String.valueOf(record.value()))
                .add("value-len").add(String.valueOf(record.serializedValueSize()))
                .toString();
        System.out.println(result);
    }
    public void  consumeTopic(String topicName, Consumer<ConsumerRecord<?,?>> fun){
        KafkaConsumer con= KafkaConsumerFactory.createConsumer();
        int paritions = con.partitionsFor(topicName).size();
        int noOfThread = (MAX_THREADS < paritions) ? MAX_THREADS :paritions;
         es = Executors.newFixedThreadPool(noOfThread);
        con.close();
        for(int i=0;i<noOfThread;i++){
            es.submit(()->{
                KafkaConsumer consumer = KafkaConsumerFactory.createConsumer();
                try{
                    while (!stopRequest){
                        consumer.subscribe(Collections.singletonList(topicName));
                        ConsumerRecords<?,?> records = consumer.poll(5000);

                        records.forEach(fun);
                        consumer.commitSync();
                    }
                }catch(Exception e){
                    e.printStackTrace();
                } finally {
                    consumer.close();
                }
            });
        }
    }
}

我浏览了一些互联网教程,其中一些直接得出结论

  

演员非常好而且比传统线程更快。

但没有解释它如何变得比线程更快?

我尝试了一些示例Akka(来自激活器的Akka示例)代码,并在所有actor中打印了Thread.currentThread.getName,并发现了名为(helloakka-akka.actor.default-dispatcher-X)的不同调度程序线程。

但是怎么样?谁在创建这些线程?他们的配置在哪里?线程和Actor之间的映射关系是什么?

每次发送消息都会Akka创建新的线程吗?或者在内部使用线程池?

如果我需要100个线程来执行某些任务的部分并行执行,我是否需要创建100个Actors并向每个线程发送1条消息?或者我需要创建1个actor并在其队列中放入100条消息,它将分叉为100个线程。

真的很困惑

1 个答案:

答案 0 :(得分:3)

迁移到actor系统对于基于执行器的系统来说不是一项小任务,但可以完成。它需要您重新思考您设计系统的方式并考虑演员的影响。例如,在线程体系结构中,您可以为业务流程创建一些处理程序,将其放在runnable中,然后让它在线程上执行操作。这完全不适合演员范式。您必须重新构建系统以处理消息传递并使用消息传递来调用任务。此外,您必须将您对业务流程的思考方式从强制方法改为基于消息的方法。考虑一下购买产品的简单任务。我会假设你知道如何在遗嘱执行人中做到这一点。在演员系统中,你这样做:

(购买产品) - &gt; UserActor - &gt; (BillCredit卡) - &gt; CCProcessing Actor - &gt; (购买批准和开票项目) - &gt;库存管理器 - &gt; ......等等

在每个阶段,括号中的内容是发送给相关actor的异步消息,该异步消息执行业务逻辑,然后将消息转发给流程中的下一个actor。

现在这只是创建基于actor的系统的一种方法,还有许多其他技术,但核心基础是你不能强制性地思考,而是作为每个独立运行的步骤集合。然后消息按照常规顺序通过系统爆炸,但你无法确定顺序,或者即使消息将到达那里,所以你必须设计语义来处理它。在上面的系统中,我可能会有另一个角色每隔两分钟检查一次尚未显示给计费的孤立订单。当然,这意味着我的消息需要意识到以确保如果我第二次发送它们确定,他们不会向用户收取两次费用。

我知道我没有处理你的具体例子,我只是想为你提供一些上下文,演员不仅仅是创建执行者的另一种方式(我想你可以用这种方式滥用它们但不建议)而是一种完全不同的设计范式。一个非常值得学习的范例,如果你实现了飞跃,你永远不会再想要执行者了。