首页 文章 精选 留言 我的

精选列表

搜索[starter],共2647篇文章
优秀的个人博客,低调大师

Storm starter - RollingTopWords

计算top N words的topology, 用于比如trending topics or trending images on Twitter. 实现了滑动窗口计数和TopN排序, 比较有意思, 具体分析一下代码 Topology 这是一个稍微复杂些的topology, 主要体现在使用不同的grouping方式, fieldsGrouping和globalGrouping String spoutId = "wordGenerator"; String counterId = "counter"; String intermediateRankerId = "intermediateRanker"; String totalRankerId = "finalRanker"; builder.setSpout(spoutId, new TestWordSpout(), 5); builder.setBolt(counterId, new RollingCountBolt(9, 3), 4).fieldsGrouping(spoutId, new Fields("word")); builder.setBolt(intermediateRankerId, new IntermediateRankingsBolt(TOP_N), 4).fieldsGrouping(counterId, new Fields("obj")); builder.setBolt(totalRankerId, new TotalRankingsBolt TOP_N)).globalGrouping(intermediateRankerId); RollingCountBolt 首先使用RollingCountBolt, 并且此处是按照word进行fieldsGrouping的, 所以相同的word会被发送到同一个bolt, 这个field id是在上一级的declareOutputFields时指定的 RollingCountBolt, 用于基于时间窗口的counting, 所以需要两个参数, the length of the sliding window in seconds和the emit frequency in seconds newRollingCountBolt(9, 3), 意味着output the latest 9 minutes sliding window every 3 minutes 1. 创建SlidingWindowCounter(SlidingWindowCounter和SlotBasedCounter参考下面)counter = new SlidingWindowCounter(this.windowLengthInSeconds / this.windowUpdateFrequencyInSeconds); 如何定义slot数? 对于9 min的时间窗口, 每3 min emit一次数据, 那么就需要9/3=3个slot 那么在3 min以内, 不停的调用countObjAndAck(tuple)来递增所有对象该slot上的计数 每3分钟会触发调用emitCurrentWindowCounts, 用于滑动窗口(通过getCountsThenAdvanceWindow), 并emit (Map<obj, 窗口内的计数和>, 实际使用时间) 因为实际emit触发时间, 不可能刚好是3 min, 会有误差, 所以需要给出实际使用时间 2. TupleHelpers.isTickTuple(tuple), TickTuple 前面没有说的一点是, 如何触发emit? 这是比较值得说明的一点, 因为其使用Storm的TickTuple特性. 这个功能挺有用, 比如数据库批量存储, 或者这里的时间窗口的统计等应用 "__system" component会定时往task发送 "__tick" stream的tuple 发送频率由TOPOLOGY_TICK_TUPLE_FREQ_SECS来配置, 可以在default.ymal里面配置 也可以在代码里面通过getComponentConfiguration()来进行配置, public Map<String, Object> getComponentConfiguration() { Map<String, Object> conf = new HashMap<String, Object>(); conf.put(Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS, emitFrequencyInSeconds); return conf; 配置完成后, storm就会定期的往task发送ticktuple 只需要通过isTickTuple来判断是否为tickTuple, 就可以完成定时触发的功能 public static boolean isTickTuple(Tuple tuple) { return tuple.getSourceComponent().equals(Constants.SYSTEM_COMPONENT_ID) \\ SYSTEM_COMPONENT_ID == "__system" && tuple.getSourceStreamId().equals(Constants.SYSTEM_TICK_STREAM_ID); \\ SYSTEM_TICK_STREAM_ID == "__tick" } 最终, 这个blot的输出为, collector.emit(new Values(obj, count, actualWindowLengthInSeconds)); obj, count(窗口内的计数和), 实际使用时间 SlotBasedCounter 基于slot的counter, 模板类, 可以指定被计数对象的类型T 这个类其实很简单, 实现计数对象和一组slot(用long数组实现)的map, 并可以对任意slot做increment或reset等操作 关键结构为Map<T,long[]> objToCounts, 为每个obj都对应于一个大小为numSlots的long数组, 所以对每个obj可以计numSlots个数 incrementCount, 递增某个obj的某个slot, 如果是第一次需要创建counts数组 getCount, getCounts, 获取某obj的某slot值, 或某obj的所有slot值的和 wipeSlot, resetSlotCountToZero, reset所有对象的某solt为0, reset某obj的某slot为0 wipeZeros, 删除所有total count为0的obj, 以释放空间 public final class SlotBasedCounter<T> implements Serializable { private static final long serialVersionUID = 4858185737378394432L; private final Map<T, long[]> objToCounts = new HashMap<T, long[]>(); private final int numSlots; public SlotBasedCounter(int numSlots) { if (numSlots <= 0) { throw new IllegalArgumentException("Number of slots must be greater than zero (you requested " + numSlots + ")"); } this.numSlots = numSlots; } public void incrementCount(T obj, int slot) { long[] counts = objToCounts.get(obj); if (counts == null) { counts = new long[this.numSlots]; objToCounts.put(obj, counts); } counts[slot]++; } public long getCount(T obj, int slot) { long[] counts = objToCounts.get(obj); if (counts == null) { return 0; } else { return counts[slot]; } } public Map<T, Long> getCounts() { Map<T, Long> result = new HashMap<T, Long>(); for (T obj : objToCounts.keySet()) { result.put(obj, computeTotalCount(obj)); } return result; } private long computeTotalCount(T obj) { long[] curr = objToCounts.get(obj); long total = 0; for (long l : curr) { total += l; } return total; } /** * Reset the slot count of any tracked objects to zero for the given slot. * * @param slot */ public void wipeSlot(int slot) { for (T obj : objToCounts.keySet()) { resetSlotCountToZero(obj, slot); } } private void resetSlotCountToZero(T obj, int slot) { long[] counts = objToCounts.get(obj); counts[slot] = 0; } private boolean shouldBeRemovedFromCounter(T obj) { return computeTotalCount(obj) == 0; } /** * Remove any object from the counter whose total count is zero (to free up memory). */ public void wipeZeros() { Set<T> objToBeRemoved = new HashSet<T>(); for (T obj : objToCounts.keySet()) { if (shouldBeRemovedFromCounter(obj)) { objToBeRemoved.add(obj); } } for (T obj : objToBeRemoved) { objToCounts.remove(obj); } } } SlidingWindowCounter SlidingWindowCounter只是对SlotBasedCounter做了进一步的封装, 通过headSlot和tailSlot提供sliding window的概念 incrementCount, 只能对headSlot进行increment, 其他slot作为窗口中的历史数据 核心的操作为, getCountsThenAdvanceWindow 1. 取出Map<T, Long> counts, 对象和窗口内所有slots求和值的map 2. 调用wipeZeros, 删除已经不被使用的obj, 释放空间 3. 最重要的一步, 清除tailSlot, 并advanceHead, 以实现滑动窗口 advanceHead的实现, 如何在数组实现循环的滑动窗口 public final class SlidingWindowCounter<T> implements Serializable { private static final long serialVersionUID = -2645063988768785810L; private SlotBasedCounter<T> objCounter; private int headSlot; private int tailSlot; private int windowLengthInSlots; public SlidingWindowCounter(int windowLengthInSlots) { if (windowLengthInSlots < 2) { throw new IllegalArgumentException("Window length in slots must be at least two (you requested " + windowLengthInSlots + ")"); } this.windowLengthInSlots = windowLengthInSlots; this.objCounter = new SlotBasedCounter<T>(this.windowLengthInSlots); this.headSlot = 0; this.tailSlot = slotAfter(headSlot); } public void incrementCount(T obj) { objCounter.incrementCount(obj, headSlot); } /** * Return the current (total) counts of all tracked objects, then advance the window. * * Whenever this method is called, we consider the counts of the current sliding window to be available to and * successfully processed "upstream" (i.e. by the caller). Knowing this we will start counting any subsequent * objects within the next "chunk" of the sliding window. * * @return */ public Map<T, Long> getCountsThenAdvanceWindow() { Map<T, Long> counts = objCounter.getCounts(); objCounter.wipeZeros(); objCounter.wipeSlot(tailSlot); advanceHead(); return counts; } private void advanceHead() { headSlot = tailSlot; tailSlot = slotAfter(tailSlot); } private int slotAfter(int slot) { return (slot + 1) % windowLengthInSlots; } } IntermediateRankingsBolt 这个bolt作用就是对于中间结果的排序, 为什么要增加这步, 应为数据量比较大, 如果直接全放到一个节点上排序, 会负载太重 所以先通过IntermediateRankingsBolt, 过滤掉一些 这里仍然使用, 对于obj进行fieldsGrouping, 保证对于同一个obj, 不同时间段emit的统计数据会被发送到同一个task IntermediateRankingsBolt继承自AbstractRankerBolt(参考下面) 并实现了updateRankingsWithTuple, void updateRankingsWithTuple(Tuple tuple) { Rankable rankable = RankableObjectWithFields.from(tuple); super.getRankings().updateWith(rankable); } 逻辑很简单, 将Tuple转化Rankable, 并更新Rankings列表 参考AbstractRankerBolt, 该bolt会定时将Ranking列表emit出去 Rankable Rankable除了继承Comparable接口, 还增加getObject()和getCount()接口 public interface Rankable extends Comparable<Rankable> { Object getObject(); long getCount(); } RankableObjectWithFields RankableObjectWithFields实现Rankable接口 1. 提供将Tuple转化为RankableObject Tuple由若干field组成, 第一个field作为obj, 第二个field作为count, 其余的都放到List<Object> otherFields中 2. 实现Rankable定义的getObject()和getCount()接口 3. 实现Comparable接口, 包含compareTo, equals public class RankableObjectWithFields implements Rankable public static RankableObjectWithFields from(Tuple tuple) { List<Object> otherFields = Lists.newArrayList(tuple.getValues()); Object obj = otherFields.remove(0); Long count = (Long) otherFields.remove(0); return new RankableObjectWithFields(obj, count, otherFields.toArray()); } Rankings Rankings维护需要排序的List, 并提供对List相应的操作 核心的数据结构如下, 用来存储rankable对象的list List<Rankable> rankedItems = Lists.newArrayList(); 提供一些简单的操作, 比如设置maxsize(list size), getRankings(返回rankedItems, 排序列表) 核心的操作是, public void updateWith(Rankable r) { addOrReplace(r); rerank(); shrinkRankingsIfNeeded(); } 上一级的blot会定期的发送某个时间窗口的(obj, count), 所以obj之间的排序是在不断变化的 1. 替换已有的, 或新增rankable对象(包含obj, count) 2. 从新排序(Collections.sort) 3. 由于只需要topN, 所以大于maxsize的需要删除 AbstractRankerBolt 首先以TopN为参数, 创建Rankings对象 private final Rankings rankings; public AbstractRankerBolt(int topN, int emitFrequencyInSeconds) { count = topN; this.emitFrequencyInSeconds = emitFrequencyInSeconds; rankings = new Rankings(count); } 在execute中, 也是定时触发emit, 同样是通过emitFrequencyInSeconds来配置tickTuple 一般情况, 只是使用updateRankingsWithTuple不断更新Rankings 这里updateRankingsWithTuple是abstract函数, 需要子类重写具体的update逻辑 public final void execute(Tuple tuple, BasicOutputCollector collector) { if (TupleHelpers.isTickTuple(tuple)) { emitRankings(collector); } else { updateRankingsWithTuple(tuple); } } 最终将整个rankings列表emit出去 private void emitRankings(BasicOutputCollector collector) { collector.emit(new Values(rankings)); getLogger().info("Rankings: " + rankings); } TotalRankingsBolt 该bolt会使用globalGrouping, 意味着所有的数据都会被发送到同一个task进行最终的排序. TotalRankingsBolt同样继承自AbstractRankerBolt void updateRankingsWithTuple(Tuple tuple) { Rankings rankingsToBeMerged = (Rankings) tuple.getValue(0); super.getRankings().updateWith(rankingsToBeMerged); } 唯一的不同是, 这里updateWith的参数是个rankable列表, 在Rankings里面的实现一样, 只是多了遍历 最终可以得到, 全局的TopN的Rankings列表 本文章摘自博客园,原文发布日期:2013-05-22

优秀的个人博客,低调大师

Storm starter - SingleJoinExample

Storm常见模式——流聚合 Topology 1.定义两个spout, 分别是genderSpout, ageSpout Fields, ("id", "gender"), ("id", "age"), 最终join的结果应该是("id", "gender", "age") 2. 在设置SingleJoinBolt需要将outFields作为参数, 即告诉bolt, join完的结果应该包含哪些fields 并且对于两个spout都是以Fields("id")进行fieldsGrouping, 保证相同id都会发到同一个task public class SingleJoinExample { public static void main(String[] args) { FeederSpout genderSpout = new FeederSpout(new Fields("id", "gender")); FeederSpout ageSpout = new FeederSpout(new Fields("id", "age")); TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("gender", genderSpout); builder.setSpout("age", ageSpout); builder.setBolt("join", new SingleJoinBolt(new Fields("gender", "age"))) .fieldsGrouping("gender", new Fields("id")) .fieldsGrouping("age", new Fields("id")); } SingleJoinBolt 由于不能保证bolt可以同时收到某个id的所有tuple, 所以必须把收到的tuple都先在memory里面cache, 至到收到某id的所有的tuples, 再做join. 做完join后, 这些tuple就可以从cache里面删除, 但是如果某id的某些tuple丢失, 就会导致该id的其他tuples被一直cache. 解决这个问题, 对cache数据设置timeout, 过期后就删除, 并发送这些tuples的fail通知. 可见这个场景, 使用TimeCacheMap正合适, TimeCacheMap<List<Object>,, Map,> List<Object>, 被join的field, 对于上面的例子就是"id”, 之所以是List, 应该是为了支持多fields join Map<GlobalStreamId, Tuple>,记录tuple和stream的关系 对于这个例子, 从TimeCacheMap的bucket里面取出下面两个k,v, 然后进行join {id, {agestream, (id, age)}} {id, {genderstream, (id, gender)}} 1. prepare一般的prepare的逻辑都很简单, 而这里确很复杂...a, 设置Timeout和ExpireCallbacktimeout 设的是, Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS, 默认是30s, 这个可以根据场景自己调整 应该设法保证不同spout中tuple的发送顺序, 以保证相同id的tuple以较短时间间隔被收到, 比如这个例子应该按id排序然后emit 否则如果出现, ("id", "gender")被第一个emit, 而 ("id", "age")被最后一个emit, 会导致不断的timeout 设置ExpireCallback, 对于所有timeout的tuples, 发送fail通知 private class ExpireCallback implements TimeCacheMap.ExpiredCallback<List<Object>, Map<GlobalStreamId, Tuple>> { @Override public void expire(List<Object> id, Map<GlobalStreamId, Tuple> tuples) { for(Tuple tuple: tuples.values()) { _collector.fail(tuple); } } } b. 找出_idFields(哪些field是相同的, 可以用作join) 和_fieldLocations (outfield和spout stream的关系, 比如gender属于genderstream)通过context.getThisSources()取出spout sources列表, 并通过getComponentOutputFields取到fields列表 _idFields, 逻辑很简单, 每次都拿新的fields和idFields做retainAll(取出set共同部分), 最终会得到所有spout fields的相同部分 _fieldLocations, 拿_outFields和spout fields进行匹配, 找到后记录下关系 其实, 我觉得这部分准备工作, 在调用的时候用参数指明就可以了, 犯不着那么麻烦的来做 比如参数变为("id", {"gender", genderstream}, {"age", agestream}) @Override public void prepare(Map conf, TopologyContext context, OutputCollector collector) { _fieldLocations = new HashMap<String, GlobalStreamId>(); _collector = collector; int timeout = ((Number) conf.get(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS)).intValue(); _pending = new TimeCacheMap<List<Object>, Map<GlobalStreamId, Tuple>>(timeout, new ExpireCallback()); _numSources = context.getThisSources().size(); Set<String> idFields = null; for(GlobalStreamId source: context.getThisSources().keySet()) { Fields fields = context.getComponentOutputFields(source.get_componentId(), source.get_streamId()); Set<String> setFields = new HashSet<String>(fields.toList()); if(idFields==null) idFields = setFields; else idFields.retainAll(setFields); for(String outfield: _outFields) { for(String sourcefield: fields) { if(outfield.equals(sourcefield)) { _fieldLocations.put(outfield, source); } } } } _idFields = new Fields(new ArrayList<String>(idFields)); if(_fieldLocations.size()!=_outFields.size()) { throw new RuntimeException("Cannot find all outfields among sources"); } } 2, execute a, 从tuple中取出_idFields和streamid 如果在_pending(TimeCacheMap)中没有此_idFields, 为这个_idFields创新新的hashmap并put到bucket b, 取出该_idFields所对应的所有Map<GlobalStreamId, Tuple> parts, 并检测当前收到的是否是无效tuple(从同一个stream emit的具有相同id的tuple) 将新的tuple, put到该_idFields所对应的map. parts.put(streamId, tuple); c, 判断如果parts的size等于spout sources的数目, 对于这个例子为2, 意思是当从genderstream和agestream过来的tuple都已经收到时 从_pending(TimeCacheMap)删除该_idFields的cache数据, 因为已经可以join, 不需要继续等待了 并根据_outFields以及_fieldLocations, 去各个stream的tuple中取出值 最终emit结果, (((id, age), (id, gender)), (age, gender)) ArrayList<Tuple>(parts.values()), joinResult Ack所有的tuple @Override public void execute(Tuple tuple) { List<Object> id = tuple.select(_idFields); GlobalStreamId streamId = new GlobalStreamId(tuple.getSourceComponent(), tuple.getSourceStreamId()); if(!_pending.containsKey(id)) { _pending.put(id, new HashMap<GlobalStreamId, Tuple>()); } Map<GlobalStreamId, Tuple> parts = _pending.get(id); if(parts.containsKey(streamId)) throw new RuntimeException("Received same side of single join twice"); parts.put(streamId, tuple); if(parts.size()==_numSources) { _pending.remove(id); List<Object> joinResult = new ArrayList<Object>(); for(String outField: _outFields) { GlobalStreamId loc = _fieldLocations.get(outField); joinResult.add(parts.get(loc).getValueByField(outField)); } _collector.emit(new ArrayList<Tuple>(parts.values()), joinResult); for(Tuple part: parts.values()) { _collector.ack(part); } } } TimeCacheMap Storm常见模式——TimeCacheMap 解决什么问题? 常常需要在memory里面cache key-value, 比如实现快速查找表 但是memeory是有限的, 所以希望只保留最新的cache的, 过期的key-value可以被删除. 所以TimeCacheMap就是用来解决这个问题的, 在一定time内cache map(kv set) 1. 构造参数 TimeCacheMap(int expirationSecs, int numBuckets, ExpiredCallback<K, V> callback) 首先需要expirationSecs, 表示多久过期 然后, numBuckets, 表示时间粒度, 比如expirationSecs = 60s, 而numBuckets=10, 那么一个bucket就代表6s的时间窗, 并且6s会发生一次过期数据删除 最后, ExpiredCallback<K, V> callback, 当发生超时的时候, 需要对超时的K,V做些操作的话, 可以定义这个callback, 比如发送fail通知 2. 数据成员 核心结构, 使用linkedlist来实现bucket list, 用HashMap<K, V>来实现每个bucket private LinkedList<HashMap<K, V>> _buckets; 辅助成员, lock对象和定期的cleaner thread private final Object _lock = new Object(); private Thread _cleaner; 3. 构造函数 其实核心就是启动_cleaner Daemon线程 _cleaner的逻辑其实很简单, 定期的把最后一个bucket删除, 在bucket list开头加上新的bucket, 并且如果有定义callback, 对所有timeout的kv调用callback 同时这里考虑线程安全, 会对操作过程加锁synchronized(_lock) 唯一需要讨论的是, sleepTime 即如果保证数据在定义的expirationSecs时间后, 被删除 定义, sleepTime = expirationMillis / (numBuckets-1) a, 如果cleaner刚刚完成删除last, 添加first bucket, 这时put的K,V的过期时间为, expirationSecs / (numBuckets-1) * numBuckets = expirationSecs * (1 + 1 / (numBuckets-1)) 需要等待完整的numBuckets个sleepTime, 所以时间会略大于expirationSecs b, 如果反之, 刚完成put k,v操作后, cleaner开始clean操作, 那么k,v的过期时间为, expirationSecs / (numBuckets-1) * numBuckets - expirationSecs / (numBuckets-1) = expirationSecs 这种case会比a少等一个sleepTime, 时间恰恰是expirationSecs 所以这个方法保证, 数据会在[b,a]的时间区间内被删除 public TimeCacheMap(int expirationSecs, int numBuckets, ExpiredCallback<K, V> callback) { if(numBuckets<2) { throw new IllegalArgumentException("numBuckets must be >= 2"); } _buckets = new LinkedList<HashMap<K, V>>(); for(int i=0; i<numBuckets; i++) { _buckets.add(new HashMap<K, V>()); } _callback = callback; final long expirationMillis = expirationSecs * 1000L; final long sleepTime = expirationMillis / (numBuckets-1); _cleaner = new Thread(new Runnable() { public void run() { try { while(true) { Map<K, V> dead = null; Time.sleep(sleepTime); synchronized(_lock) { dead = _buckets.removeLast(); _buckets.addFirst(new HashMap<K, V>()); } if(_callback!=null) { for(Entry<K, V> entry: dead.entrySet()) { _callback.expire(entry.getKey(), entry.getValue()); } } } } catch (InterruptedException ex) { } } }); _cleaner.setDaemon(true); _cleaner.start(); } 4. 其他操作 首先, 所有操作都会使用synchronized(_lock)保证线程互斥 其次, 所有操作的复杂度都是O(numBuckets), 因为每个item都是hashmap, 都是O(1)操作 最重要的是Put, 只会将新的k,v, put到第一个(即最新的)bucket, 并且将之前旧bucket里面的相同key的cache数据删除 public void put(K key, V value) { synchronized(_lock) { Iterator<HashMap<K, V>> it = _buckets.iterator(); HashMap<K, V> bucket = it.next(); bucket.put(key, value); while(it.hasNext()) { bucket = it.next(); bucket.remove(key); } } } 其他还支持如下操作, public boolean containsKey(K key) public V get(K key) public Object remove(K key) public int size() //将所有bucket的HashMap的size累加 本文章摘自博客园,原文发布日期: 2013-05-24

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册