跳转至

博客

关于 golang 的条件编译个人实践

在 Go 中,当你遇到 “build constraints exclude all Go files” 错误时,意味着在某个包中没有符合当前构建条件的文件。

核心是诊断触发的条件

项目中遇到一个服务需要调用 Cplus 开发的库函数实现某些功能,但是仅提供了 Windows 和 Linux 两个平台,导致我们无法在 Darwin 环境运行该服务,算是业务驱动我去更精细化的了解技术了。

我期望的条件编译规则如下:

  1. Linux 和 Windows 编译包含库函数的代码;
  2. Darwin 不编译;
  3. 可以选择是否调用库函数;

规则 1, 2 可以使用平台条件编译实现:

// 规则一触发
//go:build !darwin

// 规则二触发
//go:build darwin

规则 3 需要依赖与 -tags 配置进一步控制编译结果

// 符合规则 1, 2 且允许规则 3 时编译
//go:build !darwin && tag01

// 符合规则 2 或不满足规则 3
//go:build darwin || !tag

这样配置已经没有问题了,但是仍然提示 “build constraints exclude all Go files” 错误?

我们可以使用 go list 命令来查看哪些文件被包括在内以及哪些被排除:

go list -tags ${TAG_NAME} -json ${PACKAGE_NAME}

示例输出如下:

{
    "Dir": "",
    "ImportPath": "",
    "Name": "bitanswer",
    "Files": [],
    "GoFiles": [],
    "IgnoredGoFiles": [
        "file1.go",
        "file2.go"
    ],
    "GoFilesExclude": [
        "file1.go",
        "file2.go"
    ]
}

结论

golang 文件夹命名也有条件编译的功能,当文件夹命名符合 filename_os 时,文件中的 //go:build 规则会失效。

指标体系搭建方法论

什么是指标体系

指标体系=指标+体系,简言之,就是一系列相互关联的用于衡量业务发展状况的指标的集合。

  • 指标:是对业务单元的度量值,使业务可以描述、度量和拆解;

  • 体系:往往由一系列观察和思考业务的角度组成,即一系列的维度构成。

在数据分析领域,维度是不可缺少的一部分,离开维度谈指标可谓耍流氓。

可以朴素的理解为:希望以什么视角(维度)分析什么数据(指标),如果以日常监控或分析为目的,运营一般会选择按日进行监控,如每天注册用户数、日活等;如果是汇报或者宣发场景,可能会有按月、年、历史累计等维度做汇总,如某年度新增注册用户数等。

为什么要搭建指标体系

  • 衡量业务发展状况:通过监控指标体系看清业务发展,并针对性调整业务策略,确保业务良性增长;
  • 统一公司内各部门间的业务口径,减少沟通成本;
  • 指导基础数据搭建,从数仓到数据报表、数据产品的应用层面,严格参考数据指标体系建设,确保指标一致性;
  • 指导数据分析,各业务部门自行分析数据时,可在一致性的 指标体系的基础上搭建场景化的分析模型、报表。

常用数据指标体系搭建方法论

科学分析思维搭建——OSM模型

  • O(Object): 即业务目标,在搭建模型之前,我们先思考一个问题:我们的业务、产品、甚至是其中的一个功能存在的目的是什么、能够解决用户什么问题、满足用户的什么需求?
  • S(Strategy): 策略,清楚业务目标之后,为了达成上述目标,我们应当采取什么样的业务策略;
  • M(Measure): 度量,即选择合适的指标用于衡量我们的策略是否有效,反应目标的达成情况。

举个例子:在一个文件转换服务中,核心功能是实现一个类型自动转换的服务,并通过提升文件转换效率和稳定性来提升用户体验。

影响转换效率的主要是任务调度策略,比如分级调度

数据分析模型搭建——AARRR模型

AARRR 模型就是业界知名的海盗模型,这是一个从获取客户开始到最终形成收益和口碑传播为目的的一个业务运营的思考过程,整个过程一共有五大环节,AARRR就是由这五个环节的英文名首字母组成,基本流程如下:

A(Acquisition)-> A(Activation)-> R(Retention)-> R(Revenue)-> R(Referral)

即获取、激活、留存、挖掘(变现)、传播。总之,AARRR 模型的核心思路就是从获客到获取收益的整个过程上量化数据、分析数据并反哺业务;相对而言,该模型更适用于渴望增长的创业公司的数据指标体系搭建参考,当然了,该模型对业务覆盖面较广,成熟公司若按该思路搭建的指标体系也会比较全面。事实上,AARRR模型是《增长黑客》这本书里抽象出来的概念,感兴趣的同学可以去看看,书中还有大量的产品设计和运营案例讲解,包含facebook,谷歌,微软还有uber等企业曾经走过的或成功或失败的案例,很值得产品、运营和数据分析师们一读!

场景化搭建——人、货、场分析模型

大数据平台层级结构

本文主要介绍大数据的应用层级划分,帮助自己梳理数据在大数据平台的生命周期。

根据大数据平台中的数据流入流出,可分为原始数据层、数据仓库、数据应用层。

我对 Context 包的理解

引言

讲真的,我一直不明白 go 的 context 怎么用,之前在用 Java 开发时就不清楚,所以这次专门花时间把 go 的 context 弄清楚。

我记得第一次接触 context 时,文档上说这个是用来做并发控制的,可以设置超时时间,超时就会快快速返回,可以携带一些信息,在生命周期中共享。生命周期这个概念很重要,我们在开发时需要掌控程序的生命周期。

我们不能简单的认为只要函数中带着 context 参数往下传递就可以做到超时取消,快速返回,其实这是一个错误的思想,其取消机制采用的也是通知机制,但出的透传并不会起作用,比如你这么写代码:

func main()  {
    ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second)
    defer cancel()
    go Monitor(ctx)

    time.Sleep(20 * time.Second)
}

func Monitor(ctx context.Context)  {
    for {
        fmt.Print("monitor")
    }
}

我们需要学会正确的使用 context。

Clash 源码详解系列集锦

众所周知,Clash for Windows 衫裤跑路了,让我默哀一分钟,For Freedom!!!

所以一个奇怪的想法出现了,我能否扛起大旗走下去!...我瞎说的,第一步就先读懂源码吧。

【第一弹】Clash 中缓存管理

程序初始化会读取用户目录下的 ~/.config/clash/* 配置文件

执行 tree ~/.config/clash/* 可以查看 clash 配置文件结构如下:

├── cache.db
├── config.ini
├── config.yaml
├── Country.mmdb
├── logs
   ├── 2024-02-28-144228.log
   ├── 2024-03-04-175206.log
   ├── 2024-03-04-175247.log
   └── 2024-03-06-121729.log
└── profiles
    ├── 1703153138868.yml
    ├── 1709629664192.yml
    └── list.yml

2 directories, 11 files

源码解析

constant/config.go 包含了初始化代码:

1. 获取用户目录,初始化 HomeDir

这是一段很标准的获取用户 HOME 目录的代码,我们可以灵活的应用在其他项目中。

currentUser, err := user.Current()
if err != nil {
    dir := os.Getenv("HOME")
    if dir == "" {
        log.Fatalf("Can't get current user: %s", err.Error())
    }
    HomeDir = dir
} else {
    HomeDir = currentUser.HomeDir
}

2. 初始化 ~/.config 文件;

dirPath := path.Join(HomeDir, ".config", Name)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
    if err := os.MkdirAll(dirPath, 0777); err != nil {
        log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error())
    }
}

3. 初始化 ~/.config/config.ini 文件;

ConfigPath = path.Join(dirPath, "config.ini")
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
    log.Info("Can't find config, create a empty file")
    os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0644)
}

有趣的是,作者没有使用 func Create(name string) (*File, error) 方法, 定义如下:

// Create creates or truncates the named file. If the file already exists,
// it is truncated. If the file does not exist, it is created with mode 0666
// (before umask). If successful, methods on the returned File can
// be used for I/O; the associated file descriptor has mode O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

作者使用了 O_WRONLY0644 权限更加合适。

这些数字和权限的映射关系如下:

4:读权限(Read) 2:写权限(Write) 1:执行权限(eXecute) 数字的意义是各个权限的相加。例如,0666 表示 4 (读) + 2 (写) + 2 (写) + 1 (执行) + 1 (执行) + 1 (执行)。

4. 初始化 Country.mmdb

MMDBPath = path.Join(dirPath, "Country.mmdb")
if _, err := os.Stat(MMDBPath); os.IsNotExist(err) {
    log.Info("Can't find MMDB, start download")
    err := downloadMMDB(MMDBPath)
    if err != nil {
        log.Fatalf("Can't download MMDB: %s", err.Error())
    }
}

这个真是太重要了,属于是 Clash 本地存储方案的核心。

在项目方案设计是,我们可以借鉴这个思想。

不过为什么没有用 sqllite 我们可以探讨一下:

"MMDB" 和 "SQLite" 是两种完全不同的数据库系统,用于不同的用途,并有一些关键的区别。

  1. 类型:
  2. MMDB(MaxMind DB): 是MaxMind公司创建的数据库格式,主要用于存储地理位置信息,特别是IP地址与地理位置的映射关系。它不是通用的数据库管理系统,而是专注于提供对地理位置信息的高效检索。
  3. SQLite: 是一种嵌入式数据库引擎,它是一种关系型数据库管理系统(RDBMS)。SQLite是一款轻量级、自包含的数据库,适用于嵌入式系统和移动应用等场景。

  4. 用途:

  5. MMDB: 主要用于 IP 地址定位,以确定访问者的地理位置信息。通常在网络分析、广告定向和一些安全性检查中使用。
  6. SQLite: 可以用于各种通用的数据库需求,包括嵌入式系统、移动应用、桌面应用和小型服务等。它支持 SQL 查询语言,可以存储和检索多种类型的数据。

  7. 数据模型:

  8. MMDB: 使用特定的树状结构,适用于高效的IP地址到地理位置信息的查找。
  9. SQLite: 遵循关系型数据库模型,支持表、行和列的概念,以及 SQL 查询语言。

  10. 性能和用途限制:

  11. MMDB: 针对特定的地理位置查询优化,对于其他类型的数据存储和查询并不适用。
  12. SQLite: 虽然可以处理各种数据类型,但在大规模高并发读写的情况下可能不如一些专门设计的数据库系统。

总的来说,MMDB 和 SQLite 面向不同的应用场景。MMDB 更适合处理与地理位置信息相关的数据,而 SQLite 是一种通用的关系型数据库引擎,适用于各种数据存储和检索需求。

func downloadMMDB(path string) (err error) {
    resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz")
    if err != nil {
        return
    }
    defer resp.Body.Close()

    gr, err := gzip.NewReader(resp.Body)
    if err != nil {
        return
    }
    defer gr.Close()

    tr := tar.NewReader(gr)
    for {
        h, err := tr.Next()
        if err == io.EOF {
            break
        } else if err != nil {
            return err
        }

        if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") {
            continue
        }

        f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            return err
        }
        defer f.Close()
        _, err = io.Copy(f, tr)
        if err != nil {
            return err
        }
    }

    return nil
}

总览

var (
    HomeDir    string
    ConfigPath string
    MMDBPath   string
)


func init() {
    currentUser, err := user.Current()
    if err != nil {
        dir := os.Getenv("HOME")
        if dir == "" {
            log.Fatalf("Can't get current user: %s", err.Error())
        }
        HomeDir = dir
    } else {
        HomeDir = currentUser.HomeDir
    }

    dirPath := path.Join(HomeDir, ".config", Name)
    if _, err := os.Stat(dirPath); os.IsNotExist(err) {
        if err := os.MkdirAll(dirPath, 0777); err != nil {
            log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error())
        }
    }

    ConfigPath = path.Join(dirPath, "config.ini")
    if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
        log.Info("Can't find config, create a empty file")
        os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0644)
    }

    MMDBPath = path.Join(dirPath, "Country.mmdb")
    if _, err := os.Stat(MMDBPath); os.IsNotExist(err) {
        log.Info("Can't find MMDB, start download")
        err := downloadMMDB(MMDBPath)
        if err != nil {
            log.Fatalf("Can't download MMDB: %s", err.Error())
        }
    }
}

func downloadMMDB(path string) (err error) {
    resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz")
    if err != nil {
        return
    }
    defer resp.Body.Close()

    gr, err := gzip.NewReader(resp.Body)
    if err != nil {
        return
    }
    defer gr.Close()

    tr := tar.NewReader(gr)
    for {
        h, err := tr.Next()
        if err == io.EOF {
            break
        } else if err != nil {
            return err
        }

        if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") {
            continue
        }

        f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            return err
        }
        defer f.Close()
        _, err = io.Copy(f, tr)
        if err != nil {
            return err
        }
    }

    return nil
}

云办公最佳实践_Code Server

这是一个可以运行在任何设备的 Vscode 解决方案,我认为应该是现有所有产品中最好的没有之一。

记得去年只有 Vscode 官方的 for web,部署起来略显复杂,没想到不到一年,有人才已经对其作了进一步封装。

个人用来还不错,比如读者正在浏览的博客,正是我在平板上使用 code server 远程部署迁移的。还是很激动的,毕竟闲置多时的平板得以再次利用,以后出门不用背笔记本了,哈哈!闲话不多说,进入正题哈。

草稿

信息系统的分类

  • 业务(数据)处理系统
  • 管理信息系统
  • 决策支持系统
  • 专家系统
  • 办公自动化系统

长难句

Data is separated from the behaviors associated with this data, modularity of program sections is compromised, and maintenance becomes complicated.

这句话主要描述了在程序中,数据和与这些数据相关联的行为(也就是操作、功能)被分开,这种分离可能会影响程序各部分的模块性,使得维护变得复杂。

让我们逐句解析:

  1. Data is separated from the behaviors associated with this data:
  2. "Data is separated": 数据被分离
  3. "from the behaviors associated with this data": 与这些数据相关联的行为

这部分指出数据和与其相关的行为之间存在分离。

  1. Modularity of program sections is compromised:
  2. "Modularity of program sections": 程序各部分的模块性
  3. "is compromised": 受到损害

这一部分表达了由于数据和相关行为的分离,程序各个部分的模块性(模块化程度)受到了影响,降低了程序结构的清晰度和组织性。

  1. Maintenance becomes complicated:
  2. "Maintenance": 维护
  3. "becomes complicated": 变得复杂

最后一部分指出由于前述的分离,程序的维护变得更加复杂,可能需要更多的工作和努力来保持和修改代码。

综合起来,这句话强调了数据和相应行为的分离可能会对程序的可维护性和模块性造成负面影响。在软件设计中,通常倡导将数据和操作尽可能紧密地结合,以提高代码的清晰度和可维护性。

云的数据考虑

云服务的搭建需要将许多数据特性考虑在内。简单列举如下:

  • 物理特性
  • 性能要求
  • 易变性
  • 容量
  • 监管要求
  • 事务边界
  • 保存期限

所有这些数据需求都会对如何存储底层数据造成决策影响。我们必须做出两个关键性的决策:

  1. 单租户还是多租户
  2. 使用何种数据存储格式:SQL、NoSQL、文件等