傳統(tǒng)應(yīng)用配置問題
- 靜態(tài)配置
- 傳統(tǒng)應(yīng)用的配置,都是靜態(tài)配置小槐,寫在配置文件中,運(yùn)行時(shí)無法動(dòng)態(tài)修改荷辕,如果修改之后凿跳,就需要重啟應(yīng)用
- 配置格式不統(tǒng)一
- 開發(fā)人員習(xí)慣不同,使用XML疮方、properties控嗜、DB存儲(chǔ)配置
- 易引起事故
- 在上線時(shí),有時(shí)會(huì)忘記修改配置文件案站,將測(cè)試環(huán)境的變量變成線上
- 配置修改麻煩躬审,周期長(zhǎng)
- 在部署多個(gè)服務(wù)器時(shí),修改配置費(fèi)時(shí)費(fèi)力
- 缺少安全審計(jì)和版本控制
- 沒有版本控制蟆盐,在出現(xiàn)問題時(shí)承边,無法及時(shí)進(jìn)行回滾。
配置中心解決方法
- 靜態(tài)配置
- 集中式配置石挂,所有的配置信息都保存到配置中心
- 配置格式不統(tǒng)一
- 配置中心統(tǒng)一管理格式博助,開發(fā)人員不用關(guān)心格式,通過界面管理
- 易引發(fā)事故
- 環(huán)境隔離痹愚,不同的環(huán)境使用不同的配置富岳,互不干擾
- 配置修改之后,可以生效
- 配置麻煩拯腮,周期長(zhǎng)
- 配置集中一次修改窖式,實(shí)時(shí)通知到所有客戶端
- 缺少安全審計(jì)和版本控制
- 所有修改都有歷史記錄,方便查找修改人和時(shí)間
- 可以按需退回到歷史版本
配置基本概念
配置定義
- 可獨(dú)立于程序的可配變量
- 同一份程序在不同配置下會(huì)有不同行為
- 應(yīng)用場(chǎng)景:連接字符串动壤、應(yīng)用配置萝喘、業(yè)務(wù)配置
配置形態(tài)
- 程序內(nèi)部硬編碼(堅(jiān)決反對(duì))
- 配置文件:將配置寫入xml、properties文件中
- 環(huán)境變量:根據(jù)環(huán)境不同琼懊,傳遞到程序中
- 啟動(dòng)參數(shù):將參數(shù)傳遞到程序中
- 基于數(shù)據(jù)庫:將配置寫入到數(shù)據(jù)庫
配置治理
- 權(quán)限控制和審計(jì):要保留用戶的操作記錄和權(quán)限
- 不同環(huán)境阁簸、集群配置管理:根據(jù)不同環(huán)境,獲取不同的配置
- 框架類組件配置管理:獲取配置哼丈,是否開啟功能
- 灰度發(fā)布:進(jìn)行公測(cè)启妹,環(huán)境隔離
配置分類和場(chǎng)景
動(dòng)態(tài)配置
- 應(yīng)用配置
- 請(qǐng)求超時(shí)、線程池醉旦、隊(duì)列饶米、緩存桨啃、數(shù)據(jù)庫連接池的容量、日志級(jí)別咙崎、限流熔斷閾值优幸、黑白名單
- 功能開關(guān)
- 藍(lán)綠發(fā)布、灰度開關(guān)褪猛、降級(jí)開關(guān)网杆、HA高可用開關(guān)、DB遷移
- 業(yè)務(wù)配置
- 促銷規(guī)律伊滋、貸款額度碳却、利率等業(yè)務(wù)參數(shù)、A/B測(cè)試
靜態(tài)配置
- 安全配置
用戶名笑旺、密碼昼浦、令牌、許可證戶等- 環(huán)境相關(guān)
- 數(shù)據(jù)庫/中間件/其他服務(wù)的連接字符串
開關(guān)驅(qū)動(dòng)開發(fā)原理
優(yōu)點(diǎn)
- 新功能和代碼發(fā)布分離筒主,減輕發(fā)布風(fēng)險(xiǎn)
- 迭代速度快关噪,快速創(chuàng)新實(shí)現(xiàn)
- 可定時(shí)高級(jí)A/B測(cè)試
- 相對(duì)復(fù)雜發(fā)布系統(tǒng),投入成本相對(duì)低
- 沒有分支開發(fā)的合并沖突問題
缺點(diǎn)
- 代碼侵入乌妙、需要定期清理
- 需要開關(guān)配置中心配合
- 需要DevOps文化和流程配合
Apollo 功能亮點(diǎn)
- 統(tǒng)一管理不同環(huán)境使兔、不同集群的配置
- Apollo提供了一個(gè)統(tǒng)一界面集中式管理不同環(huán)境(environment)、不同集群(cluster)藤韵、不同命名空間(namespace)的配置虐沥。
- 同一份代碼部署在不同的集群,可以有不同的配置泽艘,比如zk的地址等
- 通過命名空間(namespace)可以很方便的支持多個(gè)不同應(yīng)用共享同一份配置欲险,同時(shí)還允許應(yīng)用對(duì)共享的配置進(jìn)行覆蓋
- 配置修改實(shí)時(shí)生效(熱發(fā)布)
- 用戶在Apollo修改完配置并發(fā)布后,客戶端能實(shí)時(shí)(1秒)接收到最新的配置匹涮,并通知到應(yīng)用程序天试。
- 版本發(fā)布管理
- 所有的配置發(fā)布都有版本概念,從而可以方便的支持配置的回滾然低。
- 灰度發(fā)布
- 支持配置的灰度發(fā)布喜每,比如點(diǎn)了發(fā)布后,只對(duì)部分應(yīng)用實(shí)例生效脚翘,等觀察一段時(shí)間沒問題后再推給所有應(yīng)用實(shí)例灼卢。
- 權(quán)限管理绍哎、發(fā)布審核来农、操作審計(jì)
- 應(yīng)用和配置的管理都有完善的權(quán)限管理機(jī)制,對(duì)配置的管理還分為了編輯和發(fā)布兩個(gè)環(huán)節(jié)崇堰,從而減少人為的錯(cuò)誤沃于。
- 所有的操作都有審計(jì)日志涩咖,可以方便的追蹤問題。
- 客戶端配置信息監(jiān)控
- 可以方便的看到配置在被哪些實(shí)例使用
- 提供Java和.Net原生客戶端
- 提供了Java和.Net的原生客戶端繁莹,方便應(yīng)用集成
- 支持Spring Placeholder檩互,Annotation和Spring Boot的ConfigurationProperties,方便應(yīng)用使用
- 同時(shí)提供了Http接口咨演,非Java和.Net應(yīng)用也可以方便的使用
- 提供開放平臺(tái)API
- Apollo自身提供了比較完善的統(tǒng)一配置管理界面闸昨,支持多環(huán)境、多數(shù)據(jù)中心配置管理薄风、權(quán)限饵较、流程治理等特性。
- 不過Apollo出于通用性考慮遭赂,對(duì)配置的修改不會(huì)做過多限制循诉,只要符合基本的格式就能夠保存。
- 配置可能會(huì)有比較復(fù)雜的格式撇他,如xml, json茄猫,需要對(duì)格式做校驗(yàn)。
- 還有一些使用方如DAL困肩,不僅有特定的格式划纽,而且對(duì)輸入的值也需要進(jìn)行校驗(yàn)后方可保存,如檢查數(shù)據(jù)庫僻弹、用戶名和密碼是否匹配阿浓。
- 對(duì)于這類應(yīng)用,Apollo支持應(yīng)用方通過開放接口在Apollo進(jìn)行配置的修改和發(fā)布蹋绽,并且具備完善的授權(quán)和權(quán)限控制
- 部署簡(jiǎn)單
- 配置中心作為基礎(chǔ)服務(wù)芭毙,可用性要求非常高,這就要求Apollo對(duì)外部依賴盡可能地少
- 目前唯一的外部依賴是MySQL卸耘,所以部署非常簡(jiǎn)單退敦,只要安裝好Java和MySQL就可以讓Apollo跑起來
- Apollo還提供了打包腳本,一鍵就可以生成所有需要的安裝包蚣抗,并且支持自定義運(yùn)行時(shí)參數(shù)
Apollo 架構(gòu)
核心概念
概念 | 應(yīng)用 | 環(huán)境 | 集群 | 配置項(xiàng) |
---|---|---|---|---|
解釋 | 應(yīng)用程序的唯一標(biāo)識(shí),存儲(chǔ)位置為 /META-INF/app.properties |
功能不同,不同環(huán)境,存儲(chǔ)位置為 /opt/settings/server.properties |
一個(gè)應(yīng)用不同實(shí)例的分組侈百,不同集群, 擁有不同的配置 |
支持xml翰铡、properties钝域、json格式 |
注意:配置項(xiàng)存放位置:(1)私有配置env+app+cluster+namespace+item_key;(2)共有配置:env+cluster+namespace+item_key。
命名空間
一個(gè)應(yīng)用不同配置的分組锭魔,默認(rèn)的命名空間為application例证。
命名空間類型
- 私有類型:只能被所屬應(yīng)用獲取
- 共有類型:環(huán)境變量必須全局唯一
- 關(guān)聯(lián)類型:私有繼承公開并覆蓋、定制公共組建配置場(chǎng)景
基礎(chǔ)模型
基礎(chǔ)模型的流程:
1.用戶登陸Apollo系統(tǒng)之后迷捧,在配置中心修改配置并發(fā)布
2.在用戶發(fā)布配置之后织咧,會(huì)同知道Apollo客戶端更新配置
3.Apollo客戶端會(huì)定時(shí)從配置中心拉取最新的配置胀葱、更新本地配置并通知到應(yīng)用
架構(gòu)模塊介紹
Config Server
- 提供配置獲取接口
- 提供配置更新推送接口(基于Http long polling)
- 服務(wù)端使用 Spring DeferredResult實(shí)現(xiàn)異步化,從而大大增加長(zhǎng)連接數(shù)量
- 目前使用的tomcat embed默認(rèn)配置是最多10000個(gè)連接
- 接口服務(wù)對(duì)象為Apollo客戶端
Admin Service
- 提供配置管理接口
- 提供配置修改、發(fā)布等接口
- 接口服務(wù)對(duì)象為Portal
Meta Server
- Portal通過域名訪問Meta Server獲取Admin Service服務(wù)列表(IP+Port)
- Client通過域名訪問Meta Server獲取Config Service服務(wù)列表(IP+Port)
- Meta Server從Eureka獲取Config Service和Admin Service的服務(wù)信息笙蒙,相當(dāng)于是一個(gè)Eureka Client
- 增設(shè)一個(gè)Meta Server的角色主要是為了封裝服務(wù)發(fā)現(xiàn)的細(xì)節(jié)抵屿,對(duì)Portal和Client而言,永遠(yuǎn)通過一個(gè)Http接口獲取Admin Service和Config Service的服務(wù)信息捅位,而不需要關(guān)心背后實(shí)際的服務(wù)注冊(cè)和發(fā)現(xiàn)組件
- Meta Server只是一個(gè)邏輯角色轧葛,在部署時(shí)和Config Service是在一個(gè)JVM進(jìn)程中的,所以IP艇搀、端口和Config Service一致
Eureka
- 基于Eureka和Spring Cloud Netflix提供服務(wù)注冊(cè)和發(fā)現(xiàn)
- Config Service和Admin Service會(huì)向Eureka注冊(cè)服務(wù)朝群,并保持心跳
- 為了簡(jiǎn)單起見,目前Eureka在部署時(shí)和Config Service是在一個(gè)JVM進(jìn)程中的(通過Spring Cloud Netflix)
Portal
- 提供Web界面供用戶管理配置
- 通過Meta Server獲取Admin Service服務(wù)列表(IP+Port)中符,通過IP+Port訪問服務(wù)
- 在Portal側(cè)做load balance姜胖、錯(cuò)誤重試
Client
- Apollo提供的客戶端程序,為應(yīng)用提供配置獲取淀散、實(shí)時(shí)更新等功能
- 通過Meta Server獲取Config Service服務(wù)列表(IP+Port)右莱,通過IP+Port訪問服務(wù)
- 在Client側(cè)做load balance、錯(cuò)誤重試
框架如何保證實(shí)時(shí)更新
用戶在Portal發(fā)布配置之后,AdminService會(huì)將配置寫入到ReleaseMessage表中,ConfigServer會(huì)定期掃描ReleaeMessage表档插,會(huì)實(shí)時(shí)通知客戶端慢蜓。(長(zhǎng)連接,通過Http Long Polling實(shí)現(xiàn))
客戶端還會(huì)定時(shí)從Apollo配置中心拉取配置(推拉結(jié)合郭膛,Spring DeferredResult晨抡、定期拉配置)
高可用
1.Config/Admin/Portal 都是用負(fù)載均衡技術(shù),來保證高可用则剃。
2.服務(wù)端的實(shí)時(shí)推送技術(shù)耘柱、客戶端定時(shí)拉取配置,保證及時(shí)性棍现,如果拉取不到调煎,客戶端使用本地緩存的配置,保證高可用己肮。
為什么使用Eureka
- 它提供了完整的Service Registry和Service Discovery實(shí)現(xiàn)
+首先是提供了完整的實(shí)現(xiàn)士袄,并且也經(jīng)受住了Netflix自己的生產(chǎn)環(huán)境考驗(yàn),相對(duì)使用起來會(huì)比較省心谎僻。- 和Spring Cloud無縫集成
- 同時(shí)Spring Cloud還有一套非常完善的開源代碼來整合Eureka娄柳,使用起來非常方便。
- Eureka支持在應(yīng)用自身的容器中啟動(dòng)艘绍,也就是說應(yīng)用啟動(dòng)完之后赤拒,既充當(dāng)了Eureka的角色,同時(shí)也是服務(wù)的提供者鞍盗。這樣就極大的提高了服務(wù)的可用性需了。
+為了提高配置中心的可用性和降低部署復(fù)雜度,我們需要盡可能地減少外部依賴般甲。- 開源
+由于代碼是開源的肋乍,所以非常便于我們了解它的實(shí)現(xiàn)原理和排查問題
Apollo 于Spring Boot 結(jié)合
pom依賴
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.8.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
yml文件
app:
id: SampleApp
apollo:
bootstrap:
enabled: true
meta: https://localhost:8080
function:
switch:
open: false
current:
version: v1
server:
port: 9000
添加Apollo配置
package com.edu.apollo.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
@Data
public class DemoGlobalConfig {
@Value("${current.version}")
public String currentVersion;
@Value("${function.switch.open}")
public boolean switchFlag;
}
package com.edu.apollo.config;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableApolloConfig
public class DemoApolloConfig {
@Bean
public DemoGlobalConfig globalConfig() {
return new DemoGlobalConfig();
}
}
相對(duì)應(yīng)的Controller
package com.edu.apollo.controller;
import com.edu.apollo.config.DemoGlobalConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class ApolloConfigController {
@Autowired
DemoGlobalConfig demoGlobalConfig;
@Autowired
Environment environment;
@CrossOrigin
@RequestMapping(value = "/config", method = RequestMethod.GET)
public ResponseEntity<String> getApolloConfig() {
String currentVersion = demoGlobalConfig.getCurrentVersion();
return new ResponseEntity<String>(currentVersion,HttpStatus.OK);
}
//check whether the parameter is valid or not
@CrossOrigin
@RequestMapping(value = "/parameter", method = RequestMethod.GET)
public ResponseEntity<String> getApolloConfigByParameter(@RequestParam String parameter) {
String result = environment.getProperty(parameter);
if (result == null){
result = "defaultValue";
}
return new ResponseEntity<String>(result,HttpStatus.OK);
}
}
添加開關(guān)
package com.edu.apollo.service;
public interface SwitchService {
public String getSwitchData();
}
package com.edu.apollo.service.impl;
import com.edu.apollo.config.DemoGlobalConfig;
import com.edu.apollo.service.SwitchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SwitchServiceImpl implements SwitchService {
@Autowired
private DemoGlobalConfig demoGlobalConfig;
@Override
public String getSwitchData() {
if (demoGlobalConfig.isSwitchFlag()) {
return "new";
} else {
return "old";
}
}
}
package com.edu.apollo.controller;
import com.edu.apollo.service.SwitchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SwitchController {
@Autowired
private SwitchService switchService;
@GetMapping(value = "/switch")
public ResponseEntity<String> getSwitchOpen(){
return new ResponseEntity<String>(switchService.getSwitchData(),HttpStatus.OK);
}
}
在啟動(dòng)程序的時(shí)候,需要添加環(huán)境變量:-Dapollo.configService=http://localhost:8080
Apollo啟動(dòng)過程
start config service for client
start admin service for portal 8090
start portal service 8070
eureka 8080
apollo cache:/opt/data/{appid}/config-cache 要有讀寫權(quán)限
運(yùn)行步驟
校驗(yàn)是否啟動(dòng)成功
在程序啟動(dòng)之后敷存,需要進(jìn)行校驗(yàn): http://localhost:9000/config
返回值為v1
校驗(yàn)Apollo是否生效
在Apollo界面上更改配置為current.version=v2
在程序啟動(dòng)之后墓造,需要進(jìn)行校驗(yàn): http://localhost:9000/config
返回值為v2
開關(guān)功能校驗(yàn)
在程序啟動(dòng)之后,需要進(jìn)行校驗(yàn): http://localhost:9000/switch
返回值為old
在Apollo界面上更改配置為function.switch.open=true
在程序啟動(dòng)之后锚烦,需要進(jìn)行校驗(yàn): http://localhost:9000/config
返回值為v2
參考文獻(xiàn)
Apollo配置中心設(shè)計(jì)
微服務(wù)架構(gòu)~攜程Apollo配置中心架構(gòu)剖析
source code