ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

01 . Openfalcon简介及部署

2020-05-31 22:56:34  阅读:330  来源: 互联网

标签:01 http 简介 Openfalcon yum mysql falcon docker open


Open-Falcon简介

介绍

  • 监控系统是整个运维环节,乃至整个产品生命周期中最重要的一环,事前及时预警发现故障,事后提供翔实的数据用于追查定位问题。
  • 监控系统作为一个成熟的运维产品,业界有很多开源的实现可供选择。当公司刚刚起步,业务规模较小,运维团队也刚刚建立的初期,选择一款开源的监控系统,是一个省时省力,效率最高的方案。
  • 之后,随着业务规模的持续快速增长,监控的对象也越来越多,越来越复杂,监控系统的使用对象也从最初少数的几个SRE,扩大为更多的DEVS,SRE。
    这时候,监控系统的容量和用户的“使用效率”成了最为突出的问题。
  • 监控系统业界有很多杰出的开源监控系统。我们在早期,一直在用zabbix,不过随着业务的快速发展,以及互联网公司特有的一些需求,现有的开源的监控系统在性能、扩展性、和用户的使用效率方面,已经无法支撑了。
  • 因此,我们在过去的一年里,从互联网公司的一些需求出发,从各位SRE、SA、DEVS的使用经验和反馈出发,结合业界的一些大的互联网公司做监控,用监控的一些思考出发,设计开发了小米的监控系统:open-falcon。

Open-Falcon特点

  1. 强大灵活的数据采集: 自动发现,支持falcon-agent、snmp、支持用户主动push、用户自定义插件支持,opentsdb data model like (timestamp、endpoint、metric、key-value tags)
  2. 水平扩展能力: 支持每个周期上亿次的数据采集、告警判定、历史数据存储和查询.
  3. 高效率的告警策略管理: 高效的portal、支持策略模板、模板集成和覆盖、多种告警方式、支持callback调用.
  4. 人性化的告警设置: 最大告警次数、告警级别、告警恢复通知、告警暂停、不同时段不同阈值、支持维护周期.
  5. 高效的graph组件: 单机支撑200万metric的上报、归档、存储(周期为1分钟)
  6. 高效的历史数据query组件: 采用rrdtool的数据归档策略,秒级返回上百个metric一年的历史数据.
  7. dashboard: 多维度的数据展示、用户自定义Screen。
  8. 高可用: 整个系统无核心单点,易运维,易部署,可水平扩展.
  9. 开发语言: 整个系统的后端全部采用golang编写,portal和dashboard使用Python编写.

缺点:

  1. 每个Graph(数据存储)实例均是单点. (这点很大程度上解决了,Transfer中可以配置Graph双写,手工维护双写列表麻烦,但这个列表基本不怎么变)
  2. Graph扩容有损,
  3. 报警没有入库,当前未恢复的报警是存在Alarm内存中的,重启就丢了,历史报警也没入库,无法追溯.
  4. 报警现场没有保存: 因为使用rrd存储历史数据,一天后数据就被做了归档处理,查看历史报警时刻趋势图,无法查看当前准确值.
  5. 前端展示功能很鸡肋,哪怕有grafana,没有现成的grafana页面,需要自己编辑.

架构

相关服务组件详解

绘图组件

1.Falcon-Agent http:1998

数据采集组件: 部署到目标机器采集机器监控项:
agent内置了一个http接口,会自动采集预先定义的各种采集项,每隔60秒,push到transfer。

2.Transfer http:6060 rpc:8433 socket:4444

agent与transfer建立长连接,将数据汇报给tarnsfer
transfer默认监听在:8433端口上,agent会通过jsonrpc的方式来push数据上来
transfer将数据发送给judge和graph

3.Graph http:6070 rpc:6071

graph组件是存储绘图数据、历史数据的组件。transfer会把接收到的数据,转发给graph。
监听端口为6071,校验方法如下,返回ok表示服务正常。
curl -s "http://127.0.0.1:6071/health"

4.Query http:9966

绘图数据的查询接口,因为graph是分片存储的,如果要传输给dashboard,就需要query组件收集用户的数据进行聚合再返回给用户。

5.Dashboard http:8081

Dashboard是面向用户的查询界面,在这里,用户可以看到push到graph中的所有数据,并查看其趋势图。

6.Task http:8002

负责一些定时任务,索引全量更新,垃圾索引清理,自身组件监控等

报警组件

1.Sender

调用各个公司提供的mail-privider和sms-privider,按照某个并发度,从redis读取邮件,短信并发送,
alarm生成的报警短信和报警邮件是直接写入redis即可,由sender来发送。

2.UIC http:80

用户组管理,单点登录

3.Portal http:5050

配置报警策略,管理机器分组的web端

4.HBS http:6031 rpc:6030

Heartbeat Server心跳服务,只依赖Protal的DB

5.Judge http:6081 rpm:6080

告警判断模块

6.Links http:6090

报警合并依赖的web端,存放报警详情

7.Alarm http:9912

报警时间处理器: 处理报警事件,judge的报警事件写入到redis,alarm从redis读取数据

8.Mail-privider&&sms-provider http:4000

发送邮件http api

9.Nodata http:6090

检测监控数据的上报异常

Aggregator http:6055

集群聚合模块: 聚合某集群下所有机器某个指标的值,提供一种集群视觉的监控体验

Falcon-Agent监控项

每台服务器,都有安装falcon-agent,falcon-agent是一个golang开发的daemon程序,用于自发现的采集单机的各种数据和指标,这些指标包括不限于以下几个方面,共计200多项指标:

    * CPU相关
    * 磁盘相关
    * IO
    * Load
    * 内存相关
    * 网络相关
    * 端口存活、进程存活
    * ntp offset(插件)
    * 某个进程资源消耗(插件)
    * netstat.ss等相关统计项采集
    * 机器内核配置参数

只要安装了falcon-agent的机器,就会自动开始采集各项指标,主动上报,不需要用户在server做任何配置(这和zabbix有很大的不同),这样做的好处,就是用户维护方便,覆盖率高.

当然这样做也会给server造成较大的压力,不过open-falcon的服务端单机性能足够高,同时可以水平扩展,所以自动采集足够多的数据,反而是一件好的事情,对于SRE和DEV来讲,事后追查问题,不再是难题.

另外,falcon-agent提供了一个proxy-gateway,用户可以方便的通过http接口,push数据到本机的gateway,gateway会帮忙高效率的转发给server端.

数据模型

Data Mode是否强大,是否灵活,对于监控系统用户的“使用效率”至关重要,比如以zabbix为例,上报的数据为hostname(或者ip)、metric,那么用户添加告警策略,管理告警策略的时候,就只能以这两个维度进行。举一个常见的场景:

hostA的磁盘空间,小于5%,就告警,一般的服务器上,都会有两个主要的分区,根分区和home分区,在zabbix里面,就得加两条规则: 如果是hadoop的机器,一般都还会有十几块的数据盘,还得多加10多条规则,这样就很痛苦,不幸福,不利于自动化(当然zabbix可以通过配置一些自动发现策略来搞定这个,不过比较麻烦)

数据收集

transfer: 接收客户端发送的数据,做一些数据规整,检查之后,转发到多个后端系统去处理,在转发到每个后端业务系统的时候,transfer会根据一致性hash算法,进行数据分片,来达到后端业务系统的水平扩展.

transfer提供jsonRpc接口和telnet接口两种方式,judge,graph,opentsdb,judge是我们开发的高性能告警判定组件,graph使我们开发的高性能数据存储、归档查询组件,opentsdb是开源的时间序列数据存储服务,可以通过transfer的配置文件来开启.

transfer的数据来源,一般有三种

  1. falcon-agent采集的基础监控数据
  2. falcon-agent执行用户自定义的插件返回的数据
  3. client library: 线上的业务系统,都嵌入使用了统一的perfcounter.jar,对于业务系统中每个RPC接口的qps、latency都会主动采集并上报.

说明: 上面这三种数据,都会先发送给本机的proxy-gateway,再由gateway转发给transfer.

基础监控是指只要是个机器(或容器)就能监控,比如cpu,mem,net,io,disk等,这些监控项采集的方式固定,不需要配置,也不需要用户提供额外的参数指定,只要agent跑起来就可以直接采集数据上报上去,非基础监控则相反.比如端口监控,你不给我端口就不行,不然我上报所有65535个端口的监听服务你也用不了,这类监控需要用户配置后才会开始采集上报的监控(包括类似于端口监控的配置触发类监控,以及类似于mysql的插件脚本类监控),一般就不算基础监控的范畴了.

报警

报警判定,是由judge组件来完成。用户在web portal来配置相关的报警策略,存储在Mysql中,hearbeat server会定期加载Mysql中的内容,judge也会定期和hearbeat server保持沟通,来获取相关的报警策略.

heartbeat server不仅仅单纯的加载Mysql中的内容,根据模板继承、模板项覆盖、报警动作覆盖、模板和hostGroup绑定,计算出最终关联到endpoint的告警策略,提供给judge组件来使用.

transfer转发给judge的每条数据,都会触发相关策略的判定,来决定是否满足报警条件,如果满足条件,则会发送给alarm,alarm再以邮件、短信、米聊等形式通知相关用户,也可以执行用户预先配置好的callback地址.

用户可以很灵活的来配置告警判定策略,比如连续n次满足条件,连续n次的最大值满足条件、不同时间段不同的阈值,如果出于维护周期内则忽略等等.

另外也支持突升突降类的判定和告警.

API

到这里,数据已经成功的存储在了graph里,如何快速的读出来,读过去1小时的,过去一个月的,过去一年的都需要1秒之内返回.

这些都是靠graph和API组件来实现的,transfer会将数据往graph组件转发一份,graph收到数据以后,会以rdtool的数据归档方式来存储,同时提供查询的RPC接口.

API面向终端用户,收到查询请求后,会去多个graph里面,查询不同metric的数据,汇总后统一返回给用户.

存储

对于监控系统来讲,历史数据的存储和高效率查询,永远是一个很难的问题!

数据量大,目前我们的监控系统,每个周期,大概有2000万次数据上报(上报周期为1分钟和5分钟两种,各占50%),一天24小时内,从来不会有业务低峰,不管是白天还是夜晚,每个周期,总会有那么多数据要更新.

写操作多,一般的业务系统,通常是读多写少,可以方便的使用各种缓存技术,再者各类数据库,对于查询操作的处理效率远远高于写操作,而监控系统恰恰相反,写操作远远高于读,每个周期几千万次的更新操作,对于常用数据库(Mysql,Postgresql,MongoDB)都是无法完成的.

高效率的查,我们说的监控系统操作少,是相对于写入来讲,监控系统本身对于读的要求很高,用户经常会有查询上百个meitric,在过去一天、一周、一月、一年的数据,如何在一秒内返回给用户并绘图,这是个不小的挑战.

open-falcon在这块,投入了较大的精力,我们把数据按照用途分成两类,一类是用来绘图的,一类是用户做数据挖掘的.

对于绘图的数据来讲,查询要快是关键,同时不能丢失信息量,对于用户要查询100个metric,在过去一年里的数据里,数据量本身就在那里了,很那1秒之类返回,另外就算返回了,前端也无法渲染这么多的数据,还得采样,造成很多无畏的消耗和浪费。我们参考rrdtool的理念。数据每次存入的时候,会自动进行采样,存档.

我们归档策略如下: 历史数据保存5年 。同时为了不丢失信息量,数据归档的时候,会按照平均值采样、最大值采样、最小值采样.

滴滴云基于OpenFalcon的二次开发

Open-Falcon 二进制部署

# Open-Falcon,为前后端分离的架构,包含 backend 和 frontend 两部分.
# 部署有三种方式:

# 1. 源码安装(当中有一步需要编译)
# 2. 二进制包安装:(官方已经编译好的,其余跟源码安装一样)
# 3. docker 安装
List:
Package:
    mysql5.7
    redis3.2
    go1.13.4.linux-amd64.tar.gz
    open-falcon-v0.2.1.tar.gz
    python-virtualenv 
    python-devel
    openldap-devel 
    mysql-devel
节点名 IP 软件版本 硬件 网络 说明
falcon-binary 172.19.0.6 list 里面都有 2C4G Nat,内网 测试环境

注意事项

跟传统服务修改配置文件数据库密码不一样,因为 open-falcon 是前后端分离,需要给前端也授权数据库权限,否则部分前端功能报错 500

准备系统环境
# 1.初始化
init_security() {
systemctl stop firewalld
systemctl disable firewalld &>/dev/null
setenforce 0
sed -i '/^SELINUX=/ s/enforcing/disabled/'  /etc/selinux/config
sed -i '/^GSSAPIAu/ s/yes/no/' /etc/ssh/sshd_config
sed -i '/^#UseDNS/ {s/^#//;s/yes/no/}' /etc/ssh/sshd_config
systemctl enable sshd crond &> /dev/null
rpm -e postfix --nodeps
echo -e "\033[32m [安全配置] ==> OK \033[0m"
}
init_security

init_yumsource() {
if [ ! -d /etc/yum.repos.d/backup ];then
    mkdir /etc/yum.repos.d/backup
fi
mv /etc/yum.repos.d/* /etc/yum.repos.d/backup 2>/dev/null
if ! ping -c2 www.baidu.com &>/dev/null    
then
    echo "您无法上外网,不能配置yum源"
    exit    
fi
    curl -o /etc/yum.repos.d/163.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
    curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo &>/dev/null
    yum clean all
    timedatectl set-timezone Asia/Shanghai
    echo "nameserver 114.114.114.114" > /etc/resolv.conf
    echo "nameserver 8.8.8.8" >> /etc/resolv.conf
    chattr +i /etc/resolv.conf
    yum -y install ntpdate
    ntpdate -b  ntp1.aliyun.com
    echo -e "\033[32m [YUM Source] ==> OK \033[0m"
}
init_yumsource
安装 Open-Falcon 的 Database 组件服务
init_redis() {
    yum -y install redis git
    sed -i 's/bind 127.0.0.1/bind 172.19.0.6/' /etc/redis.conf    # 此处IP为你自己的主机IP
    systemctl start redis && systemctl enable redis
    if [ $? -eq 0 ] ;then
         echo -e "\033[32m [ Redis server ] ==> OK \033[0m";
    fi
}
init_redis
init_mysql() {
rpm -e mariadb-libs --nodeps
rm -rf /var/lib/mysql
rm -rf /etc/my.cnf
tar xvf /root/mysql-5.7.23-1.el7.x86_64.rpm-bundle.tar -C /usr/local/
cd /usr/local
rpm -ivh mysql-community-server-5.7.23-1.el7.x86_64.rpm \
mysql-community-client-5.7.23-1.el7.x86_64.rpm mysql-community-common-5.7.23-1.el7.x86_64.rpm \
mysql-community-libs-5.7.23-1.el7.x86_64.rpm mysql-community-devel-5.7.23-1.el7.x86_64.rpm 
rm -rf mysql-community-*
}
changepass() {
sed -i '/\[mysqld]/ a skip-grant-tables' /etc/my.cnf
systemctl restart mysqld
mysql <<EOF
        update mysql.user set authentication_string='' where user='root' and Host='localhost';
        flush privileges;
EOF
sed -i '/skip-grant/d' /etc/my.cnf
systemctl restart mysqld
yum -y install expect
systemctl start ntpd &&  systemctl enable ntpd
expect <<-EOF
spawn  mysqladmin -uroot -p password "ZHOUjian.20"
        expect {
                "password" { send "\r"  }
}
        expect eof
EOF
systemctl restart mysqld
}
main() {
init_redis
init_mysql
changepass
}
main
准备 Open-Falcon 数据目录,并下载 Open-Falcon 的 Git 仓库
init_falcon_data_directory() {
    mkdir -pv /home/open-falcon && cd  /home/open-falcon
    git clone https://github.com/open-falcon/falcon-plus.git
    cd /home/open-falcon/falcon-plus/scripts/mysql/db_schema/  #初始化mysql的表结构,必须取保mysql服务是正常启动的。
    mysql  -uroot -pZHOUjian.20 < 1_uic-db-schema.sql    
    mysql  -uroot -pZHOUjian.20 < 2_portal-db-schema.sql
    mysql  -uroot -pZHOUjian.20 < 3_dashboard-db-schema.sql
    mysql  -uroot -pZHOUjian.20 < 4_graph-db-schema.sql
    mysql  -uroot -pZHOUjian.20 < 5_alarms-db-schema.sql 
}
init_falcon_data_directory
安装后端并启动
export FALCON_HOME=/home/work
export WORKSPACE=$FALCON_HOME/open-falcon
mkdir -p $WORKSPACE
tar xvf open-falcon-v0.2.1.tar.gz -C $WORKSPACE
cd $WORKSPACE`
grep -Ilr 3306  ./ | xargs -n1 -- sed -i 's/root:/root:ZHOUjian.20/g'
cd $WORKSPACE

# 启动
./open-falcon start

# 检查所有模块的启动状况
./open-falcon check
部署前端
# 创建工作目录
export HOME=/home/work
export WORKSPACE=$HOME/open-falcon
mkdir -p $WORKSPACE
cd $WORKSPACE

# 克隆前端代码
cd $WORKSPACE
git clone https://github.com/open-falcon/dashboard.git

# 安装依赖包
yum install -y python-virtualenv python-devel openldap-devel
yum groupinstall "Development tools"
cd $WORKSPACE/dashboard/
virtualenv ./env
./env/bin/pip install -r pip_requirements.txt -i https://pypi.douban.com/simple

# 修改配置
dashboard的配置文件为: 'rrd/config.py',请根据实际情况修改

# API_ADDR 表示后端api组件的地址
API_ADDR = "http://127.0.0.1:8080/api/v1" 

# 根据实际情况,修改PORTAL_DB_*, 默认用户名为root,默认密码为""
# 根据实际情况,修改ALARM_DB_*, 默认用户名为root,默认密码为""


# 以开发者模式启动
./env/bin/python wsgi.py
# 使用浏览器访问IP:8081即可

# 以生产模式启动
bash control start
# 停止dashboard运行
bash control stop
# 查看日志
bash control tail
# 可以修改一下wsgi.py配置文件,指定IP和端口,默认是0.0.0.0
Dashbord 用户管理

dashbord 没有默认创建任何账号包括管理账号,需要你通过页面进行注册账号。

想拥有管理全局的超级管理员账号,需要手动注册用户名为 root 的账号(第一个帐号名称为 root 的用户会被自动设置为超级管理员)。

超级管理员可以给普通用户分配权限管理。

小提示:注册账号能够被任何打开 dashboard 页面的人注册,所以当给相关的人注册完账号后,需要去关闭注册账号功能。只需要去修改 API 组件的配置文件 cfg.json,将 signup_disable 配置项修改为 true,重启 API 即可。当需要给人开账号的时候,再将配置选项改回去,用完再关掉即可。

Open-Falcon 容器部署

# 注意事项:
# 1:初始化环境跟上面一样,此处就不重复了,请执行上面两个初始化环境函数,再执行下面命令;
# 2:如果docker启动容器不小心弄错了,记得删除容器再run;
准备 docker 依赖包和 docker 的 yum 源并安装
# 安装需要的软件包,yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2

# 准备yum源
# 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

# 由于repo中默认只开启stable仓库,所以这里安装的是最新稳定版
yum install docker-ce  
 
# Docker镜像加速  
# 没有启动/etc/docker 目录不存在,需要自己建立,启动会自己创建;
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

注意

启动docker前清理一下环境,防止目录冲突

# 防止环境有之前装过的mysql,做一下清理  
# yum方式安装的mysql  
yum -y remove mysql mysql-server mysql-libs compat-mysql51  
rm -rf /var/lib/mysql  
rm -rf /etc/my.cnf       # 有可能my.cnf改名为my.cnf.rpmsave  
# 查看是否还有mysql软件  
rpm -qa|grep mysql  
rpm方式安装的mysql  
# 查看系统中是否以rpm包安装的mysql:  
rpm -qa | grep -i mysql  
MySQL-server-5.6.17-1.el6.i686  
MySQL-client-5.6.17-1.el6.i686  
# 卸载mysql:  
rpm -e MySQL-server-5.6.17-1.el6.i686  
rpm -e MySQL-client-5.6.17-1.el6.i686  
# 删除mysql服务
chkconfig --list | grep -i mysql  
chkconfig --del mysql  
# 删除分散mysql文件夹
whereis mysql 或者 find / -name mysql  
mysql: /usr/lib/mysql /usr/share/mysql  
mysql的所有目录以及文件:  
rm -rf /usr/lib/mysql  
rm -rf /usr/share/mysql
安装mysql容器
docker run -itd \
        --name falcon-mysql \
        -v /home/work/mysql-data:/var/lib/mysql \
        -e MYSQL_ROOT_PASSWORD=test123456 \
        -p 3306:3306 \
        mysql:5.7
# 安装的版本为mysql:5.7  默认用户名root 密码 test123456
初始化mysql表结构
# cd /tmp && \
  git clone --depth=1 https://github.com/open-falcon/falcon-plus && \
  cd /tmp/falcon-plus/ && \
  for x in `ls ./scripts/mysql/db_schema/*.sql`; do
      echo init mysql table $x ...;
      docker exec -i falcon-mysql mysql -uroot -ptest123456 < $x;
  done
# rm -rf /tmp/falcon-plus/
安装后端
docker pull openfalcon/falcon-plus:v0.3
docker run -itd --name falcon-plus \
         --link=falcon-mysql:db.falcon \
         --link=falcon-redis:redis.falcon \
         -p 8433:8433 \
         -p 8080:8080 \
         -e MYSQL_PORT=root:test123456@tcp\(db.falcon:3306\) \
         -e REDIS_PORT=redis.falcon:6379  \
         -v /home/work/open-falcon/data:/open-falcon/data \
         -v /home/work/open-falcon/logs:/open-falcon/logs \
         openfalcon/falcon-plus:v0.3
启动open-falcon后端模块
docker exec falcon-plus sh ctrl.sh start \ graph hbs judge transfer nodata aggregator agent gateway api alarm
# 或者您可以按照以下方式启动/停止/重启特定模块:

docker exec falcon-plus sh ctrl.sh start/stop/restart xxx
# 检查后端模块的状态

docker exec falcon-plus ./open-falcon check
# 或者您可以在主机中的/ home / work / open-falcon / logs /中查看日志
ls -l /home/work/open-falcon/logs/
安装前端
# 在容器中启动Open-Falcon仪表盘
docker run -itd --name falcon-dashboard \
        -p 8081:8081 \
        --link=falcon-mysql:db.falcon \
        --link=falcon-plus:api.falcon \
        -e API_ADDR=http://api.falcon:8080/api/v1 \
        -e PORTAL_DB_HOST=db.falcon \
        -e PORTAL_DB_PORT=3306 \
        -e PORTAL_DB_USER=root \
        -e PORTAL_DB_PASS=test123456 \
        -e PORTAL_DB_NAME=falcon_portal \
        -e ALARM_DB_HOST=db.falcon \
        -e ALARM_DB_PORT=3306 \
        -e ALARM_DB_USER=root \
        -e ALARM_DB_PASS=test123456 \
        -e ALARM_DB_NAME=alarms \
        -w /open-falcon/dashboard openfalcon/falcon-dashboard:v0.2.1  \
       './control startfg'

标签:01,http,简介,Openfalcon,yum,mysql,falcon,docker,open
来源: https://www.cnblogs.com/you-men/p/13022174.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有