概述
一般来说, 应用由三部分组成
- 可执行程序
- 配置
- 数据
软件系统几乎都有配置,它能灵活地定义软件的关键属性,组件之间的依赖关系与交互方式。
1. 关于配置的思考
1) 多还是少
- 为了简单,配置当然越少
- 为了灵活,配置当然越多越好
2) 自动还是手动
能自动当然不要手动,可是现实中少不了人工的参与, 对于配置属性进行添加,修改和删除 最好是一次修改, 到处可用
3) 推送还是拉取
推送与拉取取决于工作量的多少和意外情况的控制, 当服务器数量较多,分布较广,状态不一的时候,推送的方法会有问题, 不一定能推送到目标服务器上, 因为目标服务器可能并未启动,或者由于网络状态不可达, 所以拉取的方式更好一点。
当然不必每次都去配置仓库, 配置服务或配置文件里读取, 而是按需读取。 采用订阅-通知-读取是比较好的方式
4) 内部还是外部
-
内部配置宜多一点,外部配置宜少一点。 具体来说, 对外的, 提供给一般使用者的要尽量简单, 尽量少, 而对内的, 提供给高级管理员, 或开发者自己的配置, 可以适当多一点, 最好分个层次,不要堆积一大堆配置把人搞晕
-
配置信息放在服务外部 我们开发一个微服务, 服务本身的代码应该就一套, 可以会有多个逻辑分支和功能开关 可是配置信息可能变化多端, 最好不要都放在一起, 代码和默认配置放在一个代码仓库, 配置信息放在另外一个代码仓库, 这样做的好处是更改配置信息无需发布新的服务版本, 尽量保持服务核心代码的稳定
5) 集中式还是分布式
集中式比较简单, 要注意不要有单点失败, 不支持对于跨数据中心的灾难恢复, 受网络故障的影响比较大 分布式相对复杂一点, 要注意 SSoT(Single Source of Truth), 以一处的配置数据为黄金数据, 其他各处及时同步, 要有对于网络同步不及时的应对措施
2. 配置的版本管理
所有创建软件的东西都应该纳入版本控制
- 源代码
- 测试脚本
- 数据库脚本
- 构建和部署脚本
- 文档
- 包括各类图表, 建议用 Markdown 和可以生成图表的脚本存储, 以利于版本之间的比较
- 比如 www.websequencediagrams.com, yuml.me
- 依赖的库及工具
- 依赖当然越少越好,可是现在的库以及工具能做的事,应用程序就能省则省
- 配置脚本及数据
3. 配置的载体
配置放哪儿呢, 最熟悉的莫过于配置文件,环境变量, 注册表或者数据库了
环境变量
这是一切和环境相关配置的首选,比如网络IP, 数据库地址,数据文件目录等
配置文件
这是最方便, 最常用的配置载体, 配置文件的格式也是五花八门:
- ini
- properties
- json
- xml
- yaml
象 lua, python, ruby这样的脚本语言, 直接就用一个单独的脚本表示就好了 在一些特定领域,用 DSL 领域特定语言的配置更加容易理解
在实践中, 最好在读取配置文件之后, 将其配置信息建模 , 以 python + json 举个简单的例子 有一个在线课程系统, 为每个注册用户建立一个配置文件
{
"username": "walter",
"password": "xxx",
"email": "walter@xxx.com",
"courses": [ ],
"scores": { }
}
对应的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()
执行结果
$ 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
配置数据库
数据库常用来存储复杂的配置,在建立复杂关系方面优势明显 最常用是表结构就是经典的 key/value 形式
| 列名 | 类型 |
|---|---|
| id | uuid |
| name | varchar(256) |
| value | varchar(256) |
| create_time | timestamp |
| update_time | timestamp |
当然 Cassandra, Redis 等 NOSQL 就更简单了
环境管理
一般来说, 我们会有很多不同的测试环境和产品环境来发布我们的服务 比如我们常用的环境有如下几种
- lab env
- ats env
- bts env
- production env
每种环境就有多台服务器协同工作, 手工配置显示太麻烦, 于是众多配置管理的运维工具应运而生
- Ansible
- Chef
- Fabric
- Puppet
- SaltStack
Puppet 以前用得很多, Ansible 最近比较火, 我比较喜欢用轻量级的Fabric, 参见以前写的 程序员瑞士军刀之Fabric
配置服务
把具体的配置项及业务相关的配置信息包装成资源, 以REST API的形式暴露读取和修改接口, 大型系统中的复杂的配置甚至可以单独作为一个微服务存在
例如下图
它的好处在于
- 可以将配置信息很好进行建模,在API层面就嵌入AAA 管理 Authentication认证, Authorization授权 和 Auditing审计, 防止非法操作
- 可以基于 API 将配置流程自动化
- 以服务作为配置数据的真正单个来源 SSoT (Single Source of Truth)
- 提供订阅和通知服务, 在配置有改动时立即通知其他相关的微服务和系统
很多开源项目都可以用来构建Configure Service
| 项目 | 组织/社区 | 语言 | 特点 |
|---|---|---|---|
| Consul | hashicorp | go | 提供健值存取, 服务发现和服务配置管理功能, 提供好用的命令行和网页界面 |
| ZooKeeper | apache | java | 老牌产品, 经久耐用, 提供集中式的服务接口, 可用于分布式系统中配置信息, 服务注册信息的同步, 使用 ZAB 协议 |
| Etcd | coreos | go | 提供服务发现和分布式键值存取功能, 使用 Raft 协议, 速度快, 安装配置容易简单 |
| Eureka | netflix | java | 提供服务发现和分布式键值存取功能, 并提供服务器端动态刷新, 网飞出口, 必属精品, 连 AWS 也在用它 |
| Spring Cloud Config | spring | java | Spring Cloud 大家庭中的一员, 和其他 Spring 项目无缝集成, 后端可以用文件系统, git, 及 Eureka 和 Consul |
下面我们来用 Spring Cloud Config 来举个例子
Spring Cloud Config 很灵活, 配置信息可以使用本地文件系统, 也可以从远程 git 仓库中拉取, 从Database 及 key-value 系统获取, 或者与 Eureka , Consul 这样的系统集成.
建立一个微服务: 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
我们为 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
- www.baeldung.com/spring-clou…