geotools 读取shp属性过滤_Flink进阶之使用布隆过滤器实现UV统计

fdfc43abed48f8aa4b9b19e5affbf87a.gif

关注公众号,回复关键字“Flink”获取更多资料

1

思路分析

在实际应用中,我们往往关注在一段时间内有多少不同的用户访问了网站,也就是网站的独立访客数(Unique Visitor),也就是UV.

在UV不太大的情况下,我们可以把所有数据的userId都存在了窗口计算的状态里,在窗口收集数据的过程中,状态会不断增大。一般情况下,只要不超出内存的承受范围,这种做法也没什么问题;但如果我们遇到的数据量很大呢?把所有数据暂存放到内存里,显然不是一个好注意。

我们会想到,可以利用redis这种内存级k-v数据库,为我们做一个缓存。但如果我们遇到的情况非常极端,数据大到惊人呢?比如上亿级的用户,要去重计算UV。

如果放到redis中,亿级的用户id(每个20字节左右的话)可能需要几G甚至几十G的空间来存储。当然放到redis中,用集群进行扩展也不是不可以,但明显代价太大了。

一个更好的想法是,其实我们不需要完整地存储用户ID的信息,只要知道他在不在就行了。所以其实我们可以进行压缩处理,用一位(bit)就可以表示一个用户的状态。这个思想的具体实现就是布隆过滤器(BloomFilter)。

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilisticdatastructure),特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”。

它本身是一个很长的二进制向量,既然是二进制的向量,那么显而易见的,存放的不是0,就是1。相比于传统的List、Set、Map等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

我们的目标就是,利用某种方法(一般是Hash函数)把每个数据,对应到一个位图的某一位上去;如果数据存在,那一位就是1,不存在则为0。

2

具体实现

接下来我们就来具体实现一下。注意这里我们用到了redis连接存取数据,所以需要加入redis客户端的依赖:

<dependencies>    <dependency>        <groupId>redis.clientsgroupId>        <artifactId>jedisartifactId>        <version>2.8.1version>    dependency>dependencies>

我们准备了一份web服务器的日志数据,这里以apache服务器的一份log为例,每一行日志记录了访问者的IP、userID、访问时间、访问方法以及访问的url.

创建POJO类UserBehavior,用于将原始数据包装成此类输出.

public class UserBehavior {    // 定义私有属性    private Long userId;    private Long itemId;    private Integer categoryId;    private String behavior;    private Long timestamp;    public UserBehavior() {    }    public UserBehavior(Long userId, Long itemId, Integer categoryId, String behavior, Long timestamp) {        this.userId = userId;        this.itemId = itemId;        this.categoryId = categoryId;        this.behavior = behavior;        this.timestamp = timestamp;    }    public Long getUserId() {        return userId;    }    public void setUserId(Long userId) {        this.userId = userId;    }    public Long getItemId() {        return itemId;    }    public void setItemId(Long itemId) {        this.itemId = itemId;    }    public Integer getCategoryId() {        return categoryId;    }    public void setCategoryId(Integer categoryId) {        this.categoryId = categoryId;    }    public String getBehavior() {        return behavior;    }    public void setBehavior(String behavior) {        this.behavior = behavior;    }    public Long getTimestamp() {        return timestamp;    }    public void setTimestamp(Long timestamp) {        this.timestamp = timestamp;    }    @Override    public String toString() {        return "UserBehavior{" +                "userId=" + userId +                ", itemId=" + itemId +                ", categoryId=" + categoryId +                ", behavior='" + behavior + '\'' +                ", timestamp=" + timestamp +                '}';    }}

创建POJO类PageViewCount,用于将最终的结果数据包装输出。

public class PageViewCount {    private String url;    private Long windowEnd;    private Long count;    public PageViewCount() {    }    public PageViewCount(String url, Long windowEnd, Long count) {        this.url = url;        this.windowEnd = windowEnd;        this.count = count;    }    public String getUrl() {        return url;    }    public void setUrl(String url) {        this.url = url;    }    public Long getWindowEnd() {        return windowEnd;    }    public void setWindowEnd(Long windowEnd) {        this.windowEnd = windowEnd;    }    public Long getCount() {        return count;    }    public void setCount(Long count) {        this.count = count;    }    @Override    public String toString() {        return "PageViewCount{" +                "url='" + url + '\'' +                ", windowEnd=" + windowEnd +                ", count=" + count +                '}';    }}

在src/main/java下创建UvWithBloomFilter.java文件,具体代码如下:

public class UvWithBloomFilter {    public static void main(String[] args) throws Exception {        // 1. 创建执行环境        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();        env.setParallelism(1);        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);        // 2. 读取数据,创建DataStream        URL resource = UniqueVisitor.class.getResource("/UserBehavior.csv");        DataStream inputStream = env.readTextFile(resource.getPath());        // 3. 转换为POJO,分配时间戳和watermark        DataStream dataStream = inputStream                .map(line -> {                    String[] fields = line.split(",");                    return new UserBehavior(new Long(fields[0]), new Long(fields[1]), new Integer(fields[2]), fields[3], new Long(fields[4]));                })                .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() {                    @Override                    public long extractAscendingTimestamp(UserBehavior element) {                        return element.getTimestamp() * 1000L;                    }                });        // 开窗统计uv值        SingleOutputStreamOperator uvStream = dataStream                .filter(data -> "pv".equals(data.getBehavior()))                .timeWindowAll(Time.hours(1))                .trigger( new MyTrigger() )                .process( new UvCountResultWithBloomFliter() );        uvStream.print();        env.execute("uv count with bloom filter job");    }    // 自定义触发器    public static class MyTrigger extends Trigger<UserBehavior, TimeWindow>{        @Override        public TriggerResult onElement(UserBehavior element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {            // 每一条数据来到,直接触发窗口计算,并且直接清空窗口            return TriggerResult.FIRE_AND_PURGE;        }        @Override        public TriggerResult onProcessingTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {            return TriggerResult.CONTINUE;        }        @Override        public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {            return TriggerResult.CONTINUE;        }        @Override        public void clear(TimeWindow window, TriggerContext ctx) throws Exception {        }    }    // 自定义一个布隆过滤器    public static class MyBloomFilter {        // 定义位图的大小,一般需要定义为2的整次幂        private Integer cap;        public MyBloomFilter(Integer cap) {            this.cap = cap;        }        // 实现一个hash函数        public Long hashCode( String value, Integer seed ){            Long result = 0L;            for( int i = 0; i < value.length(); i++ ){                result = result * seed + value.charAt(i);            }            return result & (cap - 1);        }    }    // 实现自定义的处理函数    public static class UvCountResultWithBloomFliter extends ProcessAllWindowFunction<UserBehavior, PageViewCount, TimeWindow>{        // 定义jedis连接和布隆过滤器        Jedis jedis;        MyBloomFilter myBloomFilter;        @Override        public void open(Configuration parameters) throws Exception {            jedis = new Jedis("localhost", 6379);            myBloomFilter = new MyBloomFilter(1<<29);    // 要处理1亿个数据,用64MB大小的位图        }        @Override        public void process(Context context, Iterable elements, Collector out) throws Exception {            // 将位图和窗口count值全部存入redis,用windowEnd作为key            Long windowEnd = context.window().getEnd();            String bitmapKey = windowEnd.toString();            // 把count值存成一张hash表            String countHashName = "uv_count";            String countKey = windowEnd.toString();            // 1. 取当前的userId            Long userId = elements.iterator().next().getUserId();            // 2. 计算位图中的offset            Long offset = myBloomFilter.hashCode(userId.toString(), 61);            // 3. 用redis的getbit命令,判断对应位置的值            Boolean isExist = jedis.getbit(bitmapKey, offset);            if( !isExist ){                // 如果不存在,对应位图位置置1                jedis.setbit(bitmapKey, offset, true);                // 更新redis中保存的count值                Long uvCount = 0L;    // 初始count值                String uvCountString = jedis.hget(countHashName, countKey);                if( uvCountString != null && !"".equals(uvCountString) )                    uvCount = Long.valueOf(uvCountString);                jedis.hset(countHashName, countKey, String.valueOf(uvCount + 1));                out.collect(new PageViewCount("uv", windowEnd, uvCount + 1));            }        }        @Override        public void close() throws Exception {            jedis.close();        }    }}
eb74d083ddd1f733f1c0333b4ee0e048.gif 693add66c40fa6e7a2fe1ede96dbb39b.png fdfc43abed48f8aa4b9b19e5affbf87a.gif

扫码关注我们

微信号|bigdata_story

B站|大数据那些事

想获取更多更全资料

扫码加好友入群

欢迎各位大佬加入开源共享

共同面对大数据领域疑难问题

来稿请投邮箱:miaochuanhai@126.com

7c58d7f72f30eb611343e74adc51722d.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值