【Storm】Trident state 概念

Trident提供了一套强大的状态管理API,允许在内存和外部存储中管理有状态数据。状态分为Opaque transactional、transactional和non-transactional三种类型,分别对应不同的容错能力和存储需求。通过StateFactory创建自定义state,并使用QueryFunction和StateUpdater接口进行查询和更新操作。persistentAggregate方法用于使用聚合器更新state,实现高效的数据处理。MapState和Snapshottable接口提供了不同场景下的state实现方式。
摘要由CSDN通过智能技术生成

核心概念

Trident在读写有状态的数据源方面是有着一流的抽象封装;状态即可以保留在topology的内部,如内存(但易丢失,服务器重启后不可用),也可以放到外部存储当中,如HDFS,Memcached(内存级数据库)或no-sql数据库(如Hbase)。这些都是使用同一套Trident API。

Trident以一种容错的方式来管理状态(状态指结果数据),以至于当你在更新状态的时候你不需要去考虑错误以及重试的情况。这种保证每个消息被处理有且仅有一次的原理会让你更放心的使用Trident的topology。

回顾之前讲解事务章节时:

Spout类型DB中存储
普通Spout[count = 3]
事务Spout[txid = 1, count = 3]
不透明事务Spout[txid = 2, value = 3, prevValue = 1]

 

 

 

 

 

核心概念state及API

Opaque transactional state有着最为强大的兼容性,但是这是以存储更多的信息作为代价的。transactional states需要存储较少的状态信息,但是仅能和transactional spouts协同工作。最后,non-transactional state所需要存储的信息最少,但是却不能实现有且仅有一次被成功处理的语义。

state和spout类型的选择其实是一种在容错性和存储消耗之间的权衡,根据应用的需要会进行选择。

State API

Trident把所有容错相关的逻辑都放在了state里面。作为一个用户,你并不需要自己去处理复杂的txid,存储多余的信息到数据库中,或者是任何其他类似的事情。

TridentTopology topology = new TridentTopology();
TridentState wordCounts = topology.newStream("spout1", spout)
		.parallelismHint(16)
		.each(new Fields("sentence"), new Split(), new Fields("word"))
		.groupBy(new Fields("word"))
		.persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count"))
		.parallelismHint(16);

所有管理state所需的逻辑都在MemoryMapState中封装好了,数据库的更新会自动以batch的形式来进行,以避免多次访问数据库。

state的基本接口只包含下面两个方法:

public interface State {
	void beginCommit(Long txid);
	void commit(Long txid);
}

如何开发一个自己的state?

假如用数据库来存储用户Location,并且你想要在Trident中去访问这个数据。你的state的实现应该有用户的set、get方法。

import storm.trident.state.State;

public class LocationDB implements State {

	@Override
	public void beginCommit(Long txid) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void commit(Long txid) {
		// TODO Auto-generated method stub
		
	}
	
	public void setLocation(Long userId, String location) {
		// code to access database and set location
	}
	
	public String getLocation(Long userId) {
		// code to get location from database
	}
}

然后你还需要提供给Trident一个StateFactory来在Trident中创建你的state对象。

StateFactory可以如下:

import storm.trident.state.StateFactory;

public class LocationDBFactory implements StateFactory {

	@Override
	public State makeState(Map conf, IMetricsContext metrics, int partitionIndex, int numPartitions) {
		return new LocationDB();
	}
}

如何在State上查询和更新

Trident提供了一个类似QueryFunction接口用来实现Trident中在一个source state上查询的功能,如MapGet类。还提供了StateUpdater来实现Trident中更新source state的功能。

例如:写一个查询地址的操作,来查询LocationDB找到用户的Location。

假定这个topology会接受一个用户id作为输入流,如:

TridentTopology topology = new TridentTopology();
TridentState locations = topology.newStaticState(new LocationDBFactory());
topology.newStream("myspout", spout)
        .stateQuery(locations, new Fields("userId"), new QueryLocation(), new Fields("location"));

QueryLocation实现:

public class QueryLocation extends BaseQueryFunction<LocationDB, String> {

	@Override
	public List<String> batchRetrieve(LocationDB state, List<TridentTuple> inputs) {
		
		List<String> ret = new ArrayList<>();
		for (TridentTuple input : inputs) {
			ret.add(state.getLocation(input.getLong(0)));
		}
		
		return ret;
	}
	
	@Override
	public void execute(TridentTuple tuple, String location, TridentCollector collector) {
		collector.emit(new Values(location));
	}
}

QueryLocation的执行分为两个部分,batchRetrieve收集一个batch的read操作结果;execute方法负责把结果emit出去。

你可以看到,这段代码为每个输入tuple去查询了一次LocationDB,没利用batch优势,所以以一种更好的操作LocationDB应该是这样的:

public class LocationDB implements State {

	@Override
	public void beginCommit(Long txid) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void commit(Long txid) {
		// TODO Auto-generated method stub
		
	}
	
	public void setLocationsBulk(List<Long> userIds, List<String> locations) {
		// set locations in bulk
	}
	
	public String getLocationsBulk(List<Long> userIds) {
		// get locations in bulk
		return "";
	}
}

StateUpdater接口

若要更新State,你需要使用StateUpdater接口。一个StateUpdater的例子用来将新的地址信息更新到LocationDB当中。

public class LocationUpdater extends BaseStateUpdater<LocationDB> {

	@Override
	public void updateState(LocationDB state, List<TridentTuple> tuples, TridentCollector collector) {
		
		List<Long> ids = new ArrayList<Long>();
		List<String> locations = new ArrayList<String>();
		
		for (TridentTuple t : tuples) {
			ids.add(t.getLong(0));
			locations.add(t.getString(1));
		}
		
		state.setLocationsBulk(ids, locations);
	}
}

如何在Trident topology中使用上面声明的LocationUpdater?

TridentTopology topology = new TridentTopology();
TridentState locations = topology.newStream("locations", locationsSpout)
	.partitionPersist(new LocationDBFactory(), new Fields("userId", "location"), new LocationUpdater());

partitionPersist将会更新state。其内部是将state和一批更新的tuple交给LocationUpdater,由LocationUpdater完成相应的更新操作。

在这段代码中,只是简单的从输入的tuple中提取userId和对应的location,并一起更新到state中。

persistentAggregate持久化聚合

Trident有另外一种更新state的方法叫做persistentAggregate,在之前的word count例子中我们已经见过了。

persistentAggregate是在partitionPersist之上的另外一层抽象,它知道怎么去使用一个Trident聚合器来更新state。

在word count例子中,因为这是一个group好的stream,Trident会期待你提供的state是实现了MapState接口,用来进行group的字段以key的形式存在于state当中,聚合后的结果会以value的形式存储在state当中。

MapState接口如下所示:

import storm.trident.state.State;
import storm.trident.state.ValueUpdater;

public interface MapState<T> extends State{
	
	List<T> multiGet(List<List<Object>> keys);
	List<T> multiUpdate(List<List<Object>> keys, List<ValueUpdater> updaters);
	
	void multiPut(List<List<Object>> keys, List<T> vals);
}

当你在一个未经过group的stream上面进行聚合的话,Trident会期待你的state实现Snapshottable接口:

import storm.trident.state.State;
import storm.trident.state.ValueUpdater;

public interface Snapshottable<T> extends State {
	
	T get();
	T update(ValueUpdater updater);
	void set(T o);
}

如何实现MapState?

在Trident中实现MapState是非常简单的,它几乎帮你做了所有的事情。

OpaqueMap,TransactionalMap,NonTransactionalMap类实现了所有相关的逻辑,包括容错的逻辑。你只需要将一个IBackingMap的实现提供给这些类就可以了。IBackingMap接口如下所示:

public interface IBackingMap<T> {
	
	List<T> multiGet(List<List<Object>> keys);
	
	void multiPut(List<List<Object>> keys, List<T> vals);
}

OpaqueMap会用OpaqueValue的value来调用multiPut方法,TransactionalMap's会提供 TransactionalValue中的value,而NonTransactionalMap只是简单的把从Topology获取的object传递给multiPut。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值