• Jsooxam中的容器与JAVA对接或Cscript加密解密数据类型的分辨与构造


    广义上讲所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter。而Exporter的一个实例称为target,如下所示,Prometheus通过轮询的方式定期从这些target中获取样本数据:

    1|2来源有哪些?

    社区提供的

    Prometheus社区提供了丰富的Exporter实现,涵盖了从基础设施,中间件以及网络等各个方面的监控功能。这些Exporter可以实现大部分通用的监控需求。下表列举一些社区中常用的Exporter:

    范围常用Exporter
    数据库 MySQL Exporter, Redis Exporter, MongoDB Exporter, MSSQL Exporter等
    硬件 Apcupsd Exporter,IoT Edison Exporter, IPMI Exporter, Node Exporter等
    消息队列 Beanstalkd Exporter, Kafka Exporter, NSQ Exporter, RabbitMQ Exporter等
    存储 Ceph Exporter, Gluster Exporter, HDFS Exporter, ScaleIO Exporter等
    HTTP服务 Apache Exporter, HAProxy Exporter, Nginx Exporter等
    API服务 AWS ECS Exporter, Docker Cloud Exporter, Docker Hub Exporter, GitHub Exporter等
    日志 Fluentd Exporter, Grok Exporter等
    监控系统 Collectd Exporter, Graphite Exporter, InfluxDB Exporter, Nagios Exporter, SNMP Exporter等
    其他 Blockbox Exporter, JIRA Exporter, Jenkins Exporter, Confluence Exporter等

    用户自定义的

    除了直接使用社区提供的Exporter程序以外,用户还可以基于Prometheus提供的Client Library创建自己的Exporter程序,目前Promthues社区官方提供了对以下编程语言的支持:Go、Java/Scala、Python、Ruby。同时还有第三方实现的如:Bash、C++、Common Lisp、Erlang,、Haskeel、Lua、Node.js、PHP、Rust等。

    2|0Exporter的运行方式

    从Exporter的运行方式来讲,又可以分为

    2|1独立使用的

    以我们已经使用过的Node Exporter为例,由于操作系统本身并不直接支持Prometheus,同时用户也无法通过直接从操作系统层面上提供对Prometheus的支持。因此,用户只能通过独立运行一个程序的方式,通过操作系统提供的相关接口,将系统的运行状态数据转换为可供Prometheus读取的监控数据。 除了Node Exporter以外,比如MySQL Exporter、Redis Exporter等都是通过这种方式实现的。 这些Exporter程序扮演了一个中间代理人的角色。

    2|2集成到应用中的

    为了能够更好的监控系统的内部运行状态,有些开源项目如Kubernetes,ETCD等直接在代码中使用了Prometheus的Client Library,提供了对Prometheus的直接支持。这种方式打破的监控的界限,让应用程序可以直接将内部的运行状态暴露给Prometheus,适合于一些需要更多自定义监控指标需求的项目。

    3|0Exporter规范

    所有的Exporter程序都需要按照Prometheus的规范,返回监控的样本数据。以Node Exporter为例,当访问/metrics地址时会返回以下内容:

    # HELP node_cpu Seconds the cpus spent in each mode. # TYPE node_cpu counter node_cpu{cpu="cpu0",mode="idle"} 362812.7890625 # HELP node_load1 1m load average. # TYPE node_load1 gauge node_load1 3.0703125

    这是一种基于文本的格式规范,在Prometheus 2.0之前的版本还支持Protocol buffer规范。相比于Protocol buffer文本具有更好的可读性,以及跨平台性。Prometheus 2.0的版本也已经不再支持Protocol buffer。

    Exporter返回的样本数据,主要由三个部分组成:样本的一般注释信息(HELP),样本的类型注释信息(TYPE)和样本。Prometheus会对Exporter响应的内容逐行解析:

    如果当前行以# HELP开始,Prometheus将会按照以下规则对内容进行解析,得到当前的指标名称以及相应的说明信息:

    # HELP <metrics_name> <doc_string>

    如果当前行以# TYPE开始,Prometheus会按照以下规则对内容进行解析,得到当前的指标名称以及指标类型:

    # TYPE <metrics_name> <metrics_type>

    TYPE注释行必须出现在指标的第一个样本之前。如果没有明确的指标类型需要返回为untyped。 除了# 开头的所有行都会被视为是监控样本数据。 每一行样本需要满足以下格式规范:

    metric_name [ "{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}" ] value [ timestamp ]

    其中metric_name和label_name必须遵循PromQL的格式规范要求。value是一个float格式的数据,timestamp的类型为int64(从1970-01-01 00:00:00以来的毫秒数),timestamp为可选默认为当前时间。具有相同metric_name的样本必须按照一个组的形式排列,并且每一行必须是唯一的指标名称和标签键值对组合。

    需要特别注意的是对于histogram和summary类型的样本。需要按照以下约定返回样本数据:

    1 . 类型为summary或者histogram的指标x,该指标所有样本的值的总和需要使用一个单独的x_sum指标表示

    2 . 类型为summary或者histogram的指标x,该指标所有样本的总数需要使用一个单独的x_count指标表示。

    3 . 对于类型为summary的指标x,其不同分位数quantile所代表的样本,需要使用单独的x{quantile="y"}表示。

    4 . 对于类型histogram的指标x为了表示其样本的分布情况,每一个分布需要使用x_bucket{le="y"}表示,其中y为当前分布的上位数。同时必须包含一个样本x_bucket{le="+Inf"},并且其样本值必须和x_count相同。

    5 . 对于histogram和summary的样本,必须按照分位数quantile和分布le的值的递增顺序排序。

    以下是类型为histogram和summary的样本输出示例

    # A histogram, which has a pretty complex representation in the text format: # HELP http_request_duration_seconds A histogram of the request duration. # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket{le="0.05"} 24054 http_request_duration_seconds_bucket{le="0.1"} 33444 http_request_duration_seconds_bucket{le="0.2"} 100392 http_request_duration_seconds_bucket{le="+Inf"} 144320 http_request_duration_seconds_sum 53423 http_request_duration_seconds_count 144320 # Finally a summary, which has a complex representation, too: # HELP rpc_duration_seconds A summary of the RPC duration in seconds. # TYPE rpc_duration_seconds summary rpc_duration_seconds{quantile="0.01"} 3102 rpc_duration_seconds{quantile="0.05"} 3272 rpc_duration_seconds{quantile="0.5"} 4773 rpc_duration_seconds_sum 1.7560473e+07 rpc_duration_seconds_count 2693

    指定样式格式的版本
    在Exporter响应的HTTP头信息中,可以通过Content-Type指定特定的规范版本,例如:

    HTTP/1.1 200 OK Content-Encoding: gzip Content-Length: 2906 Content-Type: text/plain; version=0.0.4 Date: Sat, 17 Mar 2018 08:47:06 GMT

    其中version用于指定Text-based的格式版本,当没有指定版本的时候,默认使用最新格式规范的版本。同时HTTP响应头还需要指定压缩格式为gzip。

    4|0容器监控

    Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux/Windows/Mac机器上。容器镜像正成为一个新的标准化软件交付方式。

    例如,可以通过一下命令快速在本地启动一个Nginx服务:

    4|1安装docker
    # 安装一些必要的系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加软件源信息 # docker 官方源 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 阿里云源 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum makecache fast # CentOS7安装 Docker-ce yum -y install docker-ce mkdir /etc/docker vim /etc/docker/daemon.json { "registry-mirrors": ["https://registry.docker-cn.com"] } # 启动Docker后台服务 systemctl start docker && systemctl enable docker systemctl daemon-reload # 守护进程重启 # 运行一个nginx做测试 docker run -itd nginx

    为了能够获取到Docker容器的运行状态,用户可以通过Docker的stats命令获取到当前主机上运行容器的统计信息,可以查看容器的CPU利用率、内存使用量、网络IO总量以及磁盘IO总量等信息。

    docker stats CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 9a1648bec3b2 0.30% 196KiB / 3.855GiB 0.00% 828B / 0B 827kB / 0B 1 # 除了使用命令以外,用户还可以通过docker提供的http api查看容器的监控统计信息.

    使用CAdvisor

    CAdvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行CAdvisor用户可以轻松的获取到当前主机上容器的运行统计信息,并以图表的形式向用户展示。

    在本地运行CAdvisor也非常简单,直接运行一下命令即可:

    docker run \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:rw \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ google/cadvisor:latest # 通过访问http://localhost:8080可以查看,当前主机上容器的运行状态.

    CAdvisor是一个简单易用的工具,相比于使用Docker命令行工具,用户不用再登录到服务器中即可以可视化图表的形式查看主机上所有容器的运行状态。

    而在多主机的情况下,在所有节点上运行一个CAdvisor再通过各自的UI查看监控信息显然不太方便,同时CAdvisor默认只保存2分钟的监控数据。好消息是CAdvisor已经内置了对Prometheus的支持。访问http://localhost:8080/metrics即可获取到标准的Prometheus监控样本输出:

    下面列举了一些CAdvisor中获取的典型监控指标

    指标名称类型含义
      gauge 再过去10秒内容器CPU的平均负载
    container_cpu_usage_seconds_total    
    指标名称类型含义
    container_cpu_load_average_10s gauge 过去10秒内容器CPU的平均负载
    container_cpu_usage_seconds_total counter 容器在每个CPU内核上的累积占用时间 (单位:秒)
    container_cpu_system_seconds_total counter System CPU累积占用时间(单位:秒)
    container_cpu_user_seconds_total counter User CPU累积占用时间(单位:秒)
    container_fs_usge_bytes gauge 容器中文件系统的使用量(单位:字节)
    container_network_receive_bytes_total counter 容器网络累计接受数据总量(单位: 字节)
    container_network_transmit_bytes_total counter 容器网络累计传输数据总量(单位: 字节)

    5|0与Prometheus集成

    修改/etc/prometheus/prometheus.yml,将cAdvisor添加监控数据采集任务目标当中:

    - job_name: 'docker' static_configs: - targets: ['172.19.0.27:8080'] systemctl restart prometheus

    启动Prometheus服务,可以在Prometheus UI中看到当前所有的Target状态:

    当能够正常采集到cAdvisor的样本数据后,可以通过一下表达式计算容器的CPU使用率.

    sum(irate(container_cpu_usage_seconds_total{image!=""}[1m])) without (cpu)

    查询容器内存使用量(单位: 字节)

    container_memory_usage_bytes{image!=""}

    查询容器网络接收量速率(单位: 字节/秒)

    sum(rate(container_network_receive_bytes_total{image!=""}[1m])) without (interface)

    查询容器网络传输量速率

    sum(rate(container_network_transmit_bytes_total{image!=""}[1m])) without (interface)

    查询容器文件系统读取速率

    sum(rate(container_fs_reads_bytes_total{image!=""}[1m])) without (device) # 为了方便看出效果,我们使用dd命令 docker exec -it 628d /bin/bash dd if=/dev/zero of=test bs=1M count=1000

    • 查询容器文件系统写入速率(单位: 字节/秒)
    sum(rate(container_fs_writes_bytes_total{image!=""}[1m])) without (device)

    6|0Prometheus网络探测

    接下来我们主要介绍Prometheus下如何进行白盒监控,我们之前监控主机的资源用量、容器的运行状态、数据库中间件的运行数据。 这些都是支持业务和服务的基础设施,通过白盒能够了解其内部的实际运行状态,通过对监控指标的观察能够预判可能出现的问题,从而对潜在的不确定因素进行优化。而从完整的监控逻辑的角度,除了大量的应用白盒监控以外,还应该添加适当的黑盒监控。
    黑盒监控即以用户的身份测试服务的外部可见性,常见的黑盒监控包括HTTP探针、TCP探针等用于检测站点或者服务的可访问性,以及访问效率等。

    黑盒监控相较于白盒监控最大的不同在于黑盒监控是以故障为导向当故障发生时,黑盒监控能快速发现故障,而白盒监控则侧重于主动发现或者预测潜在的问题。一个完善的监控目标是要能够从白盒的角度发现潜在问题,能够在黑盒的角度快速发现已经发生的问题。

    6|1安装Blackbox Exporter

    Blackbox Exporter是Prometheus社区提供的官方黑盒监控解决方案,其允许用户通过:HTTP、HTTPS、DNS、TCP以及ICMP的方式对网络进行探测。用户可以直接使用go get命令获取Blackbox Exporter源码并生成本地可执行文件:

    6|2下载安装blackbox_exporter

    wget https://github.com/prometheus/blackbox_exporter/releases/download/v0.16.0/blackbox_exporter-0.16.0.linux-amd64.tar.gz

    tar xvf blackbox_exporter-0.16.0.linux-amd64.tar.gz -C /usr/local/prometheus/ mv blackbox_exporter-0.16.0.linux-amd64/ blackbox_exporter useradd prometheus chown -R prometheus:prometheus /usr/local/prometheus/ vim /usr/lib/systemd/system/blackbox_exporter.service [Unit] Description=blackbox_exporter After=network.target [Service] Type=simple User=prometheus ExecStart=/usr/local/prometheus/blackbox_exporter/blackbox_exporter --config.file=/usr/local/prometheus/blackbox_exporter/blackbox.yml Restart=on-failure [Install] WantedBy=multi-user.target systemctl enable blackbox_exporter.service systemctl start blackbox_exporter.service

    运行Blackbox Exporter时,需要用户提供探针的配置信息,这些配置信息可能是一些自定义的HTTP头信息,也可能是探测时需要的一些TSL配置,也可能是探针本身的验证行为。在Blackbox Exporter每一个探针配置称为一个module,并且以YAML配置文件的形式提供给Blackbox Exporter。 每一个module主要包含以下配置内容,包括探针类型(prober)、验证访问超时时间(timeout)、以及当前探针的具体配置项:

    # 探针类型:http、 tcp、 dns、 icmp. prober: <prober_string> # 超时时间 [ timeout: <duration> ] # 探针的详细配置,最多只能配置其中的一个 [ http: <http_probe> ] [ tcp: <tcp_probe> ] [ dns: <dns_probe> ] [ icmp: <icmp_probe> ]

    下面是一个简化的探针配置文件blockbox.yml,包含两个HTTP探针配置项

    modules: http_2xx: prober: http http: method: GET http_post_2xx: prober: http http: method: POST

    通过运行一下命令,并指定使用的探针设置文件启动Blockbox Exporter实例:

    blackbox_exporter --config.file=/etc/prometheus/blackbox.yml or systemctl restart blackbox_exporter.service

    启动成功后,就可以通过访问http://172.19.0.27:9115/probe?module=http_2xx&target=baidu.com对baidu.com进行探测。这里通过在URL中提供module参数指定了当前使用的探针,target参数指定探测目标,探针的探测结果通过Metrics的形式返回:

    # HELP probe_dns_lookup_time_seconds Returns the time taken for probe dns lookup in seconds # TYPE probe_dns_lookup_time_seconds gauge probe_dns_lookup_time_seconds 0.004359875 # HELP probe_duration_seconds Returns how long the probe took to complete in seconds # TYPE probe_duration_seconds gauge probe_duration_seconds 0.046153996 # HELP probe_failed_due_to_regex Indicates if probe failed due to regex # TYPE probe_failed_due_to_regex gauge probe_failed_due_to_regex 0 # HELP probe_http_content_length Length of http content response # TYPE probe_http_content_length gauge probe_http_content_length 81 # HELP probe_http_duration_seconds Duration of http request by phase, summed over all redirects # TYPE probe_http_duration_seconds gauge probe_http_duration_seconds{phase="connect"} 0.00105657 probe_http_duration_seconds{phase="processing"} 0.039457402 probe_http_duration_seconds{phase="resolve"} 0.004359875 probe_http_duration_seconds{phase="tls"} 0 probe_http_duration_seconds{phase="transfer"} 0.000337184 # HELP probe_http_last_modified_timestamp_seconds Returns the Last-Modified HTTP \ response header in unixtime # TYPE probe_http_last_modified_timestamp_seconds gauge probe_http_last_modified_timestamp_seconds 1.26330408e+09 # HELP probe_http_redirects The number of redirects # TYPE probe_http_redirects gauge probe_http_redirects 0 # HELP probe_http_ssl Indicates if SSL was used for the final redirect # TYPE probe_http_ssl gauge probe_http_ssl 0 # HELP probe_http_status_code Response HTTP status code # TYPE probe_http_status_code gauge probe_http_status_code 200 # HELP probe_http_uncompressed_body_length Length of uncompressed response body # TYPE probe_http_uncompressed_body_length gauge probe_http_uncompressed_body_length 81 # HELP probe_http_version Returns the version of HTTP of the probe response # TYPE probe_http_version gauge probe_http_version 1.1 # HELP probe_ip_protocol Specifies whether probe ip protocol is IP4 or IP6 # TYPE probe_ip_protocol gauge probe_ip_protocol 4 # HELP probe_success Displays whether or not the probe was a success # TYPE probe_success gauge probe_success 1

    从返回的样本中,用户可以获取站点的DNS解析耗时,站点响应时间,HTTP响应状态码等等和站点访问质量相关的监控指标,从而帮助管理员主动的发现故障和问题.

    7|0Prometheus集成

    接下来,只需要在Prometheus下配置对Blockbox Exporter实例的采集任务即可、最直观的配置方式.

    - job_name: 'baidu_http2xx_probe' params: module: - http_2xx target: - baidu.com metrics_path: /probe static_configs: - targets: ['172.19.0.27:9115'] - job_name: 'prometheus_http2xx_probe' params: module: - http_2xx target: - prometheus.io metrics_path: /probe static_configs: - targets: ['172.19.0.27:9115'] systemctl restart prometheus

    这里分别配置了名为baidu_http2x_probe和prometheus_http2xx_probe的采集任务,并且通过params指定使用的探针(module)以及探测目标(target).

    那问题就来了,假如我们有N个目标站点且都需要M种探测方式,那么Prometheus中将包含N * M个采集任务,从配置管理的角度来说显然是不可接受的。这里我们也可以采用Relabling的方式对这些配置进行简化,配置方式如下:

    - job_name: 'blackbox' metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - http://prometheus.io # Target to probe with http. - https://prometheus.io # Target to probe with https. - http://example.com:8080 # Target to probe with http on port 8080. relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: 172.19.0.27:9115

    这里针对每一个探针服务(如http_2xx)定义一个采集任务,并且直接将任务的采集目标定义为我们需要探测的站点,在采集样本数据之前通过relabel_configs对采集任务进行动态配置.

    * 第一步, 根据Target实例的地址,写入__param_target标签中,__param_<name>形式的标签来表示, # 在采集任务时会在请求目标地址中添加<name>参数,等同于params的设置. * 第二步, 获取__param_target的值,并覆写到instance标签中. * 第三步, 覆写Target实例的__address__标签值为BlockBox Exporter实例的访问地址.

    8|0HTTP探针

    HTTP探针是进行黑盒监控时最常用的探针之一,通过HTTP探针能够网站或者HTTP服务建立有效的监控,包括其本身的可用性,以及用户体验相关的如响应时间等等。除了能够在服务出现异常的时候及时报警,还能帮助系统管理员分析和优化网站体验。

    Blockbox Exporter中所有的探针均是以Module的信息进行配置。如下所示,配置了一个最简单的HTTP探针:

    modules: http_2xx_example: prober: http http:

    通过prober配置项指定探针类型。配置项http用于自定义探针的探测方式,这里有没对http配置项添加任何配置,表示完全使用HTTP探针的默认配置,该探针将使用HTTP GET的方式对目标服务进行探测,并且验证返回状态码是否为2XX,是则表示验证成功,否则失败。

    8|1自定义HTTP请求

    HTTP服务通常会以不同的形式对外展现,有些可能就是一些简单的网页,而有些则可能是一些基于REST的API服务。 对于不同类型的HTTP的探测需要管理员能够对HTTP探针的行为进行更多的自定义设置,包括:HTTP请求方法、HTTP头信息、请求参数等。对于某些启用了安全认证的服务还需要能够对HTTP探测设置相应的Auth支持。对于HTTPS类型的服务还需要能够对证书进行自定义设置。

    如下所示,这里通过method定义了探测时使用的请求方法,对于一些需要请求参数的服务,还可以通过headers定义相关的请求头信息,使用body定义请求内容:

    http_post_2xx: prober: http timeout: 5s http: method: POST headers: Content-Type: application/json body: '{}'

    如果HTTP服务启用了安全认证,Blockbox Exporter内置了对basic_auth的支持,可以直接设置相关的认证信息即可:

    http_basic_auth_example: prober: http timeout: 5s http: method: POST headers: Host: "login.example.com" basic_auth: username: "username" password: "mysecret"

    对于使用了Bear Token的服务也可以通过bearer_token配置项直接指定令牌字符串,或者通过bearer_token_file指定令牌文件。

    对于一些启用了HTTPS的服务,但是需要自定义证书的服务,可以通过tls_config指定相关的证书信息:

    http_custom_ca_example: prober: http http: method: GET tls_config: ca_file: "/certs/my_cert.crt"
    • 自定义探针行为
    • 在默认情况下HTTP探针只会对HTTP返回状态码进行校验,如果状态码为2XX(200 <= StatusCode < 300)则表示探测成功,并且探针返回的指标probe_success值为1。
    • 如果用户需要指定HTTP返回状态码,或者对HTTP版本有特殊要求,如下所示,可以使用valid_http_versions和valid_status_codes进行定义:
    http_2xx_example: prober: http timeout: 5s http: valid_http_versions: ["HTTP/1.1", "HTTP/2"] valid_status_codes: []

    默认情况下,Blockbox返回的样本数据中也会包含指标probe_http_ssl,用于表明当前探针是否使用了SSL:

    # HELP probe_http_ssl Indicates if SSL was used for the final redirect # TYPE probe_http_ssl gauge probe_http_ssl 0

    而如果用户对于HTTP服务是否启用SSL有强制的标准。则可以使用fail_if_ssl和fail_if_not_ssl进行配置。fail_if_ssl为true时,表示如果站点启用了SSL则探针失败,反之成功。fail_if_not_ssl刚好相反。

    http_2xx_example: prober: http timeout: 5s http: valid_status_codes: [] method: GET no_follow_redirects: false fail_if_ssl: false fail_if_not_ssl: false

    除了基于HTTP状态码,HTTP协议版本以及是否启用SSL作为控制探针探测行为成功与否的标准以外,还可以匹配HTTP服务的响应内容。使用fail_if_matches_regexp和fail_if_not_matches_regexp用户可以定义一组正则表达式,用于验证HTTP返回内容是否符合或者不符合正则表达式的内容。

    http_2xx_example: prober: http timeout: 5s http: method: GET fail_if_matches_regexp: - "Could not connect to database" fail_if_not_matches_regexp: - "Download the latest version here"

    最后需要提醒的时,默认情况下HTTP探针会走IPV6的协议。 在大多数情况下,可以使用preferred_ip_protocol=ip4强制通过IPV4的方式进行探测。在Bloackbox响应的监控样本中,也会通过指标probe_ip_protocol,表明当前的协议使用情况:

    # HELP probe_ip_protocol Specifies whether probe ip protocol is IP4 or IP6 # TYPE probe_ip_protocol gauge probe_ip_protocol 6

    除了支持对HTTP协议进行网络探测以外,Blackbox还支持对TCP、DNS、ICMP等其他网络协议![]

  • 相关阅读:
    web接口调用
    UISegmentedControl
    CocoaPods安装和使用教程
    NSTimeInterval
    OC-NSString从文件中读取字符串
    XMPP(mac)方案二 ejabberd 服务器搭建 (转载)
    监听 IOS响应式编程框架ReactiveCocoa(RAC)使用示例(转载)
    iOS开发-文件管理
    OC中NSString 的常用方法 (转载)
    NSAttributedString
  • 原文地址:https://www.cnblogs.com/longjiang/p/13090495.html
Copyright © 2020-2023  润新知