├── 201703
├── assets
│ ├── elk_architecture_new.png
│ ├── elk_architecture_old.png
│ ├── elk_kibana_01.png
│ ├── elk_kibana_02.png
│ ├── elk_parse_log_01.png
│ ├── elk_parse_log_java.png
│ ├── elk_parse_log_nginx.png
│ ├── elk_parse_log_node.png
│ ├── logstash_scale_01.png
│ ├── logstash_scale_02.png
│ ├── logstash_scale_03.png
│ ├── logstash_scale_04.png
│ ├── logstash_scale_05.png
│ ├── logstash_scale_06.png
│ ├── logstash_scale_07.png
│ ├── service_registry_01.png
│ ├── service_registry_02.png
│ ├── service_registry_case_1.png
│ ├── service_registry_case_1_zk.png
│ ├── service_registry_case_2.png
│ ├── service_registry_case_2_zk.png
│ ├── service_registry_case_3.png
│ ├── service_registry_case_3_zk.png
│ ├── service_registry_case_4.png
│ ├── service_registry_case_4_zk.png
│ ├── service_registry_re_01.png
│ ├── service_registry_re_02.png
│ ├── service_registry_result_01.png
│ ├── service_registry_result_01_bak.png
│ ├── service_registry_services.png
│ ├── service_registry_zk_01.png
│ └── service_registry_zk_02.png
├── elk.md
├── elk_parse_log.md
├── logstash_deploye_scale.md
└── service_registry.md
├── 201704
├── assets
│ ├── discovery_01.png
│ ├── discovery_02.png
│ ├── discovery_code_01.png
│ ├── discovery_code_02.png
│ ├── discovery_demo_01.png
│ ├── discovery_demo_02.png
│ ├── discovery_demo_03.png
│ ├── discovery_demo_04.png
│ ├── discovery_demo_05.png
│ ├── discovery_demo_06.png
│ ├── es_cluster_01.png
│ ├── es_cluster_02.png
│ ├── es_cluster_03.png
│ ├── rmi_01.png
│ ├── rmi_code_01.png
│ ├── rmi_code_02.png
│ ├── rmi_code_03.png
│ ├── rmi_code_04.png
│ ├── rmi_demo_01.png
│ ├── rmi_demo_02.png
│ ├── rmi_demo_03.png
│ ├── swarm_01.png
│ ├── swarm_02.png
│ ├── swarm_03.png
│ ├── swarm_04.png
│ ├── swarm_05.png
│ ├── swarm_06.png
│ ├── swarm_07.png
│ ├── swarm_08.png
│ ├── swarm_09.png
│ ├── swarm_10.png
│ ├── swarm_11.png
│ ├── swarm_12.png
│ ├── swarm_13.png
│ ├── swarm_14.png
│ └── swarm_scheduling.png
├── docker_swarm.md
├── es_cluster.md
├── rmi.md
└── service_discovery.md
├── 201705
├── MQ.md
├── assets
│ ├── cadvisor_01.png
│ ├── cadvisor_02.png
│ ├── cadvisor_03.png
│ ├── cadvisor_architect.png
│ ├── cadvisor_es_01.png
│ ├── cadvisor_es_02.png
│ ├── https_01.png
│ ├── https_ca_bar.png
│ ├── https_ca_dv.png
│ ├── https_ca_ev.png
│ ├── https_ca_ov.png
│ ├── https_theory_01.png
│ ├── https_theory_02.png
│ ├── https_theory_03.png
│ ├── https_theory_04.png
│ └── mq_01.png
├── cadvisor.md
├── functional_programming.md
└── https.md
├── 201706
├── assets
│ ├── event_01.png
│ ├── event_02.png
│ ├── event_03.png
│ ├── event_04.png
│ ├── event_05.png
│ ├── event_06.png
│ ├── event_07.png
│ ├── event_08.png
│ ├── event_09.png
│ ├── event_10.png
│ ├── event_11.png
│ ├── event_12.png
│ ├── event_13.png
│ ├── jms_01.png
│ ├── jms_02.png
│ ├── jms_03.png
│ ├── jms_activemq_01.png
│ ├── jms_ptp_01.png
│ ├── jms_ptp_02.png
│ ├── jms_ptp_03.png
│ ├── jms_pubsub_01.png
│ ├── jms_pubsub_02.png
│ ├── redis_search_01.png
│ ├── redis_search_02.png
│ ├── redis_search_03.png
│ └── redis_search_04.png
├── event.md
├── jms.md
├── redis-search.md
└── rest-api.md
├── 201707
├── assets
│ ├── k8s-frame-01.png
│ ├── k8s-frame-02.png
│ ├── k8s-frame-03.png
│ ├── k8s-frame-04.png
│ ├── k8s-frame-05.png
│ ├── k8s-frame-06.png
│ ├── k8s-frame-07.png
│ ├── k8s-pod-01.png
│ ├── k8s-pod-02.png
│ ├── k8s-pod-03.png
│ ├── k8s-pod-04.png
│ ├── k8s-pod-05.png
│ ├── k8s-pod-06.png
│ ├── k8s-pod-07.png
│ ├── k8s-pod-08.png
│ ├── k8s-pod-09.png
│ ├── k8s-pod-10.png
│ ├── k8s-pod-11.png
│ ├── k8s-pod-12.png
│ ├── k8s-pod-13.png
│ ├── k8s-pod-14.png
│ ├── k8s-pod-15.png
│ ├── k8s-pod-16.png
│ ├── k8s-pod-17.png
│ ├── k8s-svc-01.png
│ ├── k8s-svc-02.png
│ ├── k8s-svc-03.png
│ ├── k8s-svc-04.png
│ ├── k8s-svc-05.png
│ ├── k8s-svc-06.png
│ ├── k8s-svc-07.png
│ ├── k8s-svc-08.png
│ ├── k8s-svc-09.png
│ ├── k8s-svc-10.png
│ ├── k8s-svc-11.png
│ ├── k8s-svc-12.png
│ ├── k8s-svc-13.png
│ ├── k8s-svc-14.png
│ ├── k8s-svc-15.png
│ ├── k8s-svc-16.png
│ ├── k8s-svc-17.png
│ ├── k8s-svc-18.png
│ ├── k8s-svc-19.png
│ ├── k8s-svc-20.png
│ └── k8s-svc-21.png
├── k8s-architecture.md
├── k8s-pod.md
├── k8s-service.md
└── thread-pool.md
├── 201708
├── assets
│ ├── java-nio-01.jpg
│ ├── java-nio-02.jpg
│ ├── java-nio-03.png
│ ├── java-nio-04.png
│ ├── java-socket-01.png
│ ├── microservice-for-failure-01.png
│ ├── microservice-for-failure-02.png
│ ├── microservice-for-failure-03.png
│ ├── microservice-for-failure-04.png
│ ├── microservice-for-failure-05.png
│ ├── microservice-for-failure-06.png
│ └── microservice-for-failure-07.png
├── java-nio.md
├── java-socket.md
└── microservice-for-failure.md
├── 201710
├── assets
│ ├── java-analysis-01.png
│ ├── java-analysis-02.png
│ ├── java-analysis-03.png
│ ├── java-analysis-04.png
│ ├── java-analysis-05.png
│ ├── java-analysis-06.png
│ ├── java-analysis-07.png
│ ├── java-analysis-08.png
│ ├── java-analysis-09.png
│ ├── java-analysis-10.png
│ ├── java-analysis-11.png
│ ├── java-analysis-12.png
│ ├── java-analysis-13.png
│ ├── java-analysis-14.png
│ ├── java-analysis-15.png
│ ├── java-analysis-16.png
│ └── java-analysis-17.png
└── java-analysis.md
├── 201802
├── assets
│ ├── java-threadpool-00.png
│ ├── java-threadpool-01.png
│ ├── java-threadpool-02.png
│ ├── java-threadpool-03.png
│ ├── java-threadpool-04.png
│ └── java-threadpool-05.png
└── java-threadpool.md
├── 201804
└── js-this.md
├── .gitignore
├── README.md
├── _config.yml
└── convert_md.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .git/*
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/201703/assets/elk_architecture_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_architecture_new.png
--------------------------------------------------------------------------------
/201703/assets/elk_architecture_old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_architecture_old.png
--------------------------------------------------------------------------------
/201703/assets/elk_kibana_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_kibana_01.png
--------------------------------------------------------------------------------
/201703/assets/elk_kibana_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_kibana_02.png
--------------------------------------------------------------------------------
/201703/assets/elk_parse_log_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_parse_log_01.png
--------------------------------------------------------------------------------
/201703/assets/elk_parse_log_java.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_parse_log_java.png
--------------------------------------------------------------------------------
/201703/assets/elk_parse_log_nginx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_parse_log_nginx.png
--------------------------------------------------------------------------------
/201703/assets/elk_parse_log_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/elk_parse_log_node.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_01.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_02.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_03.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_04.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_05.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_06.png
--------------------------------------------------------------------------------
/201703/assets/logstash_scale_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/logstash_scale_07.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_01.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_02.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_1.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_1_zk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_1_zk.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_2.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_2_zk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_2_zk.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_3.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_3_zk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_3_zk.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_4.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_case_4_zk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_case_4_zk.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_re_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_re_01.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_re_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_re_02.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_result_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_result_01.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_result_01_bak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_result_01_bak.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_services.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_services.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_zk_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_zk_01.png
--------------------------------------------------------------------------------
/201703/assets/service_registry_zk_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201703/assets/service_registry_zk_02.png
--------------------------------------------------------------------------------
/201703/elk.md:
--------------------------------------------------------------------------------
1 | # 基于ELK+Filebeat搭建日志中心
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | > 本文是基于docker进行的容器化搭建ELK
5 |
6 | ## 当前环境
7 | 1. 系统:centos7
8 | 2. docker 1.12.1
9 |
10 |
11 | ## 介绍
12 |
13 | ### ElasticSearch
14 |
15 | Elasticsearch 是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。它是一个建立在全文搜索引擎 Apache Lucene 基础上的搜索引擎,使用 Java 语言编写。
16 |
17 | ### Logstash
18 | Logstash 是一个具有实时渠道能力的数据收集引擎,主要用于日志的收集与解析,并将其存入
19 | ElasticSearch中。
20 |
21 | ### Kibana
22 | Kibana 是一款基于 Apache 开源协议,使用 JavaScript 语言编写,为 Elasticsearch 提供分析和可视化的 Web 平台。它可以在 Elasticsearch 的索引中查找,交互数据,并生成各种维度的表图。
23 |
24 | ### Filebeat
25 | 引入Filebeat作为日志搜集器,主要是为了解决Logstash开销大的问题。相比Logstash,Filebeat 所占系统的 CPU 和内存几乎可以忽略不计。
26 |
27 | ## 架构
28 | ### 不引入Filebeat
29 | 
30 |
31 | ### 引入Filebeat
32 | 
33 |
34 |
35 | ## 部署
36 |
37 | ### 启动ElasticSearch
38 | ```
39 | docker run -d -p 9200:9200 --name elasticsearch elasticsearch
40 |
41 | ```
42 | ### 启动Logstash
43 |
44 | ```
45 | # 1. 新建配置文件logstash.conf
46 | input {
47 | beats {
48 | port => 5044
49 | }
50 | }
51 |
52 | output {
53 | stdout {
54 | codec => rubydebug
55 | }
56 | elasticsearch {
57 | #填写实际情况elasticsearch的访问IP,因为是跨容器间的访问,使用内网、公网IP,不要填写127.0.0.1|localhost
58 | hosts => ["{$ELASTIC_IP}:9200"]
59 |
60 | }
61 | }
62 |
63 | # 2.启动容器,暴露并映射端口,挂载配置文件
64 | docker run -d --expose 5044 -p 5044:5044 --name logstash -v "$PWD":/config-dir logstash -f /config-dir/logstash.conf
65 | ```
66 |
67 |
68 | ### 启动Filebeat
69 | 下载地址:[https://www.elastic.co/downloads/beats/filebeat](https://www.elastic.co/downloads/beats/filebeat)
70 |
71 |
72 | ```
73 | # 1.下载Filebeat压缩包
74 | wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-5.2.2-linux-x86_64.tar.gz
75 |
76 | # 2.解压文件
77 | tar -xvf filebeat-5.2.2-linux-x86_64.tar.gz
78 |
79 | # 3.新建配置文件filebeat.yml
80 | filebeat:
81 | prospectors:
82 | - paths:
83 | - /tmp/test.log #日志文件地址
84 | input_type: log #从文件中读取
85 | tail_files: true #以文件末尾开始读取数据
86 | output:
87 | logstash:
88 | hosts: ["{$LOGSTASH_IP}:5044"] #填写logstash的访问IP
89 |
90 | # 4.运行filebeat
91 | ./filebeat-5.2.2-linux-x86_64/filebeat -e -c filebeat.yml
92 | ```
93 |
94 |
95 | ### 启动Kibana
96 | ```
97 | docker run -d --name kibana -e ELASTICSEARCH_URL=http://{$ELASTIC_IP}:9200 -p 5601:5601 kibana
98 | ```
99 |
100 |
101 | ## 测试
102 | 1. 模拟日志数据
103 |
104 | ```
105 | # 1.创建日志文件
106 | touch /tmp/test.log
107 |
108 | # 2.向日志文件中写入一条nginx访问日志
109 | echo '127.0.0.1 - - [13/Mar/2017:22:57:14 +0800] "GET / HTTP/1.1" 200 3700 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36" "-"' >> /tmp/test.log
110 | ```
111 |
112 | 2. 访问 http://{$KIBANA_IP}:5601
113 |
114 | 
115 | 
116 | ## 总结
117 | 本文主要讲述了如何一步步搭建ELK的过程,以及Filebeat在其中所起的作用。
118 | 这儿仅仅给大家做了一个演示,要在生产环境中部署时,还需使用数据卷进行数据持久化,容器内存问题也需考虑,elasticsearch与logstash都是相对吃内存的,如果不加以限制,很可能会拖垮你整个服务器。
当然安全因素也是大家不能忽视的,如传输的安全性、端口权限的最小化暴露程度,防火墙设置等。
119 |
120 | ## 后续
121 | 1. logstash解析日志格式,如JAVA、nginx、nodejs等日志;
122 | 2. elasticsearch的常用搜索语法;
123 | 2. 通过kibana制作可视化图表;
124 |
125 |
126 |
--------------------------------------------------------------------------------
/201703/elk_parse_log.md:
--------------------------------------------------------------------------------
1 | # ELK实战之解析各类日志文件
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 |
5 | > ELK环境是基于docker进行的容器化部署
6 | > 关于容器化部署,详情见上一篇 [“ELK:基于ELK+Filebeat的日志搭建”](https://github.com/jasonGeng88/blog/blob/master/201703/elk.md)
7 |
8 | ## 当前环境
9 | 1. logstash:5.2
10 |
11 |
12 | ## 介绍
13 | 基于上一篇讲述了ELK日志系统的搭建,那么就该讲讲ELK在生产中的实际使用场景了。
14 |
15 | 作为一个日志中心,它会收集各种各样的日志,可以用于问题排查,数据监控,统计分析等等。那么对于繁多的日志,它们都有各自的存储格式,我们如何来区分它们,对于不同的日志格式,我们又是如何去解析的呢?
16 |
17 | 一长串没有结构化的日志,给人的感觉很凌乱。我们需要的是提取日志中的有效字段,并以我们期望的形式进行展现。下面我将和大家一起来探究日志解析的奥秘。
18 |
19 | ## 原理
20 | 依照前文,使用filebeat来上传日志数据,logstash进行日志收集与处理,elasticsearch作为日志存储与搜索引擎,最后使用kibana展现日志的可视化输出。所以不难发现,日志解析主要还是logstash做的事情。
21 |
22 | 说到logstash,它到底有哪些东西呢?我们来简单看下:
23 | 
24 |
25 | 从上图中可以看到,logstash主要包含三大模块:
26 |
27 | 1. INPUTS: 收集所有数据源的日志数据([源有file、redis、beats等](https://www.elastic.co/guide/en/logstash/current/input-plugins.html),*filebeat就是使用了beats源*);
28 | 2. FILTERS: 解析、整理日志数据(**本文重点**);
29 | 3. OUTPUTS: 将解析的日志数据输出至存储器([elasticseach、file、syslog等](https://www.elastic.co/guide/en/logstash/current/output-plugins.html));
30 |
31 |
32 | 看来FILTERS是我们探究的重点,先来来看看它常用到的几个插件(*后面日志解析会用到*):
33 |
34 | 1. grok:采用正则的方式,解析原始日志格式,使其结构化;
35 | 2. geoip:根据IP字段,解析出对应的地理位置、经纬度等;
36 | 3. date:解析选定时间字段,将其时间作为logstash每条记录产生的时间(*若没有指定该字段,默认使用read line的时间作为该条记录时间*);
37 |
38 | *注意:codec也是经常会使用到的,它主要作用在INPUTS和OUTPUTS中,[提供有json的格式转换、multiline的多行日志合并等](https://www.elastic.co/guide/en/logstash/current/codec-plugins.html)*
39 |
40 | ## 场景
41 | 说了这么多,到底怎么用呢?我们还是通过几个例子,具体来看看是怎么实现的吧。
42 | 秉承先易后难的原则,希望大家全部看完后,对以后遇到更复杂的日志,也能处理的游刃有余。
43 |
44 | **1. NodeJS 日志**
45 |
46 | * 日志格式
47 |
48 | ```
49 | $time - $remote_addr $log_level $path - $msg
50 | ```
51 |
52 | * 日志内容
53 |
54 | ```
55 | 2017-03-15 18:34:14.535 - 112.65.171.98 INFO /root/ws/socketIo.js - xxxxxx与ws server断开连接
56 | ```
57 |
58 | * filebeat配置(*建议filebeat使用rpm安装,以systemctl start filebeat方式启动*)
59 |
60 | ```
61 | filebeat:
62 | prospectors:
63 | - document_type: nodejs #申明type字段为nodejs,默认为log
64 | paths:
65 | - /var/log/nodejs/log #日志文件地址
66 | input_type: log #从文件中读取
67 | tail_files: true #以文件末尾开始读取数据
68 | output:
69 | logstash:
70 | hosts: ["${LOGSTASH_IP}:5044"]
71 |
72 | #General Setting
73 | name: "server1" #设置beat的名称,默认为主机hostname
74 | ```
75 |
76 | * logstash中FILTERS配置
77 |
78 | ```
79 | filter {
80 | if [type] == "nodejs" { #根据filebeat中设置的type字段,来过滤不同的解析规则
81 | grok{
82 | match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} - %{IPORHOST:clientip} %{LOGLEVEL:level} %{PATH:path} - %{GREEDYDATA:msg}" }
83 | }
84 | geoip {
85 | source => "clientip" #填写IP字段
86 | }
87 | }
88 | }
89 | ```
90 |
91 | * 结果(*为方便演示,数据有删减*)
92 | 
93 |
94 | * **Filter配置讲解**
95 |
96 | 1. grok中的match内容:
97 | 1. key:表示所需解析的内容;
98 | 2. value:表示解析的匹配规则,提取出对应的字段;
99 | 3. 解析语法:%{正则模板:自定义字段},其中TIMESTAMP_ISO8601、IPORHOST等都是grok提供的正则模板([可在此查阅](https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns));
100 | 2. geoip:通过分析IP值,产生IP对应的地理位置信息;
101 |
102 | *这里是否发现@timestamp与timestamp不一致,@timestamp表示该日志的读取时间,在elasticsearch中作为时间检索索引。下面讲解Nginx日志时,会去修正这一问题。*
103 |
104 | ****
105 |
106 | **2. Nginx 访问日志**
107 |
108 | * 日志格式
109 |
110 | ```
111 | $remote_addr - $remote_user [$time_local]
112 | "$request" $status $body_bytes_sent "$http_referer"
113 | "$http_user_agent" "$http_x_forwarded_for"
114 | ```
115 |
116 | * 日志内容
117 |
118 | ```
119 | 112.65.171.98 - - [15/Mar/2017:18:18:06 +0800] "GET /index.html HTTP/1.1" 200 1150 "http://www.yourdomain.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36" "-"
120 | ```
121 |
122 | * filebeat中prospectors的配置
123 |
124 | ```
125 | - document_type: nginx
126 | paths:
127 | - /var/log/nginx/access.log #日志文件地址
128 | input_type: log #从文件中读取
129 | tail_files: true #以文件末尾开始读取数据
130 | ```
131 |
132 | * logstash中FILTERS配置
133 |
134 | ```
135 | filter {
136 | if [type] == "nginx" {
137 | grok{
138 | match => { "message" => "%{COMBINEDAPACHELOG}" }
139 | }
140 |
141 | date {
142 | match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z", "ISO8601" ]
143 | target => "@timestamp" #可省略
144 | }
145 | }
146 |
147 | }
148 | ```
149 |
150 | * 结果
151 | 
152 |
153 | * **Filter配置讲解**
154 | 1. grok:
155 | 1. 是不是很不可思议,上一示例中我们匹配规则写了一长串,这个仅仅一个COMBINEDAPACHELOG就搞定了!
156 | 2. grok除了提供上面那种基础的正则规则,还对常用的日志(java,http,syslog等)提供的相应解析模板,本质还是那么一长串正则,[详情见grok的120中正则模板](https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns);
157 | 2. date:
158 | 1. match:数组中第一个值为要匹配的时间字段,后面的n个是匹配规则,它们的关系是or的关系,满足一个即可;
159 | 2. target:将match中匹配的时间替换该字段,默认替换@timestamp;
160 |
161 | *目前为止我们解析的都是单行的日志,向JAVA这样的,若果是多行的日志我们又该怎么做呢?*
162 |
163 | ****
164 |
165 | **3. JAVA Log4j 日志**
166 |
167 | * 日志内容
168 |
169 | ```
170 | '2017-03-16 15:52:39,580 ERROR TestController:26 - test:
171 | java.lang.NullPointerException
172 | at com.test.TestController.tests(TestController.java:22)
173 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
174 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
175 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
176 | at java.lang.reflect.Method.invoke(Method.java:497)
177 | at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
178 | at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)'
179 |
180 | ```
181 |
182 | * filebeat中prospectors的配置
183 |
184 | ```
185 | - document_type: tomcat
186 | paths:
187 | - /var/log/java/log #日志文件地址
188 | input_type: log #从文件中读取
189 | tail_files: true #以文件末尾开始读取数据
190 | multiline:
191 | pattern: ^\d{4}
192 | match: after
193 | negate: true
194 | ```
195 |
196 | * logstash中FILTERS配置
197 |
198 | ```
199 | filter {
200 | if [type] == "tomcat" {
201 | grok{
202 | match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{JAVALOGMESSAGE:msg}" }
203 | }
204 |
205 | date {
206 | match => [ "timestamp" , "yyyy-MM-dd HH:mm:ss,S", "ISO8601" ]
207 | }
208 | }
209 |
210 | }I
211 | ```
212 |
213 | * 结果
214 | 
215 |
216 |
217 | * **Filebeat配置讲解**
218 | 1. multiline 合并多行日志:
219 | 1. pattern:匹配规则,这里指匹配每条日志开始的年份;
220 | 2. match:有before与after,这里指从该行开始向后匹配;
221 | 3. negate:是否开始一个新记录,这里指当pattern匹配后,结束之前的记录,创建一条新日志记录;
222 |
223 | *当然在logstash input中使用codec multiline设置是一样的*
224 |
225 | *小技巧:关于grok的正则匹配,官方有给出[Grok Constructor](http://grokconstructor.appspot.com/)方法,在这上面提供了debugger、自动匹配等工具,方便大家编写匹配规则*
226 |
227 |
228 | ## 总结
229 | 本文开始简单介绍了logstash的三大模块:INPUTS、FILTERS、OUTPUTS。之后通过Demo了3个小示例,给大家讲解了FILTERS中grok、geoip、date三个常用插件的使用,以及在处理多行日志上的做法。
230 |
231 | 在描述的过程中可能不能面面俱到,但我还是始终坚持“知其然知其所以然”的理念。写的每一行代码,你都得心中有数。功能的实现不意味着结束,我们何不多折磨自己一下,走好最后的一公里。
232 |
233 | 最后,有兴趣可以去看一下它的官方手册,对这三大模块,各自都提供了非常多的插件支持。我这里只是一个简单的使用,希望对大家有所帮助。
234 |
235 |
236 |
--------------------------------------------------------------------------------
/201703/logstash_deploye_scale.md:
--------------------------------------------------------------------------------
1 | # [译] ELK日志中心分布式架构的逐步演化
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 |
5 | ## 当前环境
6 | 1. logstash:5.2
7 |
8 | ## 说明
9 | 记得写 [“基于ELK+Filebeat搭建日志中心”](https://github.com/jasonGeng88/blog/blob/master/201703/elk.md) 时,有朋友跟我说:“你的日志中心缺少了消息队列。” 是的,没有考虑。因为暂时用不到,架构的演变一定是根据业务的发展逐步完成的。我觉得任何东西,太少或太过都未必是好事。构架能满足公司当前的发展,那就是好的。
10 |
11 | 当然我不是指架构可以随意设计,只要满足需求就好。我们设计的架构,满足现有需求当然是先决条件,但还得看得到可预见的未来,为架构的演进预留一定的扩展性。
12 |
13 | 所以架构既得满足公司业务,还要参考一些成熟的方案,不能生搬硬套。这也是我也这篇文章的原因,自知能力有限,要讲分布式我肯定讲不好,其中势必有很多坑。所以借鉴官网原文,给目前或今后要做分布式的同学一点建议(当然也包括我~)。
14 |
15 |
16 | ***这里我不会对原文逐字翻译,会根据自己的理解,以我自己能看懂的表述来翻译给大家看。***
17 |
18 | ## 原文链接
19 |
20 | [https://www.elastic.co/guide/en/logstash/current/deploying-and-scaling.html#deploying-and-scaling](https://www.elastic.co/guide/en/logstash/current/deploying-and-scaling.html#deploying-and-scaling)
21 |
22 | # 译文
23 |
24 | ## 概述
25 | 当Logstash的使用场景逐步演进时,我们之前的架构也将随之发生改变。本文讨论了在复杂度逐渐递增下的Logstash架构一系列的演变过程。我们先从一个最简单的架构开始,然后在此架构上来逐渐增加内容。本文的示例是将数据写入到了ES(*Elasticsearch*)集群,其实Logstash可以写的[输出源](https://www.elastic.co/guide/en/logstash/5.2/output-plugins.html)非常多。
26 |
27 | ## 最简架构
28 | Logstash最简单的架构可以由一个Logstash实例和一个ES实例组成,两者直接相连。按照Logstash的[处理流程](https://www.elastic.co/guide/en/logstash/5.2/pipeline.html),我们使用了一个收集数据的[INPUT插件](https://www.elastic.co/guide/en/logstash/5.2/input-plugins.html)和一个数据写入ES的[OUTPUT插件](https://www.elastic.co/guide/en/logstash/5.2/output-plugins.html),最后按照实例配置文件上的固定配置,启动Logstash。配置文件中,INPUT插件与OUTPUT插件是必须的,且OUTPUT默认输出方式是stdout,FILTER是可选的,下文会讲到。
29 |
30 | 
31 |
32 | ## 引入 Filters
33 | 日志数据默认是无结构化的,经常包含一些无用信息,有时也会丢失一些本可从日志中获取的相关信息。你可以使用[FILTER插件](https://www.elastic.co/guide/en/logstash/5.2/filter-plugins.html)来解析你的日志,从中提取有效字段,剔除无用的信息,还可以从有效字段中衍生出额外信息。例如,filters可以从IP地址中衍生出地理信息,将其添加进日志中,也可以使用[grok filter](https://www.elastic.co/guide/en/logstash/5.2/plugins-filters-grok.html)解析文本信息,使其结构化。
34 |
35 | 当然,添加FILTER插件对性能是有一定影响的。这取决于FILTER插件执行的计算量,以及处理的日志大小。grok filter的正则计算尤其占用资源。解决资源消耗大的一种方式是利用计算机多核的特性进行并行计算。使用 -w 参数来设置 Logstash filter 任务的执行线程数。例如,bin/logstash -w 8 命令使用的是8个不同的线程来处理filter。
36 |
37 | 
38 |
39 | ## 引入 Filebeat
40 | [Filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/index.html) 是一款有Go语言编写的轻量级日志收集工具,主要作用是收集当前服务器上的日志,并将收集的数据输出到目标机器上进行进一步处理。Filebeat 使用 [Beats](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) 协议与Logstash实例进行通信。使用 [Beats input 插件](https://www.elastic.co/guide/en/logstash/5.2/plugins-inputs-beats.html) 来配置你的Logstash的实例,让其能够接收Beats传来的数据。
41 |
42 | Filebeat使用的是源数据所在机器的计算资源,Beats input 插件最小化了Logstash实例的资源需求,这种架构对于有资源限制要求的场景来说,非常有用。
43 |
44 | 
45 |
46 | ## 引入ES集群
47 |
48 | Logstash 一般不与ES的单节点进行通信,而是和多个节点组成的ES集群进行通信,采用的协议默认是HTTP。
49 |
50 | 你可以使用ES提供的REST API接口向集群写入数据,传输的数据格式为JSON。使用REST接口在代码中不需要引入JAVA的客户端类或任何额外的JAR包。相比节点协议与传输格式,没有性能上的弊端。若要做到接口安全通信,可以使用 [X-Pack Security](https://www.elastic.co/guide/en/x-pack/current/xpack-security.html) ,它支持SSL与HTTP basic的安全校验。
51 |
52 | 当你使用HTTP协议时,可以在Logstash的 ES output 插件配置中,提供ES集群的多个请求地址,ES的请求将自动做到负载均衡。多个ES节点通过路由流量到活跃节点的方式也为ES集群提供的高可用性。
53 |
54 | 你也可以使用ES的 JAVA API将数据序列化为二进制后,再进行传输。该协议可以嗅探请求的地址,你可以选择集群中任意的客户端或节点进行通信。
55 |
56 | 使用HTTP,可以将ES集群与Logstash实例相分离。 与此相反,节点协议把运行Logstash实例的机器作为一个运行中的ES节点,与ES集群连接在了一起。数据同步是将数据从一个节点传输至集群中的其余节点。当该机器作为集群的一部分,该段网络拓扑变得可用,对于使用相对少量持久连接的场景来说,使用节点协议是较合适的。
57 |
58 | 你也可以使用第三方的负载均衡硬件或软件,来处理Logstash与外部应用的连接。
59 |
60 | *注意:确保你的Logstash配置不直接连接到ES[管理集群的主节点](https://www.elastic.co/guide/en/elasticsearch/reference/5.2/modules-node.html)上。将Logstash连接到客户端或数据节点上,来保护ES集群的稳定性。*
61 |
62 | 
63 |
64 | ## 使用消息队列处理吞吐量峰值
65 |
66 | 当Logstash接收数据的能力超过了ES集群处理数据的能力时,你可以使用消息队列来作为缓冲。默认情况下,当数据的处理速率低于接收速率,Logstash的接收将产生瓶颈。由于该瓶颈会导致事件在数据源中被缓冲,所以使用消息队列来抗压将成为你架构中的重要环节。
67 |
68 | 添加一个消息队列到你部署的Logstash中,对数据丢失也提供了一定的保护。当Logstash实例在消息队列中消费数据失败时,数据将会在另一个活跃的Logstash中重新消费。
69 |
70 | 目前市面上提供的第三方消息队列,如Redis,Kafka,RabbitMQ。Logstash都提供了相应的input、output插件与之做集成。当Logstash的部署中添加了消息队列,Logstash的处理将分为两个阶段:第一阶段(传输实例),负责处理数据采集,并将其存入消息队列;第二阶段(存储实例),从消息队列中获取数据,应用所配置的filter,将处理过的数据写入ES中。
71 |
72 | 
73 |
74 | ## 采用多连接保证Logstash高可用
75 |
76 | 为了使Logstash架构更适应单节点不可用的情况,,你可以在数据源与Logstash集群间建立负载均衡。这个负载均衡管理与Logstash实例的连接,保证了在单个实例不可用的情况下,数据采集与处理的正常进行。
77 |
78 | 
79 |
80 | 上面的架构中存在一种问题,每个Logstash实例都只提供一种INPUT。当某一个实例不可用时,该类型的数据将无法继续收集,例如RSS订阅或文件输入。为了使INPUT的处理更健壮,每个Logstash实例都要配置多个input通道,如下图:
81 |
82 | 
83 |
84 | 该架构基于你配置的INPUT,可以并行工作。对于更多的INPUT输入,你可以增加更多的Logstash实例来进行水平扩展。这也增加了架构的可靠性,消除了单点故障。
85 |
86 | ## Logstash的扩展
87 |
88 | 一个成熟的Logstash部署有以下几方面:
89 |
90 | * INPUT层从数据源中采集数据,由合适的input 插件组成。
91 | * 消息队列作为数据采集的缓冲与故障转移的保护。
92 | * FILTER层从消息队列中获取数据进行解析和其他操作。
93 | * indexing层将处理的数据传输到ES。
94 |
95 | 这其中的每一层都可以通过增加计算资源进行扩展。随着你使用场景的发展与所需资源的增加,定期检查这些组件的性能。当Logstash一旦遇到输入的瓶颈,可考虑增加消息队列的存储。相反,通过增加更多的Logstash输出实例来增加ES集群的写入速率。
--------------------------------------------------------------------------------
/201703/service_registry.md:
--------------------------------------------------------------------------------
1 | # 基于Docker、Registrator、Zookeeper实现的服务自动注册
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 | >
5 | > 本文所有服务均采用docker容器化方式部署
6 |
7 |
8 | ## 当前环境
9 | 1. 系统:Mac OS
10 | 2. docker 1.12.1
11 | 3. docker-compose 1.8.0
12 |
13 | ## 场景
14 | 在微服务架构中,传统的大型单一应用会根据业务场景被拆分成众多功能单一且独立自治的微型服务。每个服务模块不仅能独立的对外暴露服务,而且根据业务需求,可以动态的进行扩容或缩减,充分利用了服务器资源。
15 |
16 | 但此架构引入了很多新的问题。其中一个是,随着服务的增多,以及服务的动态扩容,服务地址(IP、Port)硬编码的方式已经显得不太合适。我们需要寻求一种机制,让每个服务能动态的创建地址,同时调用方要能获取到这些信息、且感知地址的动态变化。
17 |
18 | ## 注册中心
19 | 为解决上述问题,业界给出的一种方案是使用注册中心,通过服务的发布-订阅模式,来解决上述场景,即所谓的 “服务的注册&发现”。
20 |
21 | 举例来讲,大家过去是否有翻查电话簿打电话的经历。有一天,你想给小明打个电话,可是不知道他的电话号码是多少。于是去翻查电话簿上对方的电话信息,这里电话簿就是所谓的注册中心,而翻查电话簿的动作就是属于服务的发现过程,那么小明给出自己的电话号码,由自己(或他人)记录在电话簿上的动作就属于服务的注册过程。
22 |
23 | 理解了小明的例子,再来看服务注册发现的流程,是不是一样呢?
24 |
25 | 
26 |
27 | 1. 服务注册:服务提供者将自身的服务信息注册进注册中心;
28 | 2. 服务订阅:服务消费者从注册中心获取服务信息,并对其进行监听;
29 | 3. 缓存服务信息:将获取到的服务信息缓存到本地,减少与注册中心的网络通信;
30 | 4. 服务调用:查找本地缓存,找到对应的服务地址,发送服务请求;
31 | 5. 变更通知:当服务节点有变动时(服务新增、删除等),注册中心将通知监听节点变化的消费者,使其更新服务信息。
32 |
33 | ## 服务注册
34 | 回到小明的例子,小明有两种方式将自己的电话写入电话簿,一种是自己亲自登记,一种是找他人代为登记。即所谓的服务 “自注册” 与 “第三方注册”。
35 |
36 | * **自注册:** 服务内部启动客户端,连接注册中心,写入服务信息。
37 | * 问题:
38 | * 服务代码对注册中心进行了硬编码,若更换了注册中心,服务代码也必须跟着调整;
39 | * 注册中心必须与每个服务都保持通信,来做心跳检测。如果服务很多时,对注册中心也是一种额外的开销;
40 |
41 | * **第三方注册(本文采用方式):** 采用协同进程的方式,监听服务进程的变化,将服务信息写入注册中心。
42 | * 好处:做到了服务与注册中心的解耦,对服务而言,完成了服务的自动化注册;
43 | * 问题:协同进程本身也要考虑高可用,否则将成为单点故障的风险点;
44 |
45 | ***考虑篇幅原因,服务消费者相关内容将在下篇进行讲述***
46 |
47 | ## 技术方案
48 |
49 | **服务注册中心:** 作为整个架构中的核心,注册中心要做到的是支持分布式、支持持久化存储。可以把它想象成一个集中化管理的中心服务器。同时负责将服务注册信息的变动实时通知给服务消费者。
50 |
51 | 这里,技术上我们选用的 [ZK (ZooKeeper)](https://zookeeper.apache.org/)。大家可以把 ZK 想象成文件服务器,注册中心在 ZK 中的展现就是节点路径图,每个节点下存放者相应的服务信息,ZK路径图如下:
52 |
53 | 
54 |
55 | **服务提供者:** 服务以 docker 容器化方式部署(实现服务端口的动态生成),并以 [docker-compose](https://docs.docker.com/compose/) 的方式来管理,这里包含各种语言实现的服务(如JAVA、PHP等),通过 [Registrator](http://gliderlabs.com/registrator/latest/) 完成服务的自动注册。
56 |
57 | ## 技术说明
58 |
59 | **Docker:** 是一个开源工具,能将一个WEB应用封装在一个轻量级,便携且独立的容器里,然后可以运行在几乎任何服务环境下。 Docker的一般使用在以下几点: 自动化打包和部署应用。
60 |
61 | **Docker-compose:** 是一个用来定义、启动和管理服务的工具,通过compose配置文件,将一个或多个 Docker 容器组合成一个服务。并通过简单的命令对服务进行管理,如启动、销毁、水平扩展等等。
62 |
63 | **Registrator:** 一个由Go语言编写的,针对docker使用的,通过检查容器在线或者停止运行状态自动注册和去注册服务的工具。
64 |
65 | **ZK:** 作为一个分布式服务框架,是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。
66 |
67 | ## 示例
68 |
69 | **代码地址:** [https://github.com/jasonGeng88/service_registry_discovery](https://github.com/jasonGeng88/service_registry_discovery)
70 |
71 | 示例主要从3个方面演示:
72 |
73 | 1. 框架搭建
74 | 2. 服务准备
75 | 3. 场景演示
76 |
77 | ### 框架搭建:
78 |
79 | #### ZK 部署
80 | > 当前位置: 项目根目录
81 |
82 | * zookeeper/docker-compose.yml (*为演示方便,这里在单台机器上运行*):
83 |
84 | ```yaml
85 | version: '2' #docker-compose版本
86 | services:
87 | zoo1:
88 | image: zookeeper
89 | restart: always
90 | ports:
91 | - 2181:2181
92 | environment:
93 | ZOO_MY_ID: 1
94 | ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
95 |
96 | zoo2:
97 | image: zookeeper
98 | restart: always
99 | ports:
100 | - 2182:2181
101 | environment:
102 | ZOO_MY_ID: 2
103 | ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
104 |
105 | zoo3:
106 | image: zookeeper
107 | restart: always
108 | ports:
109 | - 2183:2181
110 | environment:
111 | ZOO_MY_ID: 3
112 | ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
113 | ```
114 |
115 |
116 | * 启动命令(*执行上述文件*)
117 |
118 | ```
119 | cd zookeeper && docker-compose up -d
120 | ```
121 |
122 | * 查看结果
123 |
124 | 
125 |
126 | ___
127 |
128 | #### Registrator 部署
129 | > 当前位置: 项目根目录
130 |
131 | * registrator/docker-compose.yml
132 |
133 | ```yaml
134 | version: '2'
135 | services:
136 | registrator1:
137 | image: gliderlabs/registrator
138 | restart: always
139 | network_mode: "host"
140 | volumes:
141 | - /var/run/docker.sock:/tmp/docker.sock
142 | # -ip 设置服务写入注册中心的IP地址
143 | # zookeeper:// 设置连接的 ZK 协议、地址、注册的根节点
144 | command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2181/services"
145 |
146 | registrator2:
147 | image: gliderlabs/registrator
148 | restart: always
149 | network_mode: "host"
150 | volumes:
151 | - /var/run/docker.sock:/tmp/docker.sock
152 | command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2182/services"
153 |
154 | registrator3:
155 | image: gliderlabs/registrator
156 | restart: always
157 | network_mode: "host"
158 | volumes:
159 | - /var/run/docker.sock:/tmp/docker.sock
160 | command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2183/services"
161 | ```
162 |
163 | * 启动命令
164 |
165 | ```Shell
166 | cd registrator/ && docker-compose up -d
167 | ```
168 |
169 | * 查看结果
170 |
171 | 
172 |
173 | 目前框架已搭建完毕,我们来连接一台ZK,来观察节点情况:
174 |
175 | ```Shell
176 | # 进入 ZK 容器
177 | docker exec -it zookeeper_zoo1_1 /bin/bash
178 | # 连接 ZK
179 | zkCli.sh -server 127.0.0.1:2181
180 | ```
181 |
182 | 
183 |
184 | ***
185 |
186 | ### 服务准备:
187 |
188 | 项目中为演示准备了2个服务,分别是用JAVA、PHP实现的。
189 |
190 | #### java_service_1(由springboot实现):
191 |
192 | > 当前位置: services/java_service_1
193 |
194 | * 启动文件
195 |
196 | ```Java
197 | package com.example;
198 |
199 | import org.springframework.boot.SpringApplication;
200 | import org.springframework.boot.autoconfigure.SpringBootApplication;
201 |
202 | @RestController
203 | @SpringBootApplication
204 | public class DemoApplication {
205 |
206 | @RequestMapping("/")
207 | String home() {
208 | return "This is Service 1.";
209 | }
210 |
211 | public static void main(String[] args) {
212 | SpringApplication.run(DemoApplication.class, args);
213 | }
214 | }
215 |
216 | ```
217 |
218 | * 生成 jar 文件
219 |
220 | 这里使用 [Spring Boot CLI](https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started-installing-spring-boot.html) 进行打包
221 |
222 | ```Shell
223 | spring jar ROOT.jar src/main/java/com/example/DemoApplication.java
224 | ```
225 |
226 | * 构建镜像
227 |
228 | ```Shell
229 | # java_service_1 为你要构建的镜像名,tag默认为latest
230 | docker build -t java_service_1 .
231 | ```
232 |
233 | ***
234 |
235 | #### php_service_2:
236 |
237 | > 当前位置: services/php_service_2
238 |
239 | * index.php
240 |
241 | ```Php
242 | 当前位置: services
265 |
266 | * **场景 1:** 启动 service_1(JAVA)服务
267 |
268 | ```
269 | docker-compose up -d service_1
270 | ```
271 |
272 | 
273 |
274 | 查看 ZK 节点情况
275 |
276 | 
277 |
278 | ***
279 |
280 | * **场景 2:** 启动service_2(PHP)服务
281 |
282 | ```
283 | docker-compose up -d service_2
284 | ```
285 |
286 | 
287 |
288 | 查看 ZK 节点情况
289 |
290 | 
291 |
292 | ***
293 |
294 | * **场景 3:** 扩展service_2(PHP)服务,个数为2
295 |
296 | ```
297 | docker-compose up -d service_2
298 | ```
299 |
300 | 
301 |
302 | 查看 ZK 节点情况
303 |
304 | 
305 |
306 |
307 | ***
308 |
309 | * **场景 4:** 注销service_1(JAVA)服务
310 |
311 | ```
312 | docker-compose up -d service_2
313 | ```
314 |
315 | 
316 |
317 | 查看 ZK 节点情况
318 |
319 | 
320 |
321 |
322 | ## 优化点
323 | * 在生产环境中,zk安全连接、节点访问控制都是需要注意的。简单做法,可以把连接地址改成内网IP,添加防火墙策略来限制连接客户端。
324 |
325 | * Registrator这里采用的是其多个进程分别连接不同的节点,来防止Registrator的单点故障。由于Registrator所用开销较小,在服务数量与ZK节点数量不大的情况下,不会产生问题。 较好的方式是:Registrator提供失效自动地址切换功能(*目前官方文档好像没有提供此方案,有了解的同学可以留言告诉我*)。
326 |
327 | ## 总结
328 | 本文从介绍何为 “服务注册&发现” 切入,以通俗易懂的语言介绍了其内在的本质,最后讲到了实现的所用的具体技术方案,以及以 Demo 的方式,演示了 Registrator 是如何做到服务的自动化注册的。
329 |
330 | 当然,这只是实现的一种形式。注册中心用 etcd、consul 也都是可行的,而且 Registrator 官方最好的支持是 consul。我们这里就不细究它们的差别了,找到适合自己、满足业务的就是最好的:blush:。
331 |
332 | ## 后续
333 | 1. 服务发现机制与实现
334 |
335 |
336 |
--------------------------------------------------------------------------------
/201704/assets/discovery_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_01.png
--------------------------------------------------------------------------------
/201704/assets/discovery_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_02.png
--------------------------------------------------------------------------------
/201704/assets/discovery_code_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_code_01.png
--------------------------------------------------------------------------------
/201704/assets/discovery_code_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_code_02.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_01.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_02.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_03.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_04.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_05.png
--------------------------------------------------------------------------------
/201704/assets/discovery_demo_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/discovery_demo_06.png
--------------------------------------------------------------------------------
/201704/assets/es_cluster_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/es_cluster_01.png
--------------------------------------------------------------------------------
/201704/assets/es_cluster_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/es_cluster_02.png
--------------------------------------------------------------------------------
/201704/assets/es_cluster_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/es_cluster_03.png
--------------------------------------------------------------------------------
/201704/assets/rmi_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_01.png
--------------------------------------------------------------------------------
/201704/assets/rmi_code_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_code_01.png
--------------------------------------------------------------------------------
/201704/assets/rmi_code_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_code_02.png
--------------------------------------------------------------------------------
/201704/assets/rmi_code_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_code_03.png
--------------------------------------------------------------------------------
/201704/assets/rmi_code_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_code_04.png
--------------------------------------------------------------------------------
/201704/assets/rmi_demo_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_demo_01.png
--------------------------------------------------------------------------------
/201704/assets/rmi_demo_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_demo_02.png
--------------------------------------------------------------------------------
/201704/assets/rmi_demo_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/rmi_demo_03.png
--------------------------------------------------------------------------------
/201704/assets/swarm_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_01.png
--------------------------------------------------------------------------------
/201704/assets/swarm_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_02.png
--------------------------------------------------------------------------------
/201704/assets/swarm_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_03.png
--------------------------------------------------------------------------------
/201704/assets/swarm_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_04.png
--------------------------------------------------------------------------------
/201704/assets/swarm_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_05.png
--------------------------------------------------------------------------------
/201704/assets/swarm_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_06.png
--------------------------------------------------------------------------------
/201704/assets/swarm_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_07.png
--------------------------------------------------------------------------------
/201704/assets/swarm_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_08.png
--------------------------------------------------------------------------------
/201704/assets/swarm_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_09.png
--------------------------------------------------------------------------------
/201704/assets/swarm_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_10.png
--------------------------------------------------------------------------------
/201704/assets/swarm_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_11.png
--------------------------------------------------------------------------------
/201704/assets/swarm_12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_12.png
--------------------------------------------------------------------------------
/201704/assets/swarm_13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_13.png
--------------------------------------------------------------------------------
/201704/assets/swarm_14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_14.png
--------------------------------------------------------------------------------
/201704/assets/swarm_scheduling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201704/assets/swarm_scheduling.png
--------------------------------------------------------------------------------
/201704/docker_swarm.md:
--------------------------------------------------------------------------------
1 | # 看 Docker Swarm 如何做集群
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 | >
5 | > 本文所有服务均采用docker容器化方式部署
6 |
7 |
8 | ## 当前环境
9 | 1. Mac OS 10.11.x
10 | 2. Docker >= 1.12
11 |
12 | ## 目录
13 |
14 | * 基本概念
15 | * 安装
16 | * 使用场景
17 | * 分配策略
18 | * 高可用
19 | * 总结
20 |
21 | ## 基本概念
22 |
23 | ### 技术说明
24 | * Docker Engine:作为 Docker 镜像构建与容器化启动的工作引擎;
25 | * Docker Machine:安装与管理 Docker Engine 的工具;
26 | * Docker Swarm:是 Docker 自1.12后自带的集群技术,将多个独立的 Docker Engine 利用 Swarm 技术进行集群化管理;
27 |
28 | ### 原理
29 |
30 | 简单来讲,Docker 集群的实现是通过 Docker Machine 在多主机或虚拟机上创建多个 Docker Engine,在利用 Docker Swarm 以某个 Engine 作为集群的初始管理节点,其他 Engine 以管理或工作节点的方式加入,形成完成的集群环境。
31 |
32 | 
33 |
34 | ## 安装(本次演示在单台 Mac 上进行)
35 |
36 | ### 创建 Docker Engine
37 |
38 | 使用 docker-machine 命令创建 docker 工作引擎。
39 |
40 | 参数说明:
41 |
42 | -d:设置 Docker Engine 驱动(Mac 下默认是 virtualbox)
43 |
44 | --virtualbox-boot2docker-url:设置驱动地址(*如果默认驱动下载很慢,可以更改国内的下载路径*)
45 |
46 | 下面创建4个引擎,1个集群管理者和3个工作者:
47 | ??
48 |
49 | ```
50 | docker-machine create -d virtualbox manager1
51 | docker-machine create -d virtualbox worker1
52 | docker-machine create -d virtualbox worker2
53 | docker-machine create -d virtualbox worker3
54 | ```
55 |
56 | 查看运行结果(docker-machine ls):
57 |
58 | 
59 |
60 |
61 | ### 初始化集群
62 | * 连接 manager1
63 |
64 | ```
65 | eval $(docker-machine env manager1)
66 | ```
67 |
68 | 查看运行结果:
69 |
70 | 
71 |
72 | * 创建 swarm
73 |
74 | ```
75 | docker swarm init --advertise-addr 192.168.99.100
76 | ```
77 |
78 | 参数说明:
79 |
80 | --advertise-addr:设置管理节点的对外IP,供集群中的其他节点访问;
81 |
82 | 命令说明:
83 |
84 | 该命令会创建一个新的 swarm,以当前节点作为 swarm 的管理节点。同时生成2个 token (manager/worker)供其他节点加入使用(*查询token命令 docker swarm join-token*)。
85 |
86 | 查看集群节点状况(docker node ls):
87 |
88 | 
89 |
90 | ---
91 |
92 | ### 加入工作节点
93 |
94 | ```
95 | # 连接 worker1
96 | eval $(docker-machine env worker1)
97 |
98 | # 加入 swarm
99 | docker swarm join \
100 | --token WORKER_TOKEN \
101 | 192.168.99.100:2377
102 | ```
103 |
104 | *同上,将其余两个以 worker 身份加入*
105 |
106 | 查看集群节点状况(*仅可在管理节点下查看*):
107 |
108 | 
109 |
110 | ---
111 |
112 | ### 移除节点
113 |
114 | 如果想要移除集群下的某个节点,可在该节点环境下,执行该命令:
115 |
116 | ```
117 | $ eval $(docker-machine env worker3)
118 |
119 | # 当前环境在 worker3 上
120 | docker swarm leave
121 | ```
122 |
123 | 查看集群节点状况
124 |
125 | 
126 |
127 | ---
128 |
129 | ### 重新加入集群
130 |
131 | ```
132 | # 加入 swarm
133 | docker swarm join \
134 | --token WORKER_TOKEN \
135 | 192.168.99.100:2377
136 | ```
137 |
138 | 查看集群节点状况
139 |
140 | 
141 |
142 | ---
143 |
144 | ### 移除无效节点
145 |
146 | ```
147 | docker node rm INVALID_ID
148 | ```
149 |
150 | 查看集群节点状况
151 |
152 | 
153 |
154 | ## 使用场景
155 | 关于 swarm 集群的搭建已经完成了,接下来就是启动服务。启动服务的模式分为:replicated 和 global。
156 |
157 | replicated(默认方式):先在一个 node 节点上创建一个服务,通过复制的方式进行扩展,这中间涉及扩展的策略。
158 |
159 | global:在每个 node 节点上都会创建一个服务,像 Registrator(自动注册服务) 就很适合用这种方式执行。
160 |
161 | ### 以 gobal 方式创建 nginx 服务
162 |
163 | ```
164 | docker service create --mode=global --name=web nginx
165 | ```
166 |
167 | 查看服务启动情况(每个节点都起了一个 web 服务):
168 |
169 | 
170 |
171 | ### 以 replicated 方式创建 nginx 服务
172 |
173 | * 创建一个 nginx 服务
174 |
175 | ```
176 | docker service create --name=web nginx
177 | ```
178 |
179 | 查看服务启动情况:
180 |
181 | 
182 |
183 | * 将服务依次扩展至5个
184 |
185 | ```
186 | docker service scale nginx=2
187 | ```
188 |
189 | 查看服务启动情况
190 |
191 | 
192 |
193 | ```
194 | docker service scale nginx=3
195 | ```
196 |
197 | 查看服务启动情况
198 |
199 | 
200 |
201 | ```
202 | docker service scale nginx=4
203 | ```
204 |
205 | 查看服务启动情况
206 |
207 | 
208 |
209 | ```
210 | docker service scale nginx=5
211 | ```
212 |
213 | 查看服务启动情况
214 |
215 | 
216 |
217 | ## 扩展分配策略
218 |
219 | 从服务的扩展可以看出,其中存在了一定的分配策略。这里采用的是默认的 Spread 策略。下面简单描述下最常用的2种 Spread 与 BinPack。[详细的介绍还请查看官方文档(如filters、affinity的使用)](https://docs.docker.com/swarm/scheduler/strategy/)。
220 |
221 | 
222 |
223 | * Spread:依据节点个数进行平均分配。
224 | * 优势:在容器使用资源相对一致的情况下,整体资源使用在每个节点上做了平均的分配;
225 | * 不足:若每个容器所占资源相差很大,那么节点的资源使用率将存在明显差异,导致资源使用的浪费;
226 |
227 | * BinPack:尽可能的使用一个节点的资源,在节点资源不足的情况下,更换使用节点。
228 | * 优势:节点资源能得到充分使用;
229 | * 不足:单节点的负重较高,而且每个节点性能未必都一样,可能会分重CPU或重磁盘读写等。
230 |
231 | 真实场景还是得依据具体的业务来合理使用分配策略。特殊情况,还需要搭配使用 filters、affinity 来具体选择节点。[详细的介绍还请查看官方文档](https://docs.docker.com/swarm/scheduler/filter/)。
232 |
233 | ## 高可用
234 |
235 | 服务的启动都是通过 manager 节点将启动服务分发给 worker 节点,而且服务的管理也是由manager节点来控制的。
236 |
237 | * manager 节点的可用性:我们可以设置多个 manager 节点。当 primary manager 挂掉后,secondary managers 中会选举一个作为 primary manager,以保证服务的高可用性。
238 |
239 | * 服务高可用:服务如果启动失败,或中途异常中止,会进行自动重启。保障了扩展服务的可用性。
240 |
241 | ## 总结
242 |
243 | 本文主要讲述了如何使用 Docker Swarm 来做 docker 的集群管理。其中包括了集群部署、服务的启动、扩展,以及常用的分配策略。
244 |
245 | swarm 在 docker 1.12开始被集成进了docker中。相比之前版本,以 docker run swarm create 启动的方式,现节点与节点之间是通过内置的 DNS 发现机制进行通信,使用更为便捷。
246 |
247 | 最后也提一下,使用 k8s 做容器集群管理的也在多数。这里不讨论两者的好坏,如果还没用 k8s 的可以考虑使用 swarm,毕竟目前是docker 内置的服务。
248 |
249 |
--------------------------------------------------------------------------------
/201704/es_cluster.md:
--------------------------------------------------------------------------------
1 | # Elasticsearch 集群配置与容器部署
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 | >
4 | > 本文所有服务均采用docker容器化方式部署
5 |
6 |
7 | ## 当前环境
8 | 1. Mac OS 10.11.x
9 | 2. Docker >= 1.12
10 | 3. elasticsearch 5.3
11 |
12 | ## 技术说明
13 | Elasticsearch(ES) 是一个分布式可扩展的实时搜索和分析引擎。底层基于的是 Apache Lucene 的搜索引擎。
14 |
15 | ## 目录
16 | * 配置文件讲解
17 | * 容器化配置
18 | * 总结
19 |
20 | ## 简介
21 | 写这篇文章的主要原因是之前部署 ES 集群的过程中遇到了一些问题,后来在看了官方文档后,发现和网上类似的文章还是所有差别。网上基本都是版本比较早的,而且没有用容器化来部署。尤其是在 版本5 之后引入了 Bootstrap Check 机制,下面来给大家讲讲我的理解。
22 |
23 | ## 配置文件
24 | ES 有两个配置文件,分别是:
25 |
26 | * **elasticsearch.yml**:ES 的具体配置。
27 | * **log4j2.properties**:ES 日志文件。(*顺便提一下,ES 是由 JAVA 实现的,所以写 JAVA 的同学看到这文件应该很熟悉,这里就不展开了*)。
28 |
29 | ---
30 | ### **elasticsearch.yml 配置**
31 |
32 | ### 路径
33 | * path.data: ES 数据的存储地址;
34 | * path.logs: ES 日志地址;
35 |
36 | ### 网络
37 |
38 | ES 在网络方面主要提供两种协议:
39 |
40 | 1. http: 用于 REST API 的使用;
41 | 2. tcp:用于节点间通信;
42 |
43 | 其对应的模块是 http 与 transport,详情可查看 [模块列表](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules.html)。
44 |
45 | * http.host:设置 HTTP 的服务地址;
46 | * http.port:设置 HTTP 端口,默认端口 9200-9300;
47 | * transport.host:设置 TCP 的服务地址;
48 | * transport.tcp.port:设置 TCP 端口,默认端口 9300-9400;
49 |
50 | ES 还提供了一个 network.host 设置,http 与 tcp 的 host 默认是绑定在它上面的,所以大多数情况下,上面参数可不进行配置。
51 |
52 | * network.host:默认为本地回环地址。**当定义该属性时,程序环境默认切换至生产环境(即开启 Bootstrap Checks)**。
53 |
54 | ### 集群
55 | ES 中的集群是根据集群名称来关联的,它会将集群名称相同的节点自动关联成一个集群,并通过节点名称来做区分。
56 |
57 | 节点常用的分为 集群管理节点(master) 与 数据操作节点(data),默认情况下是两者都是。
58 |
59 | * cluster.name:集群名称,默认名称 elasticsearch;
60 | * node.name:节点名称,同一集群下,节点名称不能重复;
61 |
62 | ### 发现机制
63 |
64 | 上面讲了集群的组成,但是节点与节点之间是如何发现的呢?
65 |
66 | 这里采用的是 ES 自带的 Zen Discovery 发现机制,
67 |
68 | * discovery.zen.ping.unicast.hosts:需要发现的节点地址,支持 IP 和 domain,在不指定端口的情况下,会默认扫描端口 9300 - 9305。
69 |
70 | ```
71 | discovery.zen.ping.unicast.hosts:
72 | - 192.168.1.10:9300
73 | - 192.168.1.11
74 | - seeds.mydomain.com
75 | ```
76 |
77 | * discovery.zen.minimum_master_nodes:master 节点最少可用数,当节点数小于该值,服务将不可用。这也是为了避免集群中出现多个中心,导致数据不一致;
78 |
79 | ```
80 | # 计算公式:(master 节点数 / 2) + 1
81 | # 例子:(3 / 2) + 1
82 | discovery.zen.minimum_master_nodes: 2
83 | ```
84 |
85 | ### Bootstrap Checks(生产环境)
86 |
87 | 上面讲到了 Bootstrap Checks,这也是它区别之前老版本的一个地方,在以前的版本中,也有这些警告,但有时会被人忽视,造成了服务的不稳定性。在版本 5.0 之后,对这些在启动时做了强校验,来保证在生产环境下的稳定性。
88 |
89 | 这些校验主要涉及有内存、线程数、文件句柄等,
90 |
91 | * JVM heap:
92 |
93 | 建议将最小堆与最大堆设置为一样,当设置bootstrap.memory_lock时,在程序启动就会对内存进行锁定。
94 |
95 | ```
96 | ES_JAVA_OPTS=-Xms512m -Xmx512m
97 | ```
98 |
99 | * 内存锁定,禁止内存与磁盘的置换
100 |
101 | ```
102 | bootstrap.memory_lock: true
103 | ```
104 |
105 | * 取消文件数限制
106 |
107 | ```
108 | ulimit -n unlimited
109 | ```
110 |
111 | * 取消线程数限制
112 |
113 | ```
114 | ulimit -l unlimited
115 | ```
116 |
117 | ## 容器化配置
118 |
119 | 关于容器化还是通过 docker 技术来实现,这里为了简单起见,以 Docker Cli 的方式进行部署。
120 |
121 | * es1.yml
122 |
123 | ```
124 | cluster.name: my-application
125 | node.name: node-1
126 | # 可以选择宿主机地址,网络模式选择 --net=host
127 | network.host: 172.17.0.2
128 | bootstrap.memory_lock: true
129 | discovery.zen.ping.unicast.hosts: ["172.17.0.3"]
130 | discovery.zen.minimum_master_nodes: 2
131 | ```
132 |
133 | * es2.yml
134 |
135 | ```
136 | cluster.name: my-application
137 | node.name: node-2
138 | network.host: 172.17.0.3
139 | bootstrap.memory_lock: true
140 | discovery.zen.ping.unicast.hosts: ["172.17.0.2"]
141 | discovery.zen.minimum_master_nodes: 2
142 | ```
143 |
144 | * docker cli
145 |
146 | ```
147 | # 启动 es1
148 | docker run -d \
149 | --name=es1 \
150 | -p 9200:9200 \
151 | -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
152 | --ulimit memlock=-1:-1 \
153 | --ulimit nofile=65536:65536 \
154 | -v ${YOUR_PATH}/es1.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
155 | -v ${YOUR_PATH}/data:/usr/share/elasticsearch/data \
156 | -v ${YOUR_PATH}/logs:/usr/share/elasticsearch/logs \
157 | elasticsearch:5.3
158 |
159 | # 启动 es2
160 | docker run -d \
161 | --name=es2 \
162 | -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
163 | --ulimit memlock=-1:-1 \
164 | --ulimit nofile=65536:65536 \
165 | -v ${YOUR_PATH}/es2.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
166 | -v ${YOUR_PATH}/data:/usr/share/elasticsearch/data \
167 | -v ${YOUR_PATH}/logs:/usr/share/elasticsearch/logs \
168 | elasticsearch:5.3
169 |
170 | ```
171 |
172 | 查看结果:
173 |
174 | * 容器情况
175 |
176 | 
177 |
178 | * 集群情况(GET: /_cat/health?v)
179 |
180 | 
181 |
182 | * 内存锁定情况(GET: /_nodes?filter_path=**.mlockall)
183 |
184 | 
185 |
186 | *坑:在容器化中,由于没锁定内存。每次在节点加入时,容器总是意外崩溃,查看容器相关日志也没有任何说明。后来在系统日志中发现内存溢出的问题。所以再次强调,一定要设置 bootstrap.memory_lock: true,并且查看是否生效。*
187 |
188 |
189 | ## 总结
190 | 本文从 ES 的配置文件开头,从 文件路径、网络情况、集群配置以及生产环境的校验等方面讲述了配置文件的主要内容,最后以 docker 容器化的方式进行了集群的部署。虽然文章写的比较简单,但涉及的面还是比较多的,包括节点间发现的网络单播传输、集群最小节点个数的算法、jvm的内存限制、内存与磁盘的置换限制等。有兴趣的同学,可以自行去研究下。
191 |
192 |
193 |
--------------------------------------------------------------------------------
/201704/rmi.md:
--------------------------------------------------------------------------------
1 | # JAVA中最简单的分布式调用 RMI
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 |
5 |
6 | **代码地址:[https://github.com/jasonGeng88/rmi-sample-rpc](https://github.com/jasonGeng88/rmi-sample-rpc)**
7 |
8 | ## 前言
9 | 我们先来看一个例子:
10 |
11 | 系统中目前存在两个 JAVA 服务,分别是服务A、服务B。现在服务A 想要调用服务B中的某个服务,我们怎么实现呢?
12 |
13 | 有人觉得这不很简单,服务B暴露一个服务接口,服务A通过 RPC 的方式来访问这个接口,这里的 RPC 可以引用第三方实现,也可以通过简单的 REST 请求的方式实现。
14 |
15 | 是的,解决这场景的方法有很多,其实 JAVA 自身也提供了一种更简单的方式,即通过 RMI 实现跨 JVM 虚拟机的远程调用。虽然它和现在主流的 RPC 相比,可能显得比较无力。但是其设计思想,加上它的简单易用,我们不妨来看一下。
16 |
17 | ## RMI 简介
18 |
19 | RMI(Remote Method Invocation)是一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。
20 |
21 | ### 特点
22 | * 是 JAVA 自带的功能,无需集成任何的外部扩展;
23 | * 数据传输是面向对象的;
24 | * 动态下载对象资源;
25 | * 仅限 JAVA 间通信;
26 |
27 | ### 通信协议
28 | 服务间的通信通过 TCP 传输。协议约定为 rmi://,仅限JAVA之间的远程通信;
29 |
30 | ### 成员
31 |
32 | - RMI Registry:作为存储远程服务的代理对象的仓库
33 | - Server:服务端,暴露远程对象,并将其代理对象注册进 RMI Registry
34 | - Client:客户端,查找远程代理对象,远程调用服务对象
35 |
36 | ### 运行机制
37 |
38 | 
39 |
40 | 从上图可以看出,虽然 RMI 目前看上去有点过时了,但其思想和现在的服务注册与发现还是很相似的。归纳起来,包含以下几点:
41 |
42 | 1. 启动注册中心
43 | 1. 服务端:暴露服务
44 | 2. 服务端:服务注册
45 | 3. 客户端:获取服务地址(代理对象)
46 | 4. 客户端:远程调用服务
47 |
48 | ### 使用方法
49 |
50 | * 启动 RMI Registry
51 |
52 | 这里启动仓库有两种方式,一种是在程序中启动:
53 |
54 | ```
55 | import java.rmi.registry.LocateRegistry;
56 | Registry registry = LocateRegistry.createRegistry(REGISTRY_PORT);
57 | ```
58 |
59 | 另一种通过命令启动:
60 |
61 | ```
62 | /usr/bin/rmiregistry REGISTRY_PORT
63 | ```
64 |
65 | * 获取 RMI Registry
66 | * 通过环境变量 java.rmi.server.hostname 来设置仓库地址
67 |
68 | ```
69 | import java.rmi.registry.LocateRegistry;
70 | Registry registry = LocateRegistry.getRegistry(REGISTRY_PORT)
71 | ```
72 |
73 | * 定义远程服务接口
74 |
75 | * 接口继承 Remote
76 | * 接口方法必须抛出 RemoteException
77 |
78 | ```
79 | import java.rmi.Remote;
80 | public interface RemoteService extends Remote {
81 |
82 | //define your function
83 | Object run() throws RemoteException;
84 |
85 | }
86 | ```
87 |
88 | * UnicastRemoteObject.exportObject(Remote obj, int port)
89 |
90 | * 创建 Remote 对象的代理类,并实现 Serializable 接口
91 | * 在 TCP 上暴露远程服务
92 | * port 为 0 表示使用匿名随机端口 (*使用1~1023的已知端口时,注意权限问题*)
93 |
94 | ```
95 | import java.rmi.server.UnicastRemoteObject;
96 | Remote remoteProxy = UnicastRemoteObject.exportObject(your_remote_service, 0);
97 | ```
98 |
99 | * 注册远程对象到 RMI Registry(*在 Registry 中的都是对象的远程代理类,并非真正的对象*)
100 |
101 | 获取 Registry 的远程代理类,然后调用它的 rebind 将代理对象注册进仓库中 (*Naming.rebind(String name, Remote obj) 本质上也是解析 name 中的仓库地址,获取仓库的代理对象,进而进行远程注册*)
102 |
103 | ```
104 | // 本地创建或远程获取 Registry
105 | Registry registry = ...
106 | registry.rebind(String name, Remote obj);
107 | ```
108 |
109 | * 查找远程调用对象
110 |
111 | ```
112 | Registry registry = LocateRegistry.getRegistry(REGISTRY_PORT);
113 | Remote obj = registry.lookup(REMOTE_NAME);
114 | ```
115 |
116 | ## 示例
117 |
118 | ###准备工作:
119 | 定义远程对象接口
120 |
121 | ```
122 | package com.test.remote;
123 |
124 | import java.rmi.Remote;
125 | import java.rmi.RemoteException;
126 |
127 | public interface RemoteService extends Remote {
128 |
129 | Object run() throws RemoteException;
130 |
131 | Object run(Object obj) throws RemoteException;
132 |
133 | }
134 | ```
135 |
136 | ###服务B:注册远程服务
137 |
138 | * 实现远程服务对象
139 |
140 | ```
141 | package com.test.serviceB.publishService;
142 |
143 | import com.test.remote.RemoteService;
144 | import java.rmi.RemoteException;
145 |
146 | public class pService1 implements RemoteService {
147 |
148 | public Object run() {
149 | System.out.println("invoke pService1.");
150 | return "success";
151 | }
152 |
153 | public Object run(Object obj) throws RemoteException {
154 | System.out.println("invoke pService1, params is " + obj.toString());
155 | return "success";
156 | }
157 |
158 | }
159 | ```
160 |
161 | * 启动服务
162 |
163 | * 创建 RMI Registry(也可在通过命令 rmiregistry 在应用外创建)
164 | * 实例化远程服务
165 | * 导出远程对象,使其能接受远程调用
166 | * 将导出的远程对象绑定到仓库中
167 | * 等待服务调用
168 |
169 | ```
170 | public class Boot {
171 |
172 | private static final String REMOTE_P1 = "serviceB:p1";
173 | private static final int REGISTRY_PORT = 9999;
174 |
175 | public static void main(String[] args) throws RemoteException {
176 |
177 | // 实例化远程对象,并创建远程代理类
178 | RemoteService p1 = new pService1();
179 | Remote stub1 = UnicastRemoteObject.exportObject(p1, 0);
180 |
181 | // 本地创建 Registry,并注册远程代理类
182 | Registry registry = LocateRegistry.createRegistry(REGISTRY_PORT);
183 | registry.rebind(REMOTE_P1, stub1);
184 |
185 | System.out.println("service b bound");
186 |
187 | }
188 | }
189 | ```
190 |
191 | ###服务A:调用远程服务
192 |
193 | * 启动服务
194 | * 连接仓库
195 | * 在 Registry 中查找所调用服务的远程代理类
196 | * 调用代理类方法
197 |
198 | ```
199 | public class Boot {
200 |
201 | private static final String REMOTE_P1 = "serviceB:p1";
202 | private static final int REGISTRY_PORT = 9999;
203 |
204 | public static void main(String[] args) throws RemoteException {
205 |
206 | try {
207 |
208 | Registry registry = LocateRegistry.getRegistry(REGISTRY_PORT);
209 | // 从仓库中获取远程代理类
210 | RemoteService p1 = (RemoteService) registry.lookup(REMOTE_P1);
211 | // 远程动态代理
212 | String res1 = (String)p1.run();
213 | System.out.printf("The remote call for %s %s \n", REMOTE_P1, res1);
214 |
215 | } catch (NotBoundException e){
216 | e.printStackTrace();
217 | } catch (RemoteException e){
218 | e.printStackTrace();
219 | }
220 |
221 | }
222 | }
223 | ```
224 |
225 | ### 演示结果
226 |
227 | * 启动服务B
228 |
229 | ```
230 | service b bound
231 | ```
232 |
233 | * 启动服务A
234 |
235 | ```
236 | The remote call for serviceB:p1 success
237 |
238 | Process finished with exit code 0
239 | ```
240 |
241 | * 查看服务B 调用情况
242 |
243 | ```
244 | service b bound
245 | invoke pService1.
246 | ```
247 |
248 |
249 | ## 高级用法
250 |
251 | 上面示例没有涉及到远程调用的传参问题。如果需要传参,且传参的类型不是基本类型时,远程服务就需要动态的去下载资源。
252 |
253 | 这里通过设置环境变量来实现远程下载:
254 |
255 | * java.rmi.server.codebase:远程资源下载路径(必须是绝对路径),可以是file://, ftp://, http:// 等形式的;
256 | * java.rmi.server.useCodebaseOnly:默认为 true, 表示仅依赖当前的 codebase, 如果使用外部的 codebase(*服务B 需要使用 服务A 提供的下载地址时*),需将此参数设置为false;
257 |
258 | 对于跨主机的访问,RMI 加入了安全管理器(SecurityManager),那么也需要对应的安全策略文件
259 |
260 | * java.security.policy:指定策略文件地址;
261 |
262 | 其他设置:
263 |
264 | * java.rmi.server.hostname:设置仓库的主机地址;
265 | * sun.rmi.transport.tcp.handshakeTimeout:设置连接仓库的超时时间;
266 |
267 | ## 核心代码
268 |
269 | 关于源码的阅读,网上曾经看到一句话讲的很好,“源码阅读的什么样程度算好,阅读到自己能放过自己了,那就够了。”
270 |
271 | 我一般喜欢带着问题来阅读,这里我从几个问题入手,简单分享下我的理解。
272 |
273 | * 获取到的 Registry 对象到底是什么东西?
274 |
275 | 从这段代码来分析:
276 |
277 | ```
278 | Registry registry = LocateRegistry.getRegistry(REGISTRY_PORT);
279 | ```
280 |
281 | 
282 |
283 | * 远程对象到底是什么,原始对象又在哪里呢?
284 |
285 | 从这段代码来分析:
286 |
287 | ```
288 | Remote obj = UnicastRemoteObject.exportObject(Remote obj, int port);
289 | ```
290 | 
291 |
292 | 
293 |
294 |
295 | * 知道了仓库中存放、获取的都是 远程对象的代理类,那么实际的远程通信是如何完成的?
296 |
297 | 知道 JDK 动态代理的同学,肯定有一个 invoke 方法,是方法调用的关键。这里的 invoke() 具体代码在 UnicastRef 中。
298 |
299 | 
300 |
301 |
302 | ## 问题
303 |
304 | 从源码中可以看出,远程调用每次都会 新建一个 connection,感觉这里会成为一个性能的瓶颈。
305 |
306 | ## 总结
307 |
308 | 虽然 RMI 在目前看来有些过时了,但它的思想:远程仓库、服务注册、服务查找、代理调用等,和目前主流的 RPC 是不是很相似呢?一种技术的过时,往往是跟不上业务的快速发展,但它的产生至少是满足了当时的需求。
309 |
310 | 个人觉得,技术的实现会随着业务的发展不断的变化,但是核心思想一定是小步的进行,毕竟这些都是不断积累的经验总结出来的。希望本篇对大家能有所收获!
311 |
312 |
313 |
--------------------------------------------------------------------------------
/201704/service_discovery.md:
--------------------------------------------------------------------------------
1 | # 基于Docker、NodeJs实现高可用的服务发现
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 | >
5 | > 本文所有服务均采用docker容器化方式部署
6 |
7 |
8 | ## 当前环境
9 | 1. Mac OS 10.11.x
10 | 2. docker 1.12.1
11 | 3. docker-compose 1.8.0
12 | 4. node 6.10.2
13 |
14 | ## 前言
15 | 
16 |
17 | 基于上一篇的 [“基于Docker、Registrator、Zookeeper实现的服务自动注册”](https://github.com/jasonGeng88/blog/blob/master/201703/service_registry.md),完成了 “服务注册与发现” 的上半部分(即上图中的1)。本文就来讲讲图中的2、3、4、5 分别是如何实现的。
18 |
19 | ## 功能点
20 | - 服务订阅
21 | - 动态获取服务列表
22 | - 获取服务节点信息(IP、Port)
23 | - 本地缓存
24 | - 缓存服务路由表
25 | - 服务调用
26 | - 服务请求的负载均衡策略
27 | - 反向代理
28 | - 变更通知
29 | - 监听服务节点变化
30 | - 更新服务路由表
31 |
32 | ## 技术方案
33 | ### 服务发现方式
34 | 关于服务发现的方式,主要分为两种方式:客户端发现与服务端发现。它们的主要区别为:前者是由调用者本身去调用服务,后者是将调用者请求统一指向类似服务网关的服务,由服务网关代为调用。
35 |
36 | 这里采用服务端发现机制,即服务网关(*注意:服务网关的作用不仅仅是服务发现*)。
37 |
38 | 
39 |
40 | 与客户端发现相比,可见的优势有:
41 |
42 | 1. 服务调用的统一管理;
43 | 2. 减少客户端与注册中心不必要的连接数;
44 | 3. 将后端服务与调用者相隔离,降低服务对外暴露的风险;
45 |
46 | ### 所选技术
47 | 本文采用 NodeJs 作为服务网关的实现技术。当然,这不是唯一的技术手段,像nginx+lua,php等都能实现类似的功能。我这里采用 NodeJs 主要出于以下几个原因:
48 |
49 | 1. NodeJs 采用的是事件驱动、非阻塞 I/O 模型,具有天生的异步性。在处理服务网关这种以IO密集型为主的业务时,正是 NodeJs 所擅长的。
50 | 2. NodeJs 基于Chrome V8 引擎的 JavaScript 语言的运行环境,对于有一定 JavaScript 基础的同学,上手相对简单。
51 |
52 |
53 | *所有技术都有其优劣所在,NodeJs 在这里的使用也存在一定的问题(本文最后会讲述它的高可用策略):*
54 |
55 | 1. NodeJs 是基于单进程单线程的方式,这种方式存在一定的不可靠性。一旦进程崩溃,对应的服务将变得不可用;
56 | 2. 单进程单线程方式,也导致了只能利用单核CPU。为了充分利用计算机资源,还需进行服务的水平扩展;
57 |
58 | ## 代码示例
59 |
60 | 代码地址: [https://github.com/jasonGeng88/service_registry_discovery](https://github.com/jasonGeng88/service_registry_discovery)
61 |
62 | ### 代码目录
63 | 
64 |
65 | *本文主要介绍服务发现相关实现,其他部分已在上篇中介绍过,感兴趣的同学可查看上篇。*
66 |
67 | ### 目录结构(discovery项目)
68 | 
69 |
70 | ### 依赖配置(package.json)
71 | ```
72 | {
73 | "name": "service-discovery",
74 | "version": "0.0.0",
75 | "private": true,
76 | "scripts": {
77 | "start": "node ./bin/www"
78 | },
79 | "dependencies": {
80 | "debug": "~2.6.3",
81 | "express": "~4.15.2",
82 | "http-proxy": "^1.16.2",
83 | "loadbalance": "^0.2.7",
84 | "node-zookeeper-client": "^0.2.2"
85 | }
86 | }
87 | ```
88 | * debug:用于开发调试;
89 | * express:作为 NodeJs 的Web应用框架,这里主要用到了它的响应HTTP请求以及路由规则功能;
90 | * http-proxy:用作反向代理;
91 | * loadbalance:负载均衡策略,目前提供随机、轮询、权重;
92 | * node-zookeeper-client:ZK 客户端,用作获取注册中心服务信息与节点监听;
93 |
94 | ### 常量设置(constants.js)
95 |
96 | ```
97 | "use strict";
98 |
99 | function define(name, value) {
100 | Object.defineProperty(exports, name, {
101 | value: value,
102 | enumerable: true
103 | });
104 | }
105 |
106 | define('ZK_HOSTS', '${PRIVATE_IP}:2181,${PRIVATE_IP}:2182,${PRIVATE_IP}:2183');
107 | define('SERVICE_ROOT_PATH', '/services');
108 | define('ROUTE_KEY', 'services');
109 | define('SERVICE_NAME', 'service_name');
110 | define('API_NAME', 'api_name');
111 | ```
112 |
113 | ### 功能点具体实现
114 |
115 | 下面会对上面提供的功能点依次进行实现(*展示代码中只保留核心代码,详细请见代码*)
116 |
117 | * **服务订阅 - 动态获取服务列表**
118 |
119 | 文件路径|操作|方法|备注
120 | ---|---|---|---
121 | src/middlewares/discovery.js|ADD|connect|连接ZK
122 | 同上|ADD|getServices|获取服务列表
123 |
124 | ```
125 | var zookeeper = require('node-zookeeper-client');
126 | var constants = require('../constants');
127 | var debug = require('debug')('dev:discovery');
128 |
129 | var zkClient = zookeeper.createClient(constants.ZK_HOSTS);
130 |
131 | /**
132 | * 连接ZK
133 | */
134 | function connect() {
135 | zkClient.connect();
136 |
137 | zkClient.once('connected', function() {
138 | console.log('Connected to ZooKeeper.');
139 | getServices(constants.SERVICE_ROOT_PATH);
140 | });
141 | }
142 |
143 | /**
144 | * 获取服务列表
145 | */
146 | function getServices(path) {
147 | zkClient.getChildren(
148 | path,
149 | null,
150 | function(error, children, stat) {
151 | if (error) {
152 | console.log(
153 | 'Failed to list children of %s due to: %s.',
154 | path,
155 | error
156 | );
157 | return;
158 | }
159 |
160 | // 遍历服务列表,获取服务节点信息
161 | children.forEach(function(item) {
162 | getService(path + '/' + item);
163 | })
164 |
165 | }
166 | );
167 | }
168 | ```
169 |
170 | ---
171 |
172 | * **服务订阅 - 获取服务节点信息(IP、Port)**
173 |
174 | 文件路径|操作|方法|备注
175 | ---|---|---|---
176 | src/middlewares/discovery.js|ADD|getService|获取服务节点信息
177 |
178 | ```
179 | /**
180 | * 获取服务节点信息(IP,Port)
181 | */
182 | function getService(path) {
183 | zkClient.getChildren(
184 | path,
185 | null,
186 | function(error, children, stat) {
187 | if (error) {
188 | console.log(
189 | 'Failed to list children of %s due to: %s.',
190 | path,
191 | error
192 | );
193 | return;
194 | }
195 | // 打印节点信息
196 | debug('path: ' + path + ', children is ' + children);
197 | }
198 | );
199 | }
200 | ```
201 |
202 | ---
203 |
204 | * **本地缓存 - 缓存服务路由表**
205 |
206 | 文件路径|操作|方法|备注
207 | ---|---|---|---
208 | src/middlewares/discovery.js|MODIFY|getService|获取服务节点信息
209 |
210 | ```
211 | // 初始化缓存
212 | var cache = require('./local-storage');
213 | cache.setItem(constants.ROUTE_KEY, {});
214 |
215 | /**
216 | * 获取服务节点信息(IP,Port)
217 | */
218 | function getService(path) {
219 | ...
220 | // 打印节点信息
221 | debug('path: ' + path + ', children is ' + children);
222 |
223 | if (children.length > 0) {
224 | //设置本地路由缓存
225 | cache.getItem(constants.ROUTE_KEY)[path] = children;
226 | }
227 | ...
228 | }
229 | ```
230 | ---
231 |
232 | * **服务调用 - 负载均衡策略**
233 |
234 | 文件路径|操作|方法|备注
235 | ---|---|---|---
236 | src/middlewares/discovery.js|MODIFY|getService|获取服务节点信息
237 |
238 | ```
239 | /**
240 | * 获取服务节点信息(IP,Port)
241 | */
242 | function getService(path) {
243 | ...
244 | if (children.length > 0) {
245 | //设置负载策略(轮询)
246 | cache.getItem(constants.ROUTE_KEY)[path] = loadbalance.roundRobin(children);
247 | }
248 | ...
249 | }
250 | ```
251 |
252 | *请求的负载均衡,本质是对路由表中请求地址进行记录与分发。记录:上一次请求的地址;分发:按照策略选择接下来请求的地址。这里为了简便起见,将负载与缓存并在一起。*
253 |
254 | ---
255 |
256 | * **服务调用 - 反向代理**
257 |
258 | 文件路径|操作|方法|备注
259 | ---|---|---|---
260 | src/routes/reverse-proxy.js|ADD|reverseProxy|反向代理
261 |
262 | ```
263 | var proxy = require('http-proxy').createProxyServer({});
264 | var cache = require('../middlewares/local-storage');
265 | var constants = require("../constants");
266 | var debug = require('debug')('dev:reserveProxy');
267 |
268 | /**
269 | * 根据headers的 service_name 与 api_name 进行代理请求
270 | */
271 | function reverseProxy(req, res, next) {
272 | var serviceName = req.headers[constants.SERVICE_NAME];
273 | var apiName = req.headers[constants.API_NAME];
274 | var serviceNode = constants.SERVICE_ROOT_PATH + '/' + serviceName;
275 |
276 | debug(cache.getItem(constants.ROUTE_KEY)[serviceNode]);
277 |
278 | var host = cache.getItem(constants.ROUTE_KEY)[serviceNode].pick();
279 | var url = 'http://' + host + apiName;
280 | debug('The proxy url is ' + url);
281 | proxy.web(req, res, {
282 | target: url
283 | });
284 | }
285 | ```
286 |
287 | ---
288 |
289 | * **变更通知 - 监听服务节点 && 更新路由缓存**
290 |
291 | 文件路径|操作|方法|备注
292 | ---|---|---|---
293 | src/middlewares/discovery.js|MODIFY|getServices|获取服务列表
294 | 同上|MODIFY|getService|获取服务节点信息
295 |
296 | ```
297 | /**
298 | * 获取服务列表
299 | */
300 | function getServices(path) {
301 | zkClient.getChildren(
302 | path,
303 | // 监听列表变化
304 | function(event) {
305 | console.log('Got Services watcher event: %s', event);
306 | getServices(constants.SERVICE_ROOT_PATH);
307 | },
308 | function(error, children, stat) {
309 | ...
310 | }
311 | );
312 | }
313 |
314 | /**
315 | * 获取服务节点信息(IP,Port)
316 | */
317 | function getService(path) {
318 | zkClient.getChildren(
319 | path,
320 | // 监听服务节点变化
321 | function(event) {
322 | console.log('Got Serivce watcher event: %s', event);
323 | getService(path);
324 | },
325 | function(error, children, stat) {
326 | ...
327 | }
328 | );
329 | }
330 | ```
331 |
332 | ---
333 | ### 主文件(src/app.js)
334 |
335 | ```
336 | var express = require('express');
337 | var reverseProxy = require('./routes/reverse-proxy');
338 | var discovery = require('./middlewares/discovery');
339 | var app = express();
340 |
341 | // service discovery start
342 | discovery();
343 |
344 | // define the home page route
345 | app.get('/', function(req, res) {
346 | res.send('This is a Service Gateway Demo')
347 | });
348 |
349 | // define proxy route
350 | app.use('/services', reverseProxy);
351 | ```
352 |
353 | ### 启动脚本(src/bin/www)
354 | 脚本中含有单进程与多进程两种启动方式,由于 NodeJs 单进程的不可靠性,一般生产环境中采用多进程方式启动,保证它的稳定性。
355 |
356 | ```
357 | #!/usr/bin/env node
358 |
359 | var app = require('../app');
360 | var http = require('http');
361 |
362 | var port = 8080;
363 |
364 | //单进程运行
365 | //http.createServer(app).listen(port);
366 |
367 | //多进程运行
368 | var cluster = require('cluster');
369 | var numCPUs = require('os').cpus().length;
370 |
371 | if (cluster.isMaster) {
372 | console.log("master start...");
373 |
374 | // Fork workers.
375 | for (var i = 0; i < numCPUs; i++) {
376 | cluster.fork();
377 | }
378 |
379 | cluster.on('listening',function(worker,address){
380 | console.log('listening: worker ' + worker.process.pid +', Address: '+address.address+":"+address.port);
381 | });
382 |
383 | cluster.on('exit', function(worker, code, signal) {
384 | console.log('worker ' + worker.process.pid + ' died');
385 | cluster.fork();
386 | });
387 | } else if (cluster.isWorker) {
388 | http.createServer(app).listen(port);
389 | }
390 | ```
391 |
392 | ### 镜像构建
393 | *为演示方便,采用单进程方式*
394 |
395 | ```
396 | # Dockerfile
397 | FROM node:6.10.2
398 | MAINTAINER jasongeng88@gmail.com
399 | ENV TZ="Asia/Shanghai" HOME="/usr/src/app"
400 | WORKDIR ${HOME}
401 | COPY src/ ${HOME}/
402 | RUN npm install
403 | EXPOSE 8080
404 | ENTRYPOINT ["npm", "run", "start"]
405 | ```
406 |
407 | ```
408 | # 构建命令
409 | docker build -t node_discovery .
410 | ```
411 |
412 |
413 | ## 场景演示
414 |
415 | ### 改动
416 |
417 | *坑:由于 docker --net=host 在 Mac 上存在问题,所以对 Registrator 做出调整。原先向注册中心注册的是 127.0.0.1 改为 内网IP,保证容器内可以访问。*
418 |
419 | *Linux 环境下不需要此改动,服务网关网络模式应为host。*
420 |
421 |
422 | ### 准备工作
423 | 为了方便演示,对原先的服务模块进行调整,提供如下服务:
424 |
425 | 模块名 | API地址 | 请求方式| 请求参数示例 | 响应结果
426 | ---|---|---|---|---
427 | service_1|/|GET||This is Service 1.
428 | service_1|/user|GET|id=1|It's user 1.
429 | service_2|/|GET||This is Service 2.
430 |
431 | 服务模块启动如下(service_1:2个,service_2:1个)
432 |
433 | 
434 |
435 | ### 启动服务网关
436 |
437 | ```
438 | cd discovery && docker-compose up -d
439 | ```
440 |
441 | 输出效果(docker logs -f discovery_discovery_1 看出日志输出):
442 |
443 | 
444 |
445 | * **场景1:GET方式,请求服务2,API路径为 / ,无请求参数**
446 |
447 | 
448 |
449 | ---
450 |
451 | * **场景2:GET方式,请求服务1,API路径为 /user ,请求参数为id=1**
452 |
453 | 
454 |
455 | ---
456 |
457 | * **场景3:GET方式,多次请求服务1,API路径为 / ,查看负载均衡情况**
458 |
459 | 
460 | ---
461 |
462 | * **场景4:启停服务2实例,观察路由表变化**
463 |
464 | ```
465 | // 停用 serivce_2
466 | cd services && docker-compose stop service_2
467 | ```
468 |
469 | 查看网关监听变化:
470 |
471 | 
472 |
473 | ---
474 |
475 |
476 |
477 | ## 高可用
478 |
479 | 1. NodeJs 自身通过 cluster 模块,进行多进程启动,防止单进程崩溃的不稳定性;
480 | 2. 通过 Docker 容器化启动,在启动时设置restart策略,一旦服务崩溃将立即重启;
481 | 2. 上述的使用场景都在单机上运行,在分布式情况下,可以将 NodeJs 容器多主机部署,采用 nginx + NodeJs 的架构进行水平扩展;
482 |
483 |
484 | ## 总结
485 |
486 | 本文以上篇 “服务自动化注册” 遗留的功能点开头,讲述了服务发现的2种实现方式,以及其优劣。并以 NodeJs 作为服务网关的实现手段,详细介绍了其中各功能点的实现细节。最后通过场景代入的方式,展示了其效果。
487 |
488 | 对于网关的高可用,也通过了2种方式进行了保证。自身高可用通过多进程、失败重启策略进行保证;分布式下则以 nginx + NodeJs 的架构进行了保证。
489 |
490 | 文中也提到,服务发现实则只是服务网关的一个部分,服务网关还包括服务鉴权、访问控制等。这里的代码仅是个Demo示例,目的是让大家更好的看清它的本质,希望对大家有所帮助~
491 |
492 |
493 |
494 |
495 |
--------------------------------------------------------------------------------
/201705/MQ.md:
--------------------------------------------------------------------------------
1 | # 一个故事告诉你什么是消息队列
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 案例
5 | 有一天,产品跑来说:“我们要做一个用户注册功能,需要在用户注册成功后给用户发一封成功邮件。”
6 |
7 | 小明(攻城狮):“好,需求很明确了。” 不就提供一个注册接口,保存用户信息,同时发起邮件调用,待邮件发送成功后,返回用户操作成功。没一会功夫,代码就写完了。验证功能没问题后,就发布上线了。
8 |
9 | 线上正常运行了一段时间,产品匆匆地跑来说:“你做的功能不行啊,运营反馈注册操作响应太慢,已经有好多用户流失了。”
10 |
11 | 小明听得一身冷汗,赶紧回去改。他发现,原先的以单线程同步阻塞的方式进行邮件发送,确实存在问题。这次,他利用了 JAVA 多线程的特性,另起线程进行邮件发送,主线程直接返回保存结果。测试通过后,赶紧发布上线。小明心想,这下总没问题了吧。
12 |
13 | 没过多久,产品又跑来了,他说:“现在,注册操作响应是快多了。但是又有新的问题了,有用户反应,邮件收不到。能否在发送邮件时,保存一下发送的结果,对于发送失败的,进行补发。”
14 |
15 | 小明一听,哎,又得熬夜加班了。产品看他一脸苦逼的样子,忙说:“邮件服务这块,别的团队都已经做好了,你不用再自己搞了,直接用他们的服务。”
16 |
17 | 小明赶紧去和邮件团队沟通,谁知他们的服务根本就不对外开放。这下小明可开始犯愁了,明知道有这么一个服务,可是偏偏又调用不了。
18 |
19 | 邮件团队的人说,“看你愁的,我给你提供了一个类似邮局信箱的东西,你往这信箱里写上你要发送的消息,以及我们约定的地址。之后你就不用再操心了,我们自然能从约定的地址中取得消息,进行邮件的相应操作。”
20 |
21 | 后来,小明才知道,这就是外界广为流传的消息队列。你不用知道具体的服务在哪,如何调用。你要做的只是将该发送的消息,向你们约定好的地址进行发送,你的任务就完成了。对应的服务自然能监听到你发送的消息,进行后续的操作。这就是消息队列最大的特点,将同步操作转为**异步处理**,将多服务共同操作转为职责单一的单服务操作,做到了**服务间的解耦**。
22 |
23 | 哈哈,这下能高枕无忧了。太年轻,哪有万无一失的技术啊~
24 |
25 | 不久的一天,你会发现所有业务都替换了邮件发送的方式,统一使用了消息队列来进行发送。这下仅仅一个邮件服务模块,难以承受业务方源源不断的消息,大量的消息堆积在了队列中。这就需要更多的消费者(邮件服务)来共同处理队列中的消息,即所谓的**分布式消息处理**。
26 |
27 | 未完待续。。。
28 |
29 | ## 总结
30 |
31 | ### 定义
32 | 有了上面的基础,再看非常官方的解释应该也能理解了。
33 |
34 | > 消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。 ——维基百科
35 |
36 | ### 名词解释
37 | 解释还是太官方了,我们来看一个最简单的架构模型:
38 |
39 | 
40 |
41 | * Producer:消息生产者,负责产生和发送消息到 Broker;
42 | * Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue;
43 | * Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;
44 |
45 | ### 特性
46 | #### 异步性
47 | 将耗时的同步操作,通过以发送消息的方式,进行了异步化处理。减少了同步等待的时间。
48 |
49 | #### 松耦合
50 | 消息队列减少了服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节,只要定义好消息的格式就行。
51 |
52 | #### 分布式
53 | 通过对消费者的横向扩展,降低了消息队列阻塞的风险,以及单个消费者产生单点故障的可能性(*当然消息队列本身也可以做成分布式集群*)。
54 |
55 | #### 可靠性
56 | 消息队列一般会把接收到的消息存储到本地硬盘上(*当消息被处理完之后,存储信息根据不同的消息队列实现,有可能将其删除*),这样即使应用挂掉或者消息队列本身挂掉,消息也能够重新加载。
57 |
--------------------------------------------------------------------------------
/201705/assets/cadvisor_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_01.png
--------------------------------------------------------------------------------
/201705/assets/cadvisor_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_02.png
--------------------------------------------------------------------------------
/201705/assets/cadvisor_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_03.png
--------------------------------------------------------------------------------
/201705/assets/cadvisor_architect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_architect.png
--------------------------------------------------------------------------------
/201705/assets/cadvisor_es_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_es_01.png
--------------------------------------------------------------------------------
/201705/assets/cadvisor_es_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/cadvisor_es_02.png
--------------------------------------------------------------------------------
/201705/assets/https_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_01.png
--------------------------------------------------------------------------------
/201705/assets/https_ca_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_ca_bar.png
--------------------------------------------------------------------------------
/201705/assets/https_ca_dv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_ca_dv.png
--------------------------------------------------------------------------------
/201705/assets/https_ca_ev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_ca_ev.png
--------------------------------------------------------------------------------
/201705/assets/https_ca_ov.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_ca_ov.png
--------------------------------------------------------------------------------
/201705/assets/https_theory_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_theory_01.png
--------------------------------------------------------------------------------
/201705/assets/https_theory_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_theory_02.png
--------------------------------------------------------------------------------
/201705/assets/https_theory_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_theory_03.png
--------------------------------------------------------------------------------
/201705/assets/https_theory_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/https_theory_04.png
--------------------------------------------------------------------------------
/201705/assets/mq_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201705/assets/mq_01.png
--------------------------------------------------------------------------------
/201705/cadvisor.md:
--------------------------------------------------------------------------------
1 | # 容器监控方案 cAdvisor + Elasticsearch
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 |
5 | ## 当前环境
6 | 1. docker 1.12.1
7 | 2. centos 7
8 |
9 | ## 前言
10 | 什么是微服务?微服务带来的好处?想必大家都了解了很多。但在真正实践中,还是有各种各样的挑战。今天就想和大家分享一下,在服务容器化场景中的服务监控问题。
11 |
12 | 传统的服务监控,一般都是针对宿主机的,有对 CPU、内存、进程数、IO 等监控。这些指标做得非常全面,也能很好的反应宿主机的健康状况。可到了服务容器化的场景中,似乎变得有些力不从心。一台宿主机上会起 n 个容器,每个容器都会独立分配资源,如 CPU、内存等。如果还是仅仅针对宿主机做监控,效果可能不太理想。
13 |
14 | 我们的容器化部署采用的是 docker 技术,所以下面会针对 docker 来进行一系列的服务监控,以及将监控的数据集成进我们已有的日志中心(ELK)。
15 |
16 | *关于日志中心的搭建,有兴趣的可看我之前写过的一篇[“基于ELK+Filebeat搭建日志中心”](https://github.com/jasonGeng88/blog/blob/master/201703/elk.md)。*
17 |
18 | ## docker 容器监控
19 | ### cAdvisor 介绍
20 | 关于 docker 的监控,我们这里采用的是由 Google 开源的的监控工具 [cAdvisor](https://github.com/google/cadvisor)。采用它的主要原因有:
21 |
22 | * 对 docker 容器提供了原生的支持;
23 | * 开箱即用的特性,降低了部署的成本;
24 | * 除了自身提供简单的可视化界面外,还提供了外部存储的扩展,如 ES(Elasticsearch)、kafka、InfluxDB等。
25 |
26 | > Google的cAdvisor(Container Advisor)“为容器用户提供了了解运行时容器资源使用和性能特征的方法”。cAdvisor的容器抽象基于Google的lmctfy容器栈,因此原生支持Docker容器并能够“开箱即用”地支持其他的容器类型。cAdvisor部署为一个运行中的daemon,它会收集、聚集、处理并导出运行中容器的信息。这些信息能够包含容器级别的资源隔离参数、资源的历史使用状况、反映资源使用和网络统计数据完整历史状况的柱状图。
27 |
28 | ### cAdvisor 部署
29 | 由于 cAdvisor 本身也进行了容器化,所以部署极其简单。只需在运行有 docker-daemon 的宿主机上,运行如下命令:
30 |
31 | ```
32 | docker run \
33 | --volume=/:/rootfs:ro \
34 | --volume=/var/run:/var/run:rw \
35 | --volume=/sys:/sys:ro \
36 | --volume=/var/lib/docker/:/var/lib/docker:ro \
37 | --publish=8080:8080 \
38 | --detach=true \
39 | --name=cadvisor \
40 | google/cadvisor:latest
41 | ```
42 |
43 | ### cAdvisor 演示
44 | 
45 | 
46 | 
47 |
48 | 它提供了简单的可视化界面,为我们提供了容器的整体情况,以及每个容器的独立数据,数据包括有 CPU、内存、网络IO、磁盘IO等使用情况。
49 |
50 | ### cAdvisor 参数说明
51 | 上面的部署只是为了演示使用的,所以参数都使用的是缺省值。为了更好的运用 cAdvisor,有些参数我们还是需要了解的(*[详细信息参考官方文档](https://github.com/google/cadvisor/blob/master/docs/runtime_options.md)*):
52 |
53 | |参数|默认值|说明|
54 | |---|---|---|
55 | |-allow\_dynamic\_housekeeping|true|设置数据采集的频率间隔是动态的,这取决于容器的活跃程度。若设为false,采集的时间间隔将和预期的保持一致,但这会增加资源的使用率。|
56 | |-global\_housekeeping\_interval|1m0s|设置cAdvisor全局的采集行为的时间间隔,主要通过内核事件来发现新容器的产生。|
57 | |-housekeeping\_interval|1s|每个已发现的容器的数据采集频率。|
58 | |-machine\_id\_file|/etc/machine-id,/var/lib/dbus/machine-id|标识宿主机ID的文件地址,可以设置多个文件,当第一个不存在时,会依次向后查找,文件地址用逗号分隔|
59 | |-storage\_duration|2m0s|在内存中保存最近多久的历史数据|
60 | |-storage\_driver|Empty(none)|设置采集的缓存数据将推送至的存储驱动,可选项有: , bigquery, ES, influxdb, kafka, redis, statsd, stdout。|
61 |
62 | ### cAdvisor 不足
63 | 从上述的参数列表中可以看出,cAdvisor 做到的是实时监控,数据存储在内存中,并且只会保存很短的一段时间。所以它是做不到监控数据的存储,换句话说,它是记录下近几天的监控数据。为此它提供了有关的存储驱动,帮助我们将实时数据传输到我们指定的数据存储器中。
64 |
65 | 其次,cAdvisor监听的是宿主机 docker 主进程的变化,这就意味着单单依靠它是做不到分布式的监控。所以,这更添加了我们将它与 ELK 做集成的理由。
66 |
67 | ## 与日志中心(ELK)集成
68 |
69 | ### 背景
70 | 为了降低维护成本,我们这里选择 ES 作为存储驱动,将 cAdvisor 与我们已有的 ELK 日志中心进行集成。这也降低了使用者的复杂度,通过同一个平台,解决了日志收集与服务监控的问题。
71 |
72 | ### 架构
73 | 
74 |
75 | 从上图不难看出,cAdvisor 也是采用了协同进程的概念,通过监听 docker 主进程的事件,将采集到的数据远程传输的方式,push 到 ES 中。并且通过 Machine ID 来区分不同宿主机的容器信息,实现了数据的分布式监控。
76 |
77 | ### cAdvisor 支持 ES 5
78 | 其实我们用它与 ES 集成的时候,还是略坑的。如果你用的 ES 是 5.0+,那就杯具了。因为 cAdvisor 官方目前仅对 ES 2 进行了支持。具体的情况可以查看官方 Github 下的讨论:[https://github.com/google/cadvisor/pull/1597](https://github.com/google/cadvisor/pull/1597)。
79 |
80 | 这当然也体现了开源的力量。从上述的讨论中可以看出,对 ES 5 的支持已经有人提供了,也经过了测试验证,只是因为官方为了考虑兼容 ES 2,所以暂时没有采纳合并请求。但这给了我们解决方案,虽然官方不提供,我们可以根据社区提供的 ES 5 的代码,进行手动的 build 生成 cadvisor 执行文件。在以官方镜像启动容器时,将我们生成的 cadvisor 执行文件以文件挂载方式替换原有的执行文件,这就做到了对 ES 5的支持(*相信官方会很快对 ES 5 进行支持,我们也不用采用这种迂回的方式了*)。
81 |
82 | > 为了避免自己在手动编译过程会出现一些问题,我这里也提供了 build 好的 cadvisor 执行文件,下载地址:[https://github.com/jasonGeng88/cadvisor/releases/download/es5/cadvisor](https://github.com/jasonGeng88/cliadvisor/releases/download/es5/cadvisor)
83 |
84 | ### 部署
85 | ```
86 | docker run \
87 | --volume=/:/rootfs:ro \
88 | --volume=/var/run:/var/run:rw \
89 | --volume=/sys:/sys:ro \
90 | --volume=/var/lib/docker/:/var/lib/docker:ro \
91 | --volume=$YOUR_PATH/cadvisor:/usr/bin/cadvisor \
92 | --publish=8080:8080 \
93 | --detach=true \
94 | --name=cadvisor \
95 | google/cadvisor:latest \
96 | -storage_driver=elasticsearch.v5 \
97 | -storage_driver_es_index="cadvisor" \
98 | -storage_driver_es_host="http://$ELASTICSEARCH_IP:9200"
99 | ```
100 |
101 | ### 展示(*Demo数据*)
102 | 
103 | 
104 |
105 | ## 总结
106 | 本文介绍了微服务下的容器监控方案,从“开箱即用”的 cAdvisor 讲起,虽然它也能输出我们想要的监控数据。但是作为一个完整的应用,我们还要考虑它的可行性、易用性和可扩展性。同时要从实际出发,结合现有的框架体系,将 cAdvisor 融入进已有的 ELK 日志中心,并且对数据可以进行自定义的图表展示,使用上更灵活。
107 |
108 |
109 |
--------------------------------------------------------------------------------
/201705/functional_programming.md:
--------------------------------------------------------------------------------
1 | # [译] 2017年你应该了解的函数式编程
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | **原文:[https://hackernoon.com/you-should-learn-functional-programming-in-2017-91177148ec00](https://hackernoon.com/you-should-learn-functional-programming-in-2017-91177148ec00)**
5 |
6 | 函数式编程已经存在了很长了时间,早在50年代 [Lisp](https://zh.wikipedia.org/wiki/LISP) 编程语言的介绍中就有提过。如果你有关注近两年里内热门的 Clojure,Scala,Erlang,Haskell,Elixir 等语言的话,其中都有函数式编程的概念。
7 |
8 | 那么到底什么是函数式编程,为什么每个人都痴迷于它?在这篇文章中,作者将试图回答以上问题,并且激起你对函数式编程的兴趣。
9 |
10 | ## 函数式编程的简要历史
11 | 正如我们所说的,早在50年代函数式编程开始之前,Lisp 语言就已经在 IBM 700/7000 系列的科学计算机上运行了。Lisp 引入了很多与我们现在的函数式编程有关的示例与功能,我们甚至可以称 Lisp 是所有函数式编程语言的鼻祖。
12 |
13 | 这也是函数式编程中最有趣的方面,所有函数式编程语言都是基于相同的 [λ演算](https://zh.wikipedia.org/wiki/%CE%9B%E6%BC%94%E7%AE%97),这种简单数学基础。
14 |
15 | *λ演算是图灵完备的,它是一种通用的计算模型,可用于模拟任何一台单带图灵机。它名字中的希腊字母 lambda(λ),被使用在了 lambda 表达式和 lambda 项绑定函数中的变量中。*
16 |
17 | λ演算是一个极其简单但又十分强大的概念。它的核心主要有两个概念:
18 |
19 | * 函数的抽象,通过引入变量来归纳得出表达式;
20 | * 函数的应用,通过给变量赋值来对已得出的表达式进行计算;
21 |
22 | 让我们来看个小例子,单参数函数 f,将参数递增1。
23 |
24 | ```
25 | f = λ x. x+1
26 | ```
27 |
28 | 假设我们应用函数在数字5上,那么函数读取如下:
29 |
30 | ```
31 | f(5) => 5 + 1
32 | ```
33 |
34 | ## 函数式编程的基本原理
35 | 现在,数学知识已经够了。让我们看一下使函数式编程变得强大的特性有哪些?
36 |
37 | ### 头等函数(first-class function)
38 | 在函数式编程中,函数是一等公民,意思是说函数可以赋值给变量,例如在 [elixir](http://elixir-lang.org/getting-started/introduction.html) 中,
39 |
40 | double = fn(x) -> x * 2 end
41 |
42 | 然后我们可以如下来调用函数:
43 |
44 | double.(2)
45 |
46 | ### 高阶函数(higher-order function)
47 | 高阶函数的定义是,接收一个或多个函数变量作为参数,然后生成的新函数,即为高阶函数。让我们再次使用函数 double 来说明这个概念:
48 |
49 | double = fn(x) -> x * 2 end
50 | Enum.map(1..10, double)
51 |
52 | 这例子中,Enum.map 将一个枚举列表作为第一参数,之前定义的函数作为第二参数;然后将这个函数应用到枚举中的每一个元素,结果为:
53 |
54 | [2,4,6,8,10,12,14,16,18,20]
55 |
56 | ### 不可变状态(Immutable State)
57 | 在函数式编程语言中,状态是不可变的。这意味着一旦一个变量被绑定了一个值,它将不能再被重新定义。这在防止副作用与条件竞争上有明显的优势,使并发编程更简单。
58 |
59 | 和上面一样,让我们使用 Elixir 来说明一下这概念:
60 |
61 | iex> tuple = {:ok, "hello"}
62 | {:ok, "hello"}
63 | iex> put_elem(tuple, 1, "world")
64 | {:ok, "world"}
65 | iex> tuple
66 | {:ok, "hello"}
67 |
68 | 这个例子中,tuple 的值从来没有改变过,第三行 put_elem 是返回了一个完全新的 tuple, 而没有去修改原有的值。
69 |
70 |
71 | ## 函数式编程应用
72 | 作为一个程序员,我们生活在激动人心的时代,云端的承诺已经兑现。与此同时,我们每个人都能获取前所未有的计算机资源。不幸的是,随之带来的扩展性、性能、并发性的需求。
73 |
74 | 面向对象编程根本不能简单的解决这些需求,尤其是在处理并发和并行计算的时候。尝试添加并发性和并行性,只会使语言增加它的复杂性,以及差的性能表现。
75 |
76 | 函数式编程在另一方面是非常适合这些挑战的,不可变状态、闭包和高阶函数等概念,在对于编写高度并发和分布式应用程序而言,它们非常适合。
77 |
78 | 你可以通过查看“WhatsApp和Discord”等创业公司的技术资料,找到足够的证明:
79 |
80 | * [WhatsApp](https://www.wired.com/2015/09/whatsapp-serves-900-million-users-50-engineers/) 仅通过50个使用 Erlang 的开发工程师,就能够支持9亿的用户;
81 | * [Discord](https://blog.discordapp.com/how-discord-handles-push-request-bursts-of-over-a-million-per-minute-with-elixirs-genstage-8f899f0221b4) 以类似的方式使用 Elixir 处理每分钟超过一百万次的请求;
82 |
83 | 这些公司和团队能够处理这种巨大的增长,要感谢函数式编程的优势。随着函数式编程得到越来越多的认同,我坚信像 WhatsApp 和 Discord 的例子会越来越普遍。
--------------------------------------------------------------------------------
/201705/https.md:
--------------------------------------------------------------------------------
1 | # 我们为什么要用 HTTPS
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 前言
5 | 讲 HTTPS 之前,我们先来回顾一下 HTTP 协议。HTTP 是一种超文本传输协议,它是无状态的、简单快速的、基于 TCP 的可靠传输协议。
6 |
7 | 既然 HTTP 协议这么好,那怎么有冒出来了一个 HTTPS 呢?主要是因为 HTTP 是明文传输的,这就造成了很大的安全隐患。在网络传输过程中,只要数据包被人劫持,那你就相当于赤身全裸的暴露在他人面前,毫无半点隐私可言。想象一下,如果你连了一个不可信的 WIFI,正好有使用了某个支付软件进行了支付操作,那么你的密码可能就到别人手里去了,后果可想而知。
8 |
9 | 网络环境的就是这样,给你带来便利的同时,也到处充满了挑战与风险。对于小白用户,你不能期望他有多高的网络安全意识,产品应该通过技术手段,让自己变得更安全,从源头来控制风险。这就诞生了 HTTPS 协议。
10 |
11 | ## HTTPS
12 | 简单理解,HTTP 不就是因为明文传输,所以造成了安全隐患。那让数据传输以加密的方式进行,不就消除了该隐患。
13 |
14 | 从网络的七层模型来看,原先的四层 TCP 到七层 HTTP 之间是明文传输,在这之间加一个负责数据加解密的传输层(SSL/TLS),保证在网络传输的过程中数据是加密的,而 HTTP 接收到的依然是明文数据,不会有任何影响。
15 |
16 | 
17 |
18 | *SSL是Netscape开发的专门用户保护Web通讯的,目前版本为3.0。最新版本的TLS 1.0是IETF(工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。两者差别极小,可以理解为SSL 3.1,它是写入了RFC的。*
19 |
20 | ## 优势
21 | *本节参考[阮一峰的SSL/TLS协议运行机制的概述](http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html)。*
22 |
23 | 在看实现细节之前,我们先看一下 HTTP 具体的安全隐患,以及 HTTPS 的解决方案。
24 |
25 | **HTTP 三大风险**:
26 |
27 | 1. 窃听风险(eavesdropping):第三方可以获知通信内容。
28 | 2. 篡改风险(tampering):第三方可以修改通信内容。
29 | 3. 冒充风险(pretending):第三方可以冒充他人身份参与通信。
30 |
31 | **HTTPS 解决方案**
32 |
33 | 1. 所有信息都是加密传播,第三方无法窃听。
34 | 2. 具有校验机制,一旦被篡改,通信双方会立刻发现。
35 | 3. 配备身份证书,防止身份被冒充。
36 |
37 | ## 实现
38 | 通过上面的介绍,对 HTTPS 有了大致的了解。可本质问题还没说,HTTPS 是如何做到数据的加密传输?这儿不打算搬出复杂的算法公式,还是以最通俗的话述来讲讲我的理解。
39 |
40 | 首先,先来了解下加密算法的方式,常用的有两种:对称加密与非对称加密。
41 |
42 | * 对称加密:即通信双方通过相同的密钥进行信息的加解密。加解密速度快,但是安全性较差,如果其中一方泄露了密钥,那加密过程就会被人破解。
43 |
44 | * 非对称加密:相比对称加密,它一般有公钥和私钥。公钥负责信息加密,私钥负责信息解密。两把密钥分别由发送双发各自保管,加解密过程需两把密钥共同完成。安全性更高,但同时计算量也比对称加密要大很多。
45 |
46 | 对于网络通信过程,在安全的前提下,还是需要保证响应速度。如何每次都进行非对称计算,通信过程势必会受影响。所以,人们希望的安全传输,最终肯定是以对称加密的方式进行。如图:
47 |
48 | 
49 |
50 | 首先 Session Key 是如何得到的,并且在 Session Key 之前的传输都是明文的。Session Key 通过网络传输肯定不安全,所以它一定各自加密生成的。因此在这之前,双方还要互通,确认加密的算法,并且各自添加随机数,提高安全性。如图:
51 |
52 | 
53 |
54 | 这样双方确认了使用的 SSL/TLS 版本,以及生成 Session Key 的加密算法。并且双方拥有了2个随机数(客户端、服务端各自生成一个)。可是如果按照目前的随机参数,加上加密算法生成 Session Key 呢,还是存在风险,加密算法是有限固定的,而且随机数都是明文传输的,有被截获的可能。
55 |
56 | 让加密算法变得不可预测,可能性不大。那么能否再传输一个加密的随机数,这个随机数就算被截获,也无法破译。这就用到了非对称加密算法,我们在步骤二中,把公钥传输给客户端。这样客户端的第三个随机数用公钥加密,只有拥有私钥的服务端才能破译第三个参数,这样生成的 Session 就是安全的了。如图:
57 |
58 | 
59 |
60 | 不容易啊,看似完成了。现在生成的 Session Key 是不可预测的(就算被截获也无所谓),我们可以放心的进行私密通信了。真的是这样吗?我们似乎对服务端给的公钥十分信任,如果你目前通信的本身就不可信,或者被中间人劫持,由它发送伪造的公钥给你,那后果可想而知,这就是传说中的“中间人攻击”。
61 |
62 | 所以,我们得包装一下公钥,让它变得安全。这就引出了数字证书的概念,数字证书由受信任的证书机构颁发,证书包含证书文件与证书私钥文件。私钥文件由服务端自己保存,证书文件发送给请求的客户端,文件包含了域名信息、公钥以及相应的校验码。客户可以根据得到的证书文件,校验证书是否可信。如图:
63 |
64 | 
65 |
66 | 这下算简单讲完了整个的通信过程,首先在 TCP 三次握手后,进行非对称算法的加密(校验证书),将得到的参数进行加密生成会话所需的 Session Key,在之后的数据传输中,通过 Session Key 进行对称密码传输,会话信息在私密的通道下完成。
67 |
68 | *当然,实际的过程远比这来的复杂,这中间包括了复杂的算法以及细节内容,有兴趣的同学,可查阅相关的资料进行了解。*
69 |
70 | ## 证书
71 | 关于数字证书,下面简单介绍一下证书的一些类别:
72 |
73 | ### 按验证级别分类
74 | * Domain Validation(DV):最基本的验证方式,只保证访问的域名是安全的。但在证书中不会提及任何公司/组织信息,所以这算是最低级别的验证。
75 |
76 | 示例:[https://robowhois.com](https://robowhois.com)
77 |
78 | 
79 |
80 | * Organization Validation(OV):这一级别的证书解决了上面域名与公司无法对应的问题,该证书中会描述公司/组织的相关信息。确保域名安全的同时,也保证了域名与公司的绑定关系。
81 |
82 | 示例:[https://www.baidu.com](https://www.baidu.com)
83 |
84 | 
85 |
86 | * Extended Validation(EV):该级别的证书具有最高的安全性,也是最值得信任的。它除了给出更详细的公司/组织信息外,还在浏览器的地址栏上直接给出了公司名称。
87 |
88 | 示例:[https://www.github.com](https://www.github.com)
89 |
90 | 
91 | 
92 |
93 | ### 按覆盖级别分类
94 | * 单域名 SSL 证书:这类证书只能针对一个域名使用,如,当证书与 yourdomain.com 配对了,那就不能在用在 sub.yourdomain.com 上了。
95 | * 通配符域名 SSL 证书:这类证书可以覆盖某个域名下的所有子域名。如,当证书与 yourdomain.com 配对了,那默认 sub.yourdomain.com 以及其他子域名都加入了安全验证。
96 | * 多多域名 SSL 证书:这类证书可以使用在多个不同的域名上。如,域名 a.com 与 b.com 上可以配对同一个证书。
97 |
98 | ## 从 HTTP 迁移的注意点
99 | * 首先需要从相关的网站申请需要的证书;
100 | * 在服务器上开放 HTTPS 所需的443端口,并设置相应的证书地址,下面贴一段在 nginx 上的配置:
101 |
102 | ```
103 | server {
104 | listen 443;
105 | server_name localhost;
106 | ssl on;
107 | root html;
108 | index index.html index.htm;
109 | ssl_certificate cert/证书文件.pem;
110 | ssl_certificate_key cert/证书私钥.key;
111 | ssl_session_timeout 5m;
112 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
113 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
114 | ssl_prefer_server_ciphers on;
115 | location / {
116 | root html;
117 | index index.html index.htm;
118 | }
119 | }
120 | ```
121 | * 对于原先的80端口,需做重定向的跳转。将原先访问 HTTP 协议的用户,自动跳转用 HTTPS 上来。
122 | * 出于性能考虑,Session Key 的生成相对耗 CPU 的资源,所以尽量减少 Key 的生成次数。这里有两种方案:
123 | * 采用长连接方式;
124 | * 在回话创建时,对生成的 Session Key 进行缓存(仍以 nginx 为例);
125 |
126 | ```
127 | http {
128 | #配置共享会话缓存大小
129 | ssl_session_cache shared:SSL:10m;
130 | #配置会话超时时间
131 | ssl_session_timeout 10m;
132 | ...
133 | ```
134 |
135 | ## 总结
136 | 本文不涉及任何高深的概念,都是以自己的一些理解来进行讲述,希望对大家有用!
137 |
138 | 最后,推荐一个比较好用的 HTTP 抓包工具(含 HTTPS)。
139 |
140 | * [Charles 4.0 Mac破解版](http://www.sdifen.com/charles4.html)
141 | * [使用 Charles 获取 https 的数据](http://blog.csdn.net/yangmeng13930719363/article/details/51645435)
142 |
143 |
144 |
--------------------------------------------------------------------------------
/201706/assets/event_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_01.png
--------------------------------------------------------------------------------
/201706/assets/event_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_02.png
--------------------------------------------------------------------------------
/201706/assets/event_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_03.png
--------------------------------------------------------------------------------
/201706/assets/event_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_04.png
--------------------------------------------------------------------------------
/201706/assets/event_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_05.png
--------------------------------------------------------------------------------
/201706/assets/event_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_06.png
--------------------------------------------------------------------------------
/201706/assets/event_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_07.png
--------------------------------------------------------------------------------
/201706/assets/event_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_08.png
--------------------------------------------------------------------------------
/201706/assets/event_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_09.png
--------------------------------------------------------------------------------
/201706/assets/event_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_10.png
--------------------------------------------------------------------------------
/201706/assets/event_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_11.png
--------------------------------------------------------------------------------
/201706/assets/event_12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_12.png
--------------------------------------------------------------------------------
/201706/assets/event_13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/event_13.png
--------------------------------------------------------------------------------
/201706/assets/jms_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_01.png
--------------------------------------------------------------------------------
/201706/assets/jms_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_02.png
--------------------------------------------------------------------------------
/201706/assets/jms_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_03.png
--------------------------------------------------------------------------------
/201706/assets/jms_activemq_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_activemq_01.png
--------------------------------------------------------------------------------
/201706/assets/jms_ptp_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_ptp_01.png
--------------------------------------------------------------------------------
/201706/assets/jms_ptp_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_ptp_02.png
--------------------------------------------------------------------------------
/201706/assets/jms_ptp_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_ptp_03.png
--------------------------------------------------------------------------------
/201706/assets/jms_pubsub_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_pubsub_01.png
--------------------------------------------------------------------------------
/201706/assets/jms_pubsub_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/jms_pubsub_02.png
--------------------------------------------------------------------------------
/201706/assets/redis_search_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/redis_search_01.png
--------------------------------------------------------------------------------
/201706/assets/redis_search_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/redis_search_02.png
--------------------------------------------------------------------------------
/201706/assets/redis_search_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/redis_search_03.png
--------------------------------------------------------------------------------
/201706/assets/redis_search_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201706/assets/redis_search_04.png
--------------------------------------------------------------------------------
/201706/event.md:
--------------------------------------------------------------------------------
1 | # [译] 基于事件流构建的服务
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 |
5 | **原文:[https://www.confluent.io/blog/build-services-backbone-events/](https://www.confluent.io/blog/build-services-backbone-events/)**
6 |
7 | 对许多人来说,微服务是建立在请求和响应协议之上的,如 REST 等等。这种方法很自然。 我们编写程序是一回事,我们调用其他代码模块,等待响应并继续。它也与我们每天看到的大量使用情况紧密相连:前面的用户点击按钮的网站,并期待事情发生。
8 |
9 | 
10 |
11 | 但是当我们进入许多独立服务的世界时,事情就会开始变化。随着服务的数量随着时间的推移逐渐增长,同步交互的网络也随之增长。以前良性的可用性问题开始引发更广泛的中断。
12 |
13 | 在分布式系统中,排查问题对于我们不幸的运维工程师来说,将是艰巨的任务。疯狂的从一个服务到另一个服务,拼凑各个服务的信息片段。
14 |
15 | 这是一个众所周知的问题,并且有一些解决方案。一个是确保您的个人服务具有比您的系统更高的 SLAs。Google 提供了这样做的[协议](http://queue.acm.org/detail.cfm?id=3096459)。另一种方法是简单地分解将服务绑定在一起的同步关系。
16 |
17 | 
18 |
19 | 我们可以使用异步作为这样做的机制。如果你在在线零售工作,你会发现同步接口,如 getImage() 或 processOrder() 感觉自然,期望调用得到立即响应。但是当用户点击“购买”时,它会触发一个复杂而异步的过程。一个采购的过程,并将其线下交付到用户门口,这种方式已经超出原本按钮的上下文。因此,将软件分解成异步流可以使我们能够区分我们需要解决的不同问题,并允许我们拥抱一个本身就是异步的世界。
20 |
21 | 在实践中,我们发现会有轮询数据库表来进行更改,或者通过一些定时任务来进行更新。这些是打破同步关系的简单方式,但这些方式给人不透明的感觉,像有黑客篡改了你的数据一样。可能他们有一个很好的理由。
22 |
23 | 所以我们可以将所有这些问题集中到一个观察中。我们命令服务去做我们要求的事情,这样的命令式编程模式不适合独立运行的服务。
24 |
25 | 在这篇文章中,我们将看看架构硬币的另一面:不是通过命令链,而是通过事件流的方式来组合服务。这是一个有效的方法。它也为我们将在本系列后面讨论的更先进的模式形成基准,我们将事件驱动处理的想法与[流式平台](https://www.confluent.io/blog/stream-data-platform-1/)中的观点相结合。
26 |
27 | 
28 |
29 | ## 命令(Commands)、事件(Events)和查询(Queries)
30 |
31 | 在我们深入研究一个例子之前,我们需要解决三个简单的概念。服务可以通过三种方式相互交互:命令、事件和查询。 如果你以前没有考虑过这三者之间的区别,那么这很值得。
32 |
33 | 
34 |
35 | 事件的优点是事实和触发。外部数据可以被系统中的任何服务来重用。但从服务的角度来看,事件造成的耦合又要比命令和查询来得低。这个事实很重要。
36 |
37 | 服务间交互的三种方式:
38 |
39 | * 命令:是一个动作,在另一个服务中执行某些操作的请求。有些会改变系统状态。命令会期待一个响应结果。
40 | * 事件:既是事实也是触发器。对已经发生事情的一种通知。
41 | * 查询:是查找某物的一个请求。重要的是,查询是无副作用的,不会造成系统状态的改变。
42 |
43 | ## 一个简单的事件驱动流程
44 |
45 | 我们从一个简单的例子开始:客户订购小部件,这个行为会触发接下来的两个事情:
46 |
47 | 1. 处理相关的付款。
48 | 2. 系统检查以查看是否需要更多小部件。
49 |
50 | 在请求驱动的方法中,这可以表示为一连串的命令。目前没有查询。互动将如下所示:
51 |
52 | 
53 |
54 | 要注意的第一个事情是通过调用订单服务初始化“购买更多的库存”的业务流程。这混合了两个服务的责任,理想情况,我们应该更好的分离关注点。
55 |
56 | 现在,如果我们可以使用一个事件驱动方法来代表相同的流程,那么事情会变得更好。
57 |
58 | 1. UI 服务触发一个“订单请求”事件,并在返回给用户之前等待一个“订单确定”(或拒绝)的事件。
59 | 2. 订单服务与库存服务都通过触发的事情来进行响应。
60 |
61 | 
62 |
63 | 仔细看,UI 服务与订单服务的交互没有任何改变,除了他们是通过事件来交流的,而不是直接调用对方。
64 |
65 | 库存服务很有趣。订单服务不再告诉它要做什么,也不再控制是否参与互动。这是这种类型的架构非常重要的属性,称为 “Receiver Driven Flow Control”。逻辑被推送到事件的接收者,而不是发送者。责任的重任进行了翻转!
66 |
67 | 将控制转移到接收者可减少服务之间的耦合,从而为架构提供了重要的可插拔性。组件可以轻松地换入换出。
68 |
69 | 
70 |
71 | 随着架构变得越来越复杂,可插拔性的这一要素变得越来越重要。说我们要添加一个实时管理定价的服务,根据供需调整产品的价格。在一个命令驱动的世界里,我们需要引入一个可以由库存服务和订单服务调用的 maybeUpdatePrice() 方法。但在事件驱动的世界重新定价只是一种订阅共享流的一个服务,当满足相关标准时发送价格更新。
72 |
73 | 
74 |
75 | ## 混合事件与查询
76 |
77 | 上面的例子只考虑了命令/事件。 没有查询操作(请记住我们将所有交互定义为命令、事件和查询的其中一种)。查询是除了最简单的架构之外的所有架构的必需品。所以我们来扩展这个例子,让订单服务检查在处理付款之前有足够的库存。
78 |
79 | 对此的请求驱动方法将涉及向库存服务发送查询以检索当前库存数量。这导致混合模型,其中事件流纯粹用于通知,允许任何服务进入流程,但查询直接转到源。
80 |
81 | 
82 |
83 | 对于服务需要独立发展的更大的生态系统,远程查询增加了很多耦合,在运行时将服务捆绑在一起。 我们可以通过内部化来避免这种跨上下文的查询。事件流用于缓存每个服务中的数据集,使其可以在本地进行查询。
84 |
85 | 所以要添加这个库存检查,订单服务将订阅库存事件流,将它们进行本地存储。然后直接查询本地缓存来验证是否有足够的库存。
86 |
87 | 
88 | *纯事件驱动系统没有远程查询的概念 - 事件将状态传播到在本地进行查询的服务*
89 |
90 | 这种“按事件传播查询”方法有三个优点:
91 |
92 | * 更好的解耦:查询是本地的。它们不涉及跨上下文调用。这种服务的耦合性远远低于他们请求驱动时的耦合性。
93 | * 更好的自治:订单服务具有库存数据集的私有副本,因此它可以随意使用它,而不仅限于库存服务提供的查询功能。
94 | * 高效连接:如果我们在每个订单上“查询库存”,我们将有效地通过两个服务之间的网络进行连接。随着负载的增加,或者更多的资源被使用时,这可能会变得非常糟糕。按事件传播查询通过将查询(和连接)本地化来解决此问题。
95 |
96 | 这种做法不是没有缺点。服务内部变成有状态的。他们需要跟踪和处理一段时间内传播的数据集。状态的重复也可能使一些问题更难理解(我们如何原子地减少库存数量?),我们也应该注意数据是最终一致性的问题。但是,所有这些问题都有可行的解决方案,他们只需要考虑一下。
97 |
98 | ## 单独写原则
99 |
100 | 适用于这种风格的系统的有用原则是将特定类型的传播事件的责任分配给单个服务:单独写原则。因此,库存服务部门将拥有“库存清单”如何随时间推进,订单服务部门将拥有订单等。
101 |
102 | 这有助于通过单个代码路径(尽管不一定是单个进程)来保证一致性,验证和其他“写入路径”问题。所以,在下面的示例中,请注意,订单服务控制对订单进行的每个状态更改,但整个事件流量跨越订单、付款和发货,每个由其各自的服务管理。
103 |
104 | 分配事件传播的责任很重要,因为这些不仅仅是短暂的事件,也不是短暂的聊天。他们代表共同的事实,数据在外面。因此,随着时间的推移,服务需要负责策划这些共享数据集:修复错误,处理模式变化等情况。
105 |
106 | 
107 |
108 | 这里每个颜色代表 Kafka 在订单、发货和付款中的一个主题(topic)。当用户点击“购买”时,会触发“订单请求”,等待“订单确认”事件,然后再回复给用户。另外三个服务处理与其工作流程部分相关的状态转换。例如,付款处理完成后,订单服务将订单从验证推送到已确认。
109 |
110 | ## 混合模式和集群服务
111 |
112 | 对于上面描述的一些模式看起来像企业消息(Enterprise Messaging),但还是有些许的不同。企业消息在实践中侧重于状态的转移,通过网络有效地将数据库捆绑在一起。
113 |
114 | 事件协作是关于服务通过一系列事件来处理一些业务目标,这些事件将触发服务的行动。所以这是业务处理的一种模式,而不是简单的移动状态的机制。
115 |
116 | 但是,我们通常希望在我们构建的系统中利用这种模式的“faces”。事实上,这种模式的美妙之处在于它可以处理微观和宏观,或者在有意义的情况下被混合。
117 |
118 | 组合模式也很常见。 我们可能想要远程查询的灵活性,而不是本地维护数据集的开销,特别是在数据集增长时。这使部署简单的功能变得容易(如果我们要组合轻量级、无服务器架构和事件流,这一点就很重要),或者因为我们处于无状态的容器或浏览器。
119 |
120 | 诀窍是限制这些查询接口的范围,理想情况下是在界限上下文中。通常情况下,具有多个特定目标视图的架构会比单一的共享数据存储的架构要好。(界限上下文,这里是一组共享相同的部署周期或领域模型的服务。)
121 |
122 | 为了限制远程查询的范围,我们可以使用集群上下文模式。这里事件流是上下文之间的唯一沟通模式。但是,上下文中的服务会利用他们所需的事件驱动处理和请求驱动视图。
123 |
124 | 在下面的例子中,我们有三个部分,只通过事件相互沟通。 在每一个内部,我们使用更细粒度的事件驱动流。 其中一些包括视图层(查询层)。这平衡了耦合的便利性,允许我们将细粒度的服务与较大的实体相结合; 传统的应用程序或现成的产品中都存在许多真实的服务场景。
125 |
126 | 
127 | *集群上下文模式*
128 |
129 | 事件驱动服务的五大优势:
130 |
131 | * 解耦:如果基于事件,服务可以更容易地插入到现有的事件流中,或者重做工作流的一些子集。
132 | * 离线/异步流:服务卸下了保证交付给消息协商器(broker)的责任。这使得以事件驱动的方式轻松管理离线任务。
133 | * 状态转移:事件流提供了一种分发数据集的有效机制,因此可以在界限上下文中重构和查询。
134 | * 连接:从不同的服务组合/加入/扩充数据集更容易。连接是快速和本地化的。
135 | * 可追溯性:当有一个中心的、不可变的,保持叙事性的,每次互动随着时间的推移而日常化的记录时,调试分布式将变得更加容易。
136 |
137 | ## 总结
138 |
139 | 所以在事件驱动的方法我们使用事件代替命令,事件触发处理。它们也变成我们可以在本地查询的视图。我们在必要时回到远程同步查询,特别是在较小的生态系统中,但是在较大的系统中我们限制了它的范围(理想情况下,仅限于单个界限上下文)。
140 |
141 | 但所有这些方法只是模式。一起构建系统的指导原则。对于它们,我们不应该太教条化。例如,如果它是很少改变的东西(比如单点登录服务),一个全局查询服务仍然是一个好主意。
142 |
143 | 诀窍是从事件的基准开始。事件提供了更少的服务机会来将自己相互联系起来,并且将流程控制转移到接收者使得更好的分离关注和更好的可插拔性。
144 |
145 | 关于事件驱动方法的另一个有趣的事情是,它们对于大型、复杂的架构,和对于小型、高度协作的架构同样起到很好的作用。事件的支柱为服务提供自由发展所需的自治权。去除了复杂的命令和查询关系。对于运维工程师来说,系统排查仍然是痛苦的,但是希望不是那么频繁,至少现在这个故事还有一个脚本!
146 |
147 | 
148 |
149 | 但是,随着所有这些事件的讨论,我们谈到了很少的分布式日志或流处理。当我们将这种模式与 Kafka 应用时,系统的规则就会改变。协商器的保留特性成为我们可以设计的工具,允许我们接触外部的数据。某些服务可以参考。
150 |
151 | 流式平台非常自然地与这种模型的处理事件和构建视图相配。直接嵌入到服务中的视图,远程服务查询的视图,或视图实现作为一个持续的流。
152 |
153 | 这导致了一系列优化:利用事件流和事件存储之间的对偶性,混合流式处理工具,筛选叙述,加入来自许多服务的流和实现我们可以查询的视图。这些模式正在赋予权力。它们允许我们使用专门用于处理事件流的工具集来重新构想业务处理。但是,所有这些优化都是基于这里讨论的概念,仅仅应用于更现代的工具集。
154 |
155 | 在下一篇文章中,我们将通过考虑将日志的保留属性作为我们的服务生态系统的组成部分来使数据更具确定性。我们将数据保存在外部作为可以依赖的中央共享叙事的地方。
156 |
--------------------------------------------------------------------------------
/201706/jms.md:
--------------------------------------------------------------------------------
1 | # JMS 在 SpringBoot 中的使用
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 | > 本文所有服务均采用docker容器化方式部署
4 |
5 | ## 当前环境
6 | 1. Mac OS 10.11.x
7 | 2. docker 1.12.1
8 | 3. JDK 1.8
9 | 4. SpringBoot 1.5
10 |
11 |
12 | ## 前言
13 | 基于之前一篇[“一个故事告诉你什么是消息队列”](https://github.com/jasonGeng88/blog/blob/master/201705/MQ.md),了解了消息队列的使用场景以及相关的特性。本文主要讲述消息服务在 JAVA 中的使用。
14 |
15 | 市面上的有关消息队列的技术选型非常多,如果我们的代码框架要支持不同的消息实现,在保证框架具有较高扩展性的前提下,我们势必要进行一定的封装。
16 |
17 | 在 JAVA 中,大可不必如此。因为 JAVA 已经制定了一套标准的 JMS 规范。该规范定义了一套通用的接口和相关语义,提供了诸如持久、验证和事务的消息服务,其最主要的目的是允许Java应用程序访问现有的消息中间件。就和 JDBC 一样。
18 |
19 |
20 | ## 基本概念
21 | 在介绍具体的使用之前,先简单介绍一下 JMS 的一些基本知识。这里我打算分为 3 部分来介绍,即 消息队列(MQ)的连接、消息发送与消息接收。
22 |
23 | ***这里我们的技术选型是 SpringBoot、JMS、ActiveMQ***
24 |
25 | *为了更好的理解 JMS,这里没有使用 SpringBoot 零配置来搭建项目*
26 |
27 | ### MQ 的连接
28 | 使用 MQ 的第一步一定是先连接 MQ。因为这里使用的是 JMS 规范,对于任何遵守 JMS 规范的 MQ 来说,都会实现相应的```ConnectionFactory```接口,因此我们只需要创建一个```ConnectionFactory```工厂类,由它来实现 MQ 的连接,以及封装一系列特性的 MQ 参数。
29 |
30 | 例子:这里我们以 ActiveMQ 为例,
31 |
32 | maven 依赖:
33 |
34 | ```xml
35 |
36 | org.springframework.boot
37 | spring-boot-starter-parent
38 | 1.5.3.RELEASE
39 |
40 |
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-activemq
45 |
46 |
47 | ```
48 |
49 | 创建 ActiveMQ 连接工厂:
50 |
51 | ```java
52 | @Bean
53 | public ConnectionFactory connectionFactory(){
54 |
55 | ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
56 | connectionFactory.setBrokerURL(ActiveMQ_URL);
57 | connectionFactory.setUserName(ActiveMQ_USER);
58 | connectionFactory.setPassword(ActiveMQ_PASSWORD);
59 | return connectionFactory;
60 |
61 | }
62 | ```
63 |
64 | ### 消息发送
65 | 关于消息的发送,是通过 JMS 核心包中的```JmsTemplate```类来实现的,它简化了 JMS 的使用,因为在发送或同步接收消息时它帮我们处理了资源的创建和释放。从它的作用也不难推测出,它需要引用我们上面创建的连接工厂,具体代码如下:
66 |
67 | ```java
68 | @Bean
69 | public JmsTemplate jmsQueueTemplate(){
70 |
71 | return new JmsTemplate(connectionFactory());
72 |
73 | }
74 | ```
75 |
76 | ```JmsTemplate```创建完成后,我们就可以调用它的方法来发送消息了。这里有两个概念需要注意:
77 |
78 | 1. 消息会发送到哪里?-> 即需要指定发送队列的目的地(Destination),是可以在 JNDI 中进行存储和提取的 JMS 管理对象。
79 | 2. 发送的消息体具体是什么?-> 实现了```javax.jms.Message```的对象,类似于 JAVA RMI 的 Remote 对象。
80 |
81 | 代码示例:
82 |
83 | ```java
84 | @Autowired
85 | private JmsTemplate jmsQueueTemplate;
86 |
87 | /**
88 | * 发送原始消息 Message
89 | */
90 | public void send(){
91 |
92 | jmsQueueTemplate.send("queue1", new MessageCreator() {
93 | @Override
94 | public Message createMessage(Session session) throws JMSException {
95 | return session.createTextMessage("我是原始消息");
96 | }
97 | });
98 |
99 | }
100 | ```
101 |
102 | 优化:当然,我们不用每次都通过```MessageCreator ```匿名类的方式来创建```Message```对象,```JmsTemplate```类中提供了对象实体自动转换为```Message```对象的方法,```convertAndSend(String destinationName, final Object message)```。
103 |
104 | 优化代码示例:
105 |
106 | ```java
107 | /**
108 | * 发送消息自动转换成原始消息
109 | */
110 | public void convertAndSend(){
111 |
112 | jmsQueueTemplate.convertAndSend("queue1", "我是自动转换的消息");
113 |
114 | }
115 | ```
116 |
117 | *注:关于消息转换,还可以通过实现```MessageConverter```接口来自定义转换内容*
118 |
119 | ### 消息接收
120 |
121 | 讲完了消息发送,我们最后来说说消息是如何接收的。消息既然是以```Message```对象的形式发送到指定的目的地,那么消息的接收势必会去指定的目的地上去接收消息。这里采用的是监听者的方式来监听指定地点的消息,采用注解```@JmsListener```来设置监听方法。
122 |
123 | 代码示例:
124 |
125 | ```java
126 | @Component
127 | public class Listener1 {
128 |
129 | @JmsListener(destination = "queue1")
130 | public void receive(String msg){
131 | System.out.println("监听到的消息内容为: " + msg);
132 | }
133 |
134 | }
135 | ```
136 |
137 | 有了监听的目标和方法后,监听器还得和 MQ 关联起来,这样才能运作起来。这里的监听器可能不止一个,如果每个都要和 MQ 建立连接,肯定不太合适。所以需要一个监听容器工厂的概念,即接口```JmsListenerContainerFactory```,它会引用上面创建好的与 MQ 的连接工厂,由它来负责接收消息以及将消息分发给指定的监听器。当然也包括事务管理、资源获取与释放和异常转换等。
138 |
139 | 代码示例:
140 |
141 | ```java
142 | @Bean
143 | public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() {
144 |
145 | DefaultJmsListenerContainerFactory factory =
146 | new DefaultJmsListenerContainerFactory();
147 | factory.setConnectionFactory(connectionFactory());
148 | //设置连接数
149 | factory.setConcurrency("3-10");
150 | //重连间隔时间
151 | factory.setRecoveryInterval(1000L);
152 | return factory;
153 |
154 | }
155 | ```
156 |
157 | ## 场景
158 |
159 | 代码地址:[https://github.com/jasonGeng88/springboot-jms](https://github.com/jasonGeng88/springboot-jms)
160 |
161 | 对 JMS 有了基本的理解后,我们就来在具体的场景中使用一下。
162 |
163 | 首先,我们需要先启动 ActiveMQ,这里我们以 Docker 容器化的方式进行启动。
164 |
165 | 启动命令:
166 |
167 | ```
168 | docker run -d -p 8161:8161 -p 61616:61616 --name activemq webcenter/activemq
169 | ```
170 |
171 | 启动成功后,在 ActiveMQ 可视化界面查看效果(http://localhost:8161):
172 |
173 | 
174 |
175 | ---
176 |
177 | ### 点对点模式(单消费者)
178 |
179 | 下面介绍消息队列中最常用的一种场景,即点对点模式。基本概念如下:
180 |
181 | 1. 每个消息只能被一个消费者(Consumer)进行消费。一旦消息被消费后,就不再在消息队列中存在。
182 | 2. 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列。
183 | 3. 接收者在成功接收消息之后需向队列应答成功。
184 |
185 | 
186 |
187 | #### 代码实现(为简化代码,部分代码沿用上面所述):
188 |
189 | * 启动文件(Application.java)
190 |
191 | ```java
192 | @SpringBootApplication
193 | @EnableJms
194 | public class Application {
195 |
196 | ...
197 |
198 | /**
199 | * JMS 队列的模板类
200 | * connectionFactory() 为 ActiveMQ 连接工厂
201 | */
202 | @Bean
203 | public JmsTemplate jmsQueueTemplate(){
204 | return new JmsTemplate(connectionFactory());
205 | }
206 |
207 | public static void main(String[] args) {
208 | SpringApplication.run(Application.class, args);
209 | }
210 |
211 | }
212 | ```
213 |
214 | 注解```@EnableJms```设置在```@Configuration```类上,用来声明对 JMS 注解的支持。
215 |
216 | * 消息生产者(PtpProducer.java)
217 |
218 | ```java
219 | @Component
220 | public class PtpProducer {
221 |
222 | @Autowired
223 | private JmsTemplate jmsQueueTemplate;
224 |
225 | /**
226 | * 发送消息自动转换成原始消息
227 | */
228 | public void convertAndSend(){
229 | jmsQueueTemplate.convertAndSend("ptp", "我是自动转换的消息");
230 | }
231 | }
232 | ```
233 |
234 | * 生产者调用类(PtpController.java)
235 |
236 | ```java
237 | @RestController
238 | @RequestMapping(value = "/ptp")
239 | public class PtpController {
240 |
241 | @Autowired
242 | private PtpProducer ptpProducer;
243 |
244 | @RequestMapping(value = "/convertAndSend")
245 | public Object convertAndSend(){
246 | ptpProducer.convertAndSend();
247 | return "success";
248 | }
249 |
250 | }
251 | ```
252 |
253 | * 消息监听容器工厂
254 |
255 | ```java
256 | @SpringBootApplication
257 | @EnableJms
258 | public class Application {
259 |
260 | ...
261 |
262 | /**
263 | * JMS 队列的监听容器工厂
264 | */
265 | @Bean(name = "jmsQueueListenerCF")
266 | public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() {
267 | DefaultJmsListenerContainerFactory factory =
268 | new DefaultJmsListenerContainerFactory();
269 | factory.setConnectionFactory(connectionFactory());
270 | //设置连接数
271 | factory.setConcurrency("3-10");
272 | //重连间隔时间
273 | factory.setRecoveryInterval(1000L);
274 | return factory;
275 | }
276 |
277 | ...
278 |
279 | }
280 | ```
281 |
282 | * 消息监听器
283 |
284 | ```java
285 | @Component
286 | public class PtpListener1 {
287 |
288 | /**
289 | * 消息队列监听器
290 | * destination 队列地址
291 | * containerFactory 监听器容器工厂, 若存在2个以上的监听容器工厂,需进行指定
292 | */
293 | @JmsListener(destination = "ptp", containerFactory = "jmsQueueListenerCF")
294 | public void receive(String msg){
295 |
296 | System.out.println("点对点模式1: " + msg);
297 |
298 | }
299 | }
300 | ```
301 |
302 | #### 演示
303 |
304 | 启动项目启动后,通过 REST 接口的方式来调用消息生产者发送消息,请求如下:
305 |
306 | ```
307 | curl -XGET 127.0.0.1:8080/ptp/convertAndSend
308 | ```
309 |
310 | 消费者控制台信息:
311 |
312 | 
313 |
314 | ActiveMQ 控制台信息:
315 |
316 | 
317 |
318 | 列表说明:
319 |
320 | * Name:队列名称。
321 | * Number Of Pending Messages:等待消费的消息个数。
322 | * Number Of Consumers:当前连接的消费者数目,因为我们采用的是连接池的方式连接,初始连接数为 3,所以显示数字为 3。
323 | * Messages Enqueued:进入队列的消息总个数,包括出队列的和待消费的,这个数量只增不减。
324 | * Messages Dequeued:出了队列的消息,可以理解为是已经消费的消息数量。
325 |
326 |
327 | ### 点对点模式(多消费者)
328 |
329 | 基于上面一个消费者消费的模式,因为生产者可能会有很多,同时像某个队列发送消息,这时一个消费者可能会成为瓶颈。所以需要多个消费者来分摊消费压力(*消费线程池能解决一定压力,但毕竟在单机上,做不到分布式分布,所以多消费者是有必要的*),也就产生了下面的场景。
330 |
331 | 
332 |
333 | #### 代码实现
334 |
335 | * 添加新的监听器
336 |
337 | ```java
338 | @Component
339 | public class PtpListener2 {
340 |
341 | @JmsListener(destination = Constant.QUEUE_NAME, containerFactory = "jmsQueueListenerCF")
342 | public void receive(String msg){
343 |
344 | System.out.println("点对点模式2: " + msg);
345 |
346 | }
347 | }
348 | ```
349 |
350 | #### 演示
351 |
352 | 这里我们发起 10 次请求,来观察消费者的消费情况:
353 |
354 | 
355 |
356 | *这里因为监听容器设置了线程池的缘故,在实际消费过程中,监听器消费的顺序会有所差异。*
357 |
358 |
359 | ### 发布订阅模式
360 |
361 | 除了点对点模式,发布订阅模式也是消息队列中常见的一种使用。试想一下,有一个即时聊天群,你在群里发送一条消息。所有在这个群里的人(即订阅了该群的人),都会收到你发送的信息。
362 |
363 | 基本概念:
364 |
365 | 1. 每个消息可以有多个消费者。
366 | 2. 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
367 | 3. 为了消费消息,订阅者必须保持运行的状态。
368 |
369 | 
370 |
371 | #### 代码实现
372 |
373 | * 修改 JmsTemplate 模板类,使其支持发布订阅功能
374 |
375 | ```java
376 | @SpringBootApplication
377 | @EnableJms
378 | public class Application {
379 |
380 | ...
381 |
382 | @Bean
383 | public JmsTemplate jmsTopicTemplate(){
384 | JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
385 | jmsTemplate.setPubSubDomain(true);
386 | return jmsTemplate;
387 | }
388 |
389 | ...
390 |
391 | }
392 | ```
393 |
394 | * 消息生产者(PubSubProducer.java)
395 |
396 | ```java
397 | @Component
398 | public class PtpProducer {
399 |
400 | @Autowired
401 | private JmsTemplate jmsTopicTemplate;
402 |
403 | public void convertAndSend(){
404 | jmsTopicTemplate.convertAndSend("topic", "我是自动转换的消息");
405 | }
406 | }
407 | ```
408 |
409 | * 生产者调用类(PubSubController.java)
410 |
411 | ```java
412 | @RestController
413 | @RequestMapping(value = "/pubsub")
414 | public class PtpController {
415 |
416 | @Autowired
417 | private PubSubProducer pubSubProducer;
418 |
419 | @RequestMapping(value = "/convertAndSend")
420 | public String convertAndSend(){
421 | pubSubProducer.convertAndSend();
422 | return "success";
423 | }
424 |
425 | }
426 | ```
427 |
428 | * 修改 DefaultJmsListenerContainerFactory 类,使其支持发布订阅功能
429 |
430 | ```java
431 | @SpringBootApplication
432 | @EnableJms
433 | public class Application {
434 |
435 | ...
436 |
437 | /**
438 | * JMS 队列的监听容器工厂
439 | */
440 | @Bean(name = "jmsTopicListenerCF")
441 | public DefaultJmsListenerContainerFactory jmsTopicListenerContainerFactory() {
442 | DefaultJmsListenerContainerFactory factory =
443 | new DefaultJmsListenerContainerFactory();
444 | factory.setConnectionFactory(connectionFactory());
445 | factory.setConcurrency("1");
446 | factory.setPubSubDomain(true);
447 | return factory;
448 | }
449 |
450 | ...
451 |
452 | }
453 | ```
454 |
455 | * 消息监听器(这里设置2个订阅者)
456 |
457 | ```java
458 | @Component
459 | public class PubSubListener1 {
460 |
461 | @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF")
462 | public void receive(String msg){
463 | System.out.println("订阅者1 - " + msg);
464 | }
465 | }
466 |
467 | @Component
468 | public class PubSubListener2 {
469 |
470 | @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF")
471 | public void receive(String msg){
472 | System.out.println("订阅者2 - " + msg);
473 | }
474 | }
475 | ```
476 |
477 | #### 演示
478 |
479 | ```
480 | curl -XGET 127.0.0.1:8080/pubSub/convertAndSend
481 | ```
482 |
483 | 消费者控制台信息:
484 |
485 | 
486 |
487 | ActiveMQ 控制台信息:
488 |
489 | 
490 |
491 |
492 | ## 总结
493 |
494 | 这里只是对 SpringBoot 与 JMS 集成的简单说明与使用,详细的介绍可以查看 Spring 的官方文档,我这里也有幸参与 并发编程网 发起的 Spring 5 的翻译工作,我主要翻译了 [Spring 5 的 JMS 章节](http://ifeve.com/spring-5-26-jms-java-message-service/),其内容对于上述 JMS 的基本概念,都有详细的展开说明,有兴趣的可以看一下,当然翻译水平有限,英文好的建议看原文。
--------------------------------------------------------------------------------
/201706/redis-search.md:
--------------------------------------------------------------------------------
1 | # 一步步实现 Redis 搜索引擎
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 场景
5 |
6 | 大家如果是做后端开发的,想必都实现过列表查询的接口,当然有的查询条件很简单,一条 SQL 就搞定了,但有的查询条件极其复杂,再加上库表中设计的各种不合理,导致查询接口特别难写,然后加班什么的就不用说了(*不知各位有没有这种感受呢~*)。
7 |
8 | 下面以一个例子开始,这是某购物网站的搜索条件,如果让你实现这样的一个搜索接口,你会如何实现?(*当然你说借助搜索引擎,像 Elasticsearch 之类的,你完全可以实现。但我这里想说的是,如果要你自己实现呢?*)
9 |
10 |
11 | 
12 |
13 | 从上图中可以看出,搜索总共分为6大类,每大类中又分了各个子类。这中间,各大类条件之间是取的交集,各子类中有单选、多选、以及自定义的情况,最终输出符合条件的结果集。
14 |
15 | 好了,既然需求很明确了,我们就开始来实现。
16 |
17 | ## 实现1
18 | 率先登场是小A同学,他是写 SQL 方面的“专家”。小A信心满满的说:“不就是一个查询接口吗?看着条件很多,但凭着我丰富的 SQL 经验,这点还是难不倒我的。”
19 |
20 | 于是乎就写出了下面这段代码(这里以 MYSQL 为例):
21 |
22 | ```sql
23 | select ... from table_1
24 | left join table_2
25 | left join table_3
26 | left join (select ... from table_x where ...) tmp_1
27 | ...
28 | where ...
29 | order by ...
30 | limit m,n
31 | ```
32 |
33 | 代码在测试环境跑了一把,结果好像都匹配上了,于是准备上预发。这一上预发,问题就开始暴露出来。预发为了尽可能的逼真线上环境,所以数据量自然而然要比测试大的多。所以这么一个复杂的 SQL,它的执行效率可想而知。测试同学果断把小A的代码给打了回来。
34 |
35 | ## 实现2
36 | 总结了小A失败的教训,小B开始对SQL进行了优化,先是通过了```explain```关键字进行SQL性能分析,对该加索引的地方都加上了索引。同时将一条复杂SQL拆分成了多条SQL,计算结果在程序内存中进行计算。
37 |
38 | 伪代码如下:
39 |
40 | ```php
41 | $result_1 = query('select ... from table_1 where ...');
42 | $result_2 = query('select ... from table_2 where ...');
43 | $result_3 = query('select ... from table_3 where ...');
44 | ...
45 |
46 | $result = array_intersect($result_1, $result_2, $result_3, ...);
47 | ```
48 |
49 | 这种方案从性能上明显比第一种要好很多,可是在功能验收的时候,产品经理还是觉得查询速度不够快。小B自己也知道,每次查询都会向数据库查询多次,而且有些历史原因,部分条件是做不到单表查询的,所以查询等待的时间是避免不了的。
50 |
51 | ## 实现3
52 | 小C从上面的方案中看到了优化的空间。他发现小B在思路上是没问题的,将复杂条件拆分,计算各个子维度的结果集,最后将所有的子结果集进行一个汇总合并,得到最终想要的结果。
53 |
54 | 于是他突发奇想,能否事先将各个子维度的结果集给缓存起来,这要查询的时候直接去取想要的子集,而不用每次去查库计算。
55 |
56 | 这里小C采用 [Redis](https://redis.io/) 来存储缓存数据,用它的主要原因是,它提供了多种数据结构,并且在 Redis 中进行集合的交并集操作是一件很容易的事情。
57 |
58 | 具体方案,如图所示:
59 |
60 | 
61 |
62 | 这里每个条件都事先将计算好的结果集ID存入对应的key中,选用的数据结构是集合(Set)。查询操作包括:
63 |
64 | * 子类单选:直接根据条件 key,获取对应结果集;
65 | * 子类多选:根据多个条件 Key,进行并集操作,获取对应结果集;
66 | * 最终结果:将获取的所有子类结果集进行交集操作,得到最终结果;
67 |
68 | ***这其实就是所谓的[反向索引](https://zh.wikipedia.org/wiki/%E5%80%92%E6%8E%92%E7%B4%A2%E5%BC%95)。***
69 |
70 | 这里会发现,漏了一个价格的条件。从需求中可知,价格条件是个区间,并且是无穷举的。所以上述的这种穷举条件的 Key-Value 方式是做不到的。这里我们采用 Redis 的另一种数据结构进行实现,有序集合(Sorted Set):
71 |
72 | 
73 |
74 | 将所有商品加入 Key 为价格的有序集合中,值为商品ID,每个值对应的分数为商品价格的数值。这样在 Redis 的有序集合中就可以通过```ZRANGEBYSCORE```命令,根据分数(价格)区间,获取相应结果集。
75 |
76 | 至此,方案三的优化已全部结束,将数据的查询与计算通过缓存的手段,进行了分离。在每次查找时,只需要简单的查找 Redis 几次就能得出结果。查询速度上符合了验收的要求。
77 |
78 | ## 扩展
79 |
80 | ### 分页
81 |
82 | 这里你或许发现了一个严重的功能缺陷,列表查询怎么能没有分页。是的,我们马上来看 Redis 是如何实现分页的。
83 |
84 | 分页主要涉及排序,这里简单起见,就以创建时间为例。
85 |
86 | 如图所示:
87 |
88 | 
89 |
90 |
91 | 图中蓝色部分是以创建时间为分值的商品有序集合,蓝色下方的结果集即为条件计算而得的结果,通过```ZINTERSTORE```命令,赋结果集权重为0,商品时间结果为1,取交集而得的结果集赋予创建时间分值的新有序集合。对新结果集的操作即能得到分页所需的各个数据:
92 |
93 | * 页面总数为:```ZCOUNT```命令
94 | * 当前页内容:```ZRANGE```命令
95 | * 若以倒序排列:```ZREVRANGE```命令
96 |
97 | ### 数据更新
98 |
99 | 关于索引数据更新的问题,有两种方式来进行。一种是通过商品数据的修改,来即时触发更新操作,一种是通过定时脚本来进行批量更新。这里要注意的是,关于索引内容的更新,如果暴力的删除 Key,再重新设置 Key。因为 Redis 中两个操作不会是原子性进行的,所以中间可能存在空白间隙,建议采用仅移除集合中失效元素,添加新元素的方式进行。
100 |
101 | ### 性能优化
102 |
103 | Redis 是内存级操作,所以单次的查询会很快。但是如果我们的实现中会进行多次的 Redis 操作,Redis 的多次连接时间可能是不必要时间消耗。通过使用```MULTI```命令,开启一个事务,将 Redis 的多次操作放在一个事务中,最后通过```EXEC```来进行原子性执行(***注意:这里所谓的事务,只是将多个操作在一次连接中执行,如果执行过程中遇到失败,是不会回滚的***)。
104 |
105 | ## 总结
106 |
107 | 这里只是一个采用 Redis 优化查询搜索的一个简单 Demo,和现有的开源搜索引擎相比,它更轻量,学习成本页相应低些。其次,它的一些思想与开源搜索引擎是类似的,如果再加上词语解析,也可以实现类似全文检索的功能。
108 |
109 | 最后,未完,待续。。。
110 |
111 |
112 |
--------------------------------------------------------------------------------
/201706/rest-api.md:
--------------------------------------------------------------------------------
1 | # [译] REST API URI 设计的七准则
2 |
3 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
4 |
5 | **原文:[http://blog.restcase.com/7-rules-for-rest-api-uri-design](http://blog.restcase.com/7-rules-for-rest-api-uri-design)**
6 |
7 | 在了解 REST API URI 设计的规则之前,让我们快速过一下我们将要讨论的一些术语。
8 |
9 | ## URI
10 | REST API 使用统一资源标识符(URI)来寻址资源。在今天的网站上,URI 设计范围从可以清楚地传达API的资源模型,如:
11 |
12 | [http://api.example.com/louvre/leonardo-da-vinci/mona-lisa]()
13 |
14 | 到那些难以让人理解的,比如:
15 |
16 | [http://api.example.com/68dd0-a9d3-11e0-9f1c-0800200c9a66]()
17 |
18 | Tim Berners-Lee 在他的“Web架构公理”列表中列出了关于 URI 的不透明度的注释:
19 |
20 | ***唯一可以使用标识符的是对对象的引用。当你没有取消引用时,你不应该查看 URI 字符串的内容以获取其他信息。
21 | - Tim Berners-Lee***
22 |
23 | 客户端必须遵循 Web 的链接范例,将 URI 视为不透明标识符。
24 |
25 | REST API 设计人员应该创建 URI,将 REST API 的资源模型传达给潜在的客户端开发人员。 在这篇文章中,我将尝试为 REST API URsI 引入一套[设计规则](http://www.restcase.com/)。
26 |
27 | 在深入了解规则之前,先看一下在 RFC 3986 中定义的通用 URI 语法,如下所示:
28 |
29 | ***URI = scheme "://" authority "/" path ["?" query] ["#" fragment]***
30 |
31 | ## 规则#1:URI中不应包含尾随的斜杠(/)
32 |
33 | 这是作为 URI 路径中最后一个字符的最重要的规则之一,正斜杠(/)不会增加语义值,并可能导致混淆。 REST API 不应该期望有一个尾部的斜杠,并且不应该将它们包含在它们提供给客户端的链接中。
34 |
35 | 许多 Web 组件和框架将平等对待以下两个 URI:
36 |
37 | [http://api.canvas.com/shapes/]()
38 |
39 | [http://api.canvas.com/shapes]()
40 |
41 | ### 然而,URI 中的每个字符都会被计入作为资源的唯一标识。
42 |
43 | 两个不同的 URI 映射到两个不同的资源。如果 URI 不同,那么资源也会不同,反之亦然。因此,REST API 必须生成和传达清晰的 URI,并且不应容忍任何客户端尝试去对一个资源进行模糊的标识。
44 |
45 | 更多的API可能会将客户端重定向到末尾没有斜杠的 URI 上,(他们也可能会返回 301 - 用于重新定位资源的 “Moved Permanently”)。
46 |
47 | ## 规则#2:正斜杠分隔符(/)必须用于指示层次关系
48 | 在 URI 的路径部分的正斜杠(/),用于表示资源之间的层次关系。
49 |
50 | 例如:
51 |
52 | [http://api.canvas.com/shapes/polygons/quadrilaterals/squares]()
53 |
54 | ## 规则#3:应使用连字符( - )来提高 URI 的可读性
55 | 为了使你的 URI 容易被人检索和解释,请使用连字符( - )来提高长路径段中名称的可读性。在任何你将使用英文的空格或连字号的地方,在URI中都应该使用连字符来替换。
56 |
57 | 例如:
58 |
59 | [http://api.example.com/blogs/guy-levin/posts/this-is-my-first-post]()
60 |
61 | ## 规则#4:不得在 URI 中使用下划线(_)
62 |
63 | 文本查看器(如浏览器,编辑器等)经常在 URI 下加下划线,以提供可点击的视觉提示。 根据应用程序的字体,下划线(_)字符可能被这个下划线部分地遮蔽或完全隐藏。
64 |
65 | ### 为避免这种混淆,请使用连字符( - )而不是下划线
66 |
67 | ## 规则#5:URI 路径中首选小写字母
68 |
69 | 方便的话,URI 路径中首选小写字母,因为大写字母有时会导致问题。 RFC 3986 中将 URI 定义为区分大小写,但协议头和域名除外。
70 |
71 | 例如:
72 |
73 | [http://api.example.com/my-folder/my-doc]()
74 |
75 | [HTTP://API.EXAMPLE.COM/my-folder/my-doc]()
76 |
77 | 在 URI 格式规范(RFC 3986)中这两个 URI 是相同的。
78 |
79 | [http://api.example.com/My-Folder/my-doc]()
80 |
81 | 而这个 URI 与上面的两个却是不同的。
82 |
83 | ## 规则#6:文件扩展名不应包含在 URI 中
84 |
85 | 在 Web 上,字符(.)通常用于分隔 URI 的文件名和扩展名。
86 |
87 | 一个 REST API 不应在 URI 中包含人造的文件扩展名,来表示消息实体的格式。 相反,他们应该通过 header 头中 Content-Type 属性的媒体类型来确定如何处理实体的内容。
88 |
89 | [http://api.college.com/students/3248234/courses/2005/fall.json]()
90 |
91 | [http://api.college.com/students/3248234/courses/2005/fall]()
92 |
93 | 不应使用文件扩展名来表示格式偏好。
94 |
95 | 应鼓励 REST API 客户端使用 HTTP 提供的格式选择机制,即请求 header 中的 Accept 属性。
96 |
97 | 为了实现简单的链接和调试的便捷,REST API 也可以通过查询参数来支持媒体类型的选择。
98 |
99 | ## 规则#7:端点名称是单数还是复数?
100 |
101 | 这里采用保持简单的原则。虽然你的语法常识会告诉你使用复数来描述资源的单个实例是错误的,但实际的答案是保持 URI 格式一致并且始终使用复数形式。
102 |
103 | 不必处理奇怪的复数(person/people, goose/geese),这使 API 消费者的生活更美好,也使 API 提供商更容易实现(因为大多数现代框架将在一个通用的 controller 中处理 /students 和 /students/3248234)。
104 |
105 | 但是你怎么处理关系呢?如果一个关系只能存在于另一个资源中,RESTful 原则可以提供有用的指导。我们来看一下这个例子。某个学生有一些课程。这些课程在逻辑上映射到端点 /students,如下所示:
106 |
107 | [http://api.college.com/students/3248234/courses]() - 检索该学生所学习的所有课程清单,学生编号为3248234。
108 |
109 | [http://api.college.com/students/3248234/courses/physics]() - 检索该学生的物理课程,学生编号为3248234。
110 |
111 | ## 结论
112 |
113 | 当你设计 REST API 服务时,你必须注意资源,这些资源由 URI 定义。
114 |
115 | 你正在构建的服务中的每个资源,都将至少有一个 URI 来标识它。这个 URI 最好是有意义的,并能充分描述资源。URI 应遵循可预测的层次结构,以增强可理解性,从而提高可用性:可预测的意义在于它们是一致的,层次结构建立在数据具有结构关系的意义上。
116 |
117 | RESTful API 是为消费者编写的。URI 的名称和结构应该向消费者传达意义。通过遵循上述规则,你将创建一个更加清晰的 REST API。 这不是一个 REST 规则或约束,而是增强了 API。
118 |
119 | 也建议你来看看这篇文章,[http://blog.restcase.com/5-basic-rest-api-design-guidelines](http://blog.restcase.com/5-basic-rest-api-design-guidelines)
120 |
121 | 为你的客户设计,而不是为你的数据。
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-01.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-02.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-03.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-04.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-05.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-06.png
--------------------------------------------------------------------------------
/201707/assets/k8s-frame-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-frame-07.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-01.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-02.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-03.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-04.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-05.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-06.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-07.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-08.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-09.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-10.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-11.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-12.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-13.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-14.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-15.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-16.png
--------------------------------------------------------------------------------
/201707/assets/k8s-pod-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-pod-17.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-01.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-02.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-03.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-04.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-05.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-06.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-07.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-08.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-09.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-10.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-11.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-12.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-13.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-14.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-15.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-16.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-17.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-18.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-19.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-20.png
--------------------------------------------------------------------------------
/201707/assets/k8s-svc-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201707/assets/k8s-svc-21.png
--------------------------------------------------------------------------------
/201707/k8s-architecture.md:
--------------------------------------------------------------------------------
1 | # 带着问题学 Kubernetes 架构
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | 打开这篇文章的同学,想必对 docker 都不会陌生。docker 是一种虚拟容器技术,它上手比较简单,只需在宿主机上起一个 docker engine,然后就能愉快的玩耍了,如:拉镜像、起容器、挂载数据、映射端口等等。相对于 Kubernetes(K8S)的上手,可谓简单很多。
5 |
6 | 那么 K8S 是什么,又为什么上手难度大?K8S 是一个基于容器技术的分布式集群管理系统,是谷歌几十年来大规模应用容器技术的经验积累和升华的一个重要成果。所以为了能够支持大规模的集群管理,它承载了很多的组件,而且分布式本身的复杂度就很高。又因为 K8S 是谷歌出品的,依赖了很多谷歌自己的镜像,所以对于国内的同学环境搭建的难度又增加了一层。
7 |
8 | 下面,我们带着问题,一步步来看 K8S 中到底有哪些东西?
9 |
10 | 首先,既然是个分布式系统,那势必有多个 Node 节点(物理主机或虚拟机),它们共同组成一个分布式集群,并且这些节点中会有一个 Master 节点,由它来统一管理 Node 节点。
11 |
12 | 如图所示:
13 |
14 | 
15 |
16 |
17 | ### 问题一:主节点和工作节点是如何通信的呢?
18 |
19 | 首先,Master 节点启动时,会运行一个 [kube-apiserver](https://kubernetes.io/docs/admin/kube-apiserver/) 进程,它提供了集群管理的 API 接口,是集群内各个功能模块之间数据交互和通信的中心枢纽,并且它页提供了完备的集群安全机制(*后面还会讲到*)。
20 |
21 | 在 Node 节点上,使用 K8S 中的 [kubelet](https://kubernetes.io/docs/admin/kubelet/) 组件,在每个 Node 节点上都会运行一个 kubelet 进程,它负责向 Master 汇报自身节点的运行情况,如 Node 节点的注册、终止、定时上报健康状况等,以及接收 Master 发出的命令,创建相应 [Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/)。
22 |
23 | 在 K8S 中,Pod 是最基本的操作单元,它与 docker 的容器有略微的不同,因为 Pod 可能包含一个或多个容器(可以是 docker 容器),这些内部的容器是共享网络资源的,即可以通过 localhost 进行相互访问。
24 |
25 | 关于 Pod 内是如何做到网络共享的,每个 Pod 启动,内部都会启动一个 pause 容器(google的一个镜像),它使用默认的网络模式,而其他容器的网络都设置给它,以此来完成网络的共享问题。
26 |
27 | 如图所示:
28 |
29 | 
30 |
31 | ### 问题二:Master 是如何将 Pod 调度到指定的 Node 上的?
32 |
33 | 该工作由 [kube-scheduler](https://kubernetes.io/docs/admin/kube-scheduler/) 来完成,整个调度过程通过执行一些列复杂的算法最终为每个 Pod 计算出一个最佳的目标 Node,该过程由 kube-scheduler 进程自动完成。常见的有轮询调度(RR)。当然也有可能,我们需要将 Pod 调度到一个指定的 Node 上,我们可以通过节点的标签(Label)和 Pod 的 nodeSelector 属性的相互匹配,来达到指定的效果。
34 |
35 | 如图所示:
36 |
37 | 
38 |
39 | ***关于标签(Label)与选择器(Selector)的概念,后面会进一步介绍***
40 |
41 | ### 问题三:各节点、Pod 的信息都是统一维护在哪里的,由谁来维护?
42 |
43 | 从上面的 Pod 调度的角度看,我们得有一个存储中心,用来存储各节点资源使用情况、健康状态、以及各 Pod 的基本信息等,这样 Pod 的调度来能正常进行。
44 |
45 | 在 K8S 中,采用 [etcd 组件](https://github.com/coreos/etcd) 作为一个高可用强一致性的存储仓库,该组件可以内置在 K8S 中,也可以外部搭建供 K8S 使用。
46 |
47 | 集群上的所有配置信息都存储在了 etcd,为了考虑各个组件的相对独立,以及整体的维护性,对于这些存储数据的增、删、改、查,统一由 kube-apiserver 来进行调用,apiserver 也提供了 REST 的支持,不仅对各个内部组件提供服务外,还对集群外部用户暴露服务。
48 |
49 | 外部用户可以通过 REST 接口,或者 kubectl 命令行工具进行集群管理,其内在都是与 apiserver 进行通信。
50 |
51 | 如图所示:
52 |
53 | 
54 |
55 | ### 问题四:外部用户如何访问集群内运行的 Pod ?
56 |
57 | 前面讲了外部用户如何管理 K8S,而我们更关心的是内部运行的 Pod 如何对外访问。使用过 docker 的同学应该知道,如果使用 bridge 模式,在容器创建时,都会分配一个虚拟 IP,该 IP 外部是没法访问到的,我们需要做一层端口映射,将容器内端口与宿主机端口进行映射绑定,这样外部通过访问宿主机的指定端口,就可以访问到内部容器端口了。
58 |
59 | 那么,K8S 的外部访问是否也是这样实现的?答案是否定的,K8S 中情况要复杂一些。因为上面讲的 docker 是单机模式下的,而且一个容器对外就暴露一个服务。在分布式集群下,一个服务往往由多个 Application 提供,用来分担访问压力,而且这些 Application 可能会分布在多个节点上,这样又涉及到了跨主机的通信。
60 |
61 | 这里,K8S 引入了 service 的概念,将多个相同的 Pod 包装成一个完整的 service 对外提供服务,至于获取到这些相同的 Pod,每个 Pod 启动时都会设置 labels 属性,在 service 中我们通过选择器 selector,选择具有相同 name 标签属性的 Pod,作为整体服务,并将服务信息通过 apiserver 存入 etcd 中,该工作由 Service Controller 来完成。同时,每个节点上会启动一个 [kube-proxy](https://kubernetes.io/docs/admin/kube-proxy/) 进程,由它来负责服务地址到 Pod 地址的代理以及负载均衡等工作。
62 |
63 | 如图所示:
64 |
65 | 
66 |
67 | ### 问题五:Pod 如何动态扩容和缩放?
68 |
69 | 既然知道了服务是由 Pod 组成的,那么服务的扩容也就意味着 Pod 的扩容。通俗点讲,就是在需要时将 Pod 复制多份,在不需要后,将 Pod 缩减至指定份数。K8S 中通过 Replication Controller 来进行管理,为每个 Pod 设置一个期望的副本数,当实际副本数与期望不符时,就动态的进行数量调整,以达到期望值。期望数值可以由我们手动更新,或自动扩容代理来完成。
70 |
71 | 如图所示:
72 |
73 | 
74 |
75 | ### 问题六:各个组件之间是如何相互协作的?
76 |
77 | 最后,讲一下 kube-controller-manager 这个进程的作用。我们知道了 ectd 是作为集群数据的存储中心, apiserver 是管理数据中心,作为其他进程与数据中心通信的桥梁。而 Service Controller、Replication Controller 这些统一交由 kube-controller-manager 来管理,kube-controller-manager 作为一个守护进程,每个 Controller 都是一个控制循环,通过 apiserver 监视集群的共享状态,并尝试将实际状态与期望不符的进行改变。关于 Controller,manager 中还包含了 Node 节点控制器(Node Controller)、资源配额管控制器(ResourceQuota Controller)、命名空间控制器(Namespace Controller)等。
78 |
79 | 如图所示:
80 |
81 | 
82 |
83 | ## 总结
84 |
85 | 本文通过问答的方式,没有涉及任何深入的实现细节,从整体的角度,概念性的介绍了 K8S 中涉及的基本概念,其中使用相关的包括有:
86 |
87 | * Node
88 | * Pod
89 | * Label
90 | * Selector
91 | * Replication Controller
92 | * Service Controller
93 | * ResourceQuota Controller
94 | * Namespace Controller
95 | * Node Controller
96 |
97 | 以及运行进程相关的有:
98 |
99 | * kube-apiserver
100 | * kube-controller-manager
101 | * kube-scheduler
102 | * kubelet
103 | * kube-proxy
104 | * pause
105 |
106 | 这也是我学习 K8S 后对其整体架构的一次总结,因为在刚上手时,阅读官方文档,确实被如此多的内容搞得有点晕,所在在这里进行了简单的梳理。文中有理解不到位的地方,欢迎指正!
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/201707/k8s-pod.md:
--------------------------------------------------------------------------------
1 | # 带着问题学 Kubernetes 基本单元 Pod
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | * 文章一:[带着问题学 Kubernetes 架构](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-architecture.md)
5 |
6 | ## 当前环境
7 | 1. Mac OS 10.11.x
8 | 2. kubectl == v1.6.4
9 | 3. minikube == v0.19.1
10 | 4. docker == 1.11.1
11 |
12 | ## 要点
13 |
14 | * 使用 minikube 本地搭建 K8S
15 | * 证明每个 Pod 都有一个 Pause
16 | * Pod 的基本使用
17 | * Pod 生命周期的各种状态
18 | * Pod 的管理问题
19 | * Pod 的多容器场景
20 | * Pod 的数据共享问题
21 | * Pod 的网络共享的实现原理
22 |
23 |
24 | ## 准备工作
25 |
26 | ***关于 minikue 的安装,[官方文档](https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/)已经和详尽了,这里就不讲了。***
27 |
28 | * 启动 minikube(*minikube 是一个能让 K8S 本地运行的工具*)
29 |
30 | ```
31 | minikube start \
32 | --vm-driver=xhyve \
33 | --docker-env HTTP_PROXY=http://your-http-proxy-host:your-http-proxy-port \
34 | --docker-env HTTPS_PROXY=http(s)://your-https-proxy-host:your-https-proxy-port
35 | ```
36 |
37 | 稍微解释下 ```--vm-driver=xhyve```,如果早期有在 Mac 上安装 docker 的同学,应该知道 docker 会在你的电脑上安装一个 VirtualBox 的虚拟驱动。因为 docker 支持的只有 Linux 系统,为了让它能在 Mac 上运行,实际上是运行由 VirtualBox 运行的虚拟环境下的。```--vm-driver``` 默认的参数也是 VirtualBox。再来看 xhyve,它实际和 VirtualBox 类似,简单理解,它是一个更轻量的虚拟技术。
38 |
39 | ***如果 docker 下载 gcr.io 镜像有困难的话,建议配置 docker 代理(这里推荐一个 [多态代理](https://duotai.love/))。另一种取巧的方式是,先将所需的镜像通过 docker hub 下载下来,再通过 docker tag 的方式来进行重命名。***
40 |
41 | * 配置本机的 docker 环境
42 |
43 | 上面也说了,K8S 是运行在 xhyve 建立的虚拟环境下。所以本地的 docker 命令是无法连接到 K8S 所依赖的 docker-daemon 的。当然,你可以通过 ```minikube ssh``` 进入虚拟环境,再使用 docker 命令进行观察。
44 |
45 | 更直观的是,通过 ```eval $(minikube docker-env)``` 将本地 docker 与 K8S 依赖的 docker 进行绑定。这样在本地就可以通过 docker 命令观察 K8S 中的容器变化了。
46 |
47 | ***```eval $(minikube docker-env -u)``` 取消与 minikube 中的 docker 进行绑定。***
48 |
49 | ## minikube 初始状态
50 |
51 | 好了,K8S 已经成功运行起来了。我们下面来初步观察一下当前 Pod 的运行情况,同时也验证一下上一篇所说的“一个 Pod 一个 Pause”的真伪了。
52 |
53 | * 查看 K8S 上所有命名空间下的 Pod(***刚启动的话,可能需要一定时间来拉取相应的镜像。***)
54 |
55 | ```
56 | kubectl get pods --all-namespaces
57 | ```
58 |
59 | 
60 | 图1
61 |
62 | 可以看到 K8S 一共启动了3个 Pod,并且都是在 kube-system 命名空间下的。具体这3 个 Pod 的作用,大家唉看名字应该能猜到一点,不在本文介绍范围内。
63 |
64 | * 验证每个 Pod 内都会运行一个 Pause 容器
65 |
66 | ```
67 | docker ps
68 | ```
69 |
70 | 
71 | 图2
72 |
73 | 从图中可以看出,确实运行着3个 pause 容器。同时运行着5个容器,数字也与图1中 READY 的总数一致(1+3+1)。
74 |
75 | ## Pod
76 |
77 | ### Q1:如何运行和查看 Pod 信息?
78 |
79 | 先以命令式的方式进行启动,这也是最简单的启动方式,但不建议用于生产环境。(*后面会涉及以配置文件进行部署。*)
80 |
81 | ```
82 | kubectl run nginx --image=nginx
83 | ```
84 |
85 | ***注意:在不指定命名空间时,默认为 default,其他指令基本都是如此。***
86 |
87 | 是不是和```docker run```的命令很像,轻松上手。我们看一下运行情况。
88 |
89 | 
90 | 图3
91 |
92 | 图中显示,deployment 已经被创建。可以把它看作是 Pod 的管理者,是 controller-manager 中的一员(*后面还会讲到*)。
93 |
94 | ```
95 | kubectl get deploy
96 | ```
97 | 
98 | 图4
99 |
100 | 查看 Pod 列表
101 |
102 | ```
103 | kubectl get pods
104 | ```
105 | 
106 | 图5
107 |
108 | 查看指定 Pod 的详细描述
109 |
110 | ```
111 | kubectl describe pod $POD_NAME
112 | ```
113 | 
114 | 图6
115 |
116 |
117 | ### Q2:Pod 的生命周期中所经历的状态有哪些?
118 |
119 | 从图5中可以看出,Pod 当前的状态是 Running,如果自己尝试的话,可能会遇到其他状态。因为 Pod 所处的生命周期的阶段可能不一样。
120 |
121 | 下面常见的有这几种:
122 |
123 | * Pending:Pod 定义正确,提交到 Master,但其包含的容器镜像还未完全创建。通常处在 Master 对 Pod 的调度过程中。
124 | * ContainerCreating:Pod 的调度完成,被分配到指定 Node 上。处于容器创建的过程中。通常是在拉取镜像的过程中。
125 | * Running:Pod 包含的所有容器都已经成功创建,并且成功运行起来。
126 | * Successed:Pod 中所有容器都成功结束,且不会被重启。这是 Pod 的一种最终状态。
127 | * Failed:Pod 中所有容器都结束,但至少一个容器以失败状态结束。这也是 Pod 的一种最终状态。
128 |
129 | ### Q3:如何保证 Pod 正常运行?
130 |
131 | 从上面的各种状态可知,由于种种原因,Pod 可能处于上述状态的任何一种。对于异常的状态,K8S 是通过一种期望值与当前值的比对机制,来保证 Pod 的正常运行。其内部是通过 replicaset(*下一代 ReplicationController*) 做到的。
132 |
133 | ```
134 | kubectl get rs
135 | ```
136 | 
137 | 图7
138 |
139 | 观察 replicaset 发现,存在一个 nginx-xxx 的 replicaset,它的期望值与当前值都为1,表示需要一个 Pod,并且已经处于 READY 状态。
140 |
141 | 可是我们并没有直接的创建过它。而且一般也不会直接去使用它。我们通常会使用上层的 Deployment 来进行调用,因为它还提供了一些其他的特性,如更新与回滚。
142 |
143 | 验证:当手动删除 Pod 时,Deployment 会自动创建一个新的 Pod,来确保与期望值的匹配。
144 |
145 | ```
146 | kubectl delete pod $POD_NAME
147 | ```
148 | 
149 | 图8
150 |
151 |
152 | ***Deployment 相较 ReplicationController 而言,除了提供 ReplicationController 的基本功能,还支持声明式的更新和回滚。
153 | 不过目前还是 beta 版。***
154 |
155 | ### Q4:如何正确删除 Pod ?
156 |
157 | 通过上述验证,我们直接删除 Pod后,依然会创建新的 Pod,这是它的保障机制。我们只有删除它的上层管理者,即 deployment,那么由它产生的 replicaset 和 Pod 会自动删除。
158 |
159 | ```
160 | kubectl delete deploy $DEPLOY_NAME
161 | ```
162 | 
163 | 图9
164 |
165 | ### Q5:什么场景下 Pod 会运行多个容器?
166 |
167 | 为了后面演示的方便,举一个服务自动构建的例子。(*实用否,大家可以自行判断*)
168 |
169 | 假设有两个容器,一个是 nginx 容器,做静态服务器,一个是 git-sync 容器,用于定时监测 git 仓库上代码的变化,拉取最新代码到本地。这是两个独立的容器,如果把它们放在一个 Pod 里面,Pod 的特点是内部网络共享、数据空间共享。这样就大大减少了原先跨容器访问的复杂度。
170 |
171 | ***这里的例子举得可能不是特别好,如果涉及跨容器的网络通信,那么 Pod 的优势会得到更好的体现。***
172 |
173 | 这里通过配置文件来启动包含 nginx、git-sync 容器的 Pod,
174 |
175 | 配置文件 nginx-git.yml 具体内容如下:(*[官方参考文档](https://kubernetes.io/docs/resources-reference/v1.6/#replicationcontroller-v1-core)*):
176 |
177 | ```
178 | apiVersion: apps/v1beta1
179 | kind: Deployment
180 | metadata:
181 | name: nginx-git
182 | spec:
183 | replicas: 1
184 | template:
185 | metadata:
186 | labels:
187 | app: nginx
188 | spec:
189 | containers:
190 | - name: nginx # 启动 nginx 容器
191 | image: nginx
192 | volumeMounts: # 挂载数据卷
193 | - mountPath: /usr/share/nginx/html
194 | name: git-volume
195 | - name: git-sync # 启动 git-sync 容器
196 | image: openweb/git-sync
197 | env:
198 | - name: GIT_SYNC_REPO
199 | value: "https://github.com/jasonGeng88/test.git"
200 | - name: GIT_SYNC_DEST
201 | value: "/git"
202 | - name: GIT_SYNC_BRANCH
203 | value: "master"
204 | - name: GIT_SYNC_REV
205 | value: "FETCH_HEAD"
206 | - name: GIT_SYNC_WAIT
207 | value: "10"
208 | volumeMounts: # 挂载数据卷
209 | - mountPath: /git
210 | name: git-volume
211 | volumes: # 共享数据卷
212 | - name: git-volume
213 | emptyDir: {}
214 | ```
215 |
216 | 配置文件有必要的注释,应该比较容易理解,这里简单讲下两点:
217 |
218 | 1. GIT 仓库地址下只有一个 index.html 文件,内容为:```hello world 1!```。
219 | 2. 关于共享数据 ```volumes``` 的配置问题。共享数据存储的问题主要分为:数据临时存储与持久性存储。
220 | * 临时存储:
221 |
222 | ```
223 | volumes:
224 | - name: volume-name
225 | emptyDir: {}
226 | ```
227 |
228 | * 挂载宿主机存储:
229 |
230 | ```
231 | volumes:
232 | - name: volume-name
233 | hostPth:
234 | path: "/data"
235 | ```
236 |
237 | * 当然还有网络磁盘存储等(如谷歌公有云)
238 |
239 | ***注意:即使是临时存储,因为数据是 Pod 下所有容器共享的,任何一个容器重启,数据都不会丢失。当 Pod 结束时,临时性数据才会丢失。***
240 |
241 | #### 演示:
242 |
243 | * **以配置文件运行 deployment:**
244 |
245 | ```
246 | kubectl create -f nginx-git.yml
247 | ```
248 |
249 | 
250 | 图10
251 |
252 | * **访问 Pod 查看效果:**
253 |
254 | 更实用的场景是将 Pod 作为 Service 暴露,通过 Service 来进行访问,因为本文主要讲 Pod,不想引入 Service 的概念。所以我们直接来访问 Pod。
255 |
256 |
257 | * 1. 查看 Pod 对应的 IP (*也可在 Pod 详细描述中获得*)
258 |
259 | ```
260 | kubectl get -o template pod/$POD_NAME --template={{.status.podIP}}
261 | ```
262 |
263 | 
264 | 图11
265 |
266 | * 2. 进入 K8S 集群,访问 Pod 中 nginx 服务
267 |
268 | 因为获得 Pod IP 是 K8S 集群内部创建的,外面是无法与其通信的。所以我们需要通过命令 ```minikube ssh``` 进入集群,才可进行 Pod 访问。
269 |
270 | 
271 | 图12
272 |
273 | 再把 git 仓库上的 index.html 内容改为 ```hello world 2!```,再访问一下 Pod 观察结果(需等待几秒)。
274 |
275 | 
276 | 图13
277 |
278 | ### Q6:Pod 内部是如何实现网络共享的?
279 |
280 | 最后,我们来看下 Pod 的 IP 是如何生成的,以及内部容器是如何关联的。
281 |
282 | 通过 ```docker ps``` 我们发现,nginx-git 最终会生成三个容器,分别是 git-sync, nginx, pause。
283 |
284 | 
285 | 图14
286 |
287 | 通过 ```docker inspect $CONTAINER_ID``` 我们发现,git-sync 与 nginx 的网络模型都是使用了同一个容器ID的网络,而该容器ID 正好对应了 pause 这个容器。
288 |
289 | 
290 | 图15
291 |
292 | 我们再看 pause 容器的网络模型,发现它使用的是 bridge 模式,而分配的 IP 正是对应了 Pod 的 IP。由此可知,Pod 内所有容器都是通过 pause 的网络进行通信的。
293 |
294 | 
295 | 图16
296 |
297 | docker 中默认的网络模式就是 Bridge 模式。
298 |
299 | #### 证明:
300 |
301 | 上面演示已经证明了集群间通过 Pod IP 是可以访问到容器内的服务的。我们下面证明,Pod 内容器之间通过 localhost 进行互相访问。
302 |
303 | 我们进入 git-sync 容器,通过访问 localhost 的 80 端口,看是否能访问到 nginx 容器。
304 |
305 | 
306 | 图17
307 |
308 | 答案很明显了!
309 |
310 | ## 总结
311 |
312 | 本文没有按部就班的去一一介绍 Pod 的相关功能点,而是通过 K8S 的本地搭建,从 Pod 的基本使用开始,通过几个问题穿插着介绍了 Pod 的一些主要的概念与使用。
313 |
314 | 本文知识点总结:
315 |
316 | * minikube 的启动与连接
317 | * kubectl 的使用
318 | * Pod 的命令式与配置文件的启动
319 | * Pod 的查看方式(概况与详情)
320 | * Pod 生命周期中的各个状态
321 | * deployment 对 Pod 的管理
322 | * deployment, replicaset, pod 三者的关系
323 | * Pod 内多容器的场景
324 | * Pod 的共享数据的临时存储与文件挂载的持久存储
325 | * Pod 中 pause 的作用及网络通信的原理
326 |
327 | 可能关于 Pod 的有些知识点没有讲到,或者有讲的不对的地方,也欢迎提出和指正!
328 |
329 | 后面,也会去讲关于 service,configMap,update,rollout 相关的一些内容,希望对您有帮助~
330 |
331 |
--------------------------------------------------------------------------------
/201707/k8s-service.md:
--------------------------------------------------------------------------------
1 | # 带着问题学 Kubernetes 抽象对象 Service
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | * 文章一:[带着问题学 Kubernetes 架构](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-architecture.md)
5 | * 文章二:[带着问题学 Kubernetes 基本单元 Pod](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-pod.md)
6 |
7 | ## 当前环境
8 | 1. Mac OS 10.11.x
9 | 2. kubectl == v1.6.4
10 | 3. minikube == v0.19.1
11 | 4. docker == 1.11.1
12 |
13 | ## 知识点
14 | * Service 的 Selector 与 Label 匹配机制
15 | * Service 与 Pods 的地址映射关系
16 | * kube-proxy 的 iptables 代理机制
17 | * Service 的服务发现机制
18 | * Service 的服务暴露方式
19 |
20 | ## 前言
21 |
22 | 上一篇讲述了 Pod 的相关内容,了解了 Pod 的定义、生命周期以及通信机制等。正如上文说的,Pod 是存在生命周期的,它的崩溃、更新都是以创建新 Pod 替换原有的 Pod 的方式进行的,所以通过固定 Pod 地址的访问变得不太可行。我们需要通过一种上层调用的方式,来解决底层 Pod 的动态变化的场景。
23 |
24 | 庆幸,K8S 引入了 Service 这个抽象的概念。Service 会创建一个虚拟的服务,由它来整合集群内的 Pod。Service 会虚拟出一个 VIP,并在它销毁之前保持该 VIP 地址保持不变。通过对它的访问,以代理的方式负载到对应的 Pod 上,同时 Pod 生命周期的变换,也会及时反应在代理上。
25 |
26 | 下面我们几种常见的场景,来具体看看 Service 是如何工作的。
27 |
28 | ## 环境准备
29 | ### 演示镜像
30 | * 镜像名:jasonn/php-echoserver
31 | * 作用:打印当前容器的 IP
32 |
33 | ### K8S Pod 创建
34 | * 文件名:deploy-echoserver.yml (*这里以 Deployment 的方式来创建与管理 Pod*)
35 | * 文件内容:
36 |
37 | ```yaml
38 | apiVersion: apps/v1beta1
39 | kind: Deployment
40 | metadata:
41 | # Deployment 实例名称
42 | name: echoserver
43 | spec:
44 | # 设置 Pod 个数
45 | replicas: 2
46 | template:
47 | metadata:
48 | # 设置 Pod 标签
49 | labels:
50 | app: echoserver
51 | spec:
52 | # 运行 docker 镜像
53 | containers:
54 | - name: echoserver
55 | image: jasonn/php-echoserver
56 | ```
57 |
58 | * 启动命令:
59 |
60 | ```
61 | kubectl create -f deploy-echoserver.yml
62 | ```
63 |
64 | 至此,准备工作全部完成。短暂的等待后,Pod 创建成功,并且也由 deployment 管理着。
65 |
66 | 查看 deployment 启动情况:
67 |
68 | 
69 |
70 | 对 Pod 的访问情况如下(*通过```kubectl describe pods```获取 Pod 的 IP 地址*):
71 |
72 | 
73 |
74 | 
75 |
76 | 
77 |
78 |
79 | ## 问题
80 | ### ***场景1:现在 K8S 上运行着2个 Pod。我们希望通过上述所说的 Service 来整合这两个 Pod 的访问,完成对它们的统一访问,而不用向具体的 Pod 发出请求。***
81 |
82 | ### Q1: Service 如何对 Pod 进行整合
83 | 这里所说的 Pod 默认都是带有标签(label)的,我们之前创建的两个 Pod 所赋予的标签是 ```app: echoserver```,所以我们在创建 Service 时,就要通过选择器(selector)来获取符合条件的 Pod 进行整合。
84 |
85 | Service 创建脚本内容如下(*service-echoserver.yml*):
86 |
87 | ```yaml
88 | apiVersion: v1
89 | kind: Service
90 | metadata:
91 | # Service 实例名称
92 | name: svc-echoserver
93 | spec:
94 | ports:
95 | - protocol: TCP
96 | # Service 端口地址
97 | port: 8080
98 | # Pod 端口地址
99 | targetPort: 80
100 | selector:
101 | # 匹配符合标签条件的 Pod
102 | app: echoserver
103 | ```
104 |
105 | 创建 Service 命令:
106 |
107 | ```
108 | kubectl create -f service-echoserver.yml
109 | ```
110 |
111 | 由此,我们创建好了一个 Service,同时也生成了一个对应的 VIP。
112 |
113 | 查看 Serivce 创建情况:
114 |
115 | 
116 |
117 | 下面,我们来验证下是否如之前所说,对 VIP 的访问能访问到 Pod 的内容。
118 |
119 | 
120 |
121 | *我们发现不仅能成功访问,而且还提供了负载均衡的功能。后面会讲负载是怎么实现的*
122 |
123 | *PS: 标签筛选查找范围仅在同个命名空间(namespace)内。*
124 |
125 | ***
126 |
127 | ### ***场景2:了解了 Service 是通过 label & selecor 来进行整合 Pod 的。那如果 Pod 不存在标签,又或者是在不同 Namespace 下,也可能是 K8S 集群外的一个服务。现实情况往往更加复杂,这样的情况下,Service 又该如何整合。***
128 |
129 | ### Q2: Service 与 Pod 的地址映射关系由谁管理?
130 | 这里引出了另一个概念 Endpoints。我们先来看看它的一个具体情况。
131 |
132 | 
133 |
134 | 发现在 Service 创建的同时,还生成了一个 Endpoints。 该 Endpoints 与 Service 同名,它所暴露的地址信息正是对应 Pod 的地址。由此猜测是 Endpoints 维护了 Service 与 Pod 的映射关系。
135 |
136 | 为了验证我们的猜测,我们手动删除 Endpoints,发现之前能成功访问到 Pod 的 VIP,现在已经已经访问不到了。
137 |
138 | 
139 |
140 | 我们在手动把 Endpoints 创建回来,创建脚本如下(*endpoint-echoserver.yml*):
141 |
142 | ```yaml
143 | apiVersion: v1
144 | kind: Endpoints
145 | metadata:
146 | # Endpoints 实例的名称
147 | name: svc-echoserver
148 | subsets:
149 | - addresses:
150 | - ip: 172.17.0.5
151 | - ip: 172.17.0.6
152 | ports:
153 | - port: 80
154 | ```
155 |
156 | 创建命令:
157 |
158 | ```
159 | kubectl create -f endpoint-echoserver.yml
160 | ```
161 |
162 | ***注意:Endpoints 与 Service 的绑定关系通过名称来关联的,所以这两者的名称(name)一定要一致。***
163 |
164 | *如果创建失败,出现的错误信息是“...endpoints "svc-echoserver" already exists”,说明 Service 已经更新了 Endpoints。这里就说到了 Service 会定期去检查 Pod 的状态,并且将结果更新到 Endpoints 上。*
165 |
166 | VIP 再次访问时又能成功访问到,如图:
167 |
168 | 
169 |
170 | 现在我们已经能轻松的解决场景2的问题了,在创建 Service 时,只要不设置 ```Selector``` 属性,那么将不会自动创建 Endpoints,这是我们可以根据需求手动的创建指定地址(address)的 Endpoints,来解决标签无法实现的整合。
171 |
172 | ***
173 |
174 | ### ***场景3:知道了 Service、Endpoints、Pod 的三者关系后,我们来具体看看所说的代理到底是如何实现的。从之前 K8S 的架构中,我们知道 Service 的代理是由 kube-proxy 实现的。而它的代理模式(Proxy mode)主要有两种:userspace 与 iptables。自 K8S v1.2 开始,默认的代理模式就是 iptables,并且它的性能也是要高于 userspace 的,所以在这儿只讨论 iptables 的实现。***
175 |
176 | ### Q3:kube-proxy 是如何使用 iptables 做到服务代理的(*对于 iptables 不了解的同学可以直接跳过*)?
177 |
178 | 我们现在要做的呢,是将 VIP 请求给转发到对应的 Pod 上。而实现此的正是 iptables。
179 |
180 | 了解 iptables 的同学都知道四表五链的概念,而做端口地址转发的呢,主要是在 nat 表中实现。我们下面看一下一个 VIP 请求在 nat 表中是如何一步步被转发到 Pod 上的。
181 |
182 | * 1. 根据 iptables 的机制,请求是先到 nat 表的 PREROUTING 链(chain)上的,它的规则如下:
183 |
184 | 
185 |
186 | 从图中发现,请求首先经过 KUBE-SERVICE 链,其次再到 DOCKER 链上的。
187 |
188 | * 2. 我们看一下 KUBE-SERVICE 的情况:
189 |
190 | 
191 |
192 | 我们发现 KUBE-SERVICE 中包含了一系列 Service 的规则。根据我们请求的 VIP 的目的地址,对应到了下一个名叫 KUBE-SVC-PRQ3AXYQLQGIVVIU 的 Service 链上。
193 |
194 | * 3. KUBE-SVC-PRQ3AXYQLQGIVVIU 规则如下:
195 |
196 | 
197 |
198 | 从规则的名字上可以看出,该条 Service 链上记录的是2个 Endpoints 链,具体的选择是通过 50% 的随机性的进行决定(***这也是它的一个负载规则***)。
199 |
200 | * 4. 我们来看第一个名叫 KUBE-SEP-JSFY3ZFM2EVD64VQ 的 Endpoints 链的情况:
201 |
202 | 
203 |
204 | 从图中,我们已经很清晰的看到了它转发到 Pod 的具体规则。
205 |
206 | * 5. 下面以一张简单的流程图,看一下请求的转发情况:
207 |
208 | 
209 |
210 | 关于 DOCKER 链的跟踪,方法是差不多的,请求 从 nat 表结束后,在到 filter 表中,这里就不加以说明了。
211 |
212 | 而这些规则的实现正是由 Service、Endpoints 来完成的。我们在创建、更新、以及其自身的检测机制,都会对这些规则进行更新。
213 |
214 | ***
215 |
216 | ### ***场景4:Service 的创建、内部结构以及映射关系,我们都了解了。下面我们就要关心如何优雅的使用它,上面我们都是通过 Service 的 VIP 进行访问的。这存在的问题是,如果有服务与服务之间的调用,难道我还要知道所调用服务的 VIP 不成,对于 VIP 的访问能否更通用一些。***
217 |
218 | ### Q4:Service 的服务发现机制是怎样的?
219 |
220 | 对于服务与服务间的调用,实际上就是 Pod 对 Servie 的调用。而 Pod 是如何发现 Service 的,这里可选择的方式有2种。
221 |
222 | 我们通过启动一个名为 busybox 的 Pod 来观察这两种方式:
223 |
224 | ```
225 | kubectl run -i --tty busybox --image=busybox --restart=Never -- sh
226 | ```
227 |
228 | * 环境变量:
229 |
230 | 在 Pod 中,集群中的 Service 会以环境变量的方式赋值在容器中,我们可以通过 ```{SERVICE_NAME}_SERVICE_HOST``` 和 ```{SERVICE_NAME}_SERVICE_PORT``` 进行获取(*对于有多个 Port 的,可以通过带指定 PORT 名称的变量获得。*)。
231 |
232 | busybox 中 环境变量如下:
233 |
234 | 
235 |
236 |
237 | 查看访问情况:
238 |
239 | 
240 |
241 | * dns 解析:
242 |
243 | 第二种方式是通过 kube-dns 对 Service 进行域名解析,同样能达到服务发现的目的。
244 |
245 | 查看 DNS 域名解析配置:
246 | 
247 |
248 | 通过 nslookup 查询 dns 记录:
249 | 
250 |
251 | 查看访问结果:
252 | 
253 |
254 | ***
255 |
256 | ### ***场景5:集群间的服务调用解决了,可说到底还是通过的 VIP 进行的访问。VIP 对于集群外的终端用户,是无法访问的。所以我们得通过服务暴露的方式,让终端用户能与集群内的服务进行通信。***
257 |
258 | ### Q5:Service 是如何对外暴露服务的?
259 |
260 | 在 Service 的配置文件中,属性```spec.type```就是用来设置服务暴露的方式,它提供的三种方式如下:
261 |
262 | * ClusterIP: 提供一个集群内部的虚拟IP以供Pod访问(*默认类型,我们上述使用的正是这种方式*)。
263 | * NodePort: 在每个Node上打开一个端口以供外部访问。
264 | * LoadBalancer: 通过外部的负载均衡器来访问(*一般需要云提供商提供 LB 支持*)。
265 |
266 | 我们这里简单起见,还是通过 NodePort 方式进行。
267 |
268 | 修改 Service 配置文件,并重新启动:
269 |
270 | ```yaml
271 | apiVersion: v1
272 | kind: Service
273 | metadata:
274 | # Service 实例名称
275 | name: svc-echoserver
276 | spec:
277 | ports:
278 | - protocol: TCP
279 | # Service 端口地址
280 | port: 8080
281 | # Pod 端口地址
282 | targetPort: 80
283 | selector:
284 | # 匹配符合标签条件的 Pod
285 | app: echoserver
286 | type: NodePort
287 | ```
288 |
289 | ***注意:这里如果要以```kubecrl replace -f service-echoserver.yml```方式进行平滑更新,配置中需添加```spec.clusterIP```属性,值为当前 Service 的 VIP,否则更新会失败。这也符合了一开始说的 Service 在它终止之前,VIP 是不会改变的。***
290 |
291 | 查看 Service 更新情况:
292 |
293 | 
294 |
295 | 外部访问(*该 Node 地址是:192.168.64.6*):
296 |
297 | 
298 |
299 | ## 总结
300 | 文本从 Service 的标签与选择器开始,讲了 Service 整合 Pod 的过程,引出了 Service, Endpoints, Pods 三者的关系情况。随后又通过 iptables 详细展开了 kube-proxy 的代理机制。最后,以 Service 的集群内与集群外的访问设置,讲述了 Service 的服务发现与服务暴露机制。
301 |
302 | 关于 Service 的有遗漏重要的知识点,或者有讲的不对的地方,也欢迎提出和指正!最后,希望本篇对你学习 K8S 有所帮助~
303 |
304 |
305 |
--------------------------------------------------------------------------------
/201707/thread-pool.md:
--------------------------------------------------------------------------------
1 | # 线程池的正确打开方式
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 前言
5 | 今天,想拿快递公司举个例子,和大家聊聊线程池相关的几个核心参数。这里,我们把线程池比作快递公司,把对线程池的调用比作投递包裹,而线程就看作是快递员,负责把快递送到指定地点。
6 |
7 | 首先,我们得成立一个公司,这样才有人来找我们邮寄包裹。这就是“线程池的创建过程”。
8 |
9 | 既然是公司,那得有员工吧。于是就有了
10 |
11 | * 首先,我们得创建一个工厂。
12 |
13 | * corePoolSize:核心线程数
14 | * maximumPoolSize:最大线程数
15 | * keepAliveTime:线程存活时间
16 | * workQueue:工作队列
17 | * ThreadFactory:线程工厂
18 | * RejectedExecutionHandler:丢弃策略
19 |
20 | ## 核心参数
21 |
22 | * corePoolSize:核心线程数
23 | * maximumPoolSize:最大线程数
24 | * keepAliveTime:线程存活时间
25 | * workQueue:工作队列
26 | * ThreadFactory:线程工厂
27 | * RejectedExecutionHandler:丢弃策略
28 |
29 | ## scheduling delayQueue
30 |
31 | LockSupport.parkNanos(Object blocker, long nanos);
32 |
33 | ## shutdown && shutdownNow
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/201708/assets/java-nio-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/java-nio-01.jpg
--------------------------------------------------------------------------------
/201708/assets/java-nio-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/java-nio-02.jpg
--------------------------------------------------------------------------------
/201708/assets/java-nio-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/java-nio-03.png
--------------------------------------------------------------------------------
/201708/assets/java-nio-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/java-nio-04.png
--------------------------------------------------------------------------------
/201708/assets/java-socket-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/java-socket-01.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-01.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-02.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-03.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-04.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-05.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-06.png
--------------------------------------------------------------------------------
/201708/assets/microservice-for-failure-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201708/assets/microservice-for-failure-07.png
--------------------------------------------------------------------------------
/201708/java-nio.md:
--------------------------------------------------------------------------------
1 | # JAVA NIO 一步步构建I/O多路复用的请求模型
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | * 文章一:[JAVA 中原生的 socket 通信机制](https://github.com/jasonGeng88/blog/blob/master/201708/java-socket.md)
5 |
6 | ## 当前环境
7 | 1. jdk == 1.8
8 |
9 | ## 代码地址
10 | git 地址:[https://github.com/jasonGeng88/java-network-programming](https://github.com/jasonGeng88/java-network-programming)
11 |
12 | ## 知识点
13 | * nio 下 I/O 阻塞与非阻塞实现
14 | * SocketChannel 介绍
15 | * I/O 多路复用的原理
16 | * 事件选择器与 SocketChannel 的关系
17 | * 事件监听类型
18 | * 字节缓冲 ByteBuffer 数据结构
19 |
20 | ## 场景
21 |
22 | 接着上一篇中的站点访问问题,如果我们需要并发访问10个不同的网站,我们该如何处理?
23 |
24 | 在上一篇中,我们使用了```java.net.socket```类来实现了这样的需求,以一线程处理一连接的方式,并配以线程池的控制,貌似得到了当前的最优解。可是这里也存在一个问题,连接处理是同步的,也就是并发数量增大后,大量请求会在队列中等待,或直接异常抛出。
25 |
26 | 为解决这问题,我们发现元凶处在“一线程一请求”上,如果一个线程能同时处理多个请求,那么在高并发下性能上会大大改善。这里就借住 JAVA 中的 nio 技术来实现这一模型。
27 |
28 | ## nio 的阻塞实现
29 | 关于什么是 nio,从字面上理解为 New IO,就是为了弥补原本 I/O 上的不足,而在 JDK 1.4 中引入的一种新的 I/O 实现方式。简单理解,就是它提供了 I/O 的阻塞与非阻塞的两种实现方式(*当然,默认实现方式是阻塞的。*)。
30 |
31 | 下面,我们先来看下 nio 以阻塞方式是如何处理的。
32 |
33 | ### 建立连接
34 | 有了上一篇 socket 的经验,我们的第一步一定也是建立 socket 连接。只不过,这里不是采用 ```new socket()``` 的方式,而是引入了一个新的概念 ```SocketChannel```。它可以看作是 socket 的一个完善类,除了提供 Socket 的相关功能外,还提供了许多其他特性,如后面要讲到的向选择器注册的功能。
35 |
36 | 类图如下:
37 | 
38 |
39 | 建立连接代码实现:
40 |
41 | ```java
42 | // 初始化 socket,建立 socket 与 channel 的绑定关系
43 | SocketChannel socketChannel = SocketChannel.open();
44 | // 初始化远程连接地址
45 | SocketAddress remote = new InetSocketAddress(this.host, port);
46 | // I/O 处理设置阻塞,这也是默认的方式,可不设置
47 | socketChannel.configureBlocking(true);
48 | // 建立连接
49 | socketChannel.connect(remote);
50 | ```
51 |
52 | ### 获取 socket 连接
53 | 因为是同样是 I/O 阻塞的实现,所以后面的关于 socket 输入输出流的处理,和上一篇的基本相同。唯一差别是,这里需要通过 channel 来获取 socket 连接。
54 |
55 | * 获取 socket 连接
56 |
57 | ```java
58 | Socket socket = socketChannel.socket();
59 | ```
60 |
61 | * 处理输入输出流
62 |
63 | ```java
64 | PrintWriter pw = getWriter(socketChannel.socket());
65 | BufferedReader br = getReader(socketChannel.socket());
66 | ```
67 |
68 | ### 完整示例
69 |
70 | ```java
71 | package com.jason.network.mode.nio;
72 |
73 | import com.jason.network.constant.HttpConstant;
74 | import com.jason.network.util.HttpUtil;
75 |
76 | import java.io.*;
77 | import java.net.InetSocketAddress;
78 | import java.net.Socket;
79 | import java.net.SocketAddress;
80 | import java.nio.channels.SocketChannel;
81 |
82 | public class NioBlockingHttpClient {
83 |
84 | private SocketChannel socketChannel;
85 | private String host;
86 |
87 |
88 | public static void main(String[] args) throws IOException {
89 |
90 | for (String host: HttpConstant.HOSTS) {
91 |
92 | NioBlockingHttpClient client = new NioBlockingHttpClient(host, HttpConstant.PORT);
93 | client.request();
94 |
95 | }
96 |
97 | }
98 |
99 | public NioBlockingHttpClient(String host, int port) throws IOException {
100 | this.host = host;
101 | socketChannel = SocketChannel.open();
102 | socketChannel.socket().setSoTimeout(5000);
103 | SocketAddress remote = new InetSocketAddress(this.host, port);
104 | this.socketChannel.connect(remote);
105 | }
106 |
107 | public void request() throws IOException {
108 | PrintWriter pw = getWriter(socketChannel.socket());
109 | BufferedReader br = getReader(socketChannel.socket());
110 |
111 | pw.write(HttpUtil.compositeRequest(host));
112 | pw.flush();
113 | String msg;
114 | while ((msg = br.readLine()) != null){
115 | System.out.println(msg);
116 | }
117 | }
118 |
119 | private PrintWriter getWriter(Socket socket) throws IOException {
120 | OutputStream out = socket.getOutputStream();
121 | return new PrintWriter(out);
122 | }
123 |
124 | private BufferedReader getReader(Socket socket) throws IOException {
125 | InputStream in = socket.getInputStream();
126 | return new BufferedReader(new InputStreamReader(in));
127 | }
128 | }
129 | ```
130 |
131 | ## nio 的非阻塞实现
132 | ### 原理分析
133 | nio 的阻塞实现,基本与使用原生的 socket 类似,没有什么特别大的差别。
134 |
135 | 下面我们来看看它真正强大的地方。到目前为止,我们将的都是阻塞 I/O。何为阻塞 I/O,看下图:
136 |
137 | 
138 |
139 | *我们主要观察图中的前三种 I/O 模型,关于异步 I/O,一般需要依靠操作系统的支持,这里不讨论。*
140 |
141 | 从图中可以发现,阻塞过程主要发生在两个阶段上:
142 |
143 | * 第一阶段:等待数据就绪;
144 | * 第二阶段:将已就绪的数据从内核缓冲区拷贝到用户空间;
145 |
146 | 这里产生了一个从内核到用户空间的拷贝,主要是为了系统的性能优化考虑。假设,从网卡读到的数据直接返回给用户空间,那势必会造成频繁的系统中断,因为从网卡读到的数据不一定是完整的,可能断断续续的过来。通过内核缓冲区作为缓冲,等待缓冲区有足够的数据,或者读取完结后,进行一次的系统中断,将数据返回给用户,这样就能避免频繁的中断产生。
147 |
148 | 了解了 I/O 阻塞的两个阶段,下面我们进入正题。看看一个线程是如何实现同时处理多个 I/O 调用的。从上图中的非阻塞 I/O 可以看出,仅仅只有第二阶段需要阻塞,第一阶段的数据等待过程,我们是不需要关心的。不过该模型是频繁地去检查是否就绪,造成了 CPU 无效的处理,反而效果不好。如果有一种类似的好莱坞原则— “不要给我们打电话,我们会打给你” 。这样一个线程可以同时发起多个 I/O 调用,并且不需要同步等待数据就绪。在数据就绪完成的时候,会以事件的机制,来通知我们。这样不就实现了单线程同时处理多个 IO 调用的问题了吗?即所说的“I/O 多路复用模型”。
149 |
150 | ***
151 | 废话讲了一大堆,下面就来实际操刀一下。
152 |
153 | ### 创建选择器
154 |
155 | 由上面分析可以,我们得有一个选择器,它能监听所有的 I/O 操作,并且以事件的方式通知我们哪些 I/O 已经就绪了。
156 |
157 | 代码如下:
158 |
159 | ```java
160 | import java.nio.channels.Selector;
161 |
162 | ...
163 |
164 | private static Selector selector;
165 | static {
166 | try {
167 | selector = Selector.open();
168 | } catch (IOException e) {
169 | e.printStackTrace();
170 | }
171 | }
172 |
173 | ```
174 |
175 | ### 创建非阻塞 I/O
176 |
177 | 下面,我们来创建一个非阻塞的 ```SocketChannel```,代码与阻塞实现类型,唯一不同是```socketChannel.configureBlocking(false)```。
178 |
179 | ***注意:只有在```socketChannel.configureBlocking(false)```之后的代码,才是非阻塞的,如果```socketChannel.connect()```在设置非阻塞模式之前,那么连接操作依旧是阻塞调用的。***
180 |
181 | ```java
182 | SocketChannel socketChannel = SocketChannel.open();
183 | SocketAddress remote = new InetSocketAddress(host, port);
184 | // 设置非阻塞模式
185 | socketChannel.configureBlocking(false);
186 | socketChannel.connect(remote);
187 | ```
188 |
189 | ### 建立选择器与 socket 的关联
190 |
191 | 选择器与 socket 都创建好了,下一步就是将两者进行关联,好让选择器和监听到 Socket 的变化。这里采用了以 ```SocketChannel``` 主动注册到选择器的方式进行关联绑定,这也就解释了,为什么不直接```new Socket()```,而是以```SocketChannel```的方式来创建 socket。
192 |
193 | 代码如下:
194 |
195 | ```java
196 | socketChannel.register(selector,
197 | SelectionKey.OP_CONNECT
198 | | SelectionKey.OP_READ
199 | | SelectionKey.OP_WRITE);
200 | ```
201 |
202 | 上面代码,我们将 socketChannel 注册到了选择器中,并且对它的连接、可读、可写事件进行了监听。
203 |
204 | 具体的事件监听类型如下:
205 |
206 | |操作类型|值|描述|所属对象
207 | |---|---|---|---
208 | |OP_READ|1 << 0|读操作|SocketChannel
209 | |OP_WRITE|1 << 2 |写操作|SocketChannel
210 | |OP_CONNECT|1 << 3|连接socket操作|SocketChannel
211 | |OP_ACCEPT|1 << 4|接受socket操作|ServerSocketChannel
212 |
213 | ### 选择器监听 socket 变化
214 |
215 | 现在,选择器已经与我们关心的 socket 进行了关联。下面就是感知事件的变化,然后调用相应的处理机制。
216 |
217 | 这里与 Linux 下的 selector 有点不同,nio 下的 selecotr 不会去遍历所有关联的 socket。我们在注册时设置了我们关心的事件类型,每次从选择器中获取的,只会是那些符合事件类型,并且完成就绪操作的 socket,减少了大量无效的遍历操作。
218 |
219 | ```
220 | public void select() throws IOException {
221 | // 获取就绪的 socket 个数
222 | while (selector.select() > 0){
223 |
224 | // 获取符合的 socket 在选择器中对应的事件句柄 key
225 | Set keys = selector.selectedKeys();
226 |
227 | // 遍历所有的key
228 | Iterator it = keys.iterator();
229 | while (it.hasNext()){
230 |
231 | // 获取对应的 key,并从已选择的集合中移除
232 | SelectionKey key = (SelectionKey)it.next();
233 | it.remove();
234 |
235 | if (key.isConnectable()){
236 | // 进行连接操作
237 | connect(key);
238 | }
239 | else if (key.isWritable()){
240 | // 进行写操作
241 | write(key);
242 | }
243 | else if (key.isReadable()){
244 | // 进行读操作
245 | receive(key);
246 | }
247 | }
248 | }
249 | }
250 | ```
251 |
252 | ***注意:这里的```selector.select()```是同步阻塞的,等待有事件发生后,才会被唤醒。这就防止了 CPU 空转的产生。当然,我们也可以给它设置超时时间,```selector.select(long timeout)```来结束阻塞过程。***
253 |
254 | ### 处理连接就绪事件
255 |
256 | 下面,我们分别来看下,一个 socket 是如何来处理连接、写入数据和读取数据的(*这些操作都是阻塞的过程,只是我们将等待就绪的过程变成了非阻塞的了*)。
257 |
258 | 处理连接代码:
259 |
260 | ```java
261 | // SelectionKey 代表 SocketChannel 在选择器中注册的事件句柄
262 | private void connect(SelectionKey key) throws IOException {
263 | // 获取事件句柄对应的 SocketChannel
264 | SocketChannel channel = (SocketChannel) key.channel();
265 |
266 | // 真正的完成 socket 连接
267 | channel.finishConnect();
268 |
269 | // 打印连接信息
270 | InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
271 | String host = remote.getHostName();
272 | int port = remote.getPort();
273 | System.out.println(String.format("访问地址: %s:%s 连接成功!", host, port));
274 | }
275 | ```
276 |
277 | ### 处理写入就绪事件
278 |
279 | ```java
280 | // 字符集处理类
281 | private Charset charset = Charset.forName("utf8");
282 |
283 | private void write(SelectionKey key) throws IOException {
284 | SocketChannel channel = (SocketChannel) key.channel();
285 | InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
286 | String host = remote.getHostName();
287 |
288 | // 获取 HTTP 请求,同上一篇
289 | String request = HttpUtil.compositeRequest(host);
290 |
291 | // 向 SocketChannel 写入事件
292 | channel.write(charset.encode(request));
293 |
294 | // 修改 SocketChannel 所关心的事件
295 | key.interestOps(SelectionKey.OP_READ);
296 | }
297 | ```
298 |
299 | 这里有两个地方需要注意:
300 |
301 | * 第一个是使用 ```channel.write(charset.encode(request));``` 进行数据写入。有人会说,为什么不能像上面同步阻塞那样,通过```PrintWriter```包装类进行操作。因为```PrintWriter```的 ```write()``` 方法是阻塞的,也就是说要等数据真正从 socket 发送出去后才返回。
302 |
303 | 这与我们这里所讲的阻塞是不一致的,这里的操作虽然也是阻塞的,但它发生的过程是在数据从用户空间到内核缓冲区拷贝过程。至于系统将缓冲区的数据通过 socket 发送出去,这不在阻塞范围内。也解释了为什么要用 ```Charset``` 对写入内容进行编码了,因为缓冲区接收的格式是```ByteBuffer```。
304 |
305 | * 第二,选择器用来监听事件变化的两个参数是 ```interestOps``` 与 ```readyOps```。
306 |
307 | * interestOps:表示 ```SocketChannel``` 所关心的事件类型,也就是告诉选择器,当有这几种事件发生时,才来通知我。这里通过```key.interestOps(SelectionKey.OP_READ);```告诉选择器,之后我只关心“读就绪”事件,其他的不用通知我了。
308 |
309 | * readyOps:表示 ```SocketChannel``` 当前就绪的事件类型。以```key.isReadable()```为例,判断依据就是:```return (readyOps() & OP_READ) != 0;```
310 |
311 | ### 处理读取就绪事件
312 |
313 | ```java
314 | private void receive(SelectionKey key) throws IOException {
315 | SocketChannel channel = (SocketChannel) key.channel();
316 | ByteBuffer buffer = ByteBuffer.allocate(1024);
317 | channel.read(buffer);
318 | buffer.flip();
319 | String receiveData = charset.decode(buffer).toString();
320 |
321 | // 当再没有数据可读时,取消在选择器中的关联,并关闭 socket 连接
322 | if ("".equals(receiveData)) {
323 | key.cancel();
324 | channel.close();
325 | return;
326 | }
327 |
328 | System.out.println(receiveData);
329 | }
330 | ```
331 |
332 | 这里的处理基本与写入一致,唯一要注意的是,这里我们需要自行处理去缓冲区读取数据的操作。首先会分配一个固定大小的缓冲区,然后从内核缓冲区中,拷贝数据至我们刚分配固定缓冲区上。这里存在两种情况:
333 |
334 | * 我们分配的缓冲区过大,那多余的部分以0补充(*初始化时,其实会自动补0*)。
335 | * 我们分配的缓冲去过小,因为选择器会不停的遍历。只要 ```SocketChannel``` 处理读就绪状态,那下一次会继续读取。当然,分配过小,会增加遍历次数。
336 |
337 | 最后,将一下 ```ByteBuffer``` 的结构,它主要有 position, limit,capacity 以及 mark 属性。以 ```buffer.flip();``` 为例,讲下各属性的作用(*mark 主要是用来标记之前 position 的位置,是在当前 postion 无法满足的情况下使用的,这里不作讨论*)。
338 |
339 | 
340 |
341 | 从图中看出,
342 |
343 | * 容量(capacity):表示缓冲区可以保存的数据容量;
344 | * 极限(limit):表示缓冲区的当前终点,即写入、读取都不可超过该重点;
345 | * 位置(position):表示缓冲区下一个读写单元的位置;
346 |
347 |
348 | ### 完整代码
349 | ```java
350 | package com.jason.network.mode.nio;
351 |
352 | import com.jason.network.constant.HttpConstant;
353 | import com.jason.network.util.HttpUtil;
354 |
355 | import java.io.IOException;
356 | import java.net.InetSocketAddress;
357 | import java.net.SocketAddress;
358 | import java.nio.ByteBuffer;
359 | import java.nio.channels.SelectionKey;
360 | import java.nio.channels.Selector;
361 | import java.nio.channels.SocketChannel;
362 | import java.nio.charset.Charset;
363 | import java.util.Iterator;
364 | import java.util.Set;
365 |
366 | public class NioNonBlockingHttpClient {
367 |
368 | private static Selector selector;
369 | private Charset charset = Charset.forName("utf8");
370 |
371 | static {
372 | try {
373 | selector = Selector.open();
374 | } catch (IOException e) {
375 | e.printStackTrace();
376 | }
377 | }
378 |
379 |
380 | public static void main(String[] args) throws IOException {
381 |
382 | NioNonBlockingHttpClient client = new NioNonBlockingHttpClient();
383 |
384 | for (String host: HttpConstant.HOSTS) {
385 |
386 | client.request(host, HttpConstant.PORT);
387 |
388 | }
389 |
390 | client.select();
391 |
392 | }
393 |
394 | public void request(String host, int port) throws IOException {
395 | SocketChannel socketChannel = SocketChannel.open();
396 | socketChannel.socket().setSoTimeout(5000);
397 | SocketAddress remote = new InetSocketAddress(host, port);
398 | socketChannel.configureBlocking(false);
399 | socketChannel.connect(remote);
400 | socketChannel.register(selector,
401 | SelectionKey.OP_CONNECT
402 | | SelectionKey.OP_READ
403 | | SelectionKey.OP_WRITE);
404 | }
405 |
406 | public void select() throws IOException {
407 | while (selector.select(500) > 0){
408 | Set keys = selector.selectedKeys();
409 |
410 | Iterator it = keys.iterator();
411 |
412 | while (it.hasNext()){
413 |
414 | SelectionKey key = (SelectionKey)it.next();
415 | it.remove();
416 |
417 | if (key.isConnectable()){
418 | connect(key);
419 | }
420 | else if (key.isWritable()){
421 | write(key);
422 | }
423 | else if (key.isReadable()){
424 | receive(key);
425 | }
426 | }
427 | }
428 | }
429 |
430 | private void connect(SelectionKey key) throws IOException {
431 | SocketChannel channel = (SocketChannel) key.channel();
432 | channel.finishConnect();
433 | InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
434 | String host = remote.getHostName();
435 | int port = remote.getPort();
436 | System.out.println(String.format("访问地址: %s:%s 连接成功!", host, port));
437 | }
438 |
439 | private void write(SelectionKey key) throws IOException {
440 | SocketChannel channel = (SocketChannel) key.channel();
441 | InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();
442 | String host = remote.getHostName();
443 |
444 | String request = HttpUtil.compositeRequest(host);
445 | System.out.println(request);
446 |
447 | channel.write(charset.encode(request));
448 | key.interestOps(SelectionKey.OP_READ);
449 | }
450 |
451 | private void receive(SelectionKey key) throws IOException {
452 | SocketChannel channel = (SocketChannel) key.channel();
453 | ByteBuffer buffer = ByteBuffer.allocate(1024);
454 | channel.read(buffer);
455 | buffer.flip();
456 | String receiveData = charset.decode(buffer).toString();
457 |
458 | if ("".equals(receiveData)) {
459 | key.cancel();
460 | channel.close();
461 | return;
462 | }
463 |
464 | System.out.println(receiveData);
465 | }
466 | }
467 |
468 | ```
469 |
470 | ### 示例效果
471 |
472 | 
473 |
474 | ## 总结
475 |
476 | 本文从 nio 的阻塞方式讲起,介绍了阻塞 I/O 与非阻塞 I/O 的区别,以及在 nio 下是如何一步步构建一个 IO 多路复用的模型的客户端。文中需要理解的内容比较多,如果有理解错误的地方,欢迎指正~
477 |
478 | ## 后续
479 | * Netty 下的异步请求实现
480 |
481 |
--------------------------------------------------------------------------------
/201708/java-socket.md:
--------------------------------------------------------------------------------
1 | # JAVA 中原生的 socket 通信机制
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 |
5 | ## 当前环境
6 | 1. jdk == 1.8
7 |
8 | ## 知识点
9 | * socket 的连接处理
10 | * IO 输入、输出流的处理
11 | * 请求数据格式处理
12 | * 请求模型优化
13 |
14 | ## 场景
15 |
16 | 今天,和大家聊一下 JAVA 中的 socket 通信问题。这里采用最简单的一请求一响应模型为例,假设我们现在需要向 baidu 站点进行通信。我们用 JAVA 原生的 socket 该如何实现。
17 |
18 | ### 建立 socket 连接
19 | 首先,我们需要建立 socket 连接(*核心代码*)
20 |
21 | ```java
22 | import java.net.InetSocketAddress;
23 | import java.net.Socket;
24 | import java.net.SocketAddress;
25 |
26 | // 初始化 socket
27 | Socket socket = new Socket();
28 | // 初始化远程连接地址
29 | SocketAddress remote = new InetSocketAddress(host, port);
30 | // 建立连接
31 | socket.connect(remote);
32 |
33 | ```
34 |
35 | ### 处理 socket 输入输出流
36 | 成功建立 socket 连接后,我们就能获得它的输入输出流,通信的本质是对输入输出流的处理。通过输入流,读取网络连接上传来的数据,通过输出流,将本地的数据传出给远端。
37 |
38 | *socket 连接实际与处理文件流有点类似,都是在进行 IO 操作。*
39 |
40 | 获取输入、输出流代码如下:
41 |
42 | ```java
43 | // 输入流
44 | InputStream in = socket.getInputStream();
45 | // 输出流
46 | OutputStream out = socket.getOutputStream();
47 | ```
48 |
49 | 关于 IO 流的处理,我们一般会用相应的包装类来处理 IO 流,如果直接处理的话,我们需要对 ```byte[]``` 进行操作,而这是相对比较繁琐的。如果采用包装类,我们可以直接以```string```、```int```等类型进行处理,简化了 IO 字节操作。
50 |
51 | 下面以 ```BufferedReader``` 与 ```PrintWriter``` 作为输入输出的包装类进行处理。
52 |
53 | ```java
54 | // 获取 socket 输入流
55 | private BufferedReader getReader(Socket socket) throws IOException {
56 | InputStream in = socket.getInputStream();
57 | return new BufferedReader(new InputStreamReader(in));
58 | }
59 |
60 | // 获取 socket 输出流
61 | private PrintWriter getWriter(Socket socket) throws IOException {
62 | OutputStream out = socket.getOutputStream();
63 | return new PrintWriter(new OutputStreamWriter(out));
64 | }
65 |
66 | ```
67 |
68 | ### 数据请求与响应
69 |
70 | 有了 socket 连接、IO 输入输出流,下面就该向发送请求数据,以及获取请求的响应结果。
71 |
72 | 因为有了 IO 包装类的支持,我们可以直接以字符串的格式进行传输,由包装类帮我们将数据装换成相应的字节流。
73 |
74 | 因为我们与 baidu 站点进行的是 HTTP 访问,所有我们不需要额外定义输出格式。采用标准的 HTTP 传输格式,就能进行请求响应了(*某些特定的 RPC 框架,可能会有自定义的通信格式*)。
75 |
76 | 请求的数据内容处理如下:
77 |
78 | ```java
79 | public class HttpUtil {
80 |
81 | public static String compositeRequest(String host){
82 |
83 | return "GET / HTTP/1.1\r\n" +
84 | "Host: " + host + "\r\n" +
85 | "User-Agent: curl/7.43.0\r\n" +
86 | "Accept: */*\r\n\r\n";
87 | }
88 |
89 | }
90 | ```
91 |
92 | 发送请求数据代码如下:
93 |
94 | ```java
95 | // 发起请求
96 | PrintWriter writer = getWriter(socket);
97 | writer.write(HttpUtil.compositeRequest(host));
98 | writer.flush();
99 | ```
100 |
101 | 接收响应数据代码如下:
102 |
103 | ```java
104 | // 读取响应
105 | String msg;
106 | BufferedReader reader = getReader(socket);
107 | while ((msg = reader.readLine()) != null){
108 | System.out.println(msg);
109 | }
110 | ```
111 |
112 | ### 结果展示
113 |
114 | 至此,讲完了原生 socket 下的创建连接、发送请求与接收响应的所有核心代码。
115 |
116 | 完整代码如下:
117 |
118 | ```
119 | import java.io.*;
120 | import java.net.InetSocketAddress;
121 | import java.net.Socket;
122 | import java.net.SocketAddress;
123 | import com.test.network.util.HttpUtil;
124 |
125 | public class SocketHttpClient {
126 |
127 | public void start(String host, int port) {
128 |
129 | // 初始化 socket
130 | Socket socket = new Socket();
131 |
132 | try {
133 | // 设置 socket 连接
134 | SocketAddress remote = new InetSocketAddress(host, port);
135 | socket.setSoTimeout(5000);
136 | socket.connect(remote);
137 |
138 | // 发起请求
139 | PrintWriter writer = getWriter(socket);
140 | System.out.println(HttpUtil.compositeRequest(host));
141 | writer.write(HttpUtil.compositeRequest(host));
142 | writer.flush();
143 |
144 | // 读取响应
145 | String msg;
146 | BufferedReader reader = getReader(socket);
147 | while ((msg = reader.readLine()) != null){
148 | System.out.println(msg);
149 | }
150 |
151 | } catch (IOException e) {
152 | e.printStackTrace();
153 | } finally {
154 | try {
155 | socket.close();
156 | } catch (IOException e) {
157 | e.printStackTrace();
158 | }
159 | }
160 |
161 | }
162 |
163 | private BufferedReader getReader(Socket socket) throws IOException {
164 | InputStream in = socket.getInputStream();
165 | return new BufferedReader(new InputStreamReader(in));
166 | }
167 |
168 | private PrintWriter getWriter(Socket socket) throws IOException {
169 | OutputStream out = socket.getOutputStream();
170 | return new PrintWriter(new OutputStreamWriter(out));
171 | }
172 |
173 | }
174 | ```
175 |
176 | 下面,我们通过实例化一个客户端,来展示 socket 通信的结果。
177 |
178 | ```java
179 | public class Application {
180 |
181 | public static void main(String[] args) {
182 |
183 | new SocketHttpClient().start("www.baidu.com", 80);
184 |
185 | }
186 | }
187 | ```
188 |
189 | 结果输出:
190 |
191 | 
192 |
193 | ## 请求模型优化
194 | 这种方式,虽然实现功能没什么问题。但是我们细看,发现在 IO 写入与读取过程,是发生了 IO 阻塞的情况。即:
195 |
196 | ```
197 | // 会发生 IO 阻塞
198 | writer.write(HttpUtil.compositeRequest(host));
199 | reader.readLine();
200 | ```
201 |
202 | 所以如果要同时请求10个不同的站点,如下:
203 |
204 | ```java
205 | public class SingleThreadApplication {
206 |
207 | public static void main(String[] args) {
208 |
209 | // HttpConstant.HOSTS 为 站点集合
210 | for (String host: HttpConstant.HOSTS) {
211 |
212 | new SocketHttpClient().start(host, HttpConstant.PORT);
213 |
214 | }
215 |
216 | }
217 | }
218 | ```
219 |
220 | 它一定是第一个请求响应结束后,才会发起下一个站点处理。
221 |
222 | *这在服务端更明显,虽然这里的代码是客户端连接,但是具体的操作和服务端是差不多的。请求只能一个个串行处理,这在响应时间上肯定不能达标。*
223 |
224 |
225 | * 多线程处理
226 |
227 | 有人觉得这根本不是问题,JAVA 是多线程的编程语言。对于这种情况,采用多线程的模型再合适不过。
228 |
229 | ```
230 | public class MultiThreadApplication {
231 |
232 | public static void main(String[] args) {
233 |
234 | for (final String host: HttpConstant.HOSTS) {
235 |
236 | Thread t = new Thread(new Runnable() {
237 | public void run() {
238 | new SocketHttpClient().start(host, HttpConstant.PORT);
239 | }
240 | });
241 |
242 | t.start();
243 |
244 | }
245 | }
246 | }
247 | ```
248 |
249 | 这种方式起初看起来挺有用的,但并发量一大,应用会起很多的线程。都知道,在服务器上,每一个线程实际都会占据一个文件句柄。而服务器上的句柄数是有限的,而且大量的线程,造成的线程间切换的消耗也会相当的大。所以这种方式在并发量大的场景下,一定是承载不住的。
250 |
251 | * 多线程 + 线程池 处理
252 |
253 | 既然线程太多不行,那我们控制一下线程创建的数目不就行了。只启动固定的线程数来进行 socket 处理,既利用了多线程的处理,又控制了系统的资源消耗。
254 |
255 | ```java
256 | public class ThreadPoolApplication {
257 |
258 | public static void main(String[] args) {
259 |
260 | ExecutorService executorService = Executors.newFixedThreadPool(8);
261 |
262 | for (final String host: HttpConstant.HOSTS) {
263 |
264 | Thread t = new Thread(new Runnable() {
265 | public void run() {
266 | new SocketHttpClient().start(host, HttpConstant.PORT);
267 | }
268 | });
269 |
270 | executorService.submit(t);
271 | new SocketHttpClient().start(host, HttpConstant.PORT);
272 |
273 | }
274 |
275 | }
276 | }
277 | ```
278 |
279 | *关于启动的线程数,一般 CPU 密集型会设置在 N+1(**N为CPU核数**),IO 密集型设置在 2N + 1。*
280 |
281 | 这种方式,看起来是最优的了。那有没有更好的呢,如果一个线程能同时处理多个 socket 连接,并且在每个 socket 输入输出数据没有准备好的情况下,不进行阻塞,那是不是更优呢。这种技术叫做“IO多路复用”。在 JAVA 的 nio 包中,提供了相应的实现。
282 |
283 | ## 后续
284 | * JAVA 中是如何实现 IO多路复用
285 | * Netty 下的实现异步请求的
286 |
287 |
288 |
--------------------------------------------------------------------------------
/201708/microservice-for-failure.md:
--------------------------------------------------------------------------------
1 | # [译] 设计一个容错的微服务架构
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 原文地址
5 |
6 | [https://blog.risingstack.com/designing-microservices-architecture-for-failure/](https://blog.risingstack.com/designing-microservices-architecture-for-failure/)
7 |
8 | ***
9 |
10 | 微服务架构使得可以通过明确定义的服务边界来隔离故障。但是像在每个分布式系统中一样,发生网络、硬件、应用级别的错误都是很常见的。由于服务依赖关系,任何组件可能暂时无法提供服务。为了尽量减少部分中断的影响,我们需要构建容错服务,来优雅地处理这些中断的响应结果。
11 |
12 | 本文介绍了基于[RisingStack 的 Node.js 咨询和开发经验](https://risingstack.com/)构建和操作高可用性微服务系统的最常见技术和架构模式。
13 |
14 | 如果你不熟悉本文中的模式,那并不一定意味着你做错了。建立可靠的系统总是会带来额外的成本。
15 |
16 | ## 微服务架构的风险
17 |
18 | 微服务架构将应用程序逻辑移动到服务,并使用网络层在它们之间进行通信。这种通过网络间通信代替单应用程序内调用的做法,会带来额外的延迟,以及需要协调多个物理和逻辑组件的系统复杂度。分布式系统的复杂性增加也将导致更高的网络故障率。
19 |
20 | 微服务体系结构的最大优势之一是,团队可以独立设计,开发和部署他们的服务。他们对服务的生命周期拥有完全的所有权。这也意味着团队无法控制他们依赖的服务,因为它更有可能由不同的团队管理。使用微服务架构,我们需要记住,提供者服务可能会临时不可用,由于其他人员发行的错误版本,配置以及其他更改等。
21 |
22 | ## 优雅的服务降级
23 |
24 | 微服务架构的最大优点之一是您可以隔离故障,并在当组件单独故障时,进行优雅的服务降级。 例如,在中断期间,照片共享应用程序中的客户可能无法上传新图片,但仍可以浏览,编辑和共享其现有照片。
25 |
26 | 
27 |
28 | *微服务容错隔离*
29 |
30 | 在大多数情况下,由于分布式系统中的应用程序相互依赖,因此很难实现这种优雅的服务降级,您需要应用几种故障转移的逻辑(其中一些将在本文后面介绍),以为暂时的故障和中断做准备。
31 |
32 | 
33 |
34 | *服务间彼此依赖,再没有故障转移逻辑下,服务全部失败。*
35 |
36 | ## 变更管理
37 |
38 | Google的网站可靠性小组发现,大约70%的中断是由现有系统的变化引起的。当您更改服务中的某些内容时,您将部署新版本的代码或更改某些配置 - 这总有可能会造成故障,或者引入新的bug。
39 |
40 | 在微服务架构中,服务依赖于彼此。这就是为什么你应该尽量减少故障并限制它的负面影响。要处理变更中的问题,您可以实施变更管理策略和自动回滚机制。
41 |
42 | 例如,当您部署新代码或更改某些配置时,您应该先小范围的进行部分的替换,以渐进式的方式替换服务的全部实例。在这期间,需要监视它们,如果您发现它们对您的关键指标有负面影响,应立即进行服务回滚,这称为“金丝雀部署”。
43 |
44 | 
45 |
46 | *变更管理 - 回滚部署*
47 |
48 | 另一个解决方案可能是您运行两个生产环境。您始终只能部署其中一个,并且在验证新版本是否符合预期之后才,将负载均衡器指向新的。这称为蓝绿或红黑部署。
49 |
50 | 回滚代码不是坏事。你不应该在生产中遗留错误的代码,然后考虑出了什么问题。如果必要,越早回滚你的代码越好。
51 |
52 | ## 健康检查与负载均衡
53 |
54 | 实例由于出现故障、部署或自动缩放的情况,会进行持续启动、重新启动或停止操作。它可能导致它们暂时或永久不可用。为避免问题,您的负载均衡器应该从路由中跳过不健康的实例,因为它们当前无法为客户或子系统提供服务。
55 |
56 | 应用实例健康状况可以通过外部观察来确定。您可以通过重复调用```GET /health```端点或通过自我报告来实现。现在主流的服务发现解决方案,会持续从实例中收集健康信息,并配置负载均衡器,将流量仅路由到健康的组件上。
57 |
58 | ## 自我修复
59 |
60 | 自我修复可以帮助应用程序从错误中恢复过来。当应用程序可以采取必要步骤从故障状态恢复时,我们就可以说它是可以实现自我修复的。在大多数情况下,它由外部系统实现,该系统会监视实例运行状况,并在较长时间内处于故障状态时重新启动它们。自我修复在大多数情况下是非常有用的。但是在某些情况下,持续地重启应用程序可能会导致麻烦。 当您的应用程序由于超负荷或其数据库连接超时而无法给出健康的运行状况时,这种情况下的频繁的重启就可能就不太合适了。
61 |
62 | 对于这种特殊的场景(如丢失的数据库连接),要实现满足它的高级自我修复的解决方案可能很棘手。在这种情况下,您需要为应用程序添加额外的逻辑来处理边缘情况,并让外部系统知道实例不需要立即重新启动。
63 |
64 | ## 故障转移缓存
65 |
66 | 由于网络问题和我们系统的变化,服务经常会失败。然而,由于自我修复和负载均衡的保障,它们中的大多数中断是临时的,我们应该找到一个解决方案,使我们的服务在这些故障时服务仍就可以工作。这就是故障转移缓存的作用,它可以帮助并为我们的应用程序在服务故障时提供必要的数据。
67 |
68 | 故障转移缓存通常使用两个不同的到期日期; 较短的时间告诉您在正常情况下缓存可以使用的过期时间,而较长的时间可以在服务故障时缓存依旧可用的过期时间。
69 |
70 | 
71 |
72 | *故障转移缓存*
73 |
74 | 请务必提及,只有当服务使用过时的数据比没有数据更好时,才能使用故障转移缓存。
75 |
76 | 要设置缓存和故障转移缓存,可以在 HTTP 中使用标准响应头。
77 |
78 | 例如,使用 ```max-age``` 属性可以指定资源被视为有效的最大时间。使用 ```stale-if-error``` 属性,您可以明确在出现故障的情况下,依旧可以从缓存中获取资源的最大时间。
79 |
80 | 现代的 CDN 和负载均衡器都提供各种缓存和故障转移行为,但您也可以为拥有标准可靠性解决方案的公司创建一个共享库。
81 |
82 | ## 重试逻辑
83 |
84 | 在某些情况下,我们无法缓存数据,或者我们想对其进行更改,但是我们的操作最终都失败了。对于此,我们可以重试我们的操作,因为我们可以预期资源将在一段时间后恢复,或者我们的负载均衡器将请求发送到了健康的实例上。
85 |
86 | 您应该小心地为您的应用程序和客户端添加重试逻辑,因为大量的重试可能会使事情更糟,甚至阻止应用程序恢复,如当服务超载时,大量的重试只能使状况更糟。
87 |
88 | 在分布式系统中,微服务系统重试可以触发多个其他请求或重试,并启动级联效应。为了最小化重试的影响,您应该限制它们的数量,并使用指数退避算法来持续增加重试之间的延迟,直到达到最大限制。
89 |
90 | 当客户端(浏览器,其他微服务等)发起重试,并且客户端不知道在处理请求之前或之后操作失败时,您应该为你的应用程序做好幂等处理的准备。例如,当您重试购买操作时,您不应该再次向客户收取费用。为每个交易使用唯一的幂等值键可以帮助处理重试。
91 |
92 | ## 限流器和负载降级
93 |
94 | 流量限制是在一段时间内定义特定客户或应用程序可以接收或处理多少个请求的技术。例如,通过流量限制,您可以过滤掉造成流量峰值的客户和服务,或者您可以确保您的应用程序在自动缩放无法满足时,依然不会超载。
95 |
96 | 您还可以阻止较低优先级的流量,为关键事务提供足够的资源。
97 |
98 | 
99 |
100 | *限流器可以阻止流量峰值产生*
101 |
102 | 有一个不同类型的限流器,叫做并发请求限制器。当您有重要的端点,您不应该被调用超过指定的次数,而您仍然想要能提供服务时,这将是有用的。
103 |
104 | 负载降级的一系列使用,可以确保总是有足够的资源来提供关键交易。它为高优先级请求保留一些资源,不允许低优先级的事务使用它们。负载降级开关是根据系统的整体状态做出决定,而不是基于单个用户的请求量大小。负载降级有助于您的系统恢复,因为当你有一个偶发事件时(*可能是一个热点事件*),您仍能保持核心功能的正常工作。
105 |
106 | 要了解有关限流器和负载降级的更多信息,我建议查看这篇[Stripe的文章](https://stripe.com/blog/rate-limiters)。
107 |
108 | ## 快速失败原则与独立性
109 |
110 | 在微服务架构中,我们想要做到让我们的服务具备快速失败与相互独立的能力。为了在服务级别上进行故障隔离,我们可以使用舱壁模式。你可以在本文的后面阅读更多有关舱壁的内容。
111 |
112 | 我们也希望我们的组件能够快速失败,因为我们不希望对于有故障的服务,在请求超时后才断开。没有什么比挂起的请求和无响应的 UI 更令人失望。这不仅浪费资源,而且还会影响用户体验。我们的服务在调用链中是相互调用的,所以在这些延迟累加之前,我们应该特别注意防止挂起操作。
113 |
114 | 你想到的第一个想法是对每个服务调用都设置明确的超时等级。这种方法的问题是,您不能知道真正合理的超时值是多少,因为网络故障和其他问题发生的某些情况只会影响一两次操作。在这种情况下,如果只有其中一些超时,您可能不想拒绝这些请求。
115 |
116 | 我们可以说,在微服务种通过使用超时来达到快速失败的效果是一种反模式的,你应该避免使用它。取而代之,您可以应用断路器模式,依据操作的成功与失败统计数据决定。
117 |
118 | ## 舱壁模式
119 |
120 | 工业中使用舱壁将船舶划分为几个部分,以便在船体破坏的情况下,可以将船舶各个部件密封起来。
121 |
122 | 舱壁的概念在软件开发中可以被应用在隔离资源上。
123 |
124 | 通过应用舱壁模式,我们可以保护有限的资源不被耗尽。例如,对于一个有连接数限制的数据库实例来说,如果我们有两种连接它的操作,我们采用可以采用两个连接池的方式进行连接,来代替仅采用一个共享连接池的方式。由于这种客户端与资源进行了隔离,超时或过度使用池的操作页不会使其他操作失败。
125 |
126 | 泰坦尼克号沉没的主要原因之一是其舱壁设计失败,水可以通过上面的甲板倒在舱壁的顶部,导致整个船体淹没。
127 |
128 | 
129 |
130 | *泰坦尼克号舱壁设计(无效的设计)*
131 |
132 | ## 断路器
133 |
134 | 为了限制操作的持续时间,我们可以使用超时。超时可以防止挂起操作并保持系统响应。然而,在微服务中使用静态、精细的超时是一种反模式,因为我们处于高度动态的环境中,几乎不可能提出在每种情况下都能正常工作的正确的时间限制。
135 |
136 | 替代这种静态超时的手段是,我们可以使用断路器来处理错误。断路器以现实世界的电子元件命名,因为它们的作用是相同的。您可以保护资源,并帮助他们使用断路器进行恢复。它们在分布式系统中非常有用,因为在分布式系统中,重复故障可能导致雪球效应并使整个系统瘫痪。
137 |
138 | 当特定类型的错误在短时间内多次发生时,断路器会被断开。开路的断路器可以防止进一步的请求 - 就像我们平时所说的电路跳闸一样。断路器通常在一定时间后关闭,在这期间可以为底层服务提供足够的空间来恢复。
139 |
140 | 请记住,并不是所有的错误都应该触发断路器。例如,您可能希望跳过客户端问题,例如具有4xx响应代码的请求,但不包括5xx服务器端故障。一些断路器也具有半开状态。在这种状态下,服务发送第一个请求以检查系统可用性,同时让其他请求失败。如果这个第一个请求成功,它将使断路器恢复到关闭状态并使流量流动。否则,它保持打开。
141 |
142 | 
143 |
144 | *断路器*
145 |
146 | ## 测试故障
147 |
148 | 您应该不断测试您系统的常见问题,以确保您的服务可以抵抗各种故障。您应经常测试故障,让您的团队具备故障处理的能力。
149 |
150 | 对于测试,您可以使用外部服务来标识实例组,并随机终止此组中的一个实例。这样,您可以准备单个实例故障,但您甚至可以关闭整个区域来模拟云提供商的故障。
151 |
152 | 最流行的测试解决方案之一是 Netflix 的 [ChaosMonkey 弹性工具](https://github.com/Netflix/chaosmonkey)。
153 |
154 |
155 | ## 结尾
156 |
157 | 实施和运行可靠的服务并不容易。 您需要付出很多努力,同时公司也要有相应的财力投入。
158 |
159 | 可靠性有很多层次和方面,因此找到最适合您团队的解决方案很重要。您应该使可靠性成为您的业务决策流程中的一个因素,并为其分配足够的预算和时间。
160 |
161 | ## 主要收获
162 |
163 | * 动态环境和分布式系统(如微服务)会导致更高的故障机率;
164 | * 服务应该做到故障隔离,到达优雅降级,来提升用户体验;
165 | * 70%的中断是由变化引起的,代码回滚不是一件坏事;
166 | * 做到服务快速失败与独立性。团队是无法控制他们所依赖的服务情况;
167 | * 缓存、舱壁、断路器和限流器等架构模式与技术有助于构建可靠的微服务架构。
168 |
169 |
170 |
--------------------------------------------------------------------------------
/201710/assets/java-analysis-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-01.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-02.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-03.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-04.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-05.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-06.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-07.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-08.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-09.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-10.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-11.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-12.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-13.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-14.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-15.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-16.png
--------------------------------------------------------------------------------
/201710/assets/java-analysis-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201710/assets/java-analysis-17.png
--------------------------------------------------------------------------------
/201710/java-analysis.md:
--------------------------------------------------------------------------------
1 | # 记一次 JAVA 的内存泄露分析
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 当前环境
5 | 1. jdk == 1.8
6 | 2. httpasyncclient == 4.1.3
7 |
8 | ## 代码地址
9 | git 地址:[https://github.com/jasonGeng88/java-network-programming](https://github.com/jasonGeng88/java-network-programming)
10 |
11 | ## 背景
12 |
13 | 前不久,上线了一个新项目,这个项目是一个压测系统,可以简单的看做通过回放词表(http请求数据),不断地向服务发送请求,以达到压测服务的目的。在测试过程中,一切还算顺利,修复了几个小bug后,就上线了。在上线后给到第一个业务方使用时,就发现来一个严重的问题,应用大概跑了10多分钟,就收到了大量的 Full GC 的告警。
14 |
15 | 针对这一问题,我们首先和业务方确认了压测的场景内容,回放的词表数量大概是10万条,回放的速率单机在 100qps 左右,按照我们之前的预估,这远远低于单机能承受的极限。按道理是不会产生内存问题的。
16 |
17 | ## 线上排查
18 |
19 | 首先,我们需要在服务器上进行排查。通过 JDK 自带的 jmap 工具,查看一下 JAVA 应用中具体存在了哪些对象,以及其实例数和所占大小。具体命令如下:
20 |
21 | ```shell
22 | jmap -histo:live `pid of java`
23 |
24 | # 为了便于观察,还是将输出写入文件
25 | jmap -histo:live `pid of java` > /tmp/jmap00
26 | ```
27 |
28 | 经过观察,确实发现有对象被实例化了20多万,根据业务逻辑,实例化最多的也就是词表,那也就10多万,怎么会有20多万呢,我们在代码中也没有找到对此有显示声明实例化的地方。至此,我们需要对 dump 内存,在离线进行进一步分析,dump 命令如下:
29 |
30 | ```shell
31 | jmap -dump:format=b,file=heap.dump `pid of java`
32 | ```
33 |
34 | ## 离线分析
35 |
36 | 从服务器上下载了 dump 的 heap.dump 后,我们需要通过工具进行深入的分析。这里推荐的工具有 mat、visualVM。
37 |
38 | 我个人比较喜欢使用 visualVM 进行分析,它除了可以分析离线的 dump 文件,还可以与 IDEA 进行集成,通过 IDEA 启动应用,进行实时的分析应用的CPU、内存以及GC情况(*GC情况,需要在visualVM中安装visual GC 插件*)。工具具体展示如下(*这里仅仅为了展示效果,数据不是真的*):
39 |
40 | 
41 |
42 | 
43 |
44 |
45 | 当然,mat 也是非常好用的工具,它能帮我们快速的定位到内存泄露的地方,便于我们排查。
46 | 展示如下:
47 |
48 | 
49 |
50 | 
51 |
52 |
53 | ## 场景再现
54 |
55 | 经过分析,最后我们定位到是使用 httpasyncclient 产生的内存泄露问题。httpasyncclient 是 Apache 提供的一个 HTTP 的工具包,主要提供了 reactor 的 io 非阻塞模型,实现了异步发送 http 请求的功能。
56 |
57 | 下面通过一个 Demo,来简单讲下具体内存泄露的原因。
58 |
59 | ### httpasyncclient 使用介绍:
60 |
61 | * maven 依赖
62 |
63 | ```xml
64 |
65 | org.apache.httpcomponents
66 | httpasyncclient
67 | 4.1.3
68 |
69 | ```
70 |
71 | * HttpAsyncClient 客户端
72 |
73 | ```java
74 | public class HttpAsyncClient {
75 |
76 | private CloseableHttpAsyncClient httpclient;
77 |
78 | public HttpAsyncClient() {
79 | httpclient = HttpAsyncClients.createDefault();
80 | httpclient.start();
81 | }
82 |
83 | public void execute(HttpUriRequest request, FutureCallback callback){
84 | httpclient.execute(request, callback);
85 | }
86 |
87 | public void close() throws IOException {
88 | httpclient.close();
89 | }
90 |
91 | }
92 | ```
93 |
94 | ### 主要逻辑:
95 | Demo 的主要逻辑是这样的,首先创建一个缓存列表,用来保存需要发送的请求数据。然后,通过循环的方式从缓存列表中取出需要发送的请求,将其交由 httpasyncclient 客户端进行发送。
96 |
97 | 具体代码如下:
98 |
99 | ```java
100 | public class ReplayApplication {
101 |
102 | public static void main(String[] args) throws InterruptedException {
103 |
104 | //创建有内存泄露的回放客户端
105 | ReplayWithProblem replay1 = new ReplayWithProblem();
106 |
107 | //加载一万条请求数据放入缓存
108 | List cache1 = replay1.loadMockRequest(10000);
109 |
110 | //开始循环回放
111 | replay1.start(cache1);
112 |
113 | }
114 | }
115 | ```
116 |
117 | ### 回放客户端实现(内存泄露):
118 |
119 | 这里以回放百度为例,创建10000条mock数据放入缓存列表。回放时,以 while 循环每100ms 发送一个请求出去。具体代码如下:
120 |
121 | ```java
122 | public class ReplayWithProblem {
123 |
124 | public List loadMockRequest(int n){
125 |
126 | List cache = new ArrayList(n);
127 | for (int i = 0; i < n; i++) {
128 | HttpGet request = new HttpGet("http://www.baidu.com?a="+i);
129 | cache.add(request);
130 | }
131 | return cache;
132 |
133 | }
134 |
135 | public void start(List cache) throws InterruptedException {
136 |
137 | HttpAsyncClient httpClient = new HttpAsyncClient();
138 | int i = 0;
139 |
140 | while (true){
141 |
142 | final HttpUriRequest request = cache.get(i%cache.size());
143 | httpClient.execute(request, new FutureCallback() {
144 | public void completed(final HttpResponse response) {
145 | System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
146 | }
147 |
148 | public void failed(final Exception ex) {
149 | System.out.println(request.getRequestLine() + "->" + ex);
150 | }
151 |
152 | public void cancelled() {
153 | System.out.println(request.getRequestLine() + " cancelled");
154 | }
155 |
156 | });
157 | i++;
158 | Thread.sleep(100);
159 | }
160 | }
161 |
162 | }
163 | ```
164 |
165 | ### 内存分析:
166 |
167 | 启动 ReplayApplication 应用(*IDEA 中安装 VisualVM Launcher后,可以直接启动visualvm*),通过 visualVM 进行观察。
168 |
169 | * 启动情况:
170 |
171 | 
172 |
173 | * visualVM 中前后3分钟的内存对象占比情况:
174 |
175 | 
176 | 
177 |
178 | ***说明:$0代表的是对象本身,$1代表的是该对象中的第一个内部类。所以ReplayWithProblem$1: 代表的是ReplayWithProblem类中FutureCallback的回调类。***
179 |
180 | 从中,我们可以发现 FutureCallback 类会被不断的创建。因为每次异步发送 http 请求,都是通过创建一个回调类来接收结果,逻辑上看上去也正常。不急,我们接着往下看。
181 |
182 | * visualVM 中前后3分钟的GC情况:
183 |
184 | 
185 | 
186 |
187 | 从图中看出,内存的 old 在不断的增长,这就不对了。内存中维持的应该只有缓存列表的http请求体,现在在不断的增长,就有说明了不断的有对象进入old区,结合上面内存对象的情况,说明了 FutureCallback 对象没有被及时的回收。
188 |
189 | 可是该回调匿名类在 http 回调结束后,引用关系就没了,在下一次 GC 理应被回收才对。我们通过对 httpasyncclient 发送请求的源码进行跟踪了一下后发现,其内部实现是将回调类塞入到了http的请求类中,而请求类是放在在缓存队列中,所以导致回调类的引用关系没有解除,大量的回调类晋升到了old区,最终导致 Full GC 产生。
190 |
191 | * 核心代码分析:
192 |
193 | 
194 |
195 | 
196 |
197 | 
198 |
199 |
200 | ### 代码优化
201 |
202 | 找到问题的原因,我们现在来优化代码,验证我们的结论。因为```List cache1```中会保存回调对象,所以我们不能缓存请求类,只能缓存基本数据,在使用时进行动态的生成,来保证回调对象的及时回收。
203 |
204 | 代码如下:
205 |
206 | ```java
207 | public class ReplayApplication {
208 |
209 | public static void main(String[] args) throws InterruptedException {
210 |
211 | ReplayWithoutProblem replay2 = new ReplayWithoutProblem();
212 | List cache2 = replay2.loadMockRequest(10000);
213 | replay2.start(cache2);
214 |
215 | }
216 | }
217 |
218 | ```
219 |
220 | ```
221 | public class ReplayWithoutProblem {
222 |
223 | public List loadMockRequest(int n){
224 | List cache = new ArrayList(n);
225 | for (int i = 0; i < n; i++) {
226 | cache.add("http://www.baidu.com?a="+i);
227 | }
228 | return cache;
229 | }
230 |
231 | public void start(List cache) throws InterruptedException {
232 |
233 | HttpAsyncClient httpClient = new HttpAsyncClient();
234 | int i = 0;
235 |
236 | while (true){
237 |
238 | String url = cache.get(i%cache.size());
239 | final HttpGet request = new HttpGet(url);
240 | httpClient.execute(request, new FutureCallback() {
241 | public void completed(final HttpResponse response) {
242 | System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
243 | }
244 |
245 | public void failed(final Exception ex) {
246 | System.out.println(request.getRequestLine() + "->" + ex);
247 | }
248 |
249 | public void cancelled() {
250 | System.out.println(request.getRequestLine() + " cancelled");
251 | }
252 |
253 | });
254 | i++;
255 | Thread.sleep(100);
256 | }
257 | }
258 |
259 | }
260 | ```
261 |
262 | ### 结果验证
263 |
264 | * 启动情况:
265 |
266 | 
267 |
268 | * visualVM 中前后3分钟的内存对象占比情况:
269 |
270 | 
271 | 
272 |
273 | * visualVM 中前后3分钟的GC情况:
274 |
275 | 
276 | 
277 |
278 | 从图中,可以证明我们得出的结论是正确的。回调类在 Eden 区就会被及时的回收掉。old 区也没有持续的增长情况了。这一次的内存泄露问题算是解决了。
279 |
280 | ## 总结
281 |
282 | 关于内存泄露问题在第一次排查时,往往是有点不知所措的。我们需要有正确的方法和手段,配上好用的工具,这样在解决问题时,才能游刃有余。当然对JAVA内存的基础知识也是必不可少的,这时你定位问题的关键,不然就算工具告诉你这块有错,你也不能定位原因。
283 |
284 | 最后,关于 httpasyncclient 的使用,工具本身是没有问题的。只是我们得了解它的使用场景,往往产生问题多的,都是使用的不当造成的。所以,在使用工具时,对于它的了解程度,往往决定了出现 bug 的机率。
285 |
286 |
287 |
288 |
289 |
290 |
291 |
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-00.png
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-01.png
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-02.png
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-03.png
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-04.png
--------------------------------------------------------------------------------
/201802/assets/java-threadpool-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonGeng88/blog/5ba7b44538747a1ad6596d040152219f7eaa62af/201802/assets/java-threadpool-05.png
--------------------------------------------------------------------------------
/201802/java-threadpool.md:
--------------------------------------------------------------------------------
1 | # JAVA 线程池的正确打开方式
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 当前环境
5 | 1. jdk == 1.8
6 |
7 |
8 | ## Executors 使用的隐患
9 |
10 | 先来看一段代码,我们要创建一个固定线程池,假设固定线程数是4。代码如下:
11 |
12 | 
13 |
14 | ***```Executors```是JAVA并发包中提供的,用来快速创建不同类型的线程池。***
15 |
16 | 是不是很简单,创建线程池只需一行代码。对于一些个人项目或临时性的项目,这样写确实没什么问题,而且开发速度很快。但在一些大型项目中,这种做法一般是禁止的。
17 |
18 | WHY???
19 |
20 | 因为用```Executors```创建的线程池存在性能隐患,我们看一下源码就知道,用```Executors```创建线程池时,使用的队列是```new LinkedBlockingQueue()```,这是一个无边界队列,如果不断的往里加任务时,最终会导致内存问题,也就是说在项目中由于使用了无边界队列,导致的内存占用的不可控性。下图是不断添加线程任务导致老年代被占满的情况:
21 |
22 | 
23 |
24 | 当然,除了内存问题,它还存在一些其他的问题,在下面对线程池参数的介绍中会具体说明。
25 |
26 | ## 线程池的正确创建方式
27 |
28 | 其实,问题很好解决。提供的简便方式有局限性,那我们自己new一个```ThreadPoolExecutor```,无非多写几行代码而已。
29 |
30 | 关于```ThreadPoolExecutor```的具体代码如下:
31 |
32 | 
33 |
34 | ### 参数说明:
35 |
36 | * corePoolSize:核心线程数;
37 | * maximumPoolSize:最大线程数,即线程池中允许存在的最大线程数;
38 | * keepAliveTime:线程存活时间,对于超过核心线程数的线程,当线程处理空闲状态下,且维持时间达到keepAliveTime时,线程将被销毁;
39 | * unit:keepAliveTime的时间单位
40 | * workQueue:工作队列,用于存在待执行的线程任务;
41 | * threadFactory:创建线程的工厂,用于标记区分不同线程池所创建出来的线程;
42 | * handler:当到达线程数上限或工作队列已满时的拒绝处理逻辑;
43 |
44 | ### 具体代码
45 |
46 | * 自定义threadFactory。除了可以自定义创建的线程名称,方便问题排查,在```newThread(Runnable r)```创建线程的方法中,还可以进行定制化设置,如为线程设置特定上下文等。
47 |
48 | 
49 |
50 | * 自定义RejectedExecutionHandler。记录异常信息,选择不同处理逻辑,有交由当前线程执行任务,有直接抛出异常,再或者等待后继续添加任务等。
51 |
52 | 
53 |
54 | * 创建自定义线程池
55 |
56 | 
57 |
58 | ### 线程池内在处理逻辑
59 |
60 | 我们通过一些例子,来观察一下其内部的处理逻辑。基于上述具体代码,我们已经创建了一个核心线程数4,最大线程数8,线程存活时间10s,工作队列最大容量为10的一个线程池。
61 |
62 | * 初始化线程池:未添加线程任务
63 |
64 | * 这时,线程池中***不会创建任何线程***,存活线程为0,工作队列为0.
65 |
66 | * 未达核心线程数:添加4个线程任务
67 |
68 | * 由于当前存活线程数 <= 核心线程数,所以会***创建新的线程***。即存活线程为4,工作队列为0.
69 |
70 | * 核心线程数已满:添加第5个线程任务
71 | * 若当前线程池中存在空闲线程,则交由该线程处理。即存活线程为4,工作队列为0.
72 | * 若当前所有线程处理运行状态,加入工作队列。即存活线程为4,工作队列为1.(***注意:此时工作队列中的任务不会被执行,直到有线程空闲后,才能被处理***)
73 |
74 | * 工作队列未满:假设添加的任务都是耗时操作(短时间不会结束),再添加9个耗时任务
75 |
76 | * 即存活线程为4,工作队列为10.
77 |
78 | * 工作队列已满 & 未达最大线程数:再添加4个任务
79 |
80 | * 当工作队列已满,且不存在空闲线程,此时会***创建额外线程***来处理当前任务。此时存活线程为8,工作队列为10.
81 |
82 | * 工作队列已满 & 且最大线程数已满:再添加1个任务
83 | * ***触发RejectedExecutionHandler***,将当前任务交由自己设置的执行句柄进行处理。此时存活线程为8,工作队列为10.
84 |
85 | * 当任务执行完后,没有新增的任务,临时扩充的线程(大于核心线程数的)将在10s(***keepAliveTime***)后被销毁。
86 |
87 | ## 总结
88 |
89 | 最后,我们在使用线程池的时候,需要根据使用场景来自行选择。通过corePoolSize和maximumPoolSize的搭配,存活时间的选择,以及改变队列的实现方式,如:选择延迟队列,来实现定时任务的功能。并发包```Executors```中提供的一些方法确实好用,但我们仍需有保留地去使用,这样在项目中就不会挖太多的坑。
90 |
91 | ### 扩展
92 |
93 | 对于一些耗时的IO任务,盲目选择线程池往往不是最佳方案。通过异步+单线程轮询,上层再配合上一个固定的线程池,效果可能更好。类似与Reactor模型中selector轮询处理。
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/201804/js-this.md:
--------------------------------------------------------------------------------
1 | # 彻底搞懂 JS 中 this 机制
2 | > 摘要:本文属于原创,欢迎转载,转载请保留出处:[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)
3 |
4 | ## 目录
5 | * this 是什么
6 | * this 的四种绑定规则
7 | * 绑定规则的优先级
8 | * 绑定例外
9 | * 扩展:箭头函数
10 |
11 | ## this 是什么
12 |
13 | 理解this之前, 先纠正一个观点,**this 既不指向函数自身,也不指函数的词法作用域**。如果仅通过this的英文解释,太容易产生误导了。它实际是在函数被调用时才发生的绑定,也就是说this具体指向什么,取决于你是怎么调用的函数。
14 |
15 | ## this 的四种绑定规则
16 | this的4种绑定规则分别是:默认绑定、隐式绑定、显示绑定、new 绑定。优先级从低到高。
17 |
18 | ### 默认绑定
19 | 什么叫默认绑定,即没有其他绑定规则存在时的默认规则。这也是函数调用中最常用的规则。
20 |
21 | 来看这段代码:
22 |
23 | ```js
24 | function foo() {
25 | console.log( this.a );
26 | }
27 |
28 | var a = 2;
29 | foo(); //打印的是什么?
30 | ```
31 |
32 | ```foo()``` 打印的结果是2。
33 |
34 | 因为foo()是直接调用的(独立函数调用),没有应用其他的绑定规则,这里进行了默认绑定,将全局对象绑定this上,所以this.a 就解析成了全局变量中的a,即2。
35 |
36 | ***注意:在严格模式下(strict mode),全局对象将无法使用默认绑定,即执行会报undefined的错误***
37 |
38 | ```js
39 | function foo() {
40 | "use strict";
41 | console.log( this.a );
42 | }
43 |
44 | var a = 2;
45 | foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
46 | ```
47 |
48 | ***
49 |
50 | ### 隐式绑定
51 | 除了直接对函数进行调用外,有些情况是,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。
52 |
53 | ```js
54 | function foo() {
55 | console.log( this.a );
56 | }
57 |
58 | var a = 2;
59 |
60 | var obj = {
61 | a: 3,
62 | foo: foo
63 | };
64 |
65 | obj.foo(); // ?
66 | ```
67 |
68 | ```obj.foo()``` 打印的结果是3。
69 |
70 | 这里foo函数被当做引用属性,被添加到obj对象上。这里的调用过程是这样的:
71 |
72 | 获取obj.foo属性 -> 根据引用关系找到foo函数,执行调用
73 |
74 | 所以这里对foo的调用存在上下文对象obj,this进行了隐式绑定,即this绑定到了obj上,所以this.a被解析成了obj.a,即3。
75 |
76 | #### 多层调用链
77 |
78 | ```js
79 | function foo() {
80 | console.log( this.a );
81 | }
82 |
83 | var a = 2;
84 |
85 | var obj1 = {
86 | a: 4,
87 | foo: foo
88 | };
89 |
90 | var obj2 = {
91 | a: 3,
92 | obj1: obj1
93 | };
94 |
95 | obj2.obj1.foo(); //?
96 | ```
97 |
98 | ```obj2.obj1.foo()``` 打印的结果是4。
99 |
100 | 同样,我们看下函数的调用过程:
101 |
102 | 先获取obj2.obj1 -> 通过引用获取到obj1对象,再访问 obj1.foo -> 最后执行foo函数调用
103 |
104 | 这里调用链不只一层,存在obj1、obj2两个对象,那么隐式绑定具体会绑哪个对象。这里原则是获取最后一层调用的上下文对象,即obj1,所以结果显然是4(obj1.a)。
105 |
106 | #### 隐式丢失(函数别名)
107 |
108 | ***注意:这里存在一个陷阱,大家在分析调用过程时,要特别小心***
109 |
110 | 先看个代码:
111 |
112 | ```js
113 | function foo() {
114 | console.log( this.a );
115 | }
116 |
117 | var a = 2;
118 |
119 | var obj = {
120 | a: 3,
121 | foo: foo
122 | };
123 |
124 | var bar = obj.foo;
125 | bar(); //?
126 | ```
127 |
128 | **```bar()``` 打印的结果是2。**
129 |
130 | 为什么会这样,obj.foo 赋值给bar,那调用```bar()```为什么没有触发隐式绑定,使用的是默认绑定呢。
131 |
132 | 这里有个概念要理解清楚,obj.foo 是引用属性,赋值给bar的实际上就是foo函数(即:bar指向foo本身)。
133 |
134 | 那么,实际的调用关系是:通过bar找到foo函数,进行调用。整个调用过程并没有obj的参数,所以是默认绑定,全局属性a。
135 |
136 | #### 隐式丢失(回调函数)
137 |
138 | ```js
139 | function foo() {
140 | console.log( this.a );
141 | }
142 |
143 | var a = 2;
144 |
145 | var obj = {
146 | a: 3,
147 | foo: foo
148 | };
149 |
150 | setTimeout( obj.foo, 100 ); // ?
151 | ```
152 |
153 | **打印的结果是2。**
154 |
155 | 同样的道理,虽然参传是```obj.foo```,因为是引用关系,所以传参实际上传的就是foo对象本身的引用。对于```setTimeout```的调用,还是 setTimeout -> 获取参数中foo的引用参数 -> 执行 foo 函数,中间没有obj的参与。这里依旧进行的是默认绑定。
156 |
157 | ***
158 |
159 | ### 显示绑定
160 | 相对隐式绑定,this值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。
161 |
162 | 显示绑定主要是通过改变对象的prototype关联对象,这里不展开讲。具体使用上,可以通过这两个方法call(...)或apply(...)来实现(大多数函数及自己创建的函数默认都提供这两个方法)。
163 |
164 | ***call与apply是同样的作用,区别只是其他参数的设置上***
165 |
166 | ```js
167 | function foo() {
168 | console.log( this.a );
169 | }
170 |
171 | var a = 2;
172 |
173 | var obj1 = {
174 | a: 3,
175 | };
176 |
177 | var obj2 = {
178 | a: 4,
179 | };
180 | foo.call( obj1 ); // ?
181 | foo.call( obj2 ); // ?
182 | ```
183 |
184 | 打印的结果是3, 4。
185 |
186 | 这里因为显示的申明了要绑定的对象,所以this就被绑定到了obj上,打印的结果自然就是obj1.a 和obj2.a。
187 |
188 | #### 硬绑定
189 |
190 | ```js
191 | function foo() {
192 | console.log( this.a );
193 | }
194 |
195 | var a = 2;
196 |
197 | var obj1 = {
198 | a: 3,
199 | };
200 |
201 | var obj2 = {
202 | a: 4,
203 | };
204 |
205 | var bar = function(){
206 | foo.call( obj1 );
207 | }
208 |
209 | bar(); // 3
210 | setTimeout( bar, 100 ); // 3
211 |
212 | bar.call( obj2 ); // 这是多少
213 | ```
214 |
215 | ***前面两个(函数别名、回调函数)打印3,因为显示绑定了,没什么问题。***
216 |
217 | 最后一个打印是3。
218 |
219 | 这里需要注意下,虽然bar被显示绑定到obj2上,对于bar,function(){...} 中的this确实被绑定到了obj2,而foo因为通过```foo.call( obj1 )```已经显示绑定了obj1,所以在foo函数内,this指向的是obj1,不会因为bar函数内指向obj2而改变自身。所以打印的是obj1.a(即3)。
220 |
221 | ***
222 |
223 | ### new 绑定
224 | ***js中的new操作符,和其他语言中(如JAVA)的new机制是不一样的。js中,它就是一个普通函数调用,只是被new修饰了而已。***
225 |
226 | 使用new来调用函数,会自动执行如下操作:
227 |
228 | 1. 创建一个全新的对象。
229 | 2. 这个新对象会被执行[[原型]]连接。
230 | 3. 这个新对象会绑定到函数调用的this。
231 | 4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
232 |
233 | 从第三点可以看出,this指向的就是对象本身。
234 |
235 | 看个代码:
236 |
237 | ```js
238 | function foo(a) {
239 | this.a = a;
240 | }
241 |
242 | var a = 2;
243 |
244 | var bar1 = new foo(3);
245 | console.log(bar1.a); // ?
246 |
247 | var bar2 = new foo(4);
248 | console.log(bar2.a); // ?
249 | ```
250 |
251 | 最后一个打印是3, 4。
252 |
253 | 因为每次调用生成的是全新的对象,该对象又会自动绑定到this上,所以答案显而易见。
254 |
255 | ## 绑定规则优先级
256 | 上面也说过,这里在重复一下。优先级是这样的,以按照下面的顺序来进行判断:
257 | 1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
258 |
259 | var bar = new foo()
260 | 2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
261 |
262 | var bar = foo.call(obj2)
263 | 3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
264 |
265 | var bar = obj1.foo()
266 | 4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。
267 |
268 | var bar = foo()
269 |
270 | ## 规则例外
271 |
272 | 在显示绑定中,对于null和undefined的绑定将不会生效。
273 |
274 | 代码如下:
275 |
276 | ```js
277 | function foo() {
278 | console.log( this.a );
279 | }
280 | var a = 2;
281 | foo.call( null ); // 2
282 | foo.call( undefined ); // 2
283 | ```
284 |
285 | 这种情况主要是用在不关心this的具体绑定对象(用来忽略this),而传入null实际上会进行默认绑定,导致函数中可能会使用到全局变量,与预期不符。
286 |
287 | 所以对于要忽略this的情况,可以传入一个空对象ø,该对象通过```Object.create(null)```创建。这里不用{}的原因是,ø是真正意义上的空对象,它不创建Object.prototype委托,{}和普通对象一样,有原型链委托关系。
288 |
289 | ***1. 这里传null的一种具体使用场景是函数柯里化的使用***
290 |
291 |
292 | ## 扩展:箭头函数
293 | 最后,介绍一下ES6中的箭头函数。通过“=>”而不是function创建的函数,叫做箭头函数。它的this绑定取决于外层(函数或全局)作用域。
294 |
295 | ### case 1 (正常调用)
296 | * 普通函数
297 |
298 | ```js
299 | function foo(){
300 | console.log( this.a );
301 | }
302 |
303 | var a = 2;
304 |
305 | var obj = {
306 | a: 3,
307 | foo: foo
308 | };
309 |
310 | obj.foo(); //3
311 | ```
312 |
313 | * 箭头函数
314 |
315 | ```js
316 | var foo = () => {
317 | console.log( this.a );
318 | }
319 |
320 | var a = 2;
321 |
322 | var obj = {
323 | a: 3,
324 | foo: foo
325 | };
326 |
327 | obj.foo(); //2
328 | foo.call(obj); //2 ,箭头函数中显示绑定不会生效
329 | ```
330 |
331 |
332 | ### case 2 (函数回调)
333 | * 普通函数
334 |
335 | ```js
336 | function foo(){
337 | return function(){
338 | console.log( this.a );
339 | }
340 | }
341 |
342 | var a = 2;
343 |
344 | var obj = {
345 | a: 3,
346 | foo: foo
347 | };
348 |
349 | var bar = obj.foo();
350 | bar(); //2
351 | ```
352 |
353 | * 箭头函数
354 |
355 | ```js
356 | function foo(){
357 | return () => {
358 | console.log( this.a );
359 | }
360 | }
361 |
362 |
363 |
364 | var a = 2;
365 |
366 | var obj = {
367 | a: 3,
368 | foo: foo
369 | };
370 |
371 | var bar = obj.foo();
372 | bar(); //3
373 | ```
374 |
375 | 通过上面两个列子,我们看到箭头函数的this绑定**只取决于外层(函数或全局)的作用域**,对于前面的4种绑定规则是不会生效的。它也是作为this机制的一种替换,解决之前this绑定过程各种规则带来的复杂性。
376 |
377 | ***注意:对于ES6之前,箭头函数的替换版本是这样的***
378 |
379 | ```js
380 | // es6
381 | function foo(){
382 | return () => {
383 | console.log( this.a );
384 | }
385 | }
386 |
387 | var a = 2;
388 |
389 | var obj = {
390 | a: 3,
391 | foo: foo
392 | };
393 |
394 | var bar = obj.foo();
395 | bar(); //3
396 | ```
397 |
398 | 通过上面两个列子,我们看到箭头函数的this绑定**只取决于外层(函数或全局)的作用域**,对于前面的4种绑定规则是不会生效的。它也是作为this机制的一种替换,解决之前this绑定过程各种规则带来的复杂性。
399 |
400 | ***注意:对于ES6之前,箭头函数的替换版本是这样的***
401 |
402 | ```js
403 | // es6
404 | function foo(){
405 | return () => {
406 | console.log( this.a );
407 | }
408 | }
409 |
410 | // es6之前的替代方法
411 | function foo(){
412 | var self = this;
413 | return () => {
414 | console.log( self.a );
415 | }
416 | }
417 | ```
418 |
419 | ## 总结
420 | 我们在使用js的过程中,对于this的理解往往觉得比较困难,再调试过程中有时也会出现一些不符合预期的现象。很多时候,我们都是通过一些变通的方式(如:使用具体对象替换this)来规避的问题。可问题一直存在那儿,我们没有真正的去理解和解决它。
421 |
422 | 本文主要参考了《你不知道的JavaScript(上卷)》,对this到底是什么,具体怎么绑定的,有什么例外情况以及ES6中的一个优化方向,来彻底搞清楚我们一直使用的this到底是怎么玩的。
423 |
424 |
425 |
426 |
427 |
428 |
429 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 个人博客
2 |
3 | > 文章来源于工作与学习,保证原创!
4 | >
5 | > 如果对您有所帮助,欢迎点赞!
6 |
7 | 目录
8 |
9 | * 微服务系列
10 | * [ELK:基于 ELK+Filebeat 的日志搭建](https://github.com/jasonGeng88/blog/blob/master/201703/elk.md)
11 |
12 | * [ELK:实战解析各类日志文件](https://github.com/jasonGeng88/blog/blob/master/201703/elk_parse_log.md)
13 |
14 | * [[译] ELK 日志中心分布式架构的逐步演化](https://github.com/jasonGeng88/blog/blob/master/201703/logstash_deploye_scale.md)
15 |
16 | * [基于 Docker、Registrator、Zookeeper 实现的服务自动注册](https://github.com/jasonGeng88/blog/blob/master/201703/service_registry.md)
17 |
18 | * [基于 Docker、NodeJs 实现高可用的服务发现](https://github.com/jasonGeng88/blog/blob/master/201704/service_discovery.md)
19 |
20 | * [看 Docker Swarm 如何做集群](https://github.com/jasonGeng88/blog/blob/master/201704/docker_swarm.md)
21 |
22 | * [一个故事告诉你什么是消息队列](https://github.com/jasonGeng88/blog/blob/master/201705/MQ.md)
23 |
24 | * [容器监控方案 cAdvisor + Elasticsearch](https://github.com/jasonGeng88/blog/blob/master/201705/cadvisor.md)
25 |
26 | * [[译] 基于事件流构建的服务](https://github.com/jasonGeng88/blog/blob/master/201706/event.md)
27 |
28 | * [[译] 设计一个容错的微服务架构](https://github.com/jasonGeng88/blog/blob/master/201708/microservice-for-failure.md)
29 |
30 | * Kubernetes 系列
31 |
32 | * [带着问题学 Kubernetes 架构图](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-architecture.md)
33 |
34 | * [带着问题学 Kubernetes 基本单元 Pod](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-pod.md)
35 |
36 | * [带着问题学 Kubernetes 抽象对象 Service](https://github.com/jasonGeng88/blog/blob/master/201707/k8s-service.md)
37 |
38 | * 编程
39 | * [[译] 2017年你应该了解的函数式编程](https://github.com/jasonGeng88/blog/blob/master/201705/functional_programming.md)
40 | * [[译] REST API URI 设计的七准则](https://github.com/jasonGeng88/blog/blob/master/201706/rest-api.md)
41 |
42 | * JAVA
43 | * [JAVA中最简单的分布式调用 RMI](https://github.com/jasonGeng88/blog/blob/master/201704/rmi.md)
44 | * [[译]《Spring 5 官方文档》JMS (Java Message Service)](https://github.com/jasonGeng88/spring5-translate)
45 | * [JMS 在 SpringBoot 中的使用](https://github.com/jasonGeng88/blog/blob/master/201706/jms.md)
46 | * [JAVA 中原生的 socket 通信机制](https://github.com/jasonGeng88/blog/blob/master/201708/java-socket.md)
47 | * [JAVA NIO 一步步构建I/O多路复用的请求模型](https://github.com/jasonGeng88/blog/blob/master/201708/java-nio.md)
48 | * [记一次 JAVA 的内存泄露分析](https://github.com/jasonGeng88/blog/blob/master/201710/java-analysis.md)
49 | * [JAVA 线程池的正确打开方式](https://github.com/jasonGeng88/blog/blob/master/201802/java-threadpool.md)
50 |
51 | * 运维系列
52 | * [Elasticsearch 集群配置与容器化部署](https://github.com/jasonGeng88/blog/blob/master/201704/es_cluster.md)
53 | * [我们为什么要用 HTTPS](https://github.com/jasonGeng88/blog/blob/master/201705/https.md)
54 |
55 | * 搜索
56 | * [一步步实现 Redis 搜索引擎](https://github.com/jasonGeng88/blog/blob/master/201706/redis-search.md)
57 |
58 | * 前端
59 | * [彻底搞懂 JS 中 this 机制](https://github.com/jasonGeng88/blog/blob/master/201804/js-this.md)
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/convert_md.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | prefix=`echo $1 |sed 's/\(.*\)\/.*/\1/'`
4 |
5 | cat $1 |sed "s/\!\[\](\(.*\))/\!\[\](https\:\/\/github\.com\/jasonGeng88\/blog\/blob\/master\/$prefix\/\1\?raw=true)/g"
6 |
--------------------------------------------------------------------------------