2024年7月

环境准备

  • 宿主机环境
    :Windows 10
  • 虚拟机环境
    :Vagrant + VirtualBox

Vagrantfile 配置

首先,我们需要编写一个
Vagrantfile
来定义我们的虚拟机配置。假设已经在
D:\Vagrant\redis
目录下创建了一个
Vagrantfile
,其内容如下:

Vagrant.configure("2") do |config|
  config.vm.box = "local_centos7"

  [ "redis1", "redis2", "redis3" ].each_with_index do |name, index|
    config.vm.define name do |node|
      node.vm.network "private_network", ip: "192.168.50.#{index + 101}"
    end
  end

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024"
  end
end

这里准备创建了
redis1

redis3
的虚拟机,以
redis1
为实操,操作熟练后,其他虚拟机也是一样的。

为什么使用
local_centos7
而不是
centos/7

  • local_centos7
    是我们本地已经下载好的 CentOS 7 盒子,使用本地盒子可以避免每次创建虚拟机时都从网络下载,节省时间和带宽。

  • CentOS 7 盒子从官网下载有时是非常缓慢的,这里我提供了咱们国内的下载链接(点这里
    跳转
    ,提取码:
    2024

创建本地盒子

在终端(cmd)中进入
Vagrantfile
所在目录
D:\Vagrant\redis
,然后运行以下命令来添加本地盒子:

vagrant box add local_centos7 <CentOS-7...box所在路径(文件拖拽到 cmd)>

检查盒子是否创建成功

  • 查看终端的输出提示,如果没有报错,说明盒子添加成功。

  • 使用以下命令列出所有安装的盒子,查看是否存在
    local_centos7

    vagrant box list
    

截图_20240716133616

上述
Vagrantfile
如已创建(假设所在目录为:
D:\Vagrant\redis
),并且
local_centos7
已创建,就已经成功一半了。

启动虚拟机


Vagrantfile
所在目录的终端中运行以下命令启动虚拟机:

vagrant up

进入
redis1
虚拟机

启动完成后,通过以下命令进入
redis1
虚拟机:

vagrant ssh redis1

配置 YUM 源

为了提高软件包的下载速度,我们可以将 YUM 源更换为阿里云的镜像源。

  1. 切换到
    root
    用户:

    su  # 提示输入密码(vagrant)
    
  2. 下载阿里云的 CentOS 7 YUM 源配置文件:

    curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    
  3. 编辑 YUM 源配置文件,将
    http
    替换为
    https

    vi /etc/yum.repos.d/CentOS-Base.repo
    

    进入
    vi
    的命令模式,使用以下命令全局替换:

    :%s/http:/https:/g
    
  4. 清理 YUM 缓存并重新生成缓存:

    yum clean all
    yum makecache
    

测试换源是否成功

安装
wget
以测试换源是否成功:

yum install wget -y

截图_20240716134647

安装 GCC

Redis 编译需要 GCC 工具链,首先检查是否已安装:

gcc --version  # 如果输出了 gcc 的版本,则跳过 gcc 的安装

如果提示:
bash: gcc: command not found
,那么就需要安装:

yum install gcc -y

中途无报错并且输出类似如下,表示安装成功。

截图_20240716142332

安装 Redis

  1. 创建
    Redis 安装目录
    并下载
    Redis 源码

    mkdir -p /opt/software/redis && cd /opt/software/redis
    wget https://download.redis.io/redis-stable.tar.gz
    tar -xzf redis-stable.tar.gz && cd redis-stable
    
  2. 编译并安装 Redis:

    make install
    
  3. 检查 Redis 是否安装完成:

    ll /usr/local/bin | grep redis
    

    如果看到
    redis-server
    ,
    redis-cli
    等文件,说明 Redis 安装成功。

截图_20240716150320

前言

在使用第三方库时,我们会发现第三方库会提供一组文件,他们的后缀一般是
.so
(如
libname.so
),
.so.x

.so.x.y.z
。本文讨论他们之间的关系。

共享库版本号

共享库一般会由于修复bug或增加接口等原因不断更新,有些更新是向下兼容的,有些则不是。一旦不向下兼容,那么当共享库更新后,依赖该库(旧版本)的程序将无法运行,需要重新编译。

为了避免上述情况,就要对共享库进行版本控制。根据更新内容的不同可以划分不同的版本号:

  • 主版本号(Major Version Number)
    :主版本号表示库的重大升级,即更新的内容会导致不再与旧版本兼容(如接口变更),需要用户做出代码上的修改来适应新版本(或者仍使用旧版的共享库)。
  • 次版本号(Minor Version Number)
    :次版本号表示库的增量升级,即更新的内容向下兼容,不会影响用户程序,但提供了额外的功能或改进。用户不需要做出代码更改仍可继续使用该库。
  • 发布版本号(Release Version Number)
    :发布版本号表示库的一些错误的修正、性能的改进等,接口不做变化,不添加新功能。向下兼容。

不同的版本号在文件命名上就可以体现。
对于一个名为
aaa
的库,它的共享库文件名可能为:
libaaa.so.x.y.z
,其中:

  • lib
    :固定前缀
  • aaa
    :库名称
  • .so
    :共享库固定后缀
  • .x
    :主版本号
  • .y
    :次版本号
  • .z
    :发布版本号

例如
libjsoncpp.so.1.7.4
就代表着
jsoncpp
的共享库文件,版本号为1.7.4

共享库命名机制

然而若一个共享库改变了版本号并更新文件。那么对于使用旧版本共享库的用户程序来说,运行时就无法找到共享库文件了(因为名称已改变),还需要重新编译链接才可以。这就这大大增加了系统维护的复杂度和成本。

于是就诞生了
soname
命名机制,方便管理共享库的版本。

此机制设计了3类命名方式:

realname

  • 形如
    libname.so.x.y.z

    x,y,z
    分别代表主版本号,次版本号和发布版本号。
  • 一般拥有此名称的文件就是共享库的源文件
  • 在库文件生成时使用下面命令可以指定realname:
    gcc -shared -o $(realname) $(dependencies) $(flags)
    

soname

  • 形如
    libname.so.x

    x
    代表主版本号
  • 作用于用户程序运行时的加载阶段,动态链接器会根据用户程序编译时记录的
    soname
    查找对应的共享库文件
  • 通常是
    $(realname)
    文件的软链接,在库安装或更新后由库的维护者或系统管理员通过包管理器更新软链接的指向,一般不由单个用户手动进行软链接。
  • 在库文件生成时使用下面命令可以指定其
    soname

    gcc -shared -o $(realname) $(dependencies) $(flags) −Wl,−soname,$(soname)
    
  • 对于一个共享库文件,我们可以通过
    readelf -d
    命令查看其
    soname

linkname

  • 形如
    libname.so
    ,是没有任何版本编号的文件名
  • 作用于用户程序编译阶段,链接器使用
    linkname
    来寻找对应的共享库(GCC中使用-l选项指定库,如
    -laaa
    ,链接器就会去找
    libaaa.so
    ),然后将共享库的
    soname
    记录在用户程序的动态链接信息中。
  • 通常是
    $(realname)
    文件或
    $(soname)
    文件的软链接,在库安装或更新后由库的维护者或系统管理员通过包管理器更新软链接的指向,一般不由单个用户手动进行软链接。

总结

总的来说,对于Linux下的用户程序,soname命名机制主要参与了以下两个过程:


链接阶段
:链接器按照搜索路径优先级,根据
linkname
去找对应的
.so
文件,如果找到了就会在生成的可执行文件中记录
.so
文件指向的共享库文件的
soname
;如果没有找到就会去找静态库文件选择静态链接。


加载阶段
:程序运行时,动态链接器按照搜索路径优先级,根据可执行文件中记录的
soname
去找对应的
*.so.x
文件,如果找到了就会加载其指向的共享库;没找到就报错。

这样的处理确保了应用程序在运行时能够找到合适的库版本,同时允许系统管理员在不影响已有应用程序的情况下更新库文件。

参考文章

1.
Linux下动态链接库文件的realname、soname和linkname
2.
Program Library HOWTO-Shared Libraries
3.
Shared objects: sonames, real names, and link names
4.
Linux 共享库的 soname 命名机制

在这篇深入探讨Go语言在微服务架构中的应用的文章中,我们介绍了选择Go构建微服务的优势、详细分析了主要的Go微服务框架,并探讨了服务发现与注册和API网关的实现及应用。

关注TechLead,复旦博士,分享云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,复旦机器人智能实验室成员,国家级大学生赛事评审专家,发表多篇SCI核心期刊学术论文,阿里云认证的资深架构师,上亿营收AI产品研发负责人。

file

一、为什么选择Go构建微服务

1.1 Go语言简介

Go语言,也称为Golang,是Google于2009年发布的一种开源编程语言。它由Robert Griesemer、Rob Pike和Ken Thompson三位计算机科学家设计,目标是提供一种简单、高效且具有高并发能力的编程语言。Go语言的语法简洁明了,兼具了静态类型语言的安全性和动态类型语言的灵活性,特别适合用于现代软件开发中的高性能和高并发需求。

1.2 微服务架构概述

微服务架构是一种软件架构风格,它将单一应用程序拆分为一组小的服务,每个服务独立部署和管理,负责处理特定的业务功能。微服务之间通过轻量级通信机制(通常是HTTP或消息队列)进行交互。微服务架构具有良好的可扩展性、灵活性和可维护性,适合应对复杂和快速变化的业务需求。

1.3 Go语言在微服务中的优势

1.3.1 高并发性能

Go语言的设计目标之一是高并发性,它原生支持并发编程。Go的goroutine是轻量级的线程,启动和切换成本极低,可以在一个进程内轻松创建数以百万计的goroutine,处理大量并发请求。这对于微服务架构中的高并发需求尤其重要。

1.3.2 内置的高效标准库

Go语言提供了丰富且高效的标准库,包括网络通信、JSON解析、HTTP处理等常用功能。开发者可以直接利用这些标准库构建高性能的微服务,而无需引入第三方库,减少了依赖管理的复杂性。

1.3.3 编译型语言,性能优越

作为编译型语言,Go在编译时将代码直接转换为机器码,生成的可执行文件无需依赖虚拟机或解释器,运行效率高,启动速度快。这使得Go语言编写的微服务在性能上具有显著优势,能够高效处理大量请求和数据处理任务。

1.3.4 简洁易学,开发效率高

Go语言的语法简洁,设计思想明确,开发者可以在较短时间内上手并编写出高质量的代码。Go语言的代码风格一致,容易阅读和维护,有助于提升开发团队的整体效率。简洁的语法和强大的工具链,使得开发、调试、测试和部署微服务变得更加高效。

1.4 Go语言的微服务生态

1.4.1 丰富的开源框架

file
Go语言社区活跃,提供了大量优秀的开源框架和工具,支持微服务的快速开发和部署。以下是一些常用的Go微服务框架:

  • Gin
    :一个轻量级的Web框架,提供了高性能的HTTP路由和中间件机制。
  • Echo
    :另一个高性能的Web框架,支持丰富的中间件和插件,易于扩展。
  • Go Micro
    :专为微服务设计的框架,提供了服务发现、负载均衡、消息传递等一整套微服务架构的解决方案。

1.4.2 服务发现与注册

在微服务架构中,服务发现与注册是关键组件。Go语言生态中有许多工具可以实现服务发现和注册,例如:

  • Consul
    :支持服务发现、配置管理和健康检查的分布式系统。
  • Etcd
    :一个高可用的键值存储系统,用于分布式配置和服务发现。
  • Zookeeper
    :一个开源的分布式协调服务,用于服务注册和发现。

1.4.3 API网关

API网关是微服务架构中的入口,负责请求路由、负载均衡、认证授权等功能。在Go语言生态中,有多种选择构建API网关,例如:

  • Kong
    :一个基于Nginx的高性能API网关,支持多种插件和扩展。
  • Traefik
    :一个现代的反向代理和负载均衡器,专为微服务设计,支持自动化服务发现和配置。

二、Go语言的微服务框架

在构建微服务架构时,选择合适的框架至关重要。Go语言生态系统中有许多优秀的微服务框架,这些框架提供了丰富的功能和工具,帮助开发者高效地构建、部署和管理微服务。本章节将详细介绍几种常见的Go微服务框架,并讨论它们的特点、优劣势及适用场景。

2.1 Gin

2.1.1 概述

Gin是一个高性能的Go Web框架,以其简洁的API和极高的性能受到广泛欢迎。它基于httprouter构建,专注于提供快速、灵活的HTTP服务。Gin支持中间件机制,使得请求处理流程可扩展且易于管理。

2.1.2 特点

  • 高性能
    :Gin的性能在Go Web框架中名列前茅,适合构建高并发、高吞吐量的微服务。
  • 简洁的API
    :Gin提供了简洁且易于理解的API,降低了开发者的学习曲线。
  • 中间件支持
    :Gin支持中间件机制,开发者可以方便地添加和管理请求处理的各个阶段。
  • 路由灵活
    :基于httprouter的高效路由机制,支持多种路由方式和参数绑定。

2.1.3 示例代码

以下是一个使用Gin构建简单RESTful API的示例:

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()

    // 定义一个简单的GET请求处理器
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

    // 启动HTTP服务
    r.Run(":8080")
}

2.1.4 适用场景

Gin适用于需要高性能和灵活路由机制的微服务项目。由于其简洁易用的API,Gin特别适合快速开发和迭代的场景,如初创公司和小型项目。

2.2 Echo

2.2.1 概述

Echo是另一个高性能的Go Web框架,以其极简的设计和丰富的功能受到开发者的青睐。Echo的目标是提供一流的开发体验和卓越的性能,同时支持中间件、路由组、数据绑定等高级特性。

2.2.2 特点

  • 极简设计
    :Echo的设计理念是简洁和高效,API设计直观,易于上手。
  • 高性能
    :与Gin类似,Echo在性能上表现出色,适合构建高并发应用。
  • 灵活的中间件
    :支持中间件机制,开发者可以自定义请求处理的各个阶段。
  • 丰富的功能
    :包括路由组、数据绑定、验证、模板渲染等功能,满足不同应用场景的需求。

2.2.3 示例代码

以下是一个使用Echo构建简单RESTful API的示例:

package main

import (
    "github.com/labstack/echo/v4"
    "net/http"
)

func main() {
    e := echo.New()

    // 定义一个简单的GET请求处理器
    e.GET("/ping", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "message": "pong",
        })
    })

    // 启动HTTP服务
    e.Start(":8080")
}

2.2.4 适用场景

Echo适用于需要高性能和丰富功能的微服务项目。其极简的设计和强大的功能,使其适合中大型项目和企业级应用。

2.3 Go Micro

2.3.1 概述

Go Micro是一个专门为微服务设计的框架,提供了一整套构建、部署和管理微服务的工具和库。Go Micro的设计理念是简化微服务的开发和运维,使得开发者可以专注于业务逻辑,而无需关注底层基础设施。

2.3.2 特点

  • 模块化设计
    :Go Micro采用模块化设计,提供服务发现、负载均衡、消息传递、RPC等功能模块。
  • 服务发现与注册
    :内置支持Consul、Etcd、Zookeeper等服务发现机制,简化服务管理。
  • 消息传递
    :支持异步消息传递机制,如NATS、RabbitMQ等,适用于高并发场景。
  • 插件机制
    :通过插件机制,开发者可以根据需要扩展和定制框架功能。

2.3.3 示例代码

以下是一个使用Go Micro构建简单微服务的示例:

package main

import (
    "github.com/micro/go-micro/v2"
    "context"
    "fmt"
)

// 定义服务接口
type Greeter interface {
    Hello(context.Context, *Request, *Response) error
}

// 实现服务
type GreeterService struct{}

func (g *GreeterService) Hello(ctx context.Context, req *Request, rsp *Response) error {
    rsp.Msg = "Hello " + req.Name
    return nil
}

func main() {
    // 创建服务
    service := micro.NewService(
        micro.Name("greeter"),
    )

    // 初始化服务
    service.Init()

    // 注册服务
    micro.RegisterHandler(service.Server(), new(GreeterService))

    // 启动服务
    if err := service.Run(); err != nil {
        fmt.Println(err)
    }
}

2.3.4 适用场景

Go Micro适用于需要完整微服务架构解决方案的项目。它特别适合复杂的分布式系统和大规模微服务架构,帮助开发者管理服务的发现、注册、通信和负载均衡等任务。

2.4 选择指南

在选择Go微服务框架时,开发者需要综合考虑项目需求、团队技能水平和性能要求。以下是一些选择指南:

  • Gin
    :适合需要高性能和简洁API的项目,特别是小型和快速迭代的应用。
  • Echo
    :适合需要高性能和丰富功能的项目,特别是中大型和企业级应用。
  • Go Micro
    :适合需要完整微服务架构解决方案的项目,特别是复杂的分布式系统和大规模微服务架构。

三、服务发现与注册、API网关

在微服务架构中,服务发现与注册和API网关是两个关键组件,它们负责管理服务间的通信和请求路由,确保系统的可扩展性和高可用性。本文将详细介绍这两个组件的概念、实现方法及其在Go语言生态中的应用。

3.1 服务发现与注册

3.1.1 概述

服务发现与注册是微服务架构中的核心机制,负责追踪微服务实例的状态和位置,并动态更新服务列表。服务发现系统使得微服务可以灵活地加入或退出集群,支持弹性扩展和故障恢复。

3.1.2 服务发现模式

3.1.2.1 客户端发现模式

在客户端发现模式中,服务消费者直接查询服务注册中心获取可用服务实例,并通过负载均衡策略选择一个实例进行通信。客户端需要实现服务发现逻辑,这增加了客户端的复杂性。

3.1.2.2 服务器端发现模式

在服务器端发现模式中,服务消费者将请求发送到负载均衡器或API网关,后者负责查询服务注册中心并选择可用的服务实例。这种模式下,客户端逻辑简单,服务发现逻辑由负载均衡器或API网关处理。

3.1.3 常用的服务发现与注册工具

3.1.3.1 Consul

Consul是HashiCorp开发的一种分布式服务发现和配置管理工具,支持服务注册、发现、健康检查和KV存储等功能。Consul的特点包括:

  • 多数据中心支持
    :Consul可以跨多个数据中心进行服务发现。
  • 健康检查
    :Consul内置健康检查机制,确保只有健康的服务实例可用。
  • 丰富的API
    :提供HTTP和DNS API,支持多种编程语言和平台。

3.1.3.2 Etcd

Etcd是一个高可用的键值存储系统,主要用于分布式系统中的配置管理和服务发现。由CoreOS开发,Etcd的特点包括:

  • 一致性
    :Etcd基于Raft一致性算法,确保数据的一致性和可靠性。
  • 高可用性
    :Etcd支持集群模式,具备高可用性和容错能力。
  • 易于集成
    :提供HTTP API和多种语言的客户端库,易于集成到应用中。

3.1.3.3 Zookeeper

Zookeeper是Apache Hadoop生态中的一个分布式协调服务,广泛用于分布式应用的配置管理、服务注册与发现等场景。Zookeeper的特点包括:

  • 强一致性
    :Zookeeper基于ZAB协议,保证数据的一致性和持久性。
  • 丰富的功能
    :支持命名服务、配置管理、分布式锁和队列等功能。
  • 高可用性
    :Zookeeper支持集群部署,具备高可用性和可扩展性。

3.1.4 实现服务发现与注册

以下示例展示了如何使用Consul实现Go微服务的服务发现与注册:

package main

import (
    "github.com/hashicorp/consul/api"
    "log"
    "net/http"
)

func main() {
    // 创建Consul客户端
    client, err := api.NewClient(api.DefaultConfig())
    if err != nil {
        log.Fatal(err)
    }

    // 定义服务实例
    serviceID := "example-service-1"
    serviceName := "example-service"
    servicePort := 8080

    // 注册服务实例
    registration := &api.AgentServiceRegistration{
        ID:      serviceID,
        Name:    serviceName,
        Port:    servicePort,
        Check: &api.AgentServiceCheck{
            HTTP:     "http://localhost:8080/health",
            Interval: "10s",
        },
    }

    err = client.Agent().ServiceRegister(registration)
    if err != nil {
        log.Fatal(err)
    }

    // 启动HTTP服务
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}

3.2 API网关

3.2.1 概述

API网关是微服务架构中的一个重要组件,作为系统的入口点,负责请求路由、负载均衡、身份验证、速率限制等功能。API网关简化了客户端与后端服务的交互,提供统一的API接口和安全控制。

3.2.2 API网关的功能

3.2.2.1 请求路由

API网关根据请求的路径和方法,将请求路由到相应的后端服务。它可以通过配置文件或动态规则来定义路由策略,确保请求被正确转发到目标服务。

3.2.2.2 负载均衡

API网关实现负载均衡策略,将请求分配到多个后端服务实例,提升系统的吞吐量和可用性。常见的负载均衡算法包括轮询、最小连接数和随机算法。

3.2.2.3 身份验证与授权

API网关负责对进入系统的请求进行身份验证和授权,确保只有合法的请求能够访问后端服务。常见的身份验证机制包括JWT、OAuth2和API密钥等。

3.2.2.4 速率限制与流量控制

API网关可以设置速率限制策略,控制每个客户端的请求频率,防止系统被过载。流量控制机制可以保护后端服务,避免因突发流量导致的服务不可用。

3.2.3 常用的API网关工具

3.2.3.1 Kong

Kong是一个基于Nginx的开源API网关,支持高性能的API管理和插件扩展。Kong的特点包括:

  • 高性能
    :基于Nginx构建,具备高并发处理能力。
  • 插件系统
    :支持多种插件,如身份验证、日志记录、速率限制等,易于扩展。
  • 分布式架构
    :支持分布式部署,具备高可用性和可扩展性。

3.2.3.2 Traefik

Traefik是一个现代的反向代理和负载均衡器,专为微服务和容器化应用设计。Traefik的特点包括:

  • 自动化服务发现
    :支持Docker、Kubernetes、Consul等多种服务发现机制。
  • 动态配置
    :通过配置文件或API动态更新路由规则。
  • 高性能
    :具备高性能的请求处理能力,适合大规模分布式系统。

3.2.4 实现API网关

以下示例展示了如何使用Kong配置简单的API网关:

  1. 安装Kong

    docker run -d --name kong-database \
        -p 5432:5432 \
        -e "POSTGRES_USER=kong" \
        -e "POSTGRES_DB=kong" \
        postgres:9.6
    
    docker run -d --name kong \
        --link kong-database:kong-database \
        -e "KONG_DATABASE=postgres" \
        -e "KONG_PG_HOST=kong-database" \
        -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
        -p 8000:8000 \
        -p 8443:8443 \
        -p 8001:8001 \
        -p 8444:8444 \
        kong
    
  2. 配置服务和路由

    curl -i -X POST http://localhost:8001/services/ \
        --data "name=example-service" \
        --data "url=http://example.com"
    
    curl -i -X POST http://localhost:8001/services/example-service/routes \
        --data "paths[]=/example"
    
  3. 配置插件

    curl -i -X POST http://localhost:8001/services/example-service/plugins \
        --data "name=rate-limiting" \
        --data "config.second=5"
    

通过上述步骤,Kong API网关将请求路由到后端服务,并应用速率限制策略。

如有帮助,请多关注
TeahLead KrisChang,10+年的互联网和人工智能从业经验,10年+技术和业务团队管理经验,同济软件工程本科,复旦工程管理硕士,阿里云认证云服务资深架构师,上亿营收AI产品业务负责人。

用rust写了一个json小工具“JSON PICKER”,欢迎大家试用:

https://github.com/davelet/json-picker/releases/tag/V0.2

动机是平常开发的时候,经常遇到大段json,里面的很多字段是不需要的。

我所在的项目组在接口对接上出现了reponse达到数兆字节的情况

市面上已经有不少json工具,不过Mac能用的不多。即使能用,体验也不太好。
比如uTools的json插件:
utools
可以使用js的写法过滤。功能很强大,但是用起来太繁琐。

JSON PICKER 界面很简单,左边输入,中间展示结构,右边是意向节点的值。
json picker
刚开始展示的完整json。在树上点击哪个节点,右边就展示对应的值。可以点击"Copy Result"复制出来。

最下面是状态栏,如果有错误,Normal那里会显示错误信息

这个工具不是其他工具的替代品,毕竟功能很简单。它是一个补充,一般要和其他工具一起使用。比如我使用最多的是和vs code搭配。

iOS系统在后台执行程序时,有严格的限制,为了更好地管理资源和电池寿命,iOS会限制应用程序在后台的运行时间。然而,iOS提供了一些特定的策略和技术,使得应用程序可以在特定场景下保持后台运行(即“后台保活”)。以下是iOS中几种常见的后台保活方案,并附上示例代码:

一、后台任务

利用
beginBackgroundTask

endBackgroundTask
来执行后台任务。后台任务将在应用程序进入后台时仍能保持有限的时间执行任务。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (assign, nonatomic) UIBackgroundTaskIdentifier bgTask;

@end

@implementation AppDelegate

- (void)applicationDidEnterBackground:(UIApplication *)application {
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 在这里执行你的后台任务
        for (int i = 0; i < 100; i++) {
            NSLog(@"Background task running %d", i);
            [NSThread sleepForTimeInterval:1];
        }
        
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    });
}

@end

二、使用Background Fetch

利用Background Fetch,系统会间歇性地唤醒应用程序,以便它可以执行任务或获取数据。需要在Xcode的“Capabilities”中开启Background Modes,并勾选“Background fetch”。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
    return YES;
}

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // 在这里执行你的后台数据获取任务
    NSLog(@"Background fetch started");

    // 模拟数据获取
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"Background fetch completed");
        completionHandler(UIBackgroundFetchResultNewData);
    });
}

@end

三、使用远程通知(Silent Push Notification)

利用远程通知,在接收到通知时,系统会唤醒应用程序执行指定的任务。需要开启Remote notifications,在Application Capabilities中勾选“Remote notifications”。

#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted) {
            [[UIApplication sharedApplication] registerForRemoteNotifications];
        }
    }];
    return YES;
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // 在这里处理收到的远程通知
    NSLog(@"Received remote notification");

    // 模拟处理任务
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"Handled remote notification");
        completionHandler(UIBackgroundFetchResultNewData);
    });
}

@end

四、使用特定的后台模式(Background Modes)

iOS提供了一些特定的后台模式,允许程序在后台持续运行。常见的后台模式包括:

  • Audio: 允许应用程序在后台播放音频。
  • Location: 允许应用程序在后台持续获取位置更新。
  • VoIP: 允许应用程序在后台侦听VoIP事件。
  • Bluetooth: 允许应用程序与蓝牙设备通信。

1. Audio后台模式

需要在Xcode的“Capabilities”中开启Background Modes,并勾选“Audio, AirPlay, and Picture in Picture”。

#import <AVFoundation/AVFoundation.h>

@interface AppDelegate ()

@property (nonatomic, strong) AVAudioPlayer *audioPlayer;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSError *error = nil;
    NSURL *audioURL = [[NSBundle mainBundle] URLForResource:@"audioFileName" withExtension:@"mp3"];
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:&error];
    [self.audioPlayer prepareToPlay];
    
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
    [audioSession setActive:YES error:&error];
    
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.audioPlayer play];
}

@end

2. Location后台模式

需要在Xcode的“Capabilities”中开启Background Modes,并勾选“Location updates”。

#import <CoreLocation/CoreLocation.h>

@interface AppDelegate () <CLLocationManagerDelegate>

@property (nonatomic, strong) CLLocationManager *locationManager;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    [self.locationManager requestAlwaysAuthorization];
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [self.locationManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    CLLocation *location = [locations lastObject];
    NSLog(@"Background location: %@", location);
}

@end

五、使用后台URLSession

使用
NSURLSession
来执行后台下载和上传任务。需要在后台配置中开启Background Modes,并勾选“Background fetch”和“Remote notifications”。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, NSURLSessionDelegate, NSURLSessionDownloadDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) NSURLSession *backgroundSession;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.example.background"];
    self.backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSURL *url = [NSURL URLWithString:@"http://example.com/largefile.zip"];
    NSURLSessionDownloadTask *downloadTask = [self.backgroundSession downloadTaskWithURL:url];
    [downloadTask resume];
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"Download completed: %@", location);
    // 处理下载结果,比如保存文件
}

@end

通过上述几种方案,我们可以在iOS应用程序中实现各种场景下的后台保活。每种方案都有其适用的场景和限制,开发者需要根据应用的实际需求和系统提供的特性,选择合适的后台保活方案。