海量数据处理商用短链接生成器平台 - 15

第四十八章 架构师成长-大数据领域知识初探

第1集 短链平台里面涉及到的大数据需求介绍

简介: 短链平台里面涉及到的大数据需求介绍

  • 短链平台业务核心接口【大部分】开发完成

    • 商品展示
    • 账号管理 CRUD
    • 短链管理 CRUD
    • 流量包管理 CRUD
  • 进入新的环节-数据处理-ETL-可视化

    • 需求:

      • 公司电商产品推广、业务活动⻚、广告落地⻚ 缺少实时【数据反馈和渠道效果分析】
    • 百度统计案例

      • https://tongji.baidu.com/web/demo/overview/index?siteId=16847648
    • 短链案例

      • 数据分析业务

在这里插入图片描述

  • 数据可视化案例

在这里插入图片描述

  • 最简单的方式

    • 去mysql数据库 select sum(pv),sum(uv) from table where code =XXX group by xx,返回给用户就行
    • 数据量少的情况完全没问题,但是数据量大的话,估计【上午写代码,下午就办理离职手续】
  • 解决方式

    • 大数据处理体系
  • 商用短链平台-可视化数据项目掌握大数据工程师链路的核心内容

    • 精简后的链路,掌握数据产生-》传输-》处理-》分层-》存储-》可视化展示
第2集 大数据里面的概念讲解-BI商业智能-数据仓库-数据湖

简介:大数据里面的概念讲解-数据仓库-数据湖

  • 商业智能BI (Business Intelligence,简称BI)
    • 用来帮助企业更好地利用数据提高决策质量的技术集合,是从大量的数据中钻取信息与知识的过程。
    • 一套完整的解决方案,可以将来自企业的不同业务系统的数据,提取出有用的数据进行整合清洗-分析和处理,利用合适的查询和分析工具快速、准确地为企业提供报表展现与分析,为企业提供决策支持
    • 最终展现给用户的信息就是可视化报表,大屏数据
    • 基于可视化报表大屏数据 进行分析,发现问题,并去做决策【数据 驱动业务】
    • 数据可视化、数据分析、数据仓库和大数据等词汇时会有的摸不着头脑
    • 例子
老王和Anna小姐姐 开了小卖部, Anna小姐姐每天用Excel来处理销售和经营的数据,看营收和利润。

老王想发财,因此决定更深入了解市场,消费者偏好等,把店铺里的销售和经营、客流量、季节等数据弄到一起,弄了柱状图和饼状图进行可视化分析。 就能发现哪个商品最多人买,摆放什么位置,什么折扣,能更吸引消费者。

然后老王弄了一个程序,自动收集了全部excel数据放到一起,然后进行自动抽取分析存储【数据仓库】,
并生成图形报告【数据可视化】定时发给Anna小姐姐,来进行决策,

这种支持管理人员,发现数据做决策的系统,我们就称为“商业智能系统”
  • 总结
    • 商业智能BI 是一系列工具产品和技术的集合,如数据仓库技术、ETL技术、OLAP技术、前端工具等
    • 目的是为了提升数据价值、辅助企业决策
    • 业务系统与商业智能系统的区别
      • 业务系统保证企业日常运营,商业智能系统通过辅助决策,提高企业的运营能力水平

什么是数据仓库 Data Warehouse

  • 为了便于多维分析和多角度展现,将其数据按特定的模式进行存储而建立的数据库,数据仓库中的数据是细节的,集成的,面向主题的,是以 OLAP系统为分析目的,

  • 是存储和管理一个或多个主题数据的集合,支持管理决策分析,有针对性抽取的结构化历史数据,能够生成各类报表

  • 将来自不同来源的结构化数据聚合起来,用于业务智能领域的比较和分析(BI商业智能),数据仓库是包含多种数据的存储库

  • 数据仓库有两个局限

    • 一是只可以解决预先想到的问题, 需要提前建模
    • 二是数据已经被多次处理过,无法看见其最初状态
  • 什么是数据湖 Data Lake

    • 存储任何形式(包括结构化和非结构化)和任何格式(包括文本、音频、视频和图像)的原始数据
    • 数据不需要提前进行定义,在准备使用数据时再定义,提高了最高的灵活性与可扩展性
    • 适合使用机器学习和深度学习进行使用,比如数据挖掘和数据分析,以及提取非结构化数据
    • 一个新的概念,但落地还很多问题需要解决
  • 总结:在企业中两者的作用是互补的,不能认为数据湖的出现是为了取代数据仓库

第3集 互联网项目-常见数据可视化分析链路-架构图讲解

简介:互联网项目-常见数据可视化分析链路架构图讲解

  • 出发点
    • 数据从哪里来,数据到哪里去

在这里插入图片描述

  • 通用微服务+数据仓库详细链路
    • 链路存在重叠,意思是多种方式都是可以的实现
    • 短链平台的数据是其中一个链路

在这里插入图片描述

  • 数据采集传输:Flume、Kafka、Canal、Maxwell、Sqoop、Logstash,DataX
  • 数据存储:MySql、ClickHouse、HDFS、HBase、Redis
  • 数据计算:Hive、Spark、Flink、Storm
  • 数据查询:Presto、Kylin、Druid
  • 数据可视化:Echarts、Superset、DataV
第4集 大数据-ETL和数据仓库建设分层

简介:大数据-ETL和数据仓库建设分层

  • 什么是ETL(Extract-Transform-Load)

    • 抽取(extract)、转换(transform)、加载(load)缩写
    • ETL一词较常用在数据仓库,但其对象并不限于数据仓库
    • 将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据, ETL是BI(商业智能)项目重要的一个环节
  • 数仓分层介绍

在这里插入图片描述

为什么要分层

  • 开发者都希望自己的数据能够有顺序地流转,方便排查问题
  • 复杂问题简单化
    • 复杂任务分层处理,每层定位职责不一样,每个层只解决特定的问题
  • 减少重复开发量
    • 规范数据分层,开发通用的中间层,可以极大地减少重复计算的工作
  • 总结:提效+懒人的智慧,主要是增加数据计算的复用 性,每次新增加统计需求时,不用从原始数据进行计算,而是从半成品继续加工,从而更快更省成本

第四十九章 商用短链平台数据可视化-埋点采集讲解

第1集 数据从哪里来-常见数据埋点采集方案介绍

简介: 数据从哪里来-常见数据埋点采集方案介绍

  • 解决问题:数据从哪里来

在这里插入图片描述

  • 什么是数据埋点
    • 对网站、App、进行业务数据采集,包括用户行为数据及其他实际需要的数据进行采集上报
    • 方便分析用户行为、提高用户体验,通过这些采集的数据就可以进行分析并辅助公司企业做决策
  • 常见埋点方式
    • 代码埋点
      • 编写埋点代码,通过代码进行控制,前端、后端、App客户端代码,需要埋点的逻辑通过sdk函数调用,上报数据
    • 可视化埋点
      • 通用采集SDK,项目只需要引入埋点采集sdk。分析人员通过分析平台进行操作,对可交互的页面元素(如:图片、按钮、链接等)直接在界面上进行操作实现数据埋点,下发采集代码生效的埋点方式。
      • 所见即所得,使用者只需在其可视化埋点页面上,点击想要监测的元素,编辑名字编号等,埋点就完成了
      • 缺点
        • 存在滞后性,每次调整埋点后需要应用重新发版才可以看到数据,也可以通过配置中心动态下发解决
        • 相对生硬,满足不了全部数据采集,比如编码规范不统一、无法定位元素等,或者需要调用后台接口的数据等
          • 比如看一个视频,点击播放一个视频,交互行为就是一个播放,但播放的背后还想知道这个视频的名字、类别、作者、评级等信息就获取不了
    • 全埋点|无埋点
      • 对应用上的所有的可交互事件元素进行解析,对页面上所有的用户操作行为进行监听,当有操作行为(交互事件)发生时,监测工具会进行记录并上报
      • 会记录全部用户行为事件并上报,采集量过大
      • 缺点 满足不了全部数据采集,比如编码规范不统一、无法定位元素等,或者需要调用后台接口的数据等
第2集 埋点数据分类和短链平台-数据采集链路

简介: 埋点数据分类和短链平台-数据采集链路

  • 日志采集分类

    • 事件

      • 曝光事件:1个item 或者1个页面被展示出来,就称作曝光

      • 点击事件:点击某个页面、按钮

    • 属性

      • 打开 App 手机型号、网络制式、App版本信息 等公参信息
      • 浏览器所属的客户端信息,用户网络ip、地理位置信息等
  • 埋点数据上报时机分两种

    • 方式一,在离开该页面时上传在这个页面产生的所有数据

      • 优点,批量上传,服务器接收数据压力小
      • 缺点,数据有延迟
    • 方式二,每个事件动作数据产生后马上发送

      • 优点,数据没延迟,实时看到数据
      • 缺点,对服务器接压力比较大,请求频繁
  • 短链平台采用方式

    • 访问短链码
    • 记录日志-打印控制台(方便排查)
    • 发送Kafka(本身异步发送)

在这里插入图片描述

第3集 短链平台-数据可视化整体链路讲解和命名规范

简介: 短链平台-数据可视化整体链路讲解

  • 短链平台整体链路

  • 数据分层处理概述

数据分层分层描述数据生成计算工具存储
ODS原生数据,短链访问基本信息SpringBoot生成Kafka
DWD对 ODS 层做数据清洗和规范化,新老访客标记等FlinkKafka
DWM对DWD数据进一步加工补齐数据,独立访客统计,操作系统/ip/城市,做宽表Flinkkafka
DWS对DWM进行处理,多流合并,分组|聚合|开窗|统计,形成主题宽表FlinkClickHouse
ADS从ClickHouse中读取数据,根据需求进行筛选聚合,可视化展示ClickHouseSqlweb可视化展示
  • 命名规范
    • ODS层命名为ods_表名|主题名
    • DWD层命名为dwd_表名|主题名
    • DWM层命名为dwm_表名|主题名
    • DWS层命名为dws_表名|主题名

第五十章 商用短链平台-数据埋点采集开发实战

第1集 Docker容器化部署Kafka+Zookeeper实战

简介: Docker容器化部署Kafka+Zookeeper实战

  • 部署zk
docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
  • 部署kafka
docker run -d --name class_kafka \
-p 9092:9092 \
-e KAFKA_BROKER_ID=0 \
--env KAFKA_HEAP_OPTS=-Xmx256M \
--env KAFKA_HEAP_OPTS=-Xms128M \
-e KAFKA_ZOOKEEPER_CONNECT=172.18.123.230:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://120.79.150.146:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka:2.13-2.7.0
  • 找kafka的Container ID,进入容器内部,创建topic
    • docker exec -it ${CONTAINER ID} /bin/bash
    • 进入kafka默认目录 cd /opt/kafka 就跟一般的kafka一样了
#创建一个主题:    
kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic mykafka
  • 阿里云部署ZK+Kafka,本地开发注意事项
    • 网络安全组记得开发端口 9092、2181
第2集 短链访问数据 日志采集开发实战之Logback讲解

简介: 短链访问数据日志采集开发实战之Logback讲解

  • 需求

    • 控制台输出访问日志,方便测试
    • 业务数据实际输出到kafka
    • 常用的框架 log4j、logback、self4j等
  • log4j、logback、self4j 之间有啥关系

    • SLF4J(Simple logging Facade for Java) 门面设计模式 |外观设计模式
      • 把不同的日志系统的实现进行了具体的抽象化,提供统一的日志使用接口
      • 具体的日志系统就有log4j,logback等;
      • logback也是log4j的作者完成的,有更好的特性,可以取代log4j的一个日志框架, 是slf4j的原生实现
      • log4j、logback可以单独的使用,也可以绑定slf4j一起使用
    • 编码规范建议不直接用log4j、logback的API,应该用self4j, 日后更换框架所带来的成本就很低
  • Logback知识点回顾

    • appender是记录日志的方式
    • logger是配置某个包或者类采用哪几个appender记录日志,比如控制台/文件;
    • root是默认配置的输出日志,除了logger自定义配置外的其他类输出日志的方式,也是有级别和多个appender
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_HOME" value="./data/logs/link" />


    <!--采用打印到控制台,记录日志的方式-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    </appender>


    <!-- 采用保存到日志文件 记录日志的方式-->
    <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/link.log</file>
    </appender>




    <!-- 指定某个类单独打印日志 -->
    <logger name="net.class.service.impl.LogServiceImpl"
            level="INFO" additivity="false">
        <appender-ref ref="rollingFile" />
        <appender-ref ref="console" />
    </logger>


    <root level="info" additivity="false">
        <appender-ref ref="console" />
    </root>


</configuration>


第3集 短链访问数据日志采集开发实战之Logback配置实战

简介: 短链访问数据日志采集开发实战之Logback配置实战

  • Logback配置打印控制台(方便排查数据)
  • 配置文件新增logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_HOME" value="./data/logs/link" />


<!--采用打印到控制台,记录日志的方式-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>


    <!-- 采用保存到日志文件 记录日志的方式-->
    <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/link.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/link-%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder> 
    </appender>




    <!-- 指定某个类单独打印日志 -->
    <logger name="net.class.service.impl.LogServiceImpl"
            level="INFO" additivity="false">
        <appender-ref ref="rollingFile" />
        <appender-ref ref="console" />
    </logger>


    <root level="error" additivity="false">
        <appender-ref ref="console" />
    </root>


</configuration>
第4集 数据日志采集开发实战之发送Kafka消息

简介: 数据日志采集开发实战之发送Kafka消息

  • 补充

    • Logback配置可以看账号服务xxl-job的配置
    • 调整格式都行,看业务需求配置日志格式
  • Kafak依赖和配置

#----------kafka配置--------------
spring.kafka.bootstrap-servers=120.79.150.146:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
  • 编码实战
@Service
@Slf4j
public class LogServiceImpl implements LogService {


    private static final String TOPIC_NAME = "ods_link_visit_topic";


    @Autowired
    private KafkaTemplate kafkaTemplate;



    //在客户解析短链接口是调用这个方法,下面测试的时候,自己实现 
    @Override
    public JsonData recordShortLinkLog(HttpServletRequest request, String shortLinkCode,Long accountNo) {


        String ip = CommonUtil.getIpAddr(request);
        Map<String,String> headerMap = CommonUtil.getAllRequestHeader(request);


        Map<String,String> availableMap = new HashMap<>();


        availableMap.put("user-agent",headerMap.get("user-agent"));
        availableMap.put("referer",headerMap.get("referer"));
        availableMap.put("accountNo",accountNo.toString());


        LogRecord logRecord = LogRecord.builder()
                //日志类型
                .event(LogTypeEnum.SHORT_LINK_TYPE.name())
                //日志内容
                .data(availableMap)
                //客户端ip
                .ip(ip)
                //时间时间
                .ts(CommonUtil.getCurrentTimestamp())
                //业务唯一id
                .bizId(shortLinkCode).build();




        String jsonLog = JsonUtil.obj2Json(logRecord);


        //打印控制台
        log.info(jsonLog);


        //发送kafka
        kafkaTemplate.send(TOPIC_NAME,jsonLog);


        //存储Mysql 测试数据 TODO


        return JsonData.buildSuccess();
    }
}
第5集 日志采集开发实战之发送和消费Kafka链路测试

简介: 日志采集开发实战之发送和消费Kafka链路测试

  • 链路测试
    • 访问短链
    • 控制台打印日志
    • 发送消息到Kafka
    • Kafka消费消息
  • Kafka命令
创建topic
./kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic ods_link_visit_topic

查看topic
./kafka-topics.sh --list --zookeeper 172.18.123.230:2181

删除topic
./kafka-topics.sh --zookeeper 172.18.123.230:2181 --delete --topic ods_link_visit_topic

消费者消费消息
./kafka-console-consumer.sh --bootstrap-server 120.79.150.146:9092 --from-beginning --topic ods_link_visit_topic

生产者发送消息
./kafka-console-producer.sh --broker-list 120.79.150.146:9092  --topic ods_link_visit_topic

第五十一章 Flink实时计算项目搭建和ODS层处理实战

第1集 Flink实时计算项目搭建和依赖配置引入

简介: Flink实时计算项目搭建和依赖配置引入

  • 添加依赖
    <properties>
        <encoding>UTF-8</encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>

        <scala.version>2.12</scala.version>
        <flink.version>1.13.1</flink.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

        <!--flink客户端-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <!--scala版本-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-scala_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!--java版本-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <!--streaming的scala版本-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!--streaming的java版本-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>


        <!--Flink web ui-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-runtime-web_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>


        <!--使用 RocksDBStateBackend 需要加依赖-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-statebackend-rocksdb_${scala.version}</artifactId>
            <version>1.13.1</version>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

        <!--flink cep依赖包-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-cep_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <!--redis connector-->
        <dependency>
            <groupId>org.apache.bahir</groupId>
            <artifactId>flink-connector-redis_2.11</artifactId>
            <version>1.0</version>
        </dependency>

        <!--kafka connector-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>


        <!--日志输出-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>runtime</scope>
        </dependency>

        <!--json依赖包-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>


        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-jdbc_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>

    </dependencies>


    <!-- 指定仓库位置,先从aliyun找,找不到再从apache仓库找 -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
        <repository>
            <id>apache</id>
            <url>https://repository.apache.org/content/repositories/snapshots/</url>
        </repository>
    </repositories>

    <build>
        <finalName>xdclass-flink</finalName>
        <plugins>

            <!--默认编译版本比较低,所以用compiler插件,指定项目源码的jdk版本,编译后的jdk版本和编码,-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${file.encoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  • 目录结构划分

在这里插入图片描述

  • 入口类编写
@Slf4j
public class DwdShortLinkLogApp {




    public static void main(String[] args) throws Exception {




        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);


        DataStream<String> ds = env.socketTextStream("127.0.0.1", 8888);


        ds.print();


        env.execute();




    }




}
第2集 代码复用性提升-Kafka工具类封装开发实战

简介: 代码复用性提升-Kafka工具类封装开发实战

  • 需求背景
    • Flink的source和sink跟kafka交互频繁,提升代码复用性,封装工具类
    • 每次变动的只有topic和group,其他的broker等基本属性是固定不变的
  • 编码实战


   /**
     * ODS层 kafka相关配置
     */
public static final String SOURCE_TOPIC = "ods_link_visit_topic";
public static final String GROUP_ID = "dwd_short_link_group";




public class KafkaUtil {


    /**
     * kafka broker地址
     */
    private static String KAFKA_SERVER = null;


    static{
        Properties properties = new Properties();
        // 使用ClassLoader加载properties配置文件生成对应的输入流
        InputStream in = KafkaUtil.class.getClassLoader().getResourceAsStream("application.properties");
        // 使用properties对象加载输入流
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取key对应的value值
        KAFKA_SERVER = properties.getProperty("kafka.servers");
    }


    /**
     * 获取flink kafka消费者
     * @param topic
     * @param groupId
     * @return
     */
    public static FlinkKafkaConsumer<String> getKafkaConsumer(String topic, String groupId) {
        //Kafka连接配置
        Properties props = new Properties();
        props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_SERVER);
        return new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), props);
    }




    /**
     * 获取flink kafka生产者
     * @param topic
     * @return
     */
    public static FlinkKafkaProducer<String> getKafkaProducer(String topic) {
        return new FlinkKafkaProducer<>(KAFKA_SERVER, topic, new SimpleStringSchema());
    }
}
  • 配置文件 resources/application.properties
#----------kafka配置--------------
kafka.servers=120.79.150.146:9092
第3集 短链数据访问和Flink消费Kafka数据链路测试

简介: 短链数据访问和Flink消费Kafka数据链路测试

  • 访问短链

  • 发送数据

  • Flink消费打印

  • 留存数据

    • 通过nc -lk测试
    nc -lk 是一个在UNIX和Linux系统上常用的命令,用于创建一个监听特定端口的“回显服务器”。这个命令的各个组成部分的含义如下:
    
    nc 是 "netcat" 的缩写,是一个用于处理网络连接的强大工具。
    -l 选项使 nc 进入监听模式,等待其他机器的连接。
    -k 选项使 nc 保持打开状态,等待新的连接。
    当你运行 nc -lk [port] 时,nc 会监听指定的端口,并等待其他机器的连接。一旦有机器连接到这个端口,nc 会将接收到的数据原封不动地回显(发送)回去。这常常用于测试网络连接和端口可达性,或者用于简单的聊天应用。
    
    例如,运行 nc -lk 12345 会在端口12345上启动一个回显服务器。任何连接到这个端口的机器都会看到自己的数据被回显到它自己的终端上。
    
    {"ip":"141.123.11.31","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}
    
  • Kafka命令

创建topic
./kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic ods_link_visit_topic


查看topic
./kafka-topics.sh --list --zookeeper 172.18.123.230:2181


删除topic
./kafka-topics.sh --zookeeper 172.18.123.230:2181 --delete --topic ods_link_visit_topic


消费者消费消息
./kafka-console-consumer.sh --bootstrap-server 120.79.150.146:9092 --from-beginning --topic ods_link_visit_topic


生产者发送消息
./kafka-console-producer.sh --broker-list 120.79.150.146:9092  --topic ods_link_visit_topic
第4集 ODS层处理-Flink实时标记-短链新老访客统计需求

简介: ODS层处理-Flink实时标记-短链新老访客统计需求

在这里插入图片描述

需求

  • 从ODS读取数据,处理后存储到DWD层,做哪些职责?

  • 需要识别标记出短链的新老访客

  • 说明

    • 新老访客可以有天、周、月维度
    • 我们只做天维度的新老访客标记
  • 什么是ETL(Extract-Transform-Load)

    • 抽取(extract)、转换(transform)、加载(load)缩写
    • ETL一词较常用在数据仓库,但其对象并不限于数据仓库
    • 将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据, ETL是BI(商业智能)项目重要的一个环节
  • 思路(ETL的流程)

    • 通过设备唯一标识,服务端进行【天维度】状态存储,标记新老访客
    • 详情
      • 需要生成唯一设备标识
      • 利用Flink的状态存储 ValueState
第5集 浏览器指纹介绍和短链访客唯一标识设计开发实战

简介: 浏览器指纹介绍和短链访客唯一标识设计开发实战

  • 日志分析
{"ip":"141.123.11.31","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}
  • 浏览器指纹(设备终端指纹)
    • 是通过浏览器对网站可见的配置来匿名识别浏览器,从硬件、操作系统、浏览器、网络等维度进行分析
    • 浏览器指纹现在也比较多大厂都在使用:反欺诈,防止刷票脚本、机器人、异地可疑登录提示等,在注重用户隐私的情况下,进行一些数据分析
    • 比如阿里云、淘宝等账号登录,常用设备和非常用设备登录是不一样的验证级别
    • 维度参考
      • MAC地址,网络中唯一标识一个网卡,固定化
      • IP地址
      • Http的Cookie
      • Http的UserAgent,包括了客户使用的操作系统及版本、CPU 类型、浏览器及版本、渲染引擎等
      • Canvas(HTML5画布),渲染文字,可以转出值
  • 唯一标识ID设计
    • 用户通过浏览器访问页面的同时产生一个用户唯一标识(ID)
    • 考虑:唯一性和稳定性
    • 最终参考的维度:ip+UserAgent地址(会有偏差但是不多)
  • 编码实战
//2、格式进行转换、生成设备唯一标识和过滤 string->json
SingleOutputStreamOperator<JSONObject> jsonDS = ds.flatMap(new FlatMapFunction<String, JSONObject>() {
            @Override
            public void flatMap(String value, Collector<JSONObject> out) throws Exception {
                JSONObject jsonObject = JSON.parseObject(value);
                //生成web端设备唯一标识
                String udid = getDeviceId(jsonObject);
                jsonObject.put("udid", udid);
                out.collect(jsonObject);


            }
        });
        
        
     /**
     * 生成设备唯一标识
     *
     * @param jsonObject
     * @return
     */
    public static String getDeviceId(JSONObject jsonObject) {


        Map<String, String> map = new TreeMap<>();
        try {
            map.put("ip", jsonObject.getString("ip"));
            map.put("event", jsonObject.getString("event"));
            map.put("bizId", jsonObject.getString("bizId"));


            JSONObject dataJsonObj = jsonObject.getJSONObject("data");
            map.put("userAgent", dataJsonObj.getString("user-agent"));
            String deviceId = DeviceUtil.geneWebUniqueDeviceId(map);
            return deviceId;


        } catch (Exception e) {
            log.error("生产唯一deviceId异常:{}", jsonObject);
            return null;
        }


    }












//设备工具类
public class DeviceUtil {


    /**
     * 生成web设备唯一ID
     * @param map
     * @return
     */
    public static String geneWebUniqueDeviceId(Map<String,String> map){
        String deviceId = MD5(map.toString());
        return deviceId;
    }






    /**
     * MD5加密
     *
     * @param data
     * @return
     */
    public static String MD5(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
            return sb.toString().toUpperCase();
        } catch (Exception exception) {
        }
        return null;


    }


}
第6集 ODS层处理-短链访问 来源分布统计-分析开发实战

简介: ODS层处理-短链访问来源分布统计-分析开发实战

在这里插入图片描述

  • 需求

    • 短链访问来源(URL自己加参数的除外,比如utm_source)
    统计下哪些渠道推广比较好,比如投放广告,统计各个渠道的点击率来源统计
    
    
    例子:
      老王推广baidu.com,对应的短链 是g1.fit/adcdeft
      
    付费广告(花钱)
      公众号广告
      抖音首屏广告
      B站广告
      知乎广告
    
    
    免费广告(没钱)
      CSDN发文文章
      博客园发文章
      开源中国发文章
      知乎发文章 
    
    • 例子:https://tongji.baidu.com/web/demo/source/all?viewType=site&siteId=16847648

    在这里插入图片描述

  • 开发编码

    /**
     * 获取referer信息
     * @param jsonObject
     * @return
     */
    public static String getReferer(JSONObject jsonObject ){
        JSONObject dataJsonObj = jsonObject.getJSONObject("data");


        if(dataJsonObj.containsKey("referer")){


            String referer = dataJsonObj.getString("referer");
            if(StringUtils.isNotBlank(referer)){
                try {
                    URL url = new URL(referer);
                    return url.getHost();
                } catch (MalformedURLException e) {
                    log.error("提取referer失败,{}",e);
                }
            }


        }
        return "";
    }
    
//增加数据
jsonObject.put("referer",referer);
 return url.getHost();
第7集 ODS层处理-短链访问新老访客识别开发实战

简介: ODS层处理-短链访问新老访客识别开发实战

在这里插入图片描述

  • 编码实现
 //3、新老访客标记处理,根据udid对访问日志进行分组,天维度进行区分新老访客
KeyedStream<JSONObject, String> udidKeyStream = jsonDS.keyBy(
                data -> data.getString("udid")
);
  • 添加工具类
public class TimeUtil {


    /**
     * 默认日期格式
     */
    private static final String DEFAULT_PATTERN = "yyyy-MM-dd";


    /**
     * 默认日期格式
     */
    private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER  = DateTimeFormatter.ofPattern(DEFAULT_PATTERN);


    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();




    /**
     * LocalDateTime 转 字符串,指定日期格式
     * @param time
     * @param pattern
     * @return
     */
    public static String format(LocalDateTime localDateTime, String pattern){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        String timeStr = formatter.format(localDateTime.atZone(DEFAULT_ZONE_ID));
        return timeStr;
    }




    /**
     * Date 转 字符串, 指定日期格式
     * @param time
     * @param pattern
     * @return
     */
    public static String format(Date time, String pattern){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        String timeStr = formatter.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
        return timeStr;
    }


    /**
     *  Date 转 字符串,默认日期格式
     * @param time
     * @return
     */
    public static String format(Date time){


        String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
        return timeStr;
    }


    /**
     * timestamp 转 字符串,默认日期格式
     *
     * @param time
     * @return
     */
    public static String format(long timestamp) {
        String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID));
        return timeStr;
    }




    /**
     * 字符串 转 Date
     *
     * @param time
     * @return
     */
    public static Date strToDate(String time) {
        LocalDateTime localDateTime = LocalDateTime.parse(time, DEFAULT_DATE_TIME_FORMATTER);
        return Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());


    }




}




  • 自定义映射函数、

    在 Apache Flink 中,ValueState 是一个状态接口,用于存储单一的值。当你在一个 Flink 作业中为某个 key 聚合数据时(例如,通过使用 GroupedStream),Flink 会为每个不同的 key 维护一个状态。这意味着如果你根据 udid 分组数据,并为每个 userid 使用 ValueState,那么每个 udid 会有自己的 ValueState 实例。
    
    因此,答案是:对于每个不同的 userid,其对应的 ValueState 的值是独立的,不是共享的。如果你更改了某个 udid 的 ValueState 的值,这个更改不会影响到其他 udid 的状态。
    
 public static class VisitorMapFunction extends RichMapFunction<JSONObject, String>{


        //记录用户udid访问状态
        private ValueState<String> newDayVisitorState;


        @Override
        public void open(Configuration parameters) throws Exception {
            //对状态以及日期格式进行初始化
            newDayVisitorState = getRuntimeContext().getState(
                    new ValueStateDescriptor<String>("newDayVisitorState", String.class)
            );
        }


        /**
         * 新老访客和uv 对比,如果都是天维度的话就一样
         *  新老访客可以是指1天内、1个月内维度
         *  uv可以是指 月独立、日独立访客
         *
         * @param jsonObj
         * @return
         * @throws Exception
         */
        @Override
        public String map(JSONObject jsonObj) throws Exception {


            //获取之前是否有访问日期
            String beforeDateState = newDayVisitorState.value();


            //获取访问时间戳
            Long ts = jsonObj.getLong("ts");
            String currentDateStr = TimeUtil.formatYMD(ts);




            //如果状态不为空,并且状态日期和当前日期不相等,则是老访客
            if (StringUtils.isNotBlank(beforeDateState)) {
                //判断是否为同一天数据,一样则是老访客
                if (beforeDateState.equals(currentDateStr)) {
                    jsonObj.put("is_new", 0);
                    System.out.println("老访客 "+currentDateStr);
                } else {
                    //不一样是则是新访客,更新访问日期
                    jsonObj.put("is_new", 1);
                    newDayVisitorState.update(currentDateStr);
                    System.out.println("新访客 "+currentDateStr);
                }
            } else {
                //如果状态为空,则之前没访问过,是新用户,
                jsonObj.put("is_new", 1);
                newDayVisitorState.update(currentDateStr);
                System.out.println("新访客 "+currentDateStr);
            }
            return jsonObj.toJSONString();
        }
    }
第8集 短链访问新老访客识别写入DWD层实战和链路测试

简介: 短链访问新老访客识别开发写入DWD层实战

  • 编码实现
    /**
     * 写出到dwd层
     */
public static final String SINK_TOPIC = "dwd_link_visit_topic";




//4、标记新老访客
SingleOutputStreamOperator<String> jsonDSWithVisitState = udidKeyStream.map(new VisitorMapFunction());
jsonDSWithVisitState.print("新老访客ods:");


//将写sink到dwd层,kafka 存储
FlinkKafkaProducer<String> kafkaProducerSink = KafkaUtil.getKafkaProducer(SINK_TOPIC);
jsonDSWithVisitState.addSink(kafkaProducerSink);
  • 链路测试

  • Kafka命令

//测试数据
{"ip":"141.123.11.31","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}






创建topic
./kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic ods_link_visit_topic


查看topic
./kafka-topics.sh --list --zookeeper 172.18.123.230:2181


删除topic
./kafka-topics.sh --zookeeper 172.18.123.230:2181 --delete --topic ods_link_visit_topic


消费者消费消息
./kafka-console-consumer.sh --bootstrap-server 120.79.150.146:9092 --from-beginning --topic dwd_link_visit_topic


生产者发送消息
./kafka-console-producer.sh --broker-list 120.79.150.146:9092  --topic ods_link_visit_topic

第五十二章 Flink数仓知识进阶-DWS层数据处理和写出

第1集 数仓里面的概念-宽表-维度表-事实表概念讲解

简介: 数仓里面的概念-宽表-维度表-事实表概念讲解

  • 数仓概念

    • 度量值

      • 可统计次数、个数、金额等,比如订单表中的下单金额
    • 维度表

      • 对事实的描述信息,每一张维表对应现实世界中的一个对象或者概念,如:用户、商品、日期、地区维度

      • 比如分析产品销售情况,可以选择按商品类型,销售区域等等来分析

    • 事实表(每行代表一个"业务事件")

      • 联系事实与维度表的数字度量值,事实数据表 包含 描述业务内特定事件的数据
      • 是数据聚合后依据【某些维度】生成的结果表
      • 事实表里存放了能体现实际数据或详细数据,一般由维度编码和事实数据组成
    • 例子一

      • 老王,在24年11月11号,在天猫超市买了100件情趣衣服花了800元,10盒霸王生发洗发水花了400元
      • 维度:时间、老王用户、情趣商品、霸王洗发水、
      • 事实:100件、800元,10盒、400元

在这里插入图片描述

  • 例子二

    • 电商业务领域

      • 维度表: user用户表、product商品表、coupon优惠券表、provice地理信息表

      • 事实表:order_info订单表、order_detail订单明细表、product_comment商品评论表

      • 商品表里存放了商品类型,商品编码,商品名字等等这些都属于商品的属性,这张就是张维度表

      • 订单表里存放了商品的销售数量、销售额等等这张表就是张事实表

      • 某地区商品的销量,是从地区这个角度观察商品销量的

      • 事实表就是销量表,维度表就是地区表

  • 结论

    • 站在维度的角度去看事实表,看事实表的度量值
    • 事实表就是你要关注的内容,维度表就是【你观察该事物的角度,是从哪个角度去观察这个内容的,商品角度,地区角度等】
    • 维度是维度建模的基础和灵魂。在维度建模中,将度量称为“事实” , 将环境描述为“维度”。
  • 什么是宽表和窄表

    • 宽表(明细表)
      • 简单讲字段比较多的数据库表,通常是指业务主题相关的指标、维度、属性关联在一起的一张数据库表
      • 把不同的内容都放在同一张表存储,宽表不符合三范式的模型设计规范
      • 尽量满足多维,多度量,遵循维度建模的原则
      • 缺点:数据的大量冗余
      • 优点:减少表关联数量,查询性能的提高,空间换时间
    • 窄表
      • 严格按照数据库设计三范式,尽量减少数据冗余
      • 缺点:做数据分析查询OLAP时,需要大量关联多个表,性能下降
      • 优点:存储省空间,大量数据只存储某个表
  • 什么是数仓建模

    • OLTP中:Mysql数据库建表,表和表之间的关系模型,叫关系建模
    • OLAP中:根据一个事实表为中心进行建表,面向业务分析为主,叫维度建模
    • 我们这个短链的数据分析不属于严格意义上的数仓建模,但很多类似的东西,方便大家学大数据
第2集 Flink实时计算-DWM层业务需求说明

简介: Flink实时计算-DWM层业务需求说明

在这里插入图片描述

  • 数据处理分层说明

    • ODS、DWD和业务关联不大
    • DWM、DWS和业务关联就大,属于轻量级聚合部分数据
  • 业务需求

    • 需要得出短链访问的终端设备分布情况,做出【宽表】
      • 浏览器类型分布 Chrome
      • 操作系统分布 Android
      • 设备类型分布 Mobile、Computer
      • 设备生产厂商 GOOGLE、APPLE
      • 系统版本 Android 10、Intel Mac OS X 10_15_7
    • 例子
    浏览器组getBrowserName :Chrome
    设备操作系统getOs:Android
    系统版本getOSVersion: Android 10
    设备类型getDeviceType:MOBILE
    设备生产厂商getDeviceManufacturer:GOOGLE
    
    
    浏览器组getBrowserName :Firefox
    设备操作系统getOs:Mac OS X
    系统版本getOSVersion: Intel Mac OS X 10.15
    设备类型getDeviceType:COMPUTER
    设备生产厂商getDeviceManufacturer:APPLE
    
    
    浏览器组getBrowserName :Chrome
    设备操作系统getOs:Mac OS X
    系统版本getOSVersion: Intel Mac OS X 10_15_7
    设备类型getDeviceType:COMPUTER
    设备生产厂商getDeviceManufacturer:APPLE
    
  • 问题来了,怎么得到上面的信息呢?

第3集 浏览器头User-Agent提取工具UserAgentUtils讲解

简介: 浏览器头User-Agent提取工具UserAgentUtils讲解

  • UserAgentUtils工具介绍
    • 一个用来解析 User-Agent 字符串的 Java 类库,
    • 可以识别 浏览器名字,浏览器组,浏览器类型,浏览器版本,浏览器的渲染引擎,android和ios设备的类型
    • 超过150种不同的浏览器; 7种不同的浏览器类型; 9种不同的Web应用
    • 超过60种不同的操作系统; 6种不同的设备类型; 9种不同的渲染引擎;
  • 工具类依赖
 <!-- https://mvnrepository.com/artifact/eu.bitwalker/UserAgentUtils -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.21</version>
        </dependency>
  • 开发实战


@Slf4j
public class UtilTest {




    @Test
    public void testUserAgentUtil(){


        //browserName=Chrome,os=Android,manufacture=Google Inc.,deviceType=Mobile
        //String userAgentStr = "Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64";


        //browserName=Chrome,os=Mac OS X,manufacture=Apple Inc.,deviceType=Computer
        //String userAgentStr = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36";


        //browserName=Chrome,os=Android,manufacture=Google Inc.,deviceType=Mobile
        String userAgentStr = "Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64";




        UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr);
        Browser browser = userAgent.getBrowser();
        OperatingSystem operatingSystem = userAgent.getOperatingSystem();


        String browserName = browser.getGroup().getName();
        String os = operatingSystem.getGroup().getName();
        String manufacture = operatingSystem.getManufacturer().getName();
        String deviceType = operatingSystem.getDeviceType().getName();




        System.out.println("browserName="+browserName+",os="+os+",manufacture="+manufacture+",deviceType="+deviceType);






    }




}
  • 测试数据
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36




Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64
第4集 浏览器头User-Agent提取工具类封装开发实战

简介: 浏览器头User-Agent提取工具类封装开发实战

  • 编码实战




    /**
     *
     *
     * 浏览器名称
     * @param userAgent
     * @return  Firefox、Chrome
     */
    public static String getBrowserName(String userAgent) {
        Browser browser =  getBrowser(userAgent);
        String browserGroup = browser.getGroup().getName();
        return browserGroup;
    }


    /**
     * 获取deviceType
     * @param userAgent
     *
     * @return  MOBILE、COMPUTER
     */
    public static String getDeviceType(String userAgent) {
        OperatingSystem operatingSystem =  getOperatingSystem(userAgent);
        String deviceType = operatingSystem.getDeviceType().toString();
        return deviceType;
    }






    /**
     * 获取os:Windows/ios/Android
     * @param userAgent
     * @return
     */
    public static String getOS(String userAgent) {
        OperatingSystem operatingSystem =  getOperatingSystem(userAgent);
        String os = operatingSystem.getGroup().getName();
        return os;
    }




    /**
     * 获取device的生产厂家
     *
     * @param userAgent
     * @return GOOGLE、APPLE
     */
    public static String getDeviceManufacturer(String userAgent) {
        OperatingSystem operatingSystem =  getOperatingSystem(userAgent);


        String deviceManufacturer = operatingSystem.getManufacturer().toString();
        return deviceManufacturer;
    }








    /**
     * 操作系统版本
     * @param userAgent
     * @return Android 1.x、Intel Mac OS X 10.15
     */
    public static String getOSVersion(String userAgent) {
        String osVersion = "";
        if(StringUtils.isBlank(userAgent)) {
            return osVersion;
        }
        String[] strArr = userAgent.substring(userAgent.indexOf("(")+1,
                userAgent.indexOf(")")).split(";");
        if(null == strArr || strArr.length == 0) {
            return osVersion;
        }


        osVersion = strArr[1];
        return osVersion;
    }






    /**
     * 获取浏览器对象
     * @param request
     * @return
     */
    private static Browser getBrowser(String agent) {
        UserAgent userAgent = UserAgent.parseUserAgentString(agent);
        Browser browser = userAgent.getBrowser();
        return browser;
    }




    /**
     * 获取操作系统对象
     * @param userAgent
     * @return
     */
    private static OperatingSystem getOperatingSystem(String userAgent) {
        UserAgent agent = UserAgent.parseUserAgentString(userAgent);
        OperatingSystem operatingSystem = agent.getOperatingSystem();
        return operatingSystem;
    }
  • 设备信息对象
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeviceInfoDO {


    /**
     * 浏览器名称
     */
    private String browserName;


    /**
     * 操作系统
     */
    private String os;


    /**
     * 系统版本
     */
    private String osVersion;


    /**
     * 设备类型
     */
    private String deviceType;


    /**
     * 设备厂商
     */
    private String deviceManufacturer;




    /**
     * 用户唯一标识
     */
    private String udid;
}
第5集 DWD层处理-短链访问设备信息宽表开发实战

简介: DWD层处理-短链访问设备信息宽表开发实战

  • 编码实战
   /**
     * DWM层 kafka相关配置
     */
    public static final String SOURCE_TOPIC = "dwd_link_visit_topic";
    public static final String GROUP_ID = "dwm_short_link_group";




public static void main(String[] args) throws Exception {




        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);




        //1、获取dwd日志
        FlinkKafkaConsumer<String> source = KafkaUtil.getKafkaConsumer(SOURCE_TOPIC, GROUP_ID);
        DataStreamSource<String> ds = env.addSource(source);




        //2、格式进行转换 补齐设备信息
        SingleOutputStreamOperator<ShortLinkWideDO> deviceWideDS = ds.flatMap(new FlatMapFunction<String, ShortLinkWideDO>() {


            @Override
            public void flatMap(String value, Collector<ShortLinkWideDO> out) throws Exception {
                //需要到 异步查询,归一化设备、地理位置信息
                JSONObject jsonObject = JSON.parseObject(value);


                //设备信息
                DeviceInfoDO deviceInfoDO = getDeviceInfoDO(jsonObject);


                ShortLinkWideDO shortLinkWideDO = ShortLinkWideDO.builder()


                        //短链访问基本信息
                        .visitTime(jsonObject.getLong("ts"))
                        .accountNo(jsonObject.getJSONObject("data").getLong("accountNo"))
                         .code(jsonObject.getString("bizId"))
                        .referer(jsonObject.getString("referer"))
                        .isNew(jsonObject.getInteger("is_new"))
                        .ip(jsonObject.getString("ip"))




                        //设备
                        .browserName(deviceInfoDO.getBrowserName())
                        .os(deviceInfoDO.getOs())
                        .osVersion(deviceInfoDO.getOsVersion())
                        .deviceType(deviceInfoDO.getDeviceType())
                        .deviceManufacturer(deviceInfoDO.getDeviceManufacturer())
                        .udid(deviceInfoDO.getUdid())


                        .build();


                out.collect(shortLinkWideDO);
            }
        });




        deviceWideDS.print("补齐宽表后的数据");


        env.execute();


    }
  • 创建宽表对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ShortLinkWideDO {


    /**
     * 短链压缩码
     */
    private String code;


    /**
     * 租户id
     */
    private Long accountNo;




    /**
     * 访问时间
     */
    private Long visitTime;


    /**
     * 站点来源,只记录域名
     */
    private String referer;




    /**
     * 1是新访客,0是老访客
     */
    private Integer isNew;




//==============DeviceInfoDO==================


    /**
     * 浏览器名称
     */
    private String browserName;


    /**
     * 操作系统
     */
    private String os;


    /**
     * 系统版本
     */
    private String osVersion;


    /**
     * 设备类型
     */
    private String deviceType;


    /**
     * 设备厂商
     */
    private String deviceManufacturer;


    /**
     * 用户唯一标识
     */
    private String udid;




}
第6集 DWD层处理-短链访问-设备信息宽表链路数据测试

简介: DWD层处理-短链访问记录宽表链路数据测试

  • 不同浏览器访问短链
    • http://localhost:8003/026m8O3a
    • 改ip为ip访问(改成自己的ip)
    • http://192.168.0.129:8003/026m8O3a
  • 打印访问记录宽表信息

第五十三章 短链访问数据可视化-地理位置信息解析实战

在这里插入图片描述

第1集 DWM层数据处理-地理位置信息解析需求讲解

简介: DWM层数据处理-地理位置信息解析需求讲解

  • 需求说明
    • IP信息转换为地理位置信息
    • 案例:https://tongji.baidu.com/web/demo/visit/district?siteId=16847648
  • 解决方案
    • 离线
      • 纯真IP库
      • GeoLite2
      • 埃文科技
      • ip2region
        • https://github.com/lionsoul2014/ip2region
    • 在线
      • 百度地图API
        • https://lbsyun.baidu.com/index.php?title=webapi/ip-api
      • 高德地图API
        • https://lbs.amap.com/api/webservice/guide/api/ipconfig
      • 淘宝IP库
      • 阿里云产品
  • 使用链路

在这里插入图片描述

第2集 IP地理位置信息解析-高德API接入指引

简介: IP地理位置信息解析-高德API接入指引

  • 高德开放平台->开发支持->web服务API
    • 地址:https://lbs.amap.com/api/webservice/guide/api/ipconfig
    • 注册
    • 认证
  • IP解析接口测试-Postman
    • https://lbs.amap.com/api/webservice/guide/api/ipconfig
    • 获取字段:省份-城市(不包括国家)
  • 流量分析和使用限制说明
    • 思考点
      • 如果使用频率超过限制了怎么办
      • 流量包业务流量包业务是不是和短链业务类似

在这里插入图片描述

第3集 高频面试题-突破微信支付统一下单每秒600-QPS

简介: 高频面试题-如何突破微信支付统一下单600QPS

  • 很多人面试被问到,在秒杀或者高并发电商业务,微信支付的统一下单接口频率是有限制的,怎么办
    • 微信支付文档里之前是60的QPS频率限制,现在最新的文档是600的QPS频率限制
    • 如何突破这个限制?网上应该搜索不到的答案
    • https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/ico-guide/chapter1_5.shtml

在这里插入图片描述

  • 解决方式

    • 多商户策略,采用负载均衡方式进行操作
    • 记录好用户下单所用的商户信息,也可以预先绑定好
      • 例子
        • 准备5个商户号, 用户下单根据用户id取模是采用哪个商户进行调用
        • 统一下单、退款、查询订单状态等就能分摊瞬时压力,也是固定到使用对应的商户进行操作
        • 根据API进行控制好频率+监控
    • 秒杀类业务,常规都是高并发锁定库存生成订单,然后支付是可以靠前端页面做离散支付的一定时间内错峰
  • 那Ip解析地理位置信息是不是也类似

    • 对接多个在线解析平台,比如百度、高德、腾讯等
    • 做好统一的地理位置信息编码即可
第4集 DWM层处理-用户访问IP解析开发实战《上》

简介: DWM层处理-用户访问IP解析开发实战

  • 添加maven依赖

    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    
  • 补齐宽表数据DO

   /**
     * 国家
     */
    private String country;


    /**
     * 省份
     */
    private String province;


    /**
     * 城市
     */
    private String city;


    /**
     * 运营商
     */
    private String isp;


    /**
     * 访问ip
     */
    private String ip;
  • 自定义函数
@Slf4j
public class LocationMapFunction extends RichMapFunction<ShortLinkWideDO, String> {


    private CloseableHttpClient httpClient;


    /**
     * ip位置解析
     */
    private static final String IP_PARSE_URL = "https://restapi.amap.com/v3/ip?ip=%s&output=json&key=a71504beedbd521e136ac5b9cb6e76c9";


    @Override
    public void open(Configuration parameters) throws Exception {
        httpClient = createCustomHttpClient();
    }


    @Override
    public void close() throws Exception {
        if (httpClient != null) {
            httpClient.close();
        }
    }


    @Override
    public String map(ShortLinkWideDO value) throws Exception {




        String ip = value.getIp();
        String url = String.format(IP_PARSE_URL, ip);


        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {


            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity, "UTF-8");
                JSONObject locationObj = JSON.parseObject(result);


                log.info(result);
                String province = locationObj.getString("province");
                String city = locationObj.getString("city");
                value.setProvince(province);
                value.setCity(city);
                return JSON.toJSONString(value);
            }
        } catch (Exception e) {
            log.error("ip 解析错误,value={},msg={}", value, e.getMessage());
        }


        return JSON.toJSONString(value);
    }




    /**
     * 自定义连接
     *
     * @return
     */
    public CloseableHttpClient createCustomHttpClient() {




        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();


        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);




        //MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名
        connectionManager.setDefaultMaxPerRoute(300);
        //设置连接池最大是500个连接
        connectionManager.setMaxTotal(500);


        /**
         * 只请求 class.net,最大并发300
         *
         * 请求 class.net,最大并发300
         * 请求 open1024.com,最大并发200
         *
         * //MaxtTotal=400 DefaultMaxPerRoute=200
         * //只连接到http://class.net时,到这个主机的并发最多只有200;而不是400;
         * //而连接到http://class.net 和 http://open1024.com时,到每个主机的并发最多只有200;
         * // 即加起来是400(但不能超过400);所以起作用的设置是DefaultMaxPerRoute。
         *
         */


        RequestConfig requestConfig = RequestConfig.custom()
                //返回数据的超时时间
                .setSocketTimeout(20000)
                //连接上服务器的超时时间
                .setConnectTimeout(10000)
                //从连接池中获取连接的超时时间
                .setConnectionRequestTimeout(1000)
                .build();


        CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .build();


        return closeableHttpClient;
    }




}
第5集 DWM层处理-用户访问IP解析开发实战《下》

简介: DWM层处理-用户访问IP解析配置开发实战

  • 开发

在这里插入图片描述

       //3、关联ip城市
        SingleOutputStreamOperator<String> shortLinkWideDS = deviceWideDS.map(new LocationMapFunction());


        shortLinkWideDS.print("地理位置信息宽表补齐");


        //将写sink到dwm层,kafka 存储
        FlinkKafkaProducer<String> kafkaProducerSink = KafkaUtil.getKafkaProducer(SINK_TOPIC);


        shortLinkWideDS.addSink(kafkaProducerSink);


  • 测试数据
{"ip":"113.68.152.139","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}
     .register("https", SSLConnectionSocketFactory.getSocketFactory())
            .build();


    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);




    //MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名
    connectionManager.setDefaultMaxPerRoute(300);
    //设置连接池最大是500个连接
    connectionManager.setMaxTotal(500);


    /**
     * 只请求 class.net,最大并发300
     *
     * 请求 class.net,最大并发300
     * 请求 open1024.com,最大并发200
     *
     * //MaxtTotal=400 DefaultMaxPerRoute=200
     * //只连接到http://class.net时,到这个主机的并发最多只有200;而不是400;
     * //而连接到http://class.net 和 http://open1024.com时,到每个主机的并发最多只有200;
     * // 即加起来是400(但不能超过400);所以起作用的设置是DefaultMaxPerRoute。
     *
     */


    RequestConfig requestConfig = RequestConfig.custom()
            //返回数据的超时时间
            .setSocketTimeout(20000)
            //连接上服务器的超时时间
            .setConnectTimeout(10000)
            //从连接池中获取连接的超时时间
            .setConnectionRequestTimeout(1000)
            .build();


    CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
            .setConnectionManager(connectionManager)
            .build();


    return closeableHttpClient;
}

}




#### 第5集 DWM层处理-用户访问IP解析开发实战《下》

**简介: DWM层处理-用户访问IP解析配置开发实战**

- 开发

[外链图片转存中...(img-oqjvG43Y-1723435917849)]

```java
       //3、关联ip城市
        SingleOutputStreamOperator<String> shortLinkWideDS = deviceWideDS.map(new LocationMapFunction());


        shortLinkWideDS.print("地理位置信息宽表补齐");


        //将写sink到dwm层,kafka 存储
        FlinkKafkaProducer<String> kafkaProducerSink = KafkaUtil.getKafkaProducer(SINK_TOPIC);


        shortLinkWideDS.addSink(kafkaProducerSink);


  • 测试数据
{"ip":"113.68.152.139","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从零开始学习人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值