概述
一般來說, 應(yīng)用由三部分組成
- 可執(zhí)行程序
- 配置
- 數(shù)據(jù)
軟件系統(tǒng)幾乎都有配置,它能靈活地定義軟件的關(guān)鍵屬性,組件之間的依賴關(guān)系與交互方式。
1. 關(guān)于配置的思考
1) 多還是少
- 為了簡單,配置當然越少
- 為了靈活,配置當然越多越好
2) 自動還是手動
能自動當然不要手動,可是現(xiàn)實中少不了人工的參與, 對于配置屬性進行添加,修改和刪除
最好是一次修改, 到處可用
3) 推送還是拉取
推送與拉取取決于工作量的多少和意外情況的控制, 當服務(wù)器數(shù)量較多,分布較廣,狀態(tài)不一的時候,推送的方法會有問題, 不一定能推送到目標服務(wù)器上, 因為目標服務(wù)器可能并未啟動,或者由于網(wǎng)絡(luò)狀態(tài)不可達, 所以拉取的方式更好一點。
當然不必每次都去配置倉庫, 配置服務(wù)或配置文件里讀取, 而是按需讀取。 采用訂閱-通知-讀取是比較好的方式
4) 內(nèi)部還是外部
內(nèi)部配置宜多一點,外部配置宜少一點。
具體來說, 對外的, 提供給一般使用者的要盡量簡單, 盡量少, 而對內(nèi)的, 提供給高級管理員, 或開發(fā)者自己的配置, 可以適當多一點, 最好分個層次,不要堆積一大堆配置把人搞暈配置信息放在服務(wù)外部
我們開發(fā)一個微服務(wù), 服務(wù)本身的代碼應(yīng)該就一套, 可以會有多個邏輯分支和功能開關(guān)
可是配置信息可能變化多端, 最好不要都放在一起, 代碼和默認配置放在一個代碼倉庫, 配置信息放在另外一個代碼倉庫, 這樣做的好處是更改配置信息無需發(fā)布新的服務(wù)版本, 盡量保持服務(wù)核心代碼的穩(wěn)定
5) 集中式還是分布式
集中式比較簡單, 要注意不要有單點失敗, 不支持對于跨數(shù)據(jù)中心的災難恢復, 受網(wǎng)絡(luò)故障的影響比較大
分布式相對復雜一點, 要注意 SSoT(Single Source of Truth), 以一處的配置數(shù)據(jù)為黃金數(shù)據(jù), 其他各處及時同步, 要有對于網(wǎng)絡(luò)同步不及時的應(yīng)對措施
2. 配置的版本管理
所有創(chuàng)建軟件的東西都應(yīng)該納入版本控制
- 源代碼
- 測試腳本
- 數(shù)據(jù)庫腳本
- 構(gòu)建和部署腳本
- 文檔
- 包括各類圖表, 建議用 Markdown 和可以生成圖表的腳本存儲, 以利于版本之間的比較
- 比如 https://www.websequencediagrams.com, http://yuml.me
- 依賴的庫及工具
- 依賴當然越少越好,可是現(xiàn)在的庫以及工具能做的事,應(yīng)用程序就能省則省
- 配置腳本及數(shù)據(jù)
3. 配置的載體
配置放哪兒呢, 最熟悉的莫過于配置文件,環(huán)境變量, 注冊表或者數(shù)據(jù)庫了
環(huán)境變量
這是一切和環(huán)境相關(guān)配置的首選,比如網(wǎng)絡(luò)IP, 數(shù)據(jù)庫地址,數(shù)據(jù)文件目錄等
配置文件
這是最方便, 最常用的配置載體, 配置文件的格式也是五花八門:
- ini
- properties
- json
- xml
- yaml
象 lua, python, ruby這樣的腳本語言, 直接就用一個單獨的腳本表示就好了
在一些特定領(lǐng)域,用 DSL 領(lǐng)域特定語言的配置更加容易理解
在實踐中, 最好在讀取配置文件之后, 將其配置信息建模 , 以 python + json 舉個簡單的例子
有一個在線課程系統(tǒng), 為每個注冊用戶建立一個配置文件
{
"username": "walter",
"password": "xxx",
"email": "walter@xxx.com",
"courses": [ ],
"scores": { }
}
對應(yīng)的python 程序如下
import json
import os
import sys
class UserConfig:
def __init__(self, json_file):
self.read_config(json_file)
self.username = self.config_data['username']
self.password = self.config_data['password']
self.email = self.config_data['email']
def read_config(self, json_file):
json_data=open(json_file)
self.config_data = json.load(json_data)
def dump_config(self):
print self.config_data
print "username=", self.username
print "password=", self.password
print "email=", self.email
if __name__ == "__main__":
args = sys.argv
usage = "usage: python %s <config_file>" % args[0]
argc = len(args)
if(argc < 2):
print usage
else:
json_file = args[1]
print("* the json config file is " + args[1])
config = UserConfig(json_file)
config.dump_config()
執(zhí)行結(jié)果
$ python user_config.py user_config.json
* the json config file is user_config.json
{u'username': u'walter', u'courses': [], u'password': u'xxx', u'email': u'walter@xxx.com', u'scores': {}}
username= walter
password= xxx
email= walter@xxx.com
配置數(shù)據(jù)庫
數(shù)據(jù)庫常用來存儲復雜的配置,在建立復雜關(guān)系方面優(yōu)勢明顯
最常用是表結(jié)構(gòu)就是經(jīng)典的 key/value 形式
| 列名 | 類型 |
|---|---|
| id | uuid |
| name | varchar(256) |
| value | varchar(256) |
| create_time | timestamp |
| update_time | timestamp |
當然 Cassandra, Redis 等 NOSQL 就更簡單了
環(huán)境管理
一般來說, 我們會有很多不同的測試環(huán)境和產(chǎn)品環(huán)境來發(fā)布我們的服務(wù)
比如我們常用的環(huán)境有如下幾種
- lab env
- ats env
- bts env
- production env
每種環(huán)境就有多臺服務(wù)器協(xié)同工作, 手工配置顯示太麻煩, 于是眾多配置管理的運維工具應(yīng)運而生
- Ansible
- Chef
- Fabric
- Puppet
- SaltStack
Puppet 以前用得很多, Ansible 最近比較火, 我比較喜歡用輕量級的Fabric, 參見以前寫的 程序員瑞士軍刀之Fabric
配置服務(wù)
把具體的配置項及業(yè)務(wù)相關(guān)的配置信息包裝成資源, 以REST API的形式暴露讀取和修改接口, 大型系統(tǒng)中的復雜的配置甚至可以單獨作為一個微服務(wù)存在
例如下圖

它的好處在于
- 可以將配置信息很好進行建模,在API層面就嵌入AAA 管理
Authentication認證, Authorization授權(quán) 和 Auditing審計, 防止非法操作 - 可以基于 API 將配置流程自動化
- 以服務(wù)作為配置數(shù)據(jù)的真正單個來源 SSoT (Single Source of Truth)
- 提供訂閱和通知服務(wù), 在配置有改動時立即通知其他相關(guān)的微服務(wù)和系統(tǒng)
很多開源項目都可以用來構(gòu)建Configure Service
| 項目 | 組織/社區(qū) | 語言 | 特點 |
|---|---|---|---|
| Consul | hashicorp | go | 提供健值存取, 服務(wù)發(fā)現(xiàn)和服務(wù)配置管理功能, 提供好用的命令行和網(wǎng)頁界面 |
| ZooKeeper | apache | java | 老牌產(chǎn)品, 經(jīng)久耐用, 提供集中式的服務(wù)接口, 可用于分布式系統(tǒng)中配置信息, 服務(wù)注冊信息的同步, 使用 ZAB 協(xié)議 |
| Etcd | coreos | go | 提供服務(wù)發(fā)現(xiàn)和分布式鍵值存取功能, 使用 Raft 協(xié)議, 速度快, 安裝配置容易簡單 |
| Eureka | netflix | java | 提供服務(wù)發(fā)現(xiàn)和分布式鍵值存取功能, 并提供服務(wù)器端動態(tài)刷新, 網(wǎng)飛出口, 必屬精品, 連 AWS 也在用它 |
| Spring Cloud Config | spring | java | Spring Cloud 大家庭中的一員, 和其他 Spring 項目無縫集成, 后端可以用文件系統(tǒng), git, 及 Eureka 和 Consul |
下面我們來用 Spring Cloud Config 來舉個例子
Spring Cloud Config 很靈活, 配置信息可以使用本地文件系統(tǒng), 也可以從遠程 git 倉庫中拉取, 從Database 及 key-value 系統(tǒng)獲取, 或者與 Eureka , Consul 這樣的系統(tǒng)集成.
建立一個微服務(wù): config-service
啟動類 ConfigServiceApplication
package com.github.walterfan.helloworld.configservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServiceApplication.class, args);
}
}
配置文件 src/main/resources/application.yml
server:
port: 9002
logging:
level:
org.springframework.cloud: 'DEBUG'
spring:
application:
name: config-service
cloud:
config:
server:
native:
search-locations: classpath:/config
#git:
# uri: https://github.com/walterfan/helloworld
profiles:
active: native
Config service 可為其他 Micro Service 提供配置信息, 配置文件命名格式如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
我們?yōu)?taskservice 建立兩個配置文件
- taskservice-dev.yml
database:
name: SQLite
driver: org.sqlite.JDBC
url: jdbc:sqlite:/home/walter/data/wfdb.s3db
username: walter
password: pass1234
- taskservice-prod.yml
database:
name: MySQL
driver: com.mysql.jdbc.Driver
url: jdbc:mysql://waltersite/wfdb?useUnicode=true&characterEncoding=utf8
username: walter
password: pass1234
參考文檔
- Continues Delivery - Jez Humble, David Farley
- Spring Cloud Consul
- https://www.baeldung.com/spring-cloud-configuration