头歌Trident State API

任务描述

本关任务:使用 Storm Trident 完成与数据库的交互。

相关知识

1.调用Trident API 2.State接口 3.StateFactory工厂接口 4.QueryFunction接口 5.UpdateState接口 6.MapState接口

调用 Trident API

Trident State API 在内部为我们实现了所有状态管理的逻辑,我们不需要再进行诸如对比 txid ,在数据库中存储多个值等操作,仅需要简单调用 Trident API 即可,例如:

 
  1. TridentTopology topology = new TridentTopology();
  2. TridentState wordCounts =
  3. topology.newStream("spout1", spout)
  4. .each(new Fields("sentence"), new Split(), new Fields("word"))
  5. .groupBy(new Fields("word"))
  6. .persistentAggregate(MemcachedState.opaque(serverLocations), new Count(), new Fields("count"))
  7. .parallelismHint(6);

所有的管理 Opaque transactional states 状态的逻辑都在 MemcachedState.opaque() 方法内部实现了。所有的更新操作都是以 batch 为单位的,这样减少了对数据库的调用次数,极大的提高了效率。

State接口

基本 State interface (状态接口)只有两种方法:

 
  1. public interface State {
  2. void beginCommit(Long txid);
  3. void commit(Long txid);
  4. }

假设我们有一个 Location 数据库,我们要通过 Trident 查新和更新这个数据库,那么我们可以自己实现这样一个 LocationDB State ,因为我们需要查询和更新,所以我们为这个 LocationDB 可以添加对 Location 的 getset 的实现:

 
  1. public class LocationDB implements State {
  2. public void beginCommit(Long txid) {
  3. }
  4. public void commit(Long txid) {
  5. }
  6. public void setLocation(long userId, String location) {
  7. // code to access database and set location
  8. }
  9. public String getLocation(long userId) {
  10. // code to get location from database
  11. }
  12. }
StateFactory 工厂接口

Trident 提供了 State Factory 接口,我们实现了这个接口之后, Trident 就可以通过这个接口获得具体的 Trident State 实例了,下面我们就实现一个可以制造 LocationDB 实例的 LocationDBFactory

 
  1. public class LocationDBFactory implements StateFactory {
  2. public State makeState(Map conf, int partitionIndex, int numPartitions) {
  3. return new LocationDB();
  4. }
  5. }
QueryFunction 接口

这个接口是用来帮助 Trident 查询一个 State ,这个接口定义了两个方法:

 
  1. public interface QueryFunction<S extends State, T> extends EachOperation {
  2. List<T> batchRetrieve(S state, List<TridentTuple> args);
  3. void execute(TridentTuple tuple, T result, TridentCollector collector);
  4. }

接口的第一个方法 batchRetrieve() 有两个参数,分别是要查询的 State 源和查询参数,因为 trident 都是以 batch 为单位处理的,所以这个查询参数是一个List<TridentTuple>集合。

关于第二个方法 execute() 有三个参数,第一个代表查询参数中的某个 tuple ,第二个代表这个查询参数 tuple 对应的查询结果,第三个则是一个消息发送器。下面就看一个 QuaryLocation 的实例:

 
  1. public class QueryLocation extends BaseQueryFunction<LocationDB, String> {
  2. public List<String> batchRetrieve(LocationDB state, List<TridentTuple> inputs) {
  3. List<String> ret = new ArrayList();
  4. for(TridentTuple input: inputs) {
  5. ret.add(state.getLocation(input.getLong(0)));
  6. }
  7. return ret;
  8. }
  9. public void execute(TridentTuple tuple, String location, TridentCollector collector) {
  10. collector.emit(new Values(location));
  11. }
  12. }

QueryLocation 接收到 Trident 发送的查询参数,参数是一个 batch , batch 中 tuple 内容是 userId 信息,然后 batchRetrieve() 方法负责从 State 源中获取每个 userId 对应的的 location 。

最终 batchRetrieve() 查询的结果会被 execute() 方法发送出去。

但这里有个问题, batchRetrieve() 方法中针对每个 userid 都做了一次查询 State 操作,这样处理显然效率不高,也不符合 Trident 所有操作都是针对 batch 的原则。

所以,我们要对 LocationDB 这个 State 做一下改造,提供一个 bulkGetLocations() 方法来替换掉 getLocation() 方法,请看改造后的 LocationDB 的实现:

 
  1. public class LocationDB implements State {
  2. public void beginCommit(Long txid) {
  3. }
  4. public void commit(Long txid) {
  5. }
  6. public void setLocationsBulk(List<Long> userIds, List<String> locations) {
  7. // set locations in bulk
  8. }
  9. public List<String> bulkGetLocations(List<Long> userIds) {
  10. // get locations in bulk
  11. }
  12. }

改造的 LocationDB 对 Location 的查询和更新都是批量操作的,这样显然可以提高处理效率。此时,我们再稍微改一下 QueryFunction 中的 batchRetrieve() 方法:

 
  1. public class QueryLocation extends BaseQueryFunction<LocationDB, String> {
  2. public List<String> batchRetrieve(LocationDB state, List<TridentTuple> inputs) {
  3. List<Long> userIds = new ArrayList<Long>();
  4. for(TridentTuple input: inputs) {
  5. userIds.add(input.getLong(0));
  6. }
  7. return state.bulkGetLocations(userIds);
  8. }
  9. public void execute(TridentTuple tuple, String location, TridentCollector collector) {
  10. collector.emit(new Values(location));
  11. }
  12. }

QueryLocation 在 topology 中可以这么使用:

 
  1. TridentTopology topology = new TridentTopology();
  2. topology.newStream("myspout", spout)
  3. .stateQuery(locations, new Fields("userid"), new QueryLocation(), new Fields("location"))
UpdateState 接口

当我们要更新一个 State 源时,我们需要实现一个 UpdateState 接口。 UpdateState 接口只提供了一个方法:

 
  1. public interface StateUpdater<S extends State> extends Operation {
  2. void updateState(S state, List<TridentTuple> tuples, TridentCollector collector);
  3. }

我们来具体看一下 LocationUpdater 的实现:

 
  1. public class LocationUpdater extends BaseStateUpdater<LocationDB> {
  2. public void updateState(LocationDB state, List<TridentTuple> tuples, TridentCollector collector) {
  3. List<Long> ids = new ArrayList<Long>();
  4. List<String> locations = new ArrayList<String>();
  5. for(TridentTuple t: tuples) {
  6. ids.add(t.getLong(0));
  7. locations.add(t.getString(1));
  8. }
  9. state.setLocationsBulk(ids, locations);
  10. }
  11. }

对于 LocationUpdater 在 topology 中可以这么使用:

 
  1. TridentTopology topology = new TridentTopology();
  2. TridentState locations =
  3. topology.newStream("locations", locationsSpout)

通过调用 Trident Stream 的 partitionPersist 方法可以更新一个 State 。

在上面这个实例中, LocationUpdater 接收一个 State 和要更新的 batch ,最终通过调用 LocationFactory 制造的 LocationDB 中的 setLocationsBulk() 方法把 batch 中的 userid 及其 location 批量更新到 State 中。

partitionPersist 操作会返回一个 TridentState 对象,这个对象即是被 TridentTopology 更新后的 LocationDB ,所以,我们可以在 topology 中续继续对这个返回的 State 做查询操作。

另外一点需要注意的是,从上面 StateUpdater 接口可以看出,在它的 updateState() 方法中还提供了一个 TridentCollector ,因此在执行 StateUpdate 的同时仍然可以形成一个新的 Stream 。

若要操作 StateUpdater 形成的 Stream ,可以通过调用 TridentState 。 newValueStream() 方法实现。

MapState 接口

Trident 另一个 update state 的方法是persistentAggregate ,请看下面 word count 的例子:

 
  1. TridentTopology topology = new TridentTopology();
  2. TridentState wordCounts =
  3. topology.newStream("spout1", spout)
  4. .each(new Fields("sentence"), new Split(), new Fields("word"))
  5. .groupBy(new Fields("word"))
  6. .persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count"))

persistentAggregate 是在 partitionPersist 之上的另一个抽象,它会对 Trident Stream 进行聚合之后再把聚合结果更新到 State 中。

在上面这个例子中,因为聚合的是一个 groupedStreamTrident 要求这种情况下 State 需要实现 MapState 接口,被 grouped 的字段会被做为 MapSatekey ,被 grouped 的数据计算的结果会被做为 MapSatevalueMapSate 接口定义如下:

 
  1. public interface MapState<T> extends State {
  2. List<T> multiGet(List<List<Object>> keys);
  3. List<T> multiUpdate(List<List<Object>> keys, List<ValueUpdater> updaters);
  4. void multiPut(List<List<Object>> keys, List<T> vals);
  5. }
Snapshottable 接口

如果我们聚合的不是一个 groupedStreamTrident 要求我们的 State 实现 Snapshottable 接口:

 
  1. public interface Snapshottable<T> extends State {
  2. T get();
  3. T update(ValueUpdater updater);
  4. void set(T o);
  5. }

编程要求

根据提示,在右侧编辑器补充代码,使用 Storm Trident 完成与数据库的交互 。

测试说明

平台会对你编写的代码进行测试:

输入内容:

 
  1. the cow
  2. the man
  3. four score
  4. many apples

输出内容:

 
  1. the
  2. success0
  3. cow
  4. success1
  5. the
  6. success2
  7. man
  8. success3
  9. four
  10. success0
  11. score
  12. success1
  13. many
  14. success2
  15. apples
  16. success3

代码如下:

import java.util.ArrayList;

import java.util.List;

import org.apache.storm.Config;

import org.apache.storm.LocalCluster;

import org.apache.storm.generated.StormTopology;

import org.apache.storm.trident.TridentTopology;

import org.apache.storm.trident.operation.BaseFunction;

import org.apache.storm.trident.operation.TridentCollector;

import org.apache.storm.trident.testing.FixedBatchSpout;

import org.apache.storm.trident.tuple.TridentTuple;

import org.apache.storm.tuple.Fields;

import org.apache.storm.tuple.Values;

import org.apache.storm.trident.state.BaseQueryFunction;

import org.apache.storm.trident.state.State;

import java.util.Map;

import org.apache.storm.task.IMetricsContext;

import org.apache.storm.trident.state.StateFactory;



public class StateTopology {

    public static void main(String[] agrs){

        FixedBatchSpout spout = new FixedBatchSpout(

                new Fields("sentence"), 2,

                new Values("the cow"),

                new Values("the man"),

                new Values("four score"),

                new Values("many apples"));

        spout.setCycle(false);

        TridentTopology topology = new TridentTopology();

 //****请根据提示补全Topology程序****//

        /*********begin*********/

        //newStream 方法从输入源中读取数据, 并在 topology 中创建一个新的数据流 spout

        topology.newStream("spout",spout)

        //使用.each()方法,sentence tuple经过split()方法后输出word tuple

        .each(new Fields("sentence"),new Split(),new Fields("word"))

                //Stream.stateQuery(TridentState state, Fields inputFields, QueryFunction function, Fields functionFields)方法主要是根据输入从持久化存储系统中读取相应的数据并将其当作一个数据流供Strom Topology使用。

                //使用 .newStaticState()方法创建了一个外部数据库,

        .stateQuery(topology.newStaticState(new TestStateFactory()),new Fields("word"), new TestQueryLocation(), new Fields("test"));  

                //.stateQuery()将topology.newStaticState(new TestStateFactory())映射到new Fields("word") word字段

                //将new TestQueryLocation()映射到 new Fields("test") test字段 上    





        /*********end*********/

        StormTopology stormTopology = topology.build();

        LocalCluster cluster = new LocalCluster();

        Config conf = new Config();

        cluster.submitTopology("test", conf,stormTopology);

    }



    public static class Split extends BaseFunction {

        public void execute(TridentTuple tuple, TridentCollector collector) {

            String sentence = tuple.getString(0);

            for(String word: sentence.split(" ")) {

                collector.emit(new Values(word));

            }

        }

    }

    public static class TestState implements State{

        @Override

        public void beginCommit(Long arg0) {

            // TODO Auto-generated method stub

        }

        @Override

        public void commit(Long arg0) {

            // TODO Auto-generated method stub

        }

        public String getDBOption(int i){

            return "success"+i;

        }

    }

    public static class TestStateFactory implements StateFactory{



        @Override

        public State makeState(Map arg0, IMetricsContext arg1, int arg2, int arg3) {

            // TODO Auto-generated method stub

            return new TestState();

        }



    }



    public static class TestQueryLocation extends BaseQueryFunction<TestState, String>{

        @Override

        public List<String> batchRetrieve(TestState state, List<TridentTuple> arg1) {

            List<String> list = new ArrayList<String>();

            for(int i = 0 ; i< arg1.size() ; i++){

                list.add(state.getDBOption(i));

            }

            return list;

        }

        @Override

        public void execute(TridentTuple arg0, String arg1, TridentCollector arg2) {

            System.out.println(arg0.getString(0));

            System.out.println(arg1);

        }

    }

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值