Caching And Processing 2TB Mozilla Crash Reports In Memory With Hazelcast

本文描述的是mozilla在使用Hazelcast做的一个POC,Hazelcast被应用在socorro

原文地址:http://highscalability.com/blog/2011/4/12/caching-and-processing-2tb-mozilla-crash-reports-in-memory-w.html

Mozilla processes TB's of Firefox crash reports daily using HBase, Hadoop, Python and Thrift protocol. The project is called Socorro, a system for collecting, processing, and displaying crash reports from clients. Today the Socorro application stores about 2.6 million crash reports per day. During peak traffic, it receives about 2.5K crashes per minute. 

In this article we are going to demonstrate a proof of concept showing how Mozilla could integrate Hazelcast into Socorro and achieve caching and processing 2TB of crash reports with 50 node Hazelcast cluster. The video for the demo is available here.


Currently, Socorro has pythonic collectors, processors, and middleware that communicate with HBase via the Thrift protocol. One of the biggest limitations of the current architecture is that it is very sensitive to latency or outages on the HBase side. If the collectors cannot store an item in HBase then they will store it on local disk and it will not be accessible to the processors or middleware layer until it has been picked up by a cronjob and successfully stored in HBase. The same goes for processors, if they cannot retrieve the raw data and store the processed data, then it will not be available.

In the current implementation a collector stores the crash report into HBase and puts the id of the report to a queue stored in another Hbase table. The processors polls the queue and start to process the report. Given the stated problem of a potential HBase outage, the Metrics Team at Mozilla contacted us to explore the possible usage of Hazelcast Distributed Map, in front of the HBase. The idea is to replace the Thrift layer with Hazelcast client and store the crash reports into Hazelcast. An HBase Map store will be implemented and Hazelcast will write behind the entries to HBase via that persister. The system will cache the hot data and will continue to operate even if HBase is down. Beside HBase, Socorro uses Elastic Search to index the reports to search later on. In this design Elastic Search will also be fed by Hazelcast map store implementation. This enables Socorro to switch the persistence layer very easily. For small scale Socorro deployments, HBase usage becomes complex and they would like to have simpler storage options available.

So we decided to make a POC for Mozilla team and run it on EC2 servers. The details of the POC requirement can be found at the wiki page.. The implementation is available under Apache 2.0 license.

Distributed Design

The number one rule of distributed programming is "do not distribute". The throughput of the system increases as you move the operation to the data, rather than data to operation. In Hazelcast the entries are almost evenly distributed among the cluster nodes. Each node owns a set of entries. Within the application you need to process the items. There are two option here; you can either design the system with the processing in mind, and bring the data to the execution or you can take the benefit of data being distributed and do the execution on the node owning the data. If the second choice can be applied, we expect the system to behave much better in terms of latency, throughput, network and CPU usage. In this particular case the crash reports are stored in a distributed map called "crashReports" with the report id as the key. We would like to process a particular crash report on the node that owns it. 

Collectors

A Collector puts the crash report into "crashReports" map and report id with the insert date as the value into another map called "process" for the reports that need to be processed. These two put operations are done within a transaction. Since both entries in 'crashReports' and 'process' maps have same key (report id), they are owned by the same node. In Hazelcast, the key determines the owner node. 

Processors

A Processor on each node adds a localEntryListener on the map "process" and is notified when a new entry is added. Notice that the listener is not listening to the entire map, but only listens the local entries. There will be one and only one event per entry and that event will be delivered locally. Remember the "number one rule of distributed programming".

When the processor is notified of an insert, it gets the report, does the processing and puts updated item back into crashReports and removes the entry from "process" map. All these operations are local, except the put which does one more put on the backup node. There is one more thing missing here. What if the Processor crashes or was not able to process the report when it gets the notification? Good news, the information regarding a particular crash report that needs to be processed is still in the map "process". There will be another thread that loops through the local entries in "process" map and processes any lingering entries. Notice that under normal circumstances and if there is no backlog the map should be empty. 

Data size and Latency

The object that we store in Hazelcast consists of:

  •     a JSON document with a median size of 1 KB, minimum size of 256 bytes and maximum size of 20K.
  •     a binary data blob with a median size of 500 KB; min 200 KB; max 20 MB; 75th percentile 5 MB.

This makes the average of 4 MB data. And given the latency and network bandwidth among the EC2 servers the latency and throughput of our application would be much better in a more real environment.    

Demo Details

To run the application you only need to have the latest Hazelcast and Elastic Search jars. The entry point to the application is com.hazelcast.socorro.Node class. When you run the main method, It will generate one Collector and a Processor. So each node will be both a Collector and a Processor. The Collectors will simulate the generation of crash reports and will submit them into Data Grid. The Processors will receive the newly submitted crash reports, process them and store back to distributed Map. If you enable persistence, Hazelcast will persist the data to Elastic Search.

We run the application on a 50 node cluster of EC2 servers. In our simulation the average crash report size was 4MB. The aim was to store as many crash reports as we can, so we decided to go with largest possible memory which is 68.4 GB that is available at High-Memory Quadruple Extra Large Instance. 
Hazelcast Nodes uses Multicast or TCP/IP to discover each other. On EC2 environment multicast is not enabled, but nodes should be able to see each other via TCP/IP. This requires passing the IP addresses to all of the nodes somehow. In the article "Running Hazelcast on a 100 node Amazon EC2 Cluster" we described a way of deploying an application on large scale on EC2. In this demo we used a slightly different approach. We developed a tool, called Cloud Tool that starts N number of nodes. The tool takes as an input the script where we describe how to run the application and the configuration file for Hazelcast. It builds the network configuration part with the IP addresses of nodes and copies (scp) both configuration and script to the nodes. The nodes, on start, waits for the configuration and script file. After they are copied, all nodes execute the script. It is the scripts responsibility to download and run the application. This way within minutes we can deploy any Hazelcast application on any number of nodes.

With this method way we deployed the Socorro POC app on 50 m2.4xlarge instances. By default each node generates 1 crash report per second. Which makes 3K crash reports per minute in the cluster. In the app, we are listening to Hazelcast topic called "command".  This enables us externally increase and decrease the load. l3000 means "generate total of 3000 crash reports per minute".  
We did the demo and recorded all the steps. We used the Hazelcast Management Center to visualize the cluster. Through this tool we can observe the throughput of the nodes, publish messages to the topic, and observe the behavior of the cluster.

Later, we implemented the persistence to Elastic Search. But It couldn't keep up with the data load that we want to persist. We started 5 Hazelcast Nodes and 5 ES Nodes. A backlog appears while persisting to ES. Our guess is, this is because of the EC2 IO. It seems that given the file size of average 4MB, there is no way of creating a 50 node cluster, making 3K reports per minute and storing them into ES. At least this seems not to be possible in an EC2 environment and with our current ES knowledge. We still started a relatively small cluster persisting to 5 node ES. Recorded until it was obvious than we can not persist everything from memory to ES. A 10 minute video is available here.

We think on Mozilla's environment it will be easy to store to HBase and with small deployments of Socorro ES will serve quite well.

Scalability

Another goal of POC was to demonstrate how one can scale up by adding more and more nodes. So we did another recording where we start with 5 nodes. The cluster is able to generate and process 600 crash reports per minute and stored 200 GB of data in memory. Then iteratively we add 5 more nodes and increase the load. We ended at 20 node cluster that was able to process 2400 reports per minute and store in memory 800 GB of data. We would like to highlight one thing here. To add a node does not immediately scale you up. It has it's initial cost which is backup and migrations. When new nodes arrive Hazelcast will start to migrate some of the partitions(buckets) from old to new nodes to achieve equal balance across the cluster. Once the partition is moved all entries falling into it will be stored on the node owning that partition. More information on this topic can be found here.

In this demo the amount of data that we cache is huge, thus migrations take some time. Also note that during the migration, a request to the migrated data should wait until the migration is completed for the sake of consistency. That's why it is not a good idea to add nodes when your cluster have significantly large amount of data and is under high load to witch it can barely respond.

An alternative approach

Finally we would like to discuss another approach that we tried but didn't record. For the sake of the throughput we chose to process the entries locally. This approach performed better but has two limitations.

  1. All nodes of the cluster that own data should also be a processor. So you can not say that on my 50 node cluster; 20 are collectors and the rest are processor. All should be equal.
  2. For a node to process the data, it should own it first. So if you add a new node, initially there will be no partitions and it will not participate on processing. As the partitions migrate it will participate more and more. You have to wait until the cluster is balanced to fully benefit from new coming nodes.

The second approach was to store the id's of unprocessed entries in a distributed queue. And Processors take ids from the queue and process the associated record. The data is not local here, most of the time a processor will be processing a record that it doesn't own. The performance will be worse but you can separate processors from collectors and when you add a processor to the cluster it will immediately start to do its job. 

附部分POC源代码( https://github.com/fuadm/socorro-hazelcast-poc/):

Collector.java

import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MessageListener;
import com.hazelcast.core.Transaction;
import com.hazelcast.socorro.CrashReport;

import java.util.Date;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.hazelcast.socorro.Constants.CRASH_PROCESSED_MAP;
import static com.hazelcast.socorro.Constants.CRASH_REPORT_MAP;
import static com.hazelcast.socorro.CrashReport.KILO_BYTE;
import static com.hazelcast.socorro.CrashReport.randomSizeForBlob;

public class Collector {
    private volatile boolean running = true;
    private volatile int LOAD = 2*60;
    private final BlockingQueue<CrashReport> generatedCrashReports = new LinkedBlockingQueue<CrashReport>(10);
    Logger logger = Logger.getLogger(this.getClass().getName());

    public Collector(int nThreads) {
        final ExecutorService executors = Executors.newFixedThreadPool(nThreads);
        final IMap<Long, CrashReport> map = Hazelcast.getMap(CRASH_REPORT_MAP);
        final IMap<Long, Long> mapProcessed = Hazelcast.getMap(CRASH_PROCESSED_MAP);
        for (int i = 0; i < nThreads; i++) {
            executors.execute(new Runnable() {
                public void run() {
                    while (running) {
                        Transaction txn = Hazelcast.getTransaction();
                        try {
                            txn.begin();
                            CrashReport report = generatedCrashReports.take();
                            map.put(report.getId(), report);
                            mapProcessed.put(report.getId(), new Date().getTime());
                            txn.commit();
                        } catch (Throwable e) {
                            logger.log(Level.INFO, "rollbacking", e);
                            txn.rollback();
                            return;
                        }
                    }
                }
            });
        }
        listenForCommands();
        generateCrashReportsPeriodically();
        logger.info("Collector started with " + nThreads + " threads.");

    }

    private void listenForCommands() {
        Hazelcast.getTopic("command").addMessageListener(new MessageListener<Object>() {
            public void onMessage(Object message) {
                String str = (String) message;
                logger.info("Received command: " + str);
                if (str.startsWith("l")) {
                    LOAD = Integer.parseInt(str.substring(1));
                    logger.info("LOAD is set to " + LOAD);
                }
            }
        });
    }

    private void generateCrashReportsPeriodically() {
        final Random random = new Random();
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                int k = LOAD/(Hazelcast.getCluster().getMembers().size()*60);
                for (int i = 0; i < k; i++) {
                    CrashReport crashReport = new CrashReport(CrashReport.generateMap(), new byte[randomSizeForBlob() * KILO_BYTE]);
                    crashReport.setId(Hazelcast.getIdGenerator("ids").newId());
//                    crashReport.setId(random.nextInt(10));
                    generatedCrashReports.offer(crashReport);
                }
                logger.info("Generated " + k + " number of Crash Reports. Current size in the local Q is: "+ generatedCrashReports.size());
            }
        }, 0, 1000);
    }
}

Processor.java

import com.hazelcast.core.*;
import com.hazelcast.socorro.CrashReport;

import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import static com.hazelcast.socorro.Constants.CRASH_PROCESSED_MAP;
import static com.hazelcast.socorro.Constants.CRASH_REPORT_MAP;
import static com.hazelcast.socorro.CrashReport.*;

public class Processor {
    final IMap<Long, CrashReport> map = Hazelcast.getMap(CRASH_REPORT_MAP);
    final IMap<Long, Long> mapProcessed = Hazelcast.getMap(CRASH_PROCESSED_MAP);
    final ExecutorService executorService;
    final Logger logger = Logger.getLogger(this.getClass().getName());

    public Processor(int nThreads) {
        executorService = Executors.newFixedThreadPool(nThreads);
        mapProcessed.addLocalEntryListener(new EntryListener<Long, Long>() {
            @Override
            public void entryAdded(final EntryEvent<Long, Long> event) {
                final long key = event.getKey();
                processTransactional(key);
            }

            @Override
            public void entryRemoved(EntryEvent<Long, Long> event) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void entryUpdated(EntryEvent<Long, Long> event) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void entryEvicted(EntryEvent<Long, Long> event) {
                //To change body of implemented methods use File | Settings | File Templates.
            }
        });
        listenForCommands();
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                int counter = 0;
                for (final long key : mapProcessed.localKeySet()) {
                    long time = mapProcessed.get(key);
                    if(System.currentTimeMillis() - 600000 > time){
                        processTransactional(key);
                        counter++;
                    }
                }
                logger.info("There is " + counter + " number of unprocessed reports!." + ((counter == 0) ? "" : " Processing them now!"));
            }
        }
                , 300000, 300000);
        logger.info("Processor started with " + nThreads + " threads.");
    }

    private void listenForCommands() {
        Hazelcast.getTopic("command").addMessageListener(new MessageListener<Object>() {
            public void onMessage(Object message) {
                String str = (String) message;
                logger.info("Received command: " + str);
                if (str.startsWith("process")) {
                    int counter = 0;
                    for (final long key : mapProcessed.localKeySet()) {
                        processTransactional(key);
                        counter++;
                    }
                    logger.info("There is " + counter + " number of unprocessed reports!." + ((counter == 0) ? "" : " Processing them now!"));
                }
            }
        });
    }

    private void processTransactional(final long key) {
        executorService.execute(new Runnable() {
            public void run() {
                if (mapProcessed.containsKey(key) && mapProcessed.tryLock(key)) {
                    try {
                        CrashReport report = map.get(key);
                        if (report != null && !report.processed()) {
                            process(report.getJSON());
                            report.setProcessed(true);
                            map.put(key, report);
                            mapProcessed.remove(key);
                        } else {
                            mapProcessed.remove(key);
                        }
                    } finally {
                        mapProcessed.unlock(key);
                    }
                }
            }
        });
    }

    private void process(Map<String, Object> map) {
        map.put("client_crash_date", "2011-01-24 21:15:35.0");
        map.put("dump", randomString(generate(5 * KILO_BYTE, 500 * KILO_BYTE, 50 * KILO_BYTE)));
        map.put("startedDateTime", "2011-01-24 13:15:52.657344");
        map.put("app_notes", "renderers: 0x22600,0x22600,0x20400");
        map.put("crashedThread", "0");
        map.put("cpu_info", "family 6 model 23 stepping 10 | 2");
        map.put("install_age", "134773");
        map.put("distributor", "null");
        map.put("topmost_filenames", randomString(100));
        map.put("processor_notes", randomString(100));
        map.put("user_comments", "Will test without extension.");
        map.put("build_date", "2011-01-21 15:00:00.0");
        map.put("uptime", "134771");
        map.put("uuid", "4ecc5fc9-81d5-41a4-9b4c-313942110124");
        map.put("flash_version", "[blank]");
        map.put("os_version", "10.6.5 10H574");
        map.put("distributor_version", "null");
        map.put("truncated", "true");
        map.put("process_type", "null");
        map.put("id", "211153043");
        map.put("hangid", "null");
        map.put("version", "4.0b10pre");
        map.put("build", "20110121153230");
        map.put("addons_checked", "null");
        map.put("product", "firefox");
        map.put("os_name", "Mac OS X");
        map.put("last_crash", "810473");
        map.put("date_processed", "2011-01-24 13:15:48.550858");
        map.put("cpu_name", "amd64");
        map.put("reason", "eXC_BAD_ACCESS / KERN_INVALID_ADDRESS");
        map.put("address", "0x0");
        map.put("completeddatetime", "2011-01-24 13:15:57.417639");
        map.put("signature", "nsAutoCompleteController::EnterMatch");
        Map map2 = new HashMap();
        map2.put("compatibility@addons.mozilla.org", "0.7");
        map2.put("enter.selects@agadak.net", "6");
        map2.put("{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}", "1.3.3");
        map2.put("sts-ui@sidstamm.com", "0.1");
        map2.put("masspasswordreset@johnathan.nightingale", "1.04");
        map2.put("support@lastpass.com", "1.72.0");
        map2.put("{972ce4c6-7e08-4474-a285-3208198ce6fd}", "4.0b10pre");
        map.put("addons", map2);
    }

}


智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值