Flink实时数仓之用户埋点系统(一)

news2024/10/24 18:29:26

需求分析及框架选型

  • 需求分析
    • 数据采集
      • 用户行为采集
      • 业务数据采集
    • 行为日志分析
      • 用户行为日志
      • 页面日志
      • 启动日志
      • APP在线日志
    • 业务数据分析
      • 用户Insert数据
      • 用户Update数据
  • 技术选型
    • Nginx配置
    • Flume配置
    • MaxWell
    • Hadoop
    • Flink
    • 架构图

需求分析

数据采集

用户行为采集

  1. 行为数据:页面浏览、点击、在线日志等数据
  2. 活跃数据:用户注册、卸载安装、活跃等数据
  3. App性能日志:卡顿、异常等数据

业务数据采集

  1. 业务数据:支付等
  2. 维度表:渠道、商品等

行为日志分析

用户行为日志

日志结构大致可分为两类,一是页面日志,二是启动日志和在线日志。

页面日志

页面日志,以页面浏览为单位,即一个页面浏览记录,生成一条页面埋点日志。一条完整的页面日志包含,一个页面浏览记录和多个用户在该页面所做的动作记录,以及若干个该页面的曝光记录,以及一个在该页面发生的报错记录。除上述行为信息,页面日志还包含了这些行为所处的各种环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。

{
   "common": {                     -- 环境信息
      "imei":"xxx",
      "device_id":"12323",         --客户端唯一识别id,安卓传安卓唯一id,ios传idfa (可以为空,上报空值率)
      "acc_id": "aad3",             -- APP注册ID,如:aad32c13aa6d008c_1D19l (可以为空,上报空值率)
      "app_type_id":"DFTT",        --App软件唯一识别符,如:步多多为100001 (不可为空,非DFTT/ZYSRF/SXG/6位数字则报错,空值则报错)
      "qid": "kuaishou1",           -- 渠道
      "group_qid":"kuaishou",  -- 渠道分组
      "asc_qid":"xiaoh",                -- 归因渠道号 (可以为空,上报空值率)
      "app_ver":"v2.1.134",        --App版本号 ,如:1.1.1 最大99.99.99(不可为空,非标准格式及空值 报错)
      "os":"IOS",                  --操作系统,如:Android、iOS (不可为空,非:Android/iOS及空值 报错 )
      "os_version":"11.0",         --操作系统版本号,如:7.0 (不可为空,空值 报错)
      "device":"xiao mi 6",        --公共参数)客户端手机型号,如:xiaomi6 (不可为空,空值 报错 )
      "device_brand":"xiaomi",     --机型品牌,如:HUAWEI (不可为空,空值 报错)
      "pixel":"1080*1920",         --屏幕分辨率,如:1080*1920 (不可为空 ,空值 报错)
      "network":"5g",              --网络环境,如:wifi、4g、3g、2g、other (不可为空 ,非:wifi、4g、3g、2g、other及空值 报错)
      "is_tourist":1 ,             --是否是游客,如:1表示游客、0表示非游客、2表示未登录
      "obatch_id":"ddadccae",      --本次启动唯一ID:每次启动时生成一个唯一批次号,直到下次启动才变更 (可以为空 ,上报空值率)  
      "ip":"127.0.0.1",            --ip
      "is_new": 1,                 -- 是否为新用户 0老用户,1新用户(安装后启动的第一天用户都为新用户,第一天之后都为老用户) 
      "code": "xxx",              -- 平台标识 
      "lab_code": "实验A",         -- 实验code 
      "lab_group_code": "note"     -- 实验分组code  
   },
   "actions": [{                   -- 页面动作信息
      "page_url": "/good_detail",  -- 页面url(取相对路径)
      "action_type": "show",       -- 动作类型:展现传“show”、点击传“click”、关闭传“close” (不可为空,非show、click、close报错 空值报错)
      "event": "Vip",              -- 事件类型
      "sub_event": "Me"            -- 事件子类型
    }],
   "pages": [{                        -- 页面信息
      "during_time": 7648,            -- 持续时间毫秒
      "page_url": "/good_detail",     -- 页面url(取相对路径)
      "last_page_url":"",             -- 上一个页面url(取相对路径,首次访问为空)
      "event": "Vip",                 -- 事件类型
      "sub_event": "Me",              -- 页面名称
      "last_sub_event": "login"       -- 上页的名称  
   }] 
    "ts": 1585744374423             --日志上报时间戳
}

启动日志

启动日志以启动为单位,一次启动行为,生成一条启动日志。一条完整的启动日志包括一个启动记录,一个本次启动时的报错记录,以及启动时所处的环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。

{
  "common": {
      "imei":"idfv",
      "device_id":"12323",         --客户端唯一识别id,安卓传安卓唯一id,ios传idfa (可以为空,上报空值率)
      "acc_id": "aad3",            -- APP注册ID,如:aad32c13aa6d008c_1D19l (可以为空,上报空值率)
      "app_type_id":"DFTT",        --App软件唯一识别符,如:步多多为100001 (不可为空,非DFTT/ZYSRF/SXG/6位数字则报错,空值则报错)
      "qid": "xxx",                -- 渠道
      "group_qid":"xxxx",    -- 渠道分组
      "asc_qid":"",                --归因渠道号 (可以为空,上报空值率)
      "app_ver":"v2.1.134",        --App版本号 ,如:1.1.1 最大99.99.99(不可为空,非标准格式及空值 报错)
      "os":"IOS",                  --操作系统,如:Android、iOS (不可为空,非:Android/iOS及空值 报错 )
      "os_version":"11.0",         --操作系统版本号,如:7.0 (不可为空,空值 报错)
      "device":"xiao mi 6",        --公共参数)客户端手机型号,如:xiaomi6 (不可为空,空值 报错 )
      "device_brand":"xiaomi",     --机型品牌,如:HUAWEI (不可为空,空值 报错)
      "pixel":"1080*1920",         --屏幕分辨率,如:1080*1920 (不可为空 ,空值 报错)
      "network":"5g",              --网络环境,如:wifi、4g、3g、2g、other (不可为空 ,非:wifi、4g、3g、2g、other及空值 报错)
      "is_tourist":1 ,             --是否是游客,如:1表示游客、0表示非游客、2表示未登录
      "obatch_id":"ddadccae",      --本次启动唯一ID:每次启动时生成一个唯一批次号,直到下次启动才变更 (可以为空 ,上报空值率)  
      "ip":"127.0.0.1",            --ip
      "is_new": 1,                 -- 是否为新用户 0老用户,1新用户
      "code": "xxx",               -- 平台标识 
      "lab_code": "实验A",         -- 实验code 
      "lab_group_code": "note"     -- 实验分组code  
  },
  "start": {   
    "start_way": 0,          --启动方式。 0:热启动  1:代表首次安装首次启动  2:冷启动
    "entry": "icon",          --启动途径。icon:手机图标  notice:通知   install:安装后启动
    "loading_time": 18803    --启动加载时间
  },
  "ts": 1585744304000        --日志上报时间戳
}

APP在线日志

App在线日志以启动-关闭为单位,一次启动-关闭行为,生成一条启动-关闭日志。

{
  "common": {
      "imei":"idfv",
      "device_id":"12323",         --客户端唯一识别id,安卓传安卓唯一id,ios传idfa (可以为空,上报空值率)
      "acc_id": "aad3",            -- APP注册ID,如:aad32c13aa6d008c_1D19l (可以为空,上报空值率)
      "app_type_id":"DFTT",        --App软件唯一识别符,如:步多多为100001 (不可为空,非DFTT/ZYSRF/SXG/6位数字则报错,空值则报错)
      "qid": "xxx",           -- 渠道
      "group_qid":"xxxx",  -- 渠道分组
      "asc_qid":"",                -- 归因渠道号 (可以为空,上报空值率)
      "app_ver":"v2.1.134",        --App版本号 ,如:1.1.1 最大99.99.99(不可为空,非标准格式及空值 报错)
      "os":"IOS",                  --操作系统,如:Android、iOS (不可为空,非:Android/iOS及空值 报错 )
      "os_version":"11.0",         --操作系统版本号,如:7.0 (不可为空,空值 报错)
      "device":"xiao mi 6",        --公共参数)客户端手机型号,如:xiaomi6 (不可为空,空值 报错 )
      "device_brand":"xiaomi",     --机型品牌,如:HUAWEI (不可为空,空值 报错)
      "pixel":"1080*1920",         --屏幕分辨率,如:1080*1920 (不可为空 ,空值 报错)
      "network":"5g",              --网络环境,如:wifi、4g、3g、2g、other (不可为空 ,非:wifi、4g、3g、2g、other及空值 报错)
      "is_tourist":1 ,             --是否是游客,如:1表示游客、0表示非游客、2表示未登录
      "obatch_id":"ddadccae",      --本次启动唯一ID:每次启动时生成一个唯一批次号,直到下次启动才变更 (可以为空 ,上报空值率)  
      "ip":"127.0.0.1",            --ip
      "is_new": 1,                 -- 是否为新用户 0老用户,1新用户 
      "code": "xxx",              -- 平台标识 
      "lab_code": "实验A",         -- 实验code 
      "lab_group_code": "note"     -- 实验分组code  
  },
  "online": {   
    "start_way": 0,            --启动方式。 0:热启动  1:代表首次安装首次启动  2:冷启动
    "start_time":  18803111 ,  --开始时间(毫秒)
    "end_time":  188033 ,      --退出时间(毫秒)
    "online_time": 18803      --在线时长(毫秒)
  },
  "ts": 1585744304000        --日志上报时间戳
}

新老用户的判断规则
APP 端:用户安装 App 后,第一次打开 App 的当天,Android/iOS SDK 会在手机本地缓存内,创建一个首日为 true 的标记,并且设置第一天 24 点之前,该标记均为 true。
即:第一天触发的 APP 端所有事件中,is_new = 1。即第一天之后触发的 APP 端所有事件中,is_new = 0。
对于此类日志,如果首日之后用户清除了手机本地缓存中的标记,再次启动 APP 会重新设置一个首日为 true 的标记,导致本应为 0 的 is_new 字段被置为1
前端处理规则
is_new(1:新用户,0:老用户)用户安装 App 后,第一次打开 App 的当天,即第一天触发的 APP 端所有事件中,is_new = 1,第一天之后,该标记则为 false,即第一天之后触发的 APP 端所有事件中,is_new = 0。首日之后用户清除了手机本地缓存中的标记,is_new = 1此时由后端处理

业务数据分析

1)用户订单、支付、退款等业务的新增、修改、删除操作都会生成一个binlog日志,通过MaxWell采集这些日志到Kafka消息队列中

用户Insert数据

类型:“type”: “insert”

{
    "database":"databaseA",
    "table":"t_pay_order",
    "type":"insert",
    "ts":1686540443,
    "xid":16179,
    "commit":true,
    "data":{
        "uid":"1660557015483727879",
        "order_no":"P202305221603541978152962",
        "pay_order_id":"",
        "way_code":"APPLE_APP",
        "amount":1800,
        "currency":"cny",
        "state":1,
        "product_id": 1,
        "product_name":"商品1",
        "product_num":1,
        "body":"xxxxx",
        "user_id":"1660533343607898114",
        "refund_state":0,
        "refund_times":0,
        "refund_amount":0,
        "subscribed":1,
        "expired_time":"2023-06-21 16:03:39",
        "success_time":null,
        "create_time":"2023-05-22 08:03:38.805000",
        "update_time":"2023-06-12 02:01:26.996053",
        "err_code":"21011",
        "err_msg":"订单已退款或已订阅过期"
    }
}

用户Update数据

{
    "database":"note_data",
    "table":"t_pay_order",
    "type":"update",
    "ts":1686535286,
    "xid":4853,
    "commit":true,
    "data":{
        "uid":"1660557015483727876",
        "order_no":"P202305221603541978152961",
        "pay_order_id":"",
        "way_code":"APPLE_APP",
        "amount":1800,
        "currency":"cny",
        "state":3,
        "product_id":"VIP_Moth_18",
        "product_name":"月度VIP",
        "product_num":1,
        "body":"月度会员",
        "user_id":"1660533343607898114",
        "refund_state":0,
        "refund_times":0,
        "refund_amount":0,
        "subscribed":1,
        "expired_time":"2023-06-21 16:03:39",
        "success_time":null,
        "create_time":"2023-05-22 08:03:38.805000",
        "update_time":"2023-06-12 02:01:26.996053",
        "err_code":"21011",
        "err_msg":"订单已退款或已订阅过期"
    },
    "old":{
        "pay_order_id":"rfsddfx",
        "update_time":"2023-06-09 10:32:59.769593"
    }
}

技术选型

  1. 数据采集与传输:Nginx、Flume、Kafka、MaxWell
  2. 数据存储:HDFS、HBASE、Redis
  3. 计算引擎:Flink
  4. 数据存储:ClickHouse
  5. 任务调度:Flink On Yarn

Nginx配置

作用

  • 收集用户埋点日志:生成log_file文件。
  • 收集post请求中的request_body,在/data/logs/nginx/user_data/文件夹下生成log日志

配置

http {
    include       mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" ';
    log_format data_json escape=json ' $request_body ';
    access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    map $time_iso8601 $logdate {
        '~^(?<ymd>\d{4}-\d{2}-\d{2})' $ymd;
        default    'date-not-found';
    }

   server {
        listen      8090;
        server_name 127.0.0.1;

        access_log  /data/logs/nginx/user_data/user_big_data-$logdate.log  data_json;
        error_log /data/logs/nginx/user_data/user_big_data_error-$logdate.log  error;
        
        location / {
            proxy_pass  http://127.0.0.1:8090/api/log/;
        }

        location /api/log/ {
            return 200;
        }
   }
}

Flume配置

作用

  • 采集文件到kafka队列中,这里的source(数据源)是文件,channel(通道),sink(输出源)是kafka

关键配置

#定义组件
a1.sources = r1
a1.channels = c1

#配置source
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /data/logs/nginx/user_data/user_data/.*log
a1.sources.r1.positionFile =  /opt/apache-flume-1.9.0-bin/opt/taildir_position.json
a1.sources.r1.interceptors =  i1
a1.sources.r1.interceptors.i1.type = com.sinozo.data.flume.interceptor.ETLInterceptor$Builder

#配置channel
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a1.channels.c1.kafka.bootstrap.servers =127.0.0.1:9092
a1.channels.c1.kafka.topic = topic_log
a1.channels.c1.parseAsFlumeEvent = false
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel

#组装 
a1.sources.r1.channels = c1

MaxWell

作用
实时收集mysql中的binlog数据,输出到kafka队列中

关键配置

#Maxwell数据发送目的地,可选配置有stdout|file|kafka|kinesis|pubsub|sqs|rabbitmq|redis
producer=kafka
#目标Kafka集群地址
kafka.bootstrap.servers=localhost:9092
#目标Kafka topic,可静态配置,例如:maxwell,也可动态配置,例如:%{database}_%{table}
kafka_topic=topic_db

#配置只监听note_data库下t_pay_order表
exclude_dbs=*
include_dbs=note_data
include_tables=t_pay_order

#MySQL相关配置
host=localhosts
user=maxwell
password=maxwell
jdbc_options=useSSL=false&serverTimezone=Asia/Shanghai

启动命令

#!/bin/bash
MAXWELL_HOME=/opt/maxwell-1.29.2

status_maxwell(){
    result=`ps -ef | grep com.zendesk.maxwell.Maxwell | grep -v grep | wc -l`
    return $result
}

start_maxwell(){
    status_maxwell
    if [[ $? -lt 1 ]]; then
        echo "启动Maxwell"
        $MAXWELL_HOME/bin/maxwell --config $MAXWELL_HOME/config.properties --filter="exclude: *.*, include: db.*, exclude: *.*, include: *.t_pay_order"  --daemon
    else
        echo "Maxwell正在运行"
    fi
}

stop_maxwell(){
    status_maxwell
    if [[ $? -gt 0 ]]; then
        echo "停止Maxwell"
        ps -ef | grep com.zendesk.maxwell.Maxwell | grep -v grep | awk '{print $2}' | xargs kill -9
    else
        echo "Maxwell未在运行"
    fi
}

case $1 in
    start )
        start_maxwell
    ;;
    stop )
        stop_maxwell
    ;;
    restart )
       stop_maxwell
       start_maxwell
    ;;
esac

Hadoop

作用
HDFS作为存储的基础组件,防止flink计算过程中的checkPoint检查点数据以及状态数据
Yarn作为调度组件,对flink的jobManager、taskManager内存等资源进行动态分配、并对taskManager进行监控

Flink

作用
作为实时计算引擎,对业务数据、用户埋点数据进行分组、统计等计算

架构图

Flink On Yarn架构图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497853.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件测试】上岗第一天,组长就要我做自动化测试?我该咋办?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 如果你恰好刚刚进…

请说明Vue中的Error Boundaries

当我们开发基于Vue框架的应用时&#xff0c;我们经常会遇到各种错误处理的情况。Vue提供了一种非常强大且简单的方式来处理这些错误&#xff0c;那就是Error Boundaries&#xff08;错误边界&#xff09;。本文将从概念、用法和示例代码三个方面来详细介绍Vue中的Error Boundar…

数据结构—KMP 算法:

算法思想&#xff1a; KMP算法实现寻找主串中子串的位置时&#xff0c;主串指针地址不回退&#xff0c;在比对过程中串仅仅遍历一次&#xff0c;子串的回退可以是与当前主串可重新最多匹配的地址位置。 BF与KMP算法比对&#xff1a; KMP BF 主串不用回退 主串回退&#xf…

【npm】node包管理工具npm的介绍和基础使用

简言 npm 是 Node.js 的 包管理器&#xff08;Package Manager&#xff09;&#xff0c;它是专门用于管理 Node.js 项目中第三方库的工具。 本文介绍下npm和其使用方法。 npm介绍 npm 是世界上最大的软件注册中心。各大洲的开源开发者都使用 npm 共享和借用软件包&#xff…

【开源】SpringBoot框架开发陕西非物质文化遗产网站

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 设计目标2.2 研究内容2.3 研究方法与过程2.3.1 系统设计2.3.2 查阅文献2.3.3 网站分析2.3.4 网站设计2.3.5 网站实现2.3.6 系统测试与效果分析 三、系统展示四、核心代码4.1 查询民间文学4.2 查询传统音乐4.3 增改传统舞…

好书安利:《大模型应用开发极简入门:基于GPT-4和ChatGPT》这本书太好了!150页就能让你上手大模型应用开发

文章目录 前言一、ChatGPT 出现&#xff0c;一切都变得不一样了二、蛇尾书特色三、蛇尾书思维导图四、作译者简介五、业内专家书评总结 前言 ​如果问个问题&#xff1a;有哪些产品曾经创造了伟大的奇迹&#xff1f;ChatGPT 应该会当之无愧入选。仅仅发布 5 天&#xff0c;Chat…

服务器严重不够啊

必需采购服务器了&#xff0c;

JavaScript——流程控制(程序结构)

JavaScript——流程控制&#xff08;程序结构&#xff09; 流程控制就是来控制我们的代码按照什么结构顺序来执行。更倾向于一种思想结构。 流程控制分为三大结构&#xff1a;顺序结构、分支结构、循环结构 1、顺序结构 ​ 代码从上往下依次执行&#xff0c;从A到B执行&#x…

K线形态分析宝典:10种K线形态特征与应用场景详解,助您投资更有底气

在金融市场中&#xff0c;K线图是投资者们最常用的技术分析工具之一&#xff0c;通过观察K线形态可以揭示市场的走势和情绪。以下是10种常见的K线形态&#xff0c;包括详细的形态特征、作用以及应用场景&#xff0c;帮助您更好地理解市场走势&#xff0c;制定更精准的投资策略。…

springboot实现多线程开发(使用@Async注解,简单易上手)

根据springboot的核心思想便捷开发&#xff0c;使用多线程也变得简单起来&#xff0c;通过一下几个步骤即可实现。 核心注解 EnableAsync将此注解加在启动类上&#xff0c;使项目支持多线程。 Async 使用我们的Async注解在所需要进行多线程的类上即可实现。 配置线程池 …

cefsharp(winForm)调用js脚本,js脚本调用c#方法

本博文针对js-csharp交互(相互调用的应用) (一)、js调用c#方法 1.1 类名称:cs_js_obj public class cs_js_obj{//注意,js调用C#,不一定在主线程上调用的,需要用SynchronizationContext来切换到主线程//private System.Threading.SynchronizationContext context;//…

2024年需要重点关注的15种计算机病毒

2024年&#xff0c;计算机病毒威胁变得愈发多元化和复杂化。涉及勒索病毒、二维码病毒、挖矿木马等15种类型&#xff0c;这些病毒从数据勒索到系统入侵&#xff0c;对全球网络安全构成严峻挑战。不同传播方式如社交媒体、移动介质、网页、短信等使得设备容易受到感染。同时&…

vue基础教程(4)——深入理解vue项目各目录

博主个人微信小程序已经上线&#xff1a;【中二少年工具箱】。欢迎搜索试用 正文开始 专栏简介1. 总览2. node_modules3.public4.src5.assets6.components7.router8.stores9.views10.App.vue11.main.js12.index.html 专栏简介 本系列文章由浅入深&#xff0c;从基础知识到实战…

【Linux篇】gdb的使用

&#x1f49b;不要有太大压力&#x1f9e1; &#x1f49b;生活不是选择而是热爱&#x1f9e1; &#x1f49a;文章目录&#x1f49a; 1. 背景知识2. 使用 1. 背景知识 1. 程序发布的方式有两种&#xff0c;debug模式和release模式 2. Linux下&#xff0c;gcc和g编译生成的可执行…

AI新工具 MacOS 翻译提供翻译、润色和语法修改功能的插件;AI生成 Excel公式;Deepmind前华人员工创建视频生成工具

1: OpenAI Translator Bob Plugin macOS 平台的翻译提供翻译、润色和语法修改功能的插件 OpenAI Translator Bob Plugin是一款基于OpenAI的API&#xff0c;为用户提供翻译、润色和语法修改功能的插件。这款插件专门为macOS平台上的Bob软件设计&#xff0c;通过使用先进的Chat…

3.6 day1 FreeRTOS

1.总结keil5下载代码和编译代码需要注意的事项 注意要将魔术棒的的debug选项中的setting中的flashdownload中的reset and run 勾选上&#xff0c;同时将pack中的enable取消勾选 2.总结STM32Cubemx的使用方法和需要注意的事项 可以通过功能列表对引脚进行设置&#xff0c;并且可…

FreeRTOS day2

1.使用ADC采样光敏电阻数值&#xff0c;如何根据这个数值调节LED灯亮度。 while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */adc_value HAL_ADC_GetValue(&hadc);TIM3->CCR3 adc_value * 999 / 4095;printf("%d %d\r\n",adc_value,TIM3->C…

URL输入到页面渲染过程详解

当我们在浏览器中输入一个URL并按下回车键时&#xff0c;浏览器会执行一系列步骤来解析URL、发送请求、接收响应&#xff0c;并最终渲染页面。这个过程涉及到多个阶段&#xff0c;包括DNS解析、TCP握手、发送HTTP请求、服务器处理请求、返回HTTP响应、浏览器解析和渲染等。下面…

安卓手机如何使用JuiceSSH实现公网远程连接本地Linux服务器

文章目录 1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址连接测试 处于内网的虚拟机如何被外网访问呢?如何手机就能访问虚拟机呢? cpolarJuiceSSH 实现手机端远程连接Linux虚拟机(内网穿透,手机端连接Linux虚拟机) …

14. C++继承与虚函数

【继承基础概念】 继承可以让本类使用另一个类的非私有成员&#xff0c;提供共用成员的类称为父类或基类&#xff0c;使用共用成员的类称为子类或派生类&#xff0c;子类创建对象时会包含继承自父类的成员。 继承的优势是减少重复定义数据&#xff0c;当本类需要在另一个类的…