背景
ClickHouse是一个面向分析型的开源列式数据库管理系统,它主要应用于以下几个场景:
数据仓库和商业智能分析:ClickHouse擅长处理大规模的数据,可以用于构建企业级的数据仓库,支持复杂的OLAP查询,可用实时数仓,适合各种商业分析和报表应用。
实时分析和监控:ClickHouse以毫秒级的响应时间处理大量数据,非常适合实时监控、异常检测和故障预警等场景。
Web分析:ClickHouse可以采集和分析网站访问日志,计算各种指标如UV、PV、跳出率等,为网站运营提供数据支持。
用户行为分析:精细化运营分析,日活,留存率分析,路径分析,有序漏斗转化率分析,Session 分析等。
物联网数据分析:ClickHouse可以处理海量的传感器数据,用于分析设备运行状况、预测故障等。
广告技术:ClickHouse可以处理广告点击、展示等海量数据,为广告优化和效果分析提供支持。
金融交易分析:ClickHouse可以处理交易数据,为金融市场分析提供实时计算能力,欺诈检测,低延迟查询对于识别任何欺诈活动和防止损失。
游戏数据分析:ClickHouse可以收集和分析游戏用户行为数据,为游戏运营和产品优化提供依据。
酒店管理行业:利用 ClickHouse 了解最新的预订、定价、收入和物业绩效信息。制造商在 ClickHouse 数据库的基础上建立物流规划系统。
媒体和娱乐公司:使用 ClickHouse 监控流媒体质量、管理广告投放和推送个性化促销信息。
云服务提供商: ClickHouse 的基础上建立云资源监控和网络分析。
它是一个数据库: 数据库既有存储引擎,也有查询引擎。ClickHouse 可以有效地从各种来源获取数据,其查询引擎可提供低延迟查询响应。
它是一个 OLAP 数据库: 在线分析处理(OLAP)数据库不是为支持正常业务交易而设计的。它专门用于分析大量业务记录,侧重于读取和计算,而较少用于写入和事务处理。
它是一种面向列的 OLAP 数据库: 面向列的存储是分析型数据库事实上的存储格式,因为分析查询会读取大量记录,但只对有限的列感兴趣。在这种情况下,列式存储比传统的面向行存储更有效。
部署架构
数据分片和复制是完全独立的。分片是 ClickHouse 的自然组成部分,而复制则主要依赖于 Zookeeper,后者用于通知复制的状态变化。
或者是
生产环境部署提示
提示1: 使用多个副本
在测试ClickHouse时,部署只有一个主机的配置是很自然的,因为可能不想使用额外的资源或承担不必要的费用。在开发或测试环境中没有什么问题,但如果只想在生产环境中使用一个主机,这可能会有代价。如果发生故障,而只有一个副本和一个主机,就面临着丢失所有数据的风险。
对于生产环境, 应该使用多个主机并在它们之间复制数据。这不仅可以确保主机失败时数据仍然安全,还可以在多个主机上平衡用户负载,从而使资源密集型查询更快。
提示2: 不要吝惜RAM内存
ClickHouse很快,但其速度取决于可用资源,特别是RAM内存。在开发或测试环境中运行ClickHouse集群时,即使使用最小RAM量也可以看到很好的性能,但当负载增加时可能会发生变化。在一个拥有大量同时读写操作的生产环境中,RAM不足会更加明显。如果您的ClickHouse集群没有足够的内存,它将更慢,执行复杂查询也会花更长时间。
此外,当ClickHouse执行资源密集型操作时,它可能会与操作系统本身争夺RAM,最终导致OOM、宕机和数据丢失。ClickHouse的开发者建议使用至少16 GB的RAM来确保集群稳定。您可以选择更少的内存,但前提是您知道负载不会很高。
提示3:三思而后行-选择表引擎
ClickHouse支持几种具有不同特性的表引擎,但MergeTree引擎很可能是理想的选择。专门的表是为特定用途而设计的,但它们也有一些可能不太明显的局限性。Log Family引擎可能看起来适合日志,但它们不支持复制,并且数据库大小有限制。MergeTree家族的表引擎是默认选择,它们提供了ClickHouse所知名的核心数据功能。除非您确定需要不同的表引擎, 否则使用MergeTree家族的引擎就可以覆盖大多数用例。
MergeTree结构
- ReplacingMergeTree: 会根据主键进行去重,但是这是后台合并时才会去重,无法控制合并时机,尽管可以用
OPTIMIZE … FINAL
语句来强制合并执行,但是由于性能原因一般不会使用。 - CollapsingMergeTree: 异步的删除(折叠)这些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成对的行。没有成对的行会被保留。
- VersionedCollapsingMergeTree: 类似于 CollapsingMergeTree, 多了 Version 列,支持多线程乱序插入的场景,相比之下, CollapsingMergeTree 只允许严格连续插入。
- AggregatingMergeTree: 做增量数据的聚合统计,包括物化视图的数据聚。
- ReplicatedXXXMergeTree: 使得以上 MergeTree 家族拥有副本机制,保证高可用,用于生产环境
提示4:主键不要超过三列
ClickHouse中的主键与传统数据库中的主键作用不同。它们不保证唯一性,而是定义了数据的存储和检索方式。如果将所有列都用作主键,可能会获得更快的查询速度。但ClickHouse的性能不仅取决于读取数据,也取决于写入数据。当主键包含许多列时,向集群写入数据会减慢整个速度。ClickHouse中主键的最佳大小是两个或三个列,这样既可以更快地进行查询,又不会减慢数据插入速度。选择列时,请考虑将要进行的请求,选择经常用于过滤条件的列。
提示5:避免小量插入
当在ClickHouse中插入数据时,它首先会将此数据保存到磁盘上的一个部分。然后它会对这些数据进行排序、合并,并在后台将其插入到数据库的合适位置。如果频繁插入小块数据,ClickHouse将为每个小插入创建一个部分,这将减慢整个集群的速度,可能会遇到"太多部分"的错误。要高效地插入数据,请以大块的方式添加数据,并避免每秒发送多个插入语句。ClickHouse可以以很高的速度(每秒100K行也可以)插入大量数据,但这应该是一个批量插入,而不是多个较小的插入。
如果的数据来源很小,可以考虑使用外部系统(如托管的Kafka)来制作数据批次。ClickHouse与Kafka集成良好,可以有效地从中消费数据。
提示6:考虑如何删除重复数据
ClickHouse中的主键不能确保数据的唯一性。与其他数据库不同,如果在ClickHouse中插入重复数据,它将被原样添加。因此,最好的选择是在插入数据之前确保数据是唯一的。可以在像Apache Kafka这样的流处理应用程序中完成。如果做不到这一点,在运行查询时也有办法处理重复数据。一种选择是使用argMax仅选择最新版本的重复行。您也可以使用ReplacingMergeTree引擎,它会根据设计删除重复条目。最后,可以运行`OPTIMIZE TABLE ... FINAL`来合并数据部分,但这是一个资源密集型操作,只应在知道它不会影响集群性能的情况下运行。
提示7:不要为每个列创建索引
与主键一样,可能想使用多个索引来提高性能。当使用与索引匹配的过滤器查询数据时,这可能是有用的。但整体上它不会帮助您加快查询速度。与此同时,肯定会遇到这种策略的缺点。多个索引会显著减慢数据插入,因为ClickHouse需要将数据写入正确的位置,然后更新索引。
当想在生产集群中创建索引时,请选择与主键相关的列。
ClickHouse 的主键索引采用的是稀疏索引,将每列数据按照 index granularity(默认8192行)进行划分。稀疏索引的好处是条目相对稠密索引较少,能够将其加载到内存,而且对插入时建立索引的成本相对较小。
ClickHouse 数据按列进行存储,每一列都有对应的 mrk 标记文件,bin 文件。mrk 文件与主键索引对齐,主要用于记录数据在 bin 文件中的偏移量信息。
生产环境部署示例-四节点代理集群
部署架构
4 个 ClickHouse 实例利用 3 个专用 ClickHouse Keepers 和 CH 代理负载均衡器 2 个具有复制的分片: 跨分片 01 的 clickhouse-01 和 clickhouse-03 跨分片 02 的 clickhouse-02 和 clickhouse-04
version: '3.8'
services:
clickhouse-01:
image: "clickhouse/clickhouse-server:${CHVER:-latest}"
user: "101:101"
container_name: clickhouse-01
hostname: clickhouse-01
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.1
volumes:
- ${PWD}/fs/volumes/clickhouse-01/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
- ${PWD}/fs/volumes/clickhouse-01/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
ports:
- "127.0.0.1:8123:8123"
- "127.0.0.1:9000:9000"
depends_on:
- clickhouse-keeper-01
- clickhouse-keeper-02
- clickhouse-keeper-03
clickhouse-02:
image: "clickhouse/clickhouse-server:${CHVER:-latest}"
user: "101:101"
container_name: clickhouse-02
hostname: clickhouse-02
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.2
volumes:
- ${PWD}/fs/volumes/clickhouse-02/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
- ${PWD}/fs/volumes/clickhouse-02/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
ports:
- "127.0.0.1:8124:8123"
- "127.0.0.1:9001:9000"
depends_on:
- clickhouse-keeper-01
- clickhouse-keeper-02
- clickhouse-keeper-03
clickhouse-03:
image: "clickhouse/clickhouse-server:${CHVER:-latest}"
user: "101:101"
container_name: clickhouse-03
hostname: clickhouse-03
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.3
volumes:
- ${PWD}/fs/volumes/clickhouse-03/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
- ${PWD}/fs/volumes/clickhouse-03/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
ports:
- "127.0.0.1:8125:8123"
- "127.0.0.1:9002:9000"
depends_on:
- clickhouse-keeper-01
- clickhouse-keeper-02
- clickhouse-keeper-03
clickhouse-04:
image: "clickhouse/clickhouse-server:${CHVER:-latest}"
user: "101:101"
container_name: clickhouse-04
hostname: clickhouse-04
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.4
volumes:
- ${PWD}/fs/volumes/clickhouse-04/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
- ${PWD}/fs/volumes/clickhouse-04/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
ports:
- "127.0.0.1:8126:8123"
- "127.0.0.1:9003:9000"
depends_on:
- clickhouse-keeper-01
- clickhouse-keeper-02
- clickhouse-keeper-03
clickhouse-keeper-01:
image: "clickhouse/clickhouse-keeper:${CHKVER:-latest-alpine}"
user: "101:101"
container_name: clickhouse-keeper-01
hostname: clickhouse-keeper-01
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.5
volumes:
- ${PWD}/fs/volumes/clickhouse-keeper-01/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
ports:
- "127.0.0.1:9181:9181"
clickhouse-keeper-02:
image: "clickhouse/clickhouse-keeper:${CHKVER:-latest-alpine}"
user: "101:101"
container_name: clickhouse-keeper-02
hostname: clickhouse-keeper-02
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.6
volumes:
- ${PWD}/fs/volumes/clickhouse-keeper-02/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
ports:
- "127.0.0.1:9182:9181"
clickhouse-keeper-03:
image: "clickhouse/clickhouse-keeper:${CHKVER:-latest-alpine}"
user: "101:101"
container_name: clickhouse-keeper-03
hostname: clickhouse-keeper-03
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.7
volumes:
- ${PWD}/fs/volumes/clickhouse-keeper-03/etc/clickhouse-keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml
ports:
- "127.0.0.1:9183:9181"
ch-proxy:
image: contentsquareplatform/chproxy:v1.26.4
platform: linux/amd64
container_name: ch-proxy
hostname: ch-proxy
networks:
cluster_2S_2R_ch_proxy:
ipv4_address: 192.168.9.10
ports:
- "127.0.0.1:443:443"
- "127.0.0.1:80:80"
volumes:
- ${PWD}/fs/volumes/ch-proxy/config/config.yml:/opt/config.yml
depends_on:
- clickhouse-01
- clickhouse-02
- clickhouse-03
- clickhouse-04
command: [-config, /opt/config.yml]networks:
cluster_2S_2R_ch_proxy:
driver: bridge
ipam:
config:
- subnet: 192.168.9.0/24
gateway: 192.168.9.254
网络
cluster_2S_2R_ch_proxy
:- 使用
bridge
驱动,有一个子网192.168.9.0/24
,网关192.168.9.254
。
- 使用
容器:
clickhouse-01
(192.168.9.1)clickhouse-02
(192.168.9.2)clickhouse-03
(192.168.9.3)clickhouse-04
(192.168.9.4)clickhouse-keeper-01
(192.168.9.5)clickhouse-keeper-02
(192.168.9.6)clickhouse-keeper-03
(192.168.9.7)ch-proxy
(192.168.9.10)
依赖关系:
clickhouse-01
,clickhouse-02
,clickhouse-03
,clickhouse-04
都依赖clickhouse-keeper-01
,clickhouse-keeper-02
,clickhouse-keeper-03
ch-proxy
依赖clickhouse-01
,clickhouse-02
,clickhouse-03
,clickhouse-04
端口映射:
clickhouse-01
,clickhouse-02
,clickhouse-03
,clickhouse-04
都有 8123 和 9000 端口映射到本地的相应端口clickhouse-keeper-01
,clickhouse-keeper-02
,clickhouse-keeper-03
的 9181 端口映射到本地的相应端口ch-proxy
的 443 和 80 端口映射到本地的相应端口
卷挂载:
- 每个
clickhouse
容器挂载了config.xml
和users.xml
文件 - 每个
clickhouse-keeper
容器挂载了keeper_config.xml
文件 ch-proxy
容器挂载了config.yml
文件
- 每个
以下是 采用 Keeper 的 ClickHouse 架构
当数据库集群节点之间需要强一致性时,ClickHouse 会广泛使用 Keeper,最值得注意的是支持存储元数据、复制、备份、访问控制、任务调度以及用于从 Apache Kafka 和 AWS S3 提取数据的高度一致的键值存储。
Keeper 依赖于 Raft 共识算法,而 ZooKeeper 则使用 ZAB。尽管 ZAB 是一个更成熟的选择(至少从 2008 年开始开发),但该团队选择了 Raft,因为它相对简单,并且使用 C++ 库集成起来很容易。
单机版生产环境部署
用于设置生产就绪的独立 ClickHouse 实例。它还包括由 Prometheus 和 Grafana 组成的监控堆栈,用于实时性能跟踪。此外,还集成了 MinIO,以实现 ClickHouse 数据的高效备份。这种全面的设置可确保强大且可靠的数据管理环境。
示例docker-compose.yaml
version: '3'
services:
clickhouse:
container_name: clickhouse
image: clickhouse/clickhouse-server:23.8
restart: always
ports:
- 8123:8123 # http
- 9000:9000 # native protocol
- 9363:9363 # metrics server
volumes:
- clickhouse-data:/var/lib/clickhouse/
- clickhouse-logs:/var/log/clickhouse-server/
- ./config_files/clickhouse/etc/clickhouse-server:/etc/clickhouse-server
cap_add:
- SYS_NICE
- NET_ADMIN
- IPC_LOCK
ulimits:
nofile:
soft: 262144
hard: 262144
depends_on:
- minio
- minio-bucket-creatorprometheus:
container_name: prometheus
image: prom/prometheus:v2.37.9
restart: always
ports:
- 9090:9090
volumes:
- ./config_files/prometheus/:/etc/prometheus/
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yaml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
depends_on:
- minio-prometheus-monitoring-cluster
- minio-prometheus-monitoring-node
- minio-prometheus-monitoring-bucketgrafana:
container_name: grafana
image: grafana/grafana:10.0.3-ubuntu
ports:
- 3000:3000
volumes:
- grafana_data:/var/lib/grafana
- ./config_files/grafana/etc/grafana/provisioning/dashboards/:/etc/grafana/provisioning/dashboards/
- ./config_files/grafana/etc/grafana/provisioning/datasources/:/etc/grafana/provisioning/datasources/
- ./config_files/grafana/var/lib/grafana/dashboards/:/var/lib/grafana/dashboards/
# uncomment the following line, if you don't have access to grafana.com plugins and download a plugin manually (read README.md file)
- ./config_files/grafana/var/lib/grafana/plugins/:/var/lib/grafana/plugins/
depends_on:
- prometheus
minio:
container_name: minio
image: minio/minio:RELEASE.2023-09-07T02-05-02Z
restart: always
env_file:
- .env
ports:
- 9001:9001
- 9002:9002
volumes:
- minio_data:/var/lib/minio/data
command: minio server /var/lib/minio/data --address 0.0.0.0:9002 --console-address ":9001"minio-bucket-creator:
container_name: minio-bucket-creator
image: minio/mc:RELEASE.2023-09-13T23-08-58Z
env_file:
- .env
entrypoint: >
/bin/bash -c "
/usr/bin/mc alias set myminio http://minio:9002 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD;
/usr/bin/mc mb myminio/${MINIO_CLICKHOUSE_BACKUP_BUCKET:-clickhouse};
/usr/bin/mc anonymous set public myminio/${MINIO_CLICKHOUSE_BACKUP_BUCKET:-clickhouse};
exit 0;
"
depends_on:
- minio
minio-prometheus-monitoring-cluster:
container_name: minio-prometheus-monitoring-cluster
image: minio/mc:RELEASE.2023-09-13T23-08-58Z
env_file:
- .env
volumes:
- ./config_files/prometheus/templates/prometheus.yaml:/home/prometheus-template.yaml:ro
- ./config_files/prometheus/prometheus.yaml:/home/prometheus.yaml
entrypoint: >
/bin/bash -c "
cp /home/prometheus-template.yaml /home/prometheus.yaml;
/usr/bin/mc alias set myminio http://minio:9002 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD > /dev/null;
/usr/bin/mc admin prometheus generate myminio cluster | sed '1d' | awk '{print \" \" $0}' >> /home/prometheus.yaml;
"
depends_on:
- minio
minio-prometheus-monitoring-node:
container_name: minio-prometheus-monitoring-node
image: minio/mc:RELEASE.2023-09-13T23-08-58Z
env_file:
- .env
volumes:
- ./config_files/prometheus/prometheus.yaml:/home/prometheus.yaml
entrypoint: >
/bin/bash -c "
/usr/bin/mc alias set myminio http://minio:9002 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD > /dev/null;
/usr/bin/mc admin prometheus generate myminio node | sed '1d' | awk '{print \" \" $0}' >> /home/prometheus.yaml;
"
depends_on:
- minio
- minio-prometheus-monitoring-clusterminio-prometheus-monitoring-bucket:
container_name: minio-prometheus-monitoring-bucket
image: minio/mc:RELEASE.2023-09-13T23-08-58Z
env_file:
- .env
volumes:
- ./config_files/prometheus/prometheus.yaml:/home/prometheus.yaml
entrypoint: >
/bin/bash -c "
/usr/bin/mc alias set myminio http://minio:9002 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD > /dev/null;
/usr/bin/mc admin prometheus generate myminio bucket | sed '1d' | awk '{print \" \" $0}' >> /home/prometheus.yaml;
"
depends_on:
- minio
- minio-prometheus-monitoring-cluster
- minio-prometheus-monitoring-nodevolumes:
clickhouse-data:
clickhouse-logs:
prometheus_data:
grafana_data:
minio_data:
阿里云-云数据库 ClickHouse 社区兼容版
云数据库 ClickHouse 社区兼容版节点和节点完全对等,每一个节点都可以承载查询请求和写入请求,以及后台数据的计算和操作。
每个云数据库 ClickHouse 社区兼容版集群包含1个或多个分片(Shard),每个分片内部包含1个或多个副本(Replica)。
所有节点都部署在阿里云弹性计算服务器ECS之上,底层采用高可靠的云盘作为持久化存储介质
两地双中心部署架构
CH集群应用案例
某国内电商公司Data Management Platform, 主要用于对人群进行圈选,画像分析,广告投放。随着精细化自动营销的慢慢发展,主要是通过聚合商家全域数据流量(其中包括用户数据,用户行为数据等)来进行行为分析,画像分析,自动化营销。
数据分片:提到了CH集群分片,这通常意味着数据被分割成多个部分,分布在不同的服务器或节点上,以提高处理效率和可扩展性。
实时与离线处理:系统支持实时写入和离线写入,这表明架构设计了处理不同数据流的能力,能够适应不同的业务场景。
Spark与Flink任务:提到了Spark任务和Flink/FlinkSQL任务,这表明系统使用了这两种流行的大数据处理框架来处理数据。
位图索引:提到了位图本地表和标签用户位图表,位图索引是一种高效的数据结构,用于快速检索和存储大量数据。
用户ID哈希处理:用户ID通过Hash处理,这有助于保护用户隐私,同时也可以减少数据存储的需求。
Kafka消息队列:使用Kafka作为消息队列,这有助于实现数据的异步处理和解耦。
HTTP调用与自定义插件:系统支持通过HTTP调用自定义CH插件,这提供了灵活性,允许根据需要扩展或修改系统功能。
安全与性能优化:提到了Rate Limiter(限流器)、Security(安全)、Customer Plugins(自定义插件)、Circuit Breaker(断路器)和Routing(路由),这些都是系统性能和安全性的重要保障。
API网关:使用Proxy(如Apisix)作为API网关,这有助于管理API请求,提供负载均衡、服务发现等功能。
数据转换:用户行为数据结果需要转换成bitmap和标签位图结果,这表明系统需要进行数据格式的转换以适应不同的查询需求。
多节点处理:提到了CH Shard1、CH Shard2、CH ShardN,这表明系统设计了多节点处理能力,以支持大规模的数据和高并发请求。
今天先到这儿,希望对AIGC,云原生,技术领导力, 企业管理,系统架构设计与评估,团队管理, 项目管理, 产品管理,信息安全,团队建设 有参考作用 , 您可能感兴趣的文章:
构建创业公司突击小团队
国际化环境下系统架构演化
微服务架构设计
视频直播平台的系统架构演化
微服务与Docker介绍
Docker与CI持续集成/CD
互联网电商购物车架构演变案例
互联网业务场景下消息队列架构
互联网高效研发团队管理演进之一
消息系统架构设计演进
互联网电商搜索架构演化之一
企业信息化与软件工程的迷思
企业项目化管理介绍
软件项目成功之要素
人际沟通风格介绍一
精益IT组织与分享式领导
学习型组织与企业
企业创新文化与等级观念
组织目标与个人目标
初创公司人才招聘与管理
人才公司环境与企业文化
企业文化、团队文化与知识共享
高效能的团队建设
项目管理沟通计划
构建高效的研发与自动化运维
某大型电商云平台实践
互联网数据库架构设计思路
IT基础架构规划方案一(网络系统规划)
餐饮行业解决方案之客户分析流程
餐饮行业解决方案之采购战略制定与实施流程
餐饮行业解决方案之业务设计流程
供应链需求调研CheckList
企业应用之性能实时度量系统演变
如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:
作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-Petter Liu Blog。