้ ็ฝฎไธญๅฟ๏ผ่ฎฉๆนๅจ"็ง็บง็ๆ"
็ณปๅไธ๏ผๅบ็ก่ฎพๆฝ็ฏ ยท ็ฌฌ3็ฏ
ไฝ ๆๆฒกๆ็ปๅ่ฟ่ฟๆ ท็ๅบๆฏ๏ผไธบไบๆนไธไธช่ถ ๆถๆถ้ด๏ผ้่ฆ้ๆฐๆๅ ใ้ๆฐ้จ็ฝฒใ้ๆฐไธ็บฟ๏ผไธไธช็ฎๅ็ๅๆฐ่ฐๆด๏ผๅด่ฆๆ่ พๅๅฐๆถ็่ณๆดไน ๏ผ
้ ็ฝฎไธญๅฟๅฐฑๆฏไธบไบ่งฃๅณ่ฟไธช้ฎ้ข่็็ใๅฎ่ฎฉ้ ็ฝฎๅๆดไป"ๅๅธ็บง"ๅๆ"็ง็บง"๏ผ่ฎฉ่ฟ็ปดไป"้ๅฏๅคงๆณ"ๅๆ"ไธ้ฎ็ๆ"ใ
ไปๅคฉๆไปฌๆฅ่่้ ็ฝฎไธญๅฟ็่ฎพ่ฎกๅ็๏ผ็็ๅฎๆฏๅฆไฝ่ฎฉๆนๅจ"็ง็บง็ๆ"็ใ
ไธใ้ ็ฝฎ็ฎก็็ๆผ่ฟ
1.1 ็กฌ็ผ็ ๆถไปฃ
ๆๅๅง็ๆนๅผ๏ผ้ ็ฝฎ็ดๆฅๅๆญปๅจไปฃ็ ้๏ผ
const int MAX_RETRY = 3;
const int TIMEOUT_MS = 5000;
่ฟ็งๆนๅผๆ็ฎๅ๏ผไฝ้ฎ้ขไนๆๆๆพ๏ผ
- ๆนไธไธชๆฐๅญๅฐฑ่ฆๆนไปฃ็ ใ้ๆฐ็ผ่ฏใ้ๆฐ้จ็ฝฒ
- ไธๅ็ฏๅข้่ฆไธๅ็ๅผ๏ผๆไนๅค็๏ผ
- ๆๆไฟกๆฏ๏ผๅฏ็ ใๅฏ้ฅ๏ผ็ดๆฅๆด้ฒๅจไปฃ็ ไปๅบ้
๐ฏ ็ๅฎๆกไพ๏ผๆ็ตๅๅข้ไธบไบๆนไธไธชๆฏไป่ถ ๆถๆถ้ด๏ผ็ปๅไบไปฃ็ ๆไบค โ Code Review โ ๆๅ โ ๆต่ฏ็ฏๅข้ช่ฏ โ ้ขๅๅธ้ช่ฏ โ ็ไบงๅๅธ๏ผๆดๆดๆ่ พไบ 2 ๅคฉใ็ปๆ่ถ ๆถๆถ้ดไป 5 ็งๆนๆ 8 ็ง๏ผๅฐฑ่ฟไธ่กๆนๅจใ
1.2 ้ ็ฝฎๆไปถๆถไปฃ
ๆ้ ็ฝฎๆฝ็ฆปๅฐๅค้จๆไปถ๏ผ
# config.properties
max.retry=3
timeout.ms=5000
db.url=jdbc:mysql://localhost:3306/mydb
่ฟๆญฅไบไธ็น๏ผไฝ่ฟๆฏๆ้ฎ้ข๏ผ
- ้ ็ฝฎๆไปถๆๅ ๅจๅบ็จ้๏ผไฟฎๆน่ฟๆฏ่ฆ้ๆฐ้จ็ฝฒ
- ๅคไธชๆๅก้่ฆๅๆญฅๅไธไปฝ้ ็ฝฎๆไนๅ๏ผ
- ้ ็ฝฎๆไปถๆฃ่ฝๅจๅๅค๏ผ้พไปฅ็ปไธ็ฎก็
1.3 ็ฏๅขๅ้ๆถไปฃ
็จ็ฏๅขๅ้ๆณจๅ ฅ้ ็ฝฎ๏ผ
export MAX_RETRY=3
export TIMEOUT_MS=5000
export DB_URL=jdbc:mysql://prod-db:3306/mydb
้ ๅๅฎนๅจๅ้จ็ฝฒ๏ผ็ตๆดปไบๅพๅคใไฝ๏ผ
- ็ฏๅขๅ้ๆฐ้ๆ้ๅถ๏ผ้ๅธธๅ KB ๅฐๅ ๅ KB๏ผ
- ๅคๆ็ๅตๅฅ้ ็ฝฎ้พไปฅ่กจ่พพ
- ไฟฎๆนๅไป้้ๅฏๅฎนๅจๆ่ฝ็ๆ
1.4 ้ ็ฝฎไธญๅฟๆถไปฃ
้ ็ฝฎไธญๅฟ็ๆ ธๅฟ็ๅฟต๏ผ้ ็ฝฎไนๆฏไธ็งๆๅกใ
- ้ ็ฝฎ้ไธญๅญๅจใ็ปไธ็ฎก็
- ้ ็ฝฎๅๆดๅฎๆถๆจ้ใๆ ้้ๅฏ
- ๆฏๆๅค็ฏๅขใๅค็ๆฌใๅค็งๆท
- ๆไพๅฎก่ฎกใๅๆปใๆ้ๆงๅถ
่ฟๅฐฑๅไป"ๆฏไธชไบบ่ชๅทฑๅ้ฅญ"่ฟๅๅฐ"ไธญๅคฎๅจๆฟ็ปไธ้ ้"โโๆ ๅๅใ้ซๆ็ใๅฏ่ฟฝๆบฏใ
ไบใ้ ็ฝฎไธญๅฟ็ๆ ธๅฟ่ฎพ่ฎกๅ็
2.1 ๆดไฝๆถๆ
้ ็ฝฎไธญๅฟ้็จๅๅฑๆถๆ่ฎพ่ฎก๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ้
็ฝฎ็ฎก็ๆงๅถๅฐ โ
โ (Web UI / API) โ
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ โ
โ โ ้
็ฝฎ็ผ่พ โ ็ๆฌ็ฎก็ โ ๆ้็ฎก็ โ โ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ้
็ฝฎๆๅกๅฑ โ
โ (Config Server Cluster) โ
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ โ
โ โ ้
็ฝฎ่ฏปๅ โ ๅๆดๆจ้ โ SDK ๆฅๅ
ฅ โ โ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ MySQL ้็พค โ โ Redis ้็พค โ โ ๅคไปฝๅญๅจ โ
โ (้
็ฝฎๅญๅจ) โ โ (ๅๆด้็ฅ) โ โ (็ๆฌๆงๅถ) โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ ๅบ็จ A โ โ ๅบ็จ B โ โ ๅบ็จ C โ
โ SDK โ โ SDK โ โ SDK โ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
2.2 ๆ ธๅฟ็ปไปถ่ฏฆ่งฃ
้ ็ฝฎๆๅก็ซฏๆฏๆดไธช็ณป็ป็ๅคง่๏ผ่ด่ดฃ๏ผ
- ้ ็ฝฎ็ฎก็๏ผๆไพ RESTful API ๆฅๅฃ๏ผๅค็้ ็ฝฎ็ CRUD ๆไฝ
- ๅๆดๆจ้๏ผ็ฎก็ๅฎขๆท็ซฏ่ฟๆฅ๏ผๅฎๆถๆจ้้ ็ฝฎๅๆด
- ๆ้ๆงๅถ๏ผ้ช่ฏๅฎขๆท็ซฏ่บซไปฝ๏ผๆๆ้ ็ฝฎ่ฎฟ้ฎ
- ้ซๅฏ็จไฟ้๏ผ้็พค้จ็ฝฒ๏ผๆ ้่ชๅจๅๆข
// ้
็ฝฎๆๅก็ซฏๆ ธๅฟๆฅๅฃ็คบไพ
@RestController
@RequestMapping("/api/v1/configs")
public class ConfigController {
// ่ทๅ้
็ฝฎ
@GetMapping("/{appId}/{env}/{namespace}")
public ConfigResponse getConfig(
@PathVariable String appId,
@PathVariable String env,
@PathVariable String namespace,
@RequestHeader("Authorization") String token) {
// 1. ้ช่ฏๆ้
if (!authService.hasPermission(token, appId, env)) {
throw new ForbiddenException("No permission");
}
// 2. ่ทๅ้
็ฝฎ๏ผๅธฆ็ผๅญ๏ผ
Config config = configService.getConfig(appId, env, namespace);
// 3. ่ฟๅ้
็ฝฎๅ
ๅฎนๅ็ๆฌๅท
return ConfigResponse.builder()
.config(config.getContent())
.version(config.getVersion())
.releaseKey(config.getReleaseKey())
.build();
}
// ๅๅธ้
็ฝฎ
@PostMapping("/{appId}/{env}/{namespace}/release")
public ReleaseResponse releaseConfig(
@PathVariable String appId,
@PathVariable String env,
@PathVariable String namespace,
@RequestBody ReleaseRequest request) {
// 1. ็ๆๆฐ็ๆฌๅท
long newVersion = versionGenerator.next();
// 2. ๆไน
ๅ้
็ฝฎ
configService.release(appId, env, namespace,
request.getConfigs(), newVersion, request.getOperator());
// 3. ้็ฅๆๆ่ฎข้
็ๅฎขๆท็ซฏ
notificationService.notify(appId, env, namespace, newVersion);
return ReleaseResponse.success(newVersion);
}
}
ๅฎขๆท็ซฏ SDK ๅตๅ ฅๅฐไธๅกๅบ็จไธญ๏ผๆฏ้ ็ฝฎไธญๅฟ็่งฆ่ง๏ผ
// ๅฎขๆท็ซฏ SDK ไฝฟ็จ็คบไพ
public class ConfigClientExample {
public static void main(String[] args) {
// ๅๅงๅ้
็ฝฎๅฎขๆท็ซฏ
ConfigService configService = ConfigService.getAppConfig(
"my-app", // ๅบ็จ ID
"prod", // ็ฏๅข
new ConfigChangeListener() { // ๅๆด็ๅฌๅจ
@Override
public void onChange(ConfigChangeEvent event) {
// ้
็ฝฎๅๆดๅ่ฐ
for (String key : event.changedKeys()) {
ConfigChange change = event.getChange(key);
System.out.println(String.format(
"้
็ฝฎ %s ไป %s ๅๆดไธบ %s",
key, change.getOldValue(), change.getNewValue()));
// ๅจๆ่ฐๆด่ฟๆฅๆฑ ๅคงๅฐ
if ("db.pool.size".equals(key)) {
int newSize = Integer.parseInt(change.getNewValue());
connectionPool.resize(newSize);
}
}
}
});
// ่ทๅ้
็ฝฎๅผ
int timeout = configService.getIntProperty("request.timeout", 5000);
String dbUrl = configService.getProperty("db.url", "localhost:3306");
}
}
ๅญๅจๅฑๆฏ้ ็ฝฎไธญๅฟ็ๅบ็ณ๏ผ
| ๅญๅจๆนๆก | ไผๅฟ | ๅฃๅฟ | ้็จๅบๆฏ |
|---|---|---|---|
| MySQL | ๆ็็จณๅฎใๆ่ฟ็ปด | ๆ ๅ็ๆจ้่ฝๅ | ไธญๅฐ่งๆจกใไผ ็ปๆถๆ |
| Redis | ้ซๆง่ฝใPub/Sub ๆบๅถ | ๅ ๅญๆๆฌ่พ้ซ | ้ซ้ขๅๆดๅบๆฏ |
| ๆททๅๅญๅจ | ๅ ผ้กพๆง่ฝๅๅฏ้ ๆง | ๆถๆๅคๆๅบฆ้ซ | ๅคง่งๆจก็ไบง็ฏๅข |
- ้ ็ฝฎๆฐๆฎ๏ผๅญๅจๅจ MySQL๏ผๆๆฅ่ฏขใๆๅคไปฝ๏ผ
- ๅๆด้็ฅ๏ผไฝฟ็จ Redis PubSub๏ผๅฎๆถๆจ้๏ผ
- ็ๆฌๅๅฒ๏ผๅฎๆๅคไปฝๅฐๅฏน่ฑกๅญๅจ๏ผๅฎก่ฎก่ฟฝๆบฏ๏ผ
2.3 ้ ็ฝฎๆจกๅ่ฎพ่ฎก
ไธไธช็ตๆดป็้ ็ฝฎๆจกๅ้่ฆ่่ๅคไธช็ปดๅบฆ๏ผ
้
็ฝฎไธญๅฟ
โโโ ๅฝๅ็ฉบ้ด๏ผNamespace๏ผ
โ โโโ dev๏ผๅผๅ็ฏๅข๏ผ
โ โ โโโ ๅบ็จ A
โ โ โ โโโ application.yml๏ผไธป้
็ฝฎ๏ผ
โ โ โ โโโ db.yml๏ผๆฐๆฎๅบ้
็ฝฎ๏ผ
โ โ โ โโโ redis.yml๏ผ็ผๅญ้
็ฝฎ๏ผ
โ โ โโโ ๅบ็จ B
โ โโโ test๏ผๆต่ฏ็ฏๅข๏ผ
โ โโโ uat๏ผ้ขๅๅธ็ฏๅข๏ผ
โ โโโ prod๏ผ็ไบง็ฏๅข๏ผ
# ้
็ฝฎ้กน็คบไพ
db:
url: jdbc:mysql://prod-db:3306/mydb
username: app_user
password: ${DB_PASSWORD} # ๆฏๆๅ ไฝ็ฌฆ
pool:
max_size: 100
min_idle: 10
cache:
type: redis
hosts:
- redis-1:6379
- redis-2:6379
- redis-3:6379
timeout_ms: 3000
ไธใ้ ็ฝฎ็็ญๆดๆฐๆบๅถ
้ ็ฝฎไธญๅฟๆๆ ธๅฟ็่ฝๅ๏ผ้ ็ฝฎๅๆดๅ๏ผๅบ็จๆ ้้ๅฏๅณๅฏ็ๆใๆไปฌๅฎ็ฐไบ 3 ็งๅ ้ ็ฝฎ็ๆ ็่ฝๅใ่ฟๆฏๅฆไฝๅฎ็ฐ็๏ผ
3.1 ๆจๆ็ปๅๆจกๅผ
้ ็ฝฎๅๆญฅ้็จๆจๆ็ปๅ็ๆไฝณๅฎ่ทต๏ผ
โโโโโโโโโโโ โโโโโโโโโโโ
โ Client โ โ Server โ
โโโโโโฌโโโโโ โโโโโโฌโโโโโ
โ โ
โ 1. ๅปบ็ซ้ฟ่ฟๆฅ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> โ
โ โ
โ 2. ๆถๅฐๅๆด้็ฅ๏ผๅช้็ฅ็ๆฌๅท๏ผ โ
โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ 3. ไธปๅจๆๅๆๆฐ้
็ฝฎ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> โ
โ โ
โ 4. ่ฟๅๅฎๆด้
็ฝฎๅ
ๅฎน โ
โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
- ้็ฅ่ฝป้ๅ๏ผๆจ้ๅชๅ้็ๆฌๅท๏ผๆฐๆฎ้ๆๅฐ
- ๅฏ้ ๆงไฟ้๏ผๅณไฝฟ้ฟ่ฟๆฅๆญๅผ๏ผๅฎขๆท็ซฏๅฏไปฅๅฎๆถ่ฝฎ่ฏขๅ ๅบ
- ่ด่ฝฝๅฏๆง๏ผๅฎขๆท็ซฏๆถๅฐ้็ฅๅไธปๅจๆๅ๏ผๆๅก็ซฏๆ ้็ปดๆคๆจ้้ๅ
- 3็ง็ๆ๏ผไป้ ็ฝฎๅๅธๅฐๅฎขๆท็ซฏ็ๆ๏ผๅ จ็จไธ่ถ ่ฟ 3 ็ง
3.2 ้ฟ่ฟๆฅ็ๅฎ็ฐๆนๆก
// ้ฟ่ฝฎ่ฏขๅฎ็ฐ็คบไพ
@GetMapping("/notifications")
public DeferredResult<ConfigNotification> longPollNotification(
@RequestParam String appId,
@RequestParam String env,
@RequestParam long currentVersion) {
// ่ฎพ็ฝฎ่ถ
ๆถๆถ้ด 30 ็ง
DeferredResult<ConfigNotification> result =
new DeferredResult<>(30000L, "No changes");
// ๆณจๅ็ๅฌๅจ
notificationRegistry.register(appId, env, currentVersion, notification -> {
// ๆๅๆดๆถ็ซๅณ่ฟๅ
result.setResult(notification);
});
// ่ถ
ๆถๆๅฎๆๆถ็งป้ค็ๅฌๅจ
result.onCompletion(() -> {
notificationRegistry.unregister(appId, env);
});
return result;
}
3.3 ้ ็ฝฎๆดๆฐ็ๅๅญๆงไฟ่ฏ
้ ็ฝฎๆดๆฐๅฟ ้กปไฟ่ฏๅๅญๆง๏ผ่ฆไนๅ จ้จ็ๆ๏ผ่ฆไนๅ จ้จไธ็ๆใ
ๅ่ฎพ้่ฆๅๆถๆดๆฐๆฐๆฎๅบ่ฟๆฅๅฐๅๅๅฏ็ ๏ผ
# ๅ้
็ฝฎ
db:
url: jdbc:mysql://old-db:3306/mydb
password: old_password
# ็ฎๆ ้
็ฝฎ
db:
url: jdbc:mysql://new-db:3306/mydb
password: new_password
ๅฆๆๅชๆดๆฐไบ url๏ผๅฏ็ ่ฟๆฏๆง็ โ ่ฟๆฅๅคฑ่ดฅ๏ผ ๅฆๆๅชๆดๆฐไบๅฏ็ ๏ผurl ่ฟๆฏๆง็ โ ๅๆ ทๅคฑ่ดฅ๏ผ
public class ConfigManager {
private volatile ConfigSnapshot currentConfig;
private final AtomicLong configVersion = new AtomicLong(0);
// ้
็ฝฎๆดๆฐ๏ผๅๅญๆไฝ๏ผ
public void updateConfig(ConfigSnapshot newSnapshot) {
// 1. ๆ ก้ช็ๆฌๅท๏ผ้ฒๆญขไนฑๅบ๏ผ
if (newSnapshot.getVersion() <= configVersion.get()) {
return; // ๅฟฝ็ฅๆง็ๆฌ
}
// 2. ๅ ่ฝฝๆฐ้
็ฝฎๅฐไธดๆถๅ้
Map<String, String> newConfigs = newSnapshot.getConfigs();
// 3. ้ช่ฏ้
็ฝฎๅฎๆดๆง
if (!validateConfig(newConfigs)) {
throw new ConfigValidationException("Invalid config");
}
// 4. ๅๅญๆฟๆข๏ผvolatile ไฟ่ฏๅฏ่งๆง๏ผ
currentConfig = newSnapshot;
configVersion.set(newSnapshot.getVersion());
// 5. ่งฆๅๅ่ฐ้็ฅ
notifyConfigChange(newSnapshot);
}
// ่ทๅ้
็ฝฎ๏ผ็บฟ็จๅฎๅ
จ๏ผ
public String getConfig(String key) {
return currentConfig.get(key);
}
}
- ็ๆฌๅทๆบๅถ๏ผๆฏๆฌกๅๅธ็ๆๆฐ็ๆฌ๏ผๅฎขๆท็ซฏๆ็ๆฌๅทๆดไฝๆฟๆข
- ๅ็ผๅฒๅๆข๏ผๅ ๅ ่ฝฝๅฐๆฐ buffer๏ผ้ช่ฏ้่ฟๅไธๆฌกๆงๅๆขๆ้
- volatile ไฟ่ฏๅฏ่งๆง๏ผ็กฎไฟๆๆ็บฟ็จ็ซๅณ็ๅฐๆฐ้ ็ฝฎ
3.4 ็ฐๅบฆๅๅธไธๅๆป
้ ็ฝฎๅๆดไนๆ้ฃ้ฉ๏ผ้่ฆๆฏๆ็ฐๅบฆๅๅๆปใ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ฐๅบฆๅๅธๆต็จ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. ้ๆฉ็ฐๅบฆๅฎไพ๏ผๆ IP/ๆ ็ญพ๏ผ โ
โ ไพๅฆ๏ผweb-server-[01-03] โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. ๅๅธๆฐ้
็ฝฎๅฐ็ฐๅบฆๅฎไพ โ
โ ่งๅฏ็ๆงๆๆ 5-10 ๅ้ โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโดโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโ โโโโโโโโโโโโ
โ ๅผๅธธ๏ผ โ โ ๆญฃๅธธ๏ผ โ
โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
โ ็ซๅณๅๆป โ โ ๆฉๅคง็ฐๅบฆ่ๅด โ
โ ๅๆๅๅ โ โ ๆๅ
จ้ๅๅธ โ
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
@Service
public class GrayReleaseService {
// ็ฐๅบฆๅๅธ
public ReleaseResult grayRelease(GrayReleaseRequest request) {
String appId = request.getAppId();
String env = request.getEnv();
List<String> grayInstances = request.getGrayInstances();
long newVersion = request.getNewVersion();
// 1. ๆ ่ฎฐ็ฐๅบฆๅฎไพ
for (String instance : grayInstances) {
instanceRegistry.markGray(appId, env, instance, newVersion);
}
// 2. ๆจ้ๆฐ้
็ฝฎๅฐ็ฐๅบฆๅฎไพ
notificationService.notifyInstances(appId, env, grayInstances, newVersion);
// 3. ๅๅปบ็ฐๅบฆ็ๆงไปปๅก
monitorService.createGrayMonitor(appId, env, newVersion,
grayInstances, request.getMonitorDuration());
return ReleaseResult.grayStarted(newVersion, grayInstances);
}
// ๅ
จ้ๅๅธ๏ผ็ฐๅบฆ้ช่ฏ้่ฟๅ๏ผ
public ReleaseResult fullRelease(String appId, String env, long version) {
// 1. ๆธ
้ค็ฐๅบฆๆ ่ฎฐ
instanceRegistry.clearGray(appId, env);
// 2. ๅ
จ้ๆจ้
notificationService.notifyAll(appId, env, version);
return ReleaseResult.fullReleased(version);
}
// ไธ้ฎๅๆป
public ReleaseResult rollback(String appId, String env, long targetVersion) {
// 1. ่ทๅ็ฎๆ ็ๆฌ้
็ฝฎ
ConfigSnapshot targetConfig = configRepository.getVersion(appId, env, targetVersion);
// 2. ๅๅธไธบๆๆฐ็ๆฌ
long newVersion = versionGenerator.next();
configRepository.release(appId, env, targetConfig.getConfigs(), newVersion);
// 3. ๅ
จ้ๆจ้
notificationService.notifyAll(appId, env, newVersion);
// 4. ่ฎฐๅฝๅๆปๆไฝ
auditService.log(AuditAction.ROLLBACK, appId, env, newVersion,
"Rollback to version " + targetVersion);
return ReleaseResult.rolledBack(newVersion, targetVersion);
}
}
ๅใๅค็ฏๅข็ฎก็ๆนๆก
ไธไธชๅบ็จ้ๅธธ่ฆ็ปๅๅคไธช็ฏๅข๏ผๅผๅใๆต่ฏใ้ขๅๅธใ็ไบงใๆฏไธช็ฏๅข็้ ็ฝฎ้ฝไธๅฐฝ็ธๅใ
4.1 ็ฏๅข้็ฆป็ญ็ฅ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Dev Config โ โ Test Config โ โ Prod Config โ
โ Cluster โ โ Cluster โ โ Cluster โ
โ โ โ โ โ โ
โ configcenter โ โ configcenter โ โ configcenter โ
โ -dev โ โ -test โ โ -prod โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ Dev App โ โTest App โ โProd App โ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
- ๅฎๅ จ้็ฆป๏ผ้ ็ฝฎๆณ้ฒ้ฃ้ฉๆๅฐ
- ๅฏไปฅ้ๅฏนไธๅ็ฏๅขๅฎๅถ้จ็ฝฒ็ญ็ฅ
- ็ไบง็ฏๅข็ฌ็ซ๏ผๆง่ฝไธๅๆต่ฏๅฝฑๅ
- ่ฟ็ปดๆๆฌ้ซ๏ผ3 ๅฅ้็พค๏ผ
- ้ ็ฝฎๅๆญฅๅฐ้พ๏ผๅ ฌๅ ฑ้ ็ฝฎ้่ฆๅคๅถ 3 ไปฝ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ปไธ้
็ฝฎไธญๅฟ้็พค โ
โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ dev โ โ test โ โ prod โ โ
โ โ namespaceโ โ namespaceโ โ namespaceโ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- ่ฟ็ปดๆๆฌไฝ๏ผ1 ๅฅ้็พค๏ผ
- ๅ ฌๅ ฑ้ ็ฝฎๆไบๅ ฑไบซ
- ้่ฆไธฅๆ ผ็ๆ้ๆงๅถ
- ็ไบง็ฏๅข้ ็ฝฎๅฏ่ฝ่ขซ่ฏฏๆน
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Non-Prod Config Cluster โ โ Prod Config โ
โ (Dev + Test + UAT) โ โ Cluster โ
โ โ โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ โโโโโโโโโโโโ โ
โ โ dev โ โ test โ โ โ โ prod โ โ
โ โ ns โ โ ns โ โ โ โ ns โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
- ็ไบง็ฏๅข็ฌ็ซ้จ็ฝฒ๏ผๅฎๅ จๅฏๆง
- ้็ไบง็ฏๅขๅ ฑไบซ่ตๆบ๏ผ้ไฝๆๆฌ
- ๅ ฌๅ ฑ้ ็ฝฎ้่ฟ CI/CD ๅๆญฅ
4.2 ้ ็ฝฎ็ปงๆฟไธ่ฆ็
ไธๅ็ฏๅข็้ ็ฝฎๆๅคง้็ธๅ้จๅ๏ผๅฆไฝ้ฟๅ ้ๅค๏ผ
# base.yml - ๅบ็ก้
็ฝฎ๏ผๆๆ็ฏๅขๅ
ฑไบซ๏ผ
app:
name: my-service
version: 1.0.0
logging:
level: INFO
format: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
features:
enable_cache: true
enable_metrics: true
---
# dev.yml - ๅผๅ็ฏๅข้
็ฝฎ๏ผ็ปงๆฟ base.yml๏ผ
extends: base.yml
overrides:
logging:
level: DEBUG # ๅผๅ็ฏๅขๅผๅฏ DEBUG
db:
url: jdbc:mysql://localhost:3306/mydb_dev
username: dev_user
password: dev_password
---
# prod.yml - ็ไบง็ฏๅข้
็ฝฎ๏ผ็ปงๆฟ base.yml๏ผ
extends: base.yml
overrides:
logging:
level: WARN # ็ไบง็ฏๅขๅช่ฎฐๅฝ WARN ไปฅไธ
db:
url: jdbc:mysql://prod-db-master:3306/mydb
username: prod_user
password: ${DB_PASSWORD} # ไป็ฏๅขๅ้่ฏปๅ
db:
slaves:
- url: jdbc:mysql://prod-db-slave-1:3306/mydb
- url: jdbc:mysql://prod-db-slave-2:3306/mydb
็ฏๅข็นๅฎ้
็ฝฎ > ็ฏๅขๅบ็ก้
็ฝฎ > ๅ
จๅฑ้ป่ฎค้
็ฝฎ
่ฟๆ ท๏ผๅ ฌๅ ฑ้ ็ฝฎๅช้็ปดๆคไธไปฝ๏ผ็ฏๅขๅทฎๅผ้ ็ฝฎๅ็ฌ่ฆ็ใ
4.3 ้ ็ฝฎๅๆญฅ็ญ็ฅ
ๅฝๅ ฌๅ ฑ้ ็ฝฎๅๆดๆถ๏ผๅฆไฝๅๆญฅๅฐๆๆ็ฏๅข๏ผ
็ฎๅไฝๅฎนๆ้ๆผ๏ผ้ๅๅฐๅๅข้ใ
# .github/workflows/sync-config.yml
name: Sync Base Config
on:
push:
paths:
- 'configs/base.yml'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Sync to all environments
run: |
# ่ฏปๅ base.yml
BASE_CONFIG=$(cat configs/base.yml)
# ๅๆญฅๅฐๅผๅ็ฏๅข
curl -X PUT "https://configcenter.dev.internal/api/v1/configs/my-app/base" \
-H "Authorization: ${{ secrets.CONFIG_CENTER_TOKEN_DEV }}" \
-d "$BASE_CONFIG"
# ๅๆญฅๅฐๆต่ฏ็ฏๅข
curl -X PUT "https://configcenter.test.internal/api/v1/configs/my-app/base" \
-H "Authorization: ${{ secrets.CONFIG_CENTER_TOKEN_TEST }}" \
-d "$BASE_CONFIG"
# ๅๆญฅๅฐ็ไบง็ฏๅข๏ผ้่ฆๅฎกๆน๏ผ
# ็ไบง็ฏๅขๅๆญฅๅจๅ็ฌ็ workflow ไธญ
configcenter ๆฏๆ้ ็ฝฎๅ ้ๅๅๆญฅๅ่ฝ๏ผ
// ้
็ฝฎๅ
้็คบไพ
public void cloneConfig(String sourceEnv, String targetEnv, String appId) {
// 1. ่ฏปๅๆบ็ฏๅข้
็ฝฎ
Config sourceConfig = configService.getConfig(appId, sourceEnv, "application");
// 2. ๅ
้ๅฐ็ฎๆ ็ฏๅข
configService.createConfig(appId, targetEnv, "application", sourceConfig);
// 3. ่ฎฐๅฝๅฎก่ฎกๆฅๅฟ
auditService.log(AuditAction.CLONE, appId, targetEnv,
"Cloned from " + sourceEnv);
}
ไบใ้ซๅฏ็จ่ฎพ่ฎก
้ ็ฝฎไธญๅฟๆฏๅพฎๆๅกๆถๆ็ๅ ณ้ฎไพ่ต๏ผๅฟ ้กปไฟ่ฏ้ซๅฏ็จใไธๆฆ้ ็ฝฎไธญๅฟๅฎๆบ๏ผๅฏ่ฝๅฏผ่ดๆๆๆๅกๆ ๆณๅฏๅจๆ้ ็ฝฎๆ ๆณๆดๆฐใ
5.1 ๅคๅฑๆฌกๅฎน้ๆบๅถ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ้ซๅฏ็จๆถๆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โServer-1 โ โServer-2 โ โServer-3 โ
โ(Active) โ โ(Active) โ โ(Standby)โ
โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ่ด่ฝฝๅ่กก๏ผNginx/SLB๏ผ โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ Client โ โ Client โ โ Client โ
โ Local โ โ Local โ โ Local โ
โ Cache โ โ Cache โ โ Cache โ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
| ๅฑๆฌก | ๆบๅถ | ไฝ็จ |
|---|---|---|
| ๅฎขๆท็ซฏ | ๆฌๅฐ็ผๅญ | ้ ็ฝฎไธญๅฟๅฎๆบๆถ๏ผไฝฟ็จๆฌๅฐ็ผๅญ้ ็ฝฎ |
| ็ฝ็ปๅฑ | ่ด่ฝฝๅ่กก | ่ชๅจๅ้คๆ ้่็น |
| ๆๅกๅฑ | ้็พค้จ็ฝฒ | ๅค่็นไบๅค |
| ๆฐๆฎๅฑ | ไธปไปๅคๅถ | ๆฐๆฎๆไน ๅไฟ่ฏ |
5.2 ๅฎขๆท็ซฏๅฎน้่ฎพ่ฎก
public class ConfigClient {
private final ConfigServerProxy serverProxy;
private final LocalConfigCache localCache;
private final AtomicReference<ConfigSnapshot> currentConfig;
// ่ทๅ้
็ฝฎ๏ผๅธฆๅฎน้๏ผ
public String getConfig(String key) {
// 1. ไผๅ
ไฝฟ็จๅ
ๅญ็ผๅญ
ConfigSnapshot snapshot = currentConfig.get();
if (snapshot != null && snapshot.contains(key)) {
return snapshot.get(key);
}
// 2. ๅฐ่ฏไปๆๅก็ซฏ่ทๅ
try {
ConfigSnapshot newSnapshot = serverProxy.fetchConfig();
currentConfig.set(newSnapshot);
localCache.persist(newSnapshot); // ๆไน
ๅๅฐๆฌๅฐ
return newSnapshot.get(key);
} catch (Exception e) {
log.warn("Failed to fetch config from server, use local cache", e);
// 3. ๆๅก็ซฏๅคฑ่ดฅ๏ผไฝฟ็จๆฌๅฐๆไปถ็ผๅญ
ConfigSnapshot cachedSnapshot = localCache.load();
if (cachedSnapshot != null) {
currentConfig.set(cachedSnapshot);
return cachedSnapshot.get(key);
}
// 4. ๅฎๅ
จๅคฑ่ดฅ๏ผ่ฟๅ้ป่ฎคๅผ
return getDefaultConfig(key);
}
}
}
// ๆฌๅฐ็ผๅญๅฎ็ฐ
public class LocalConfigCache {
private final String cacheDir = System.getProperty("user.home") + "/config-cache";
// ๆไน
ๅ้
็ฝฎๅฐๆฌๅฐ
public void persist(ConfigSnapshot snapshot) {
String cacheFile = String.format("%s/%s-%s-%d.cache",
cacheDir, snapshot.getAppId(), snapshot.getEnv(), snapshot.getVersion());
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(cacheFile))) {
oos.writeObject(snapshot);
} catch (IOException e) {
log.error("Failed to persist config cache", e);
}
}
// ไปๆฌๅฐๅ ่ฝฝ้
็ฝฎ
public ConfigSnapshot load() {
File[] cacheFiles = new File(cacheDir).listFiles((dir, name) ->
name.startsWith(appId + "-" + env) && name.endsWith(".cache"));
if (cacheFiles == null || cacheFiles.length == 0) {
return null;
}
// ๅ ่ฝฝๆๆฐ็็ผๅญๆไปถ
Arrays.sort(cacheFiles, (a, b) -> extractVersion(b) - extractVersion(a));
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(cacheFiles[0]))) {
return (ConfigSnapshot) ois.readObject();
} catch (Exception e) {
log.error("Failed to load config cache", e);
return null;
}
}
}
public class ConfigClientWithFallback {
// ่ทๅ้
็ฝฎ๏ผๅค็บง้็บง๏ผ
public String getConfigWithFallback(String key, String defaultValue) {
// Level 1: ๅ
ๅญ็ผๅญ๏ผๆๅฟซ๏ผ
String value = memoryCache.get(key);
if (value != null) {
return value;
}
// Level 2: ๆๅก็ซฏ๏ผๅฎๆถ๏ผ
try {
value = fetchFromServer(key);
if (value != null) {
memoryCache.put(key, value);
return value;
}
} catch (Exception e) {
log.warn("Server unavailable", e);
}
// Level 3: ๆฌๅฐๆไปถ็ผๅญ๏ผๅฎน้๏ผ
value = localCache.get(key);
if (value != null) {
return value;
}
// Level 4: ้ป่ฎคๅผ๏ผๅ
ๅบ๏ผ
return defaultValue;
}
}
5.3 ๆๅก็ซฏ้ซๅฏ็จ่ฎพ่ฎก
# docker-compose.yml - ้
็ฝฎไธญๅฟ้็พค้จ็ฝฒ
version: '3.8'
services:
config-server-1:
image: configcenter:latest
environment:
- SERVER_ID=1
- CLUSTER_NODES=config-server-1:8080,config-server-2:8080,config-server-3:8080
- DB_URL=jdbc:mysql://mysql-master:3306/config_center
- REDIS_URL=redis://redis:6379
ports:
- "8081:8080"
depends_on:
- mysql-master
- redis
config-server-2:
image: configcenter:latest
environment:
- SERVER_ID=2
- CLUSTER_NODES=config-server-1:8080,config-server-2:8080,config-server-3:8080
- DB_URL=jdbc:mysql://mysql-master:3306/config_center
- REDIS_URL=redis://redis:6379
ports:
- "8082:8080"
depends_on:
- mysql-master
- redis
config-server-3:
image: configcenter:latest
environment:
- SERVER_ID=3
- CLUSTER_NODES=config-server-1:8080,config-server-2:8080,config-server-3:8080
- DB_URL=jdbc:mysql://mysql-master:3306/config_center
- REDIS_URL=redis://redis:6379
ports:
- "8083:8080"
depends_on:
- mysql-master
- redis
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
depends_on:
- config-server-1
- config-server-2
- config-server-3
@RestController
public class HealthController {
@GetMapping("/health")
public HealthStatus health() {
return HealthStatus.builder()
.status("UP")
.dbConnection(checkDbConnection())
.cacheConnection(checkCacheConnection())
.activeConnections(getActiveConnectionCount())
.build();
}
}
// Nginx ๅฅๅบทๆฃๆฅ้
็ฝฎ
upstream config_servers {
server config-server-1:8080 max_fails=3 fail_timeout=30s;
server config-server-2:8080 max_fails=3 fail_timeout=30s;
server config-server-3:8080 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://config_servers;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
}
5.4 ๆฐๆฎๅฑ้ซๅฏ็จ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ MySQL Master โโโโโโโโ>โ MySQL Slave โ
โ (่ฏปๅ) โ โ (ๅช่ฏป) โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โฒ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
ๆ
้ๅๆข๏ผVIP ๆ Proxy๏ผ
// ๆฐๆฎๅบ่ฏปๅๅ็ฆป้
็ฝฎ
public class DataSourceConfig {
@Bean
@Primary
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://mysql-master:3306/config_center")
.username("root")
.password("${DB_PASSWORD}")
.build();
}
@Bean
public DataSource slaveDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://mysql-slave:3306/config_center")
.username("readonly")
.password("${DB_PASSWORD}")
.build();
}
@Bean
public RoutingDataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slave", slave);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(master);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
# redis-sentinel.conf
sentinel monitor configcenter-redis redis-master 6379 2
sentinel down-after-milliseconds configcenter-redis 5000
sentinel failover-timeout configcenter-redis 60000
sentinel parallel-syncs configcenter-redis 1
ๅ ญใ้ ็ฝฎๅฎๅ จ
้ ็ฝฎไธญๅพๅพๅ ๅซๆๆไฟกๆฏ๏ผๆฐๆฎๅบๅฏ็ ใAPI ๅฏ้ฅใ่ฏไนฆ็ญใๅฎๅ จๆฏ้ ็ฝฎไธญๅฟ็้ไธญไน้ใ
6.1 ไผ ่พๅฎๅ จ
ๆๆ้ ็ฝฎไผ ่พๅฟ ้กปๅ ๅฏ๏ผ้ฒๆญขไธญ้ดไบบๆปๅป๏ผ
# application.yml - ๆๅก็ซฏ TLS ้
็ฝฎ
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-alias: configcenter
// ๅฎขๆท็ซฏ่ฏไนฆ้ช่ฏ
@Configuration
public class MutualTLSConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createSslConnector());
return tomcat;
}
private Connector createSslConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(8443);
// ๆๅก็ซฏ่ฏไนฆ
protocol.setSSLEnabled(true);
protocol.setKeystoreFile("/path/to/server-keystore.jks");
protocol.setKeystorePass("password");
// ๅฎขๆท็ซฏ่ฏไนฆ้ช่ฏ
protocol.setTruststoreFile("/path/to/truststore.jks");
protocol.setTruststorePass("password");
protocol.setClientAuth("want"); // ๅฏ้้ช่ฏ
// protocol.setClientAuth("true"); // ๅผบๅถ้ช่ฏ
return connector;
}
}
6.2 ๅญๅจๅฎๅ จ
// ้
็ฝฎๅ ๅฏๆๅก
@Service
public class ConfigEncryptionService {
private final EncryptionProvider encryptionProvider;
// ๅ ๅฏ้
็ฝฎๅผ
public String encrypt(String plainText) {
return encryptionProvider.encrypt(plainText);
}
// ่งฃๅฏ้
็ฝฎๅผ
public String decrypt(String cipherText) {
return encryptionProvider.decrypt(cipherText);
}
// ๅ ๅฏๆดไธช้
็ฝฎๆไปถ
public ConfigSnapshot encryptSensitiveConfigs(ConfigSnapshot config,
List<String> sensitiveKeys) {
Map<String, String> encryptedConfigs = new HashMap<>();
for (Map.Entry<String, String> entry : config.getConfigs().entrySet()) {
if (isSensitive(entry.getKey(), sensitiveKeys)) {
encryptedConfigs.put(entry.getKey(), encrypt(entry.getValue()));
} else {
encryptedConfigs.put(entry.getKey(), entry.getValue());
}
}
return new ConfigSnapshot(config.getVersion(), encryptedConfigs);
}
private boolean isSensitive(String key, List<String> sensitiveKeys) {
return sensitiveKeys.stream()
.anyMatch(pattern -> key.matches(pattern));
}
}
// ๆๆ้
็ฝฎ Key ็ๅน้
่งๅ
List<String> sensitivePatterns = Arrays.asList(
".*password.*",
".*secret.*",
".*token.*",
".*key.*",
".*credential.*"
);
// KMS ้ๆ็คบไพ
@Service
public class KMSConfigEncryption implements ConfigEncryptionService {
private final KmsClient kmsClient;
private final String keyId;
@Override
public String encrypt(String plainText) {
EncryptRequest request = EncryptRequest.builder()
.keyId(keyId)
.plaintext(ByteBuffer.wrap(plainText.getBytes()))
.build();
EncryptResult result = kmsClient.encrypt(request);
return Base64.getEncoder().encodeToString(
result.ciphertextBlob().array());
}
@Override
public String decrypt(String cipherText) {
ByteBuffer ciphertextBlob = ByteBuffer.wrap(
Base64.getDecoder().decode(cipherText));
DecryptRequest request = DecryptRequest.builder()
.ciphertextBlob(ciphertextBlob)
.build();
DecryptResult result = kmsClient.decrypt(request);
return new String(result.plaintext().array());
}
}
6.3 ่ฎฟ้ฎๆงๅถ
// ๆ้ๆจกๅ่ฎพ่ฎก
public enum Permission {
CONFIG_READ, // ่ฏปๅ้
็ฝฎ
CONFIG_WRITE, // ไฟฎๆน้
็ฝฎ
CONFIG_PUBLISH, // ๅๅธ้
็ฝฎ
CONFIG_ROLLBACK, // ๅๆป้
็ฝฎ
NAMESPACE_CREATE, // ๅๅปบๅฝๅ็ฉบ้ด
NAMESPACE_DELETE, // ๅ ้คๅฝๅ็ฉบ้ด
}
public enum Role {
VIEWER(Permission.CONFIG_READ),
DEVELOPER(Permission.CONFIG_READ, Permission.CONFIG_WRITE),
ADMIN(Permission.values());
private final Set<Permission> permissions;
Role(Permission... permissions) {
this.permissions = EnumSet.copyOf(Arrays.asList(permissions));
}
public boolean hasPermission(Permission permission) {
return permissions.contains(permission);
}
}
// ๆ้ๆฃๆฅๆฆๆชๅจ
@Aspect
@Component
public class PermissionAspect {
@Autowired
private UserService userService;
@Before("@annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
// ่ทๅๅฝๅ็จๆท
String userId = SecurityContextHolder.getCurrentUserId();
User user = userService.getUser(userId);
// ๆฃๆฅๆ้
Permission required = requirePermission.value();
if (!user.getRole().hasPermission(required)) {
throw new ForbiddenException(
"User " + userId + " does not have permission: " + required);
}
// ่ฎฐๅฝๅฎก่ฎกๆฅๅฟ
auditService.log(userId, required, joinPoint.getSignature().toShortString());
}
}
// ไฝฟ็จ็คบไพ
@RestController
@RequestMapping("/api/v1/configs")
public class ConfigController {
@RequirePermission(Permission.CONFIG_READ)
@GetMapping("/{namespace}/{app}")
public ConfigResponse getConfig(@PathVariable String namespace,
@PathVariable String app) {
return configService.getConfig(namespace, app);
}
@RequirePermission(Permission.CONFIG_PUBLISH)
@PostMapping("/{namespace}/{app}/release")
public ReleaseResponse releaseConfig(@PathVariable String namespace,
@PathVariable String app,
@RequestBody ReleaseRequest request) {
return configService.release(namespace, app, request);
}
}
6.4 ๅฎก่ฎกไธ็ๆง
// ๅฎก่ฎกๆฅๅฟๅฎไฝ
@Entity
public class AuditLog {
@Id
@GeneratedValue
private Long id;
private String userId; // ๆไฝไบบ
private String action; // ๆไฝ็ฑปๅ
private String resourceType; // ่ตๆบ็ฑปๅ๏ผ้
็ฝฎ/ๅฝๅ็ฉบ้ด/ๅบ็จ๏ผ
private String resourceId; // ่ตๆบ ID
private String detail; // ๆไฝ่ฏฆๆ
private String clientIp; // ๅฎขๆท็ซฏ IP
private Instant timestamp; // ๆไฝๆถ้ด
// ๆๆๅญๆฎต่ชๅจๆ็
public String getDetail() {
return maskSensitiveInfo(detail);
}
private String maskSensitiveInfo(String text) {
return text.replaceAll("(password|secret|token)=[^&\\s]+", "$1=***");
}
}
// ๅฎก่ฎกๆๅก
@Service
public class AuditService {
@Autowired
private AuditLogRepository auditLogRepository;
public void log(String userId, String action, String resourceType,
String resourceId, String detail) {
AuditLog log = new AuditLog();
log.setUserId(userId);
log.setAction(action);
log.setResourceType(resourceType);
log.setResourceId(resourceId);
log.setDetail(detail);
log.setClientIp(getCurrentClientIp());
log.setTimestamp(Instant.now());
auditLogRepository.save(log);
// ๆๆๆไฝๅฎๆถๅ่ญฆ
if (isSensitiveOperation(action)) {
alertService.sendAlert(log);
}
}
private boolean isSensitiveOperation(String action) {
return Arrays.asList("CONFIG_PUBLISH", "CONFIG_DELETE", "ROLLBACK")
.contains(action);
}
}
# ๅ่ญฆ่งๅ้
็ฝฎ
alert:
rules:
- name: ็ไบง็ฏๅข้
็ฝฎๅๆด
condition:
env: prod
action: CONFIG_PUBLISH
channels:
- type: feishu
webhook: ${FEISHU_WEBHOOK_URL}
at_users: ["ou_xxx", "ou_yyy"]
- name: ๆๆ้
็ฝฎไฟฎๆน
condition:
config_key_pattern: ".*(password|secret|token).*"
action: CONFIG_WRITE
channels:
- type: email
recipients: ["security@company.com"]
- type: sms
phones: ["13800138000"]
- name: ้ๅทฅไฝๆถ้ดๅๆด
condition:
time_range: "22:00-08:00"
action: CONFIG_PUBLISH
severity: HIGH
channels:
- type: phone_call
phones: ["13800138000"]
ไธใๅฎๆๆกไพ๏ผ็ตๅๅนณๅฐ้ ็ฝฎไธญๅฟ่ฝๅฐ
7.1 ไธๅก่ๆฏ
ๆ็ตๅๅนณๅฐๆ 50+ ๅพฎๆๅก๏ผ้ ็ฝฎ็ฎก็้ขไธดไปฅไธๆๆ๏ผ
- ้ ็ฝฎๆฃไนฑ๏ผๆฏไธชๆๅก่ชๅทฑ็ฎก็้ ็ฝฎ๏ผๆ ผๅผไธ็ปไธ
- ๅๆดๅฐ้พ๏ผๆนไธช่ถ ๆถๆถ้ด่ฆ้ๅฏๆๅก๏ผๅฝฑๅ็บฟไธไธๅก
- ็ฏๅขๆททไนฑ๏ผๅผๅใๆต่ฏใ็ไบง็ฏๅข้ ็ฝฎ็ปๅธธๆๆทท
- ๅฎๅ จ้ๆฃ๏ผๆฐๆฎๅบๅฏ็ ๆๆๅญๅจๅจ้ ็ฝฎๆไปถไธญ
7.2 ่งฃๅณๆนๆก
ๅบไบ่ช็ ็ configcenter ๆๅกๆญๅปบ้ ็ฝฎไธญๅฟ๏ผ็็ฑ๏ผ
- ่ชไธปๅฏๆง๏ผๅฎๅ จๆๆกๆ ธๅฟไปฃ็ ๏ผๅฟซ้ๅๅบไธๅก้ๆฑ
- ้ซๆง่ฝ๏ผไผๅๆจ้ๆบๅถ๏ผๅฎ็ฐ 3 ็งๅ ้ ็ฝฎ็ๆ
- ๅฎๅ จๅฏ้ ๏ผๅ ็ฝฎๅฎๅ็ๅฎน้ๅ็ผๅญๆบๅถ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ไบง็ฏๅข โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ configcenter ๆๅก โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ Portal โ โ Admin โ โ Config โ โ โ
โ โ โ Server โ โ Service โ โ Service โ โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ MySQL Master-Slave โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ้็ไบง็ฏๅข๏ผๅ
ฑไบซ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ configcenter ๆๅก โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ Portal โ โ Admin โ โ Config โ โ โ
โ โ โ Server โ โ Service โ โ Service โ โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ ๅฝๅ็ฉบ้ด๏ผdev / test / uat โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# ๅๆฅ็ๆฃไนฑ้
็ฝฎ
# order-service/application.properties
db.url=jdbc:mysql://localhost:3306/order_db
db.username=root
db.password=123456
redis.host=localhost
timeout.ms=5000
feature.new_ui=true
logging.level=DEBUG
# ่ฟ็งปๅ็็ปๆๅ้
็ฝฎ
# order-service/application.yml๏ผๅ
ฌๅ
ฑ้
็ฝฎ๏ผ
spring:
datasource:
url: ${db.url}
username: ${db.username}
password: ${db.password}
# order-service/db.yml๏ผๆฐๆฎๅบ้
็ฝฎ๏ผ
db:
url: jdbc:mysql://${DB_HOST}:3306/order_db
username: order_user
password: cipher:encrypted_password_here # ๅ ๅฏๅญๅจ
pool:
max_size: 100
min_idle: 10
# order-service/feature-flags.yml๏ผๅ่ฝๅผๅ
ณ๏ผ
features:
new_ui: true
new_payment: false
recommendation_v2: true
# order-service/timeout.yml๏ผ่ถ
ๆถ้
็ฝฎ๏ผ
timeout:
db_query_ms: 1000
external_api_ms: 5000
cache_ms: 100
// ๅๆฅ็้
็ฝฎ่ฏปๅๆนๅผ
@Configuration
public class OldConfig {
@Value("${timeout.ms}")
private int timeout;
@Value("${db.url}")
private String dbUrl;
}
// ๆน้ ๅ๏ผไฝฟ็จ้
็ฝฎไธญๅฟ SDK
@Configuration
@EnableConfigCenterConfig({"application", "db", "feature-flags", "timeout"})
public class NewConfig {
@Autowired
private Config dbConfig;
@Autowired
private Config featureConfig;
@ConfigChangeListener("db")
private void onDbConfigChange(ConfigChangeEvent event) {
// ๆฐๆฎๅบ้
็ฝฎๅๆด๏ผๅจๆ่ฐๆด่ฟๆฅๆฑ
if (event.isChanged("db.pool.max_size")) {
int newSize = dbConfig.getIntProperty("db.pool.max_size", 100);
dataSourcePool.resize(newSize);
log.info("Database pool size changed to: {}", newSize);
}
}
@Bean
@RefreshScope // ๆฏๆ้
็ฝฎ็ญๆดๆฐ
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(dbConfig.getProperty("db.url", ""))
.username(dbConfig.getProperty("db.username", ""))
.password(dbConfig.getProperty("db.password", ""))
.build();
}
}
# 1. ๅจ้
็ฝฎไธญๅฟไฟฎๆน้
็ฝฎ
# 2. ้ๆฉ็ฐๅบฆๅฎไพ๏ผ3 ๅฐไธญ็ 1 ๅฐ๏ผ
# 3. ๅๅธๅฐ็ฐๅบฆๅฎไพ
# 4. ่งๅฏ็ๆงๆๆ 10 ๅ้
# 5. ๅฆๆๆญฃๅธธ๏ผๅ
จ้ๅๅธ
# 6. ๅฆๆๅผๅธธ๏ผไธ้ฎๅๆป
7.3 ่ฝๅฐๆๆ
- ้ ็ฝฎๅๆดๆถ้ด๏ผไป 30 ๅ้ โ 3 ็ง
- ๆๅก้ๅฏๆฌกๆฐ๏ผๅๅฐ 80%
- ้ ็ฝฎ้่ฏฏ็๏ผ้ไฝ 90%
- ๆๆ้ ็ฝฎๅ ๅฏๅญๅจ
- ้ ็ฝฎๅๆดๅ จ็จๅฏ่ฟฝๆบฏ
- ๆ้็ป็ฒๅบฆๆงๅถ
- ็ปไธ็ฎก็ 50+ ๆๅก้ ็ฝฎ
- ๅค็ฏๅข้ ็ฝฎ้็ฆปๆธ ๆฐ
- ๆฏๆ่ชๅจๅๆต็จ
7.4 ๆไฝณๅฎ่ทตๆป็ป
- ้ ็ฝฎๅ็ฑป็ฎก็๏ผๅฐ้ ็ฝฎๆๅ่ฝๆจกๅๆๅ๏ผๆฐๆฎๅบใ็ผๅญใไธๅก่งๅ็ญ๏ผ
- ๆๆ้ ็ฝฎๅ ๅฏ๏ผๆๆๅฏ็ ใๅฏ้ฅๅฟ ้กปๅ ๅฏๅญๅจ
- ็ฐๅบฆๅๅธ๏ผ้ ็ฝฎๅๆดๅฟ ้กป็ฐๅบฆ้ช่ฏ
- ๆฌๅฐ็ผๅญ๏ผๅฎขๆท็ซฏๅฟ ้กปๅฎ็ฐๆฌๅฐ็ผๅญ๏ผไฟ่ฏ้ซๅฏ็จ
- ๆ้ๆๅฐๅ๏ผๅบ็จๅช่ฏปๅ่ชๅทฑ้่ฆ็้ ็ฝฎ
- ๅฎก่ฎกๅฎๆด๏ผๆๆ้ ็ฝฎๅๆดๅฟ ้กป่ฎฐๅฝๅฎก่ฎกๆฅๅฟ
ๅ ซใๆป็ป
้ ็ฝฎไธญๅฟๆฏ็ฐไปฃๅพฎๆๅกๆถๆ็ๅบ็ก่ฎพๆฝไนไธ๏ผๅฎ็ๆ ธๅฟไปทๅผๅจไบ๏ผ
- ้ ็ฝฎๅๆดไป"ๅฐๆถ็บง"ๅๆ"็ง็บง"๏ผๆไปฌๅฎ็ฐไบ 3 ็ง็ๆ๏ผ
- ๆ ้้ๅฏๆๅก๏ผ้ไฝๅๆด้ฃ้ฉ
- ็ปไธ็ฎก็๏ผๅๅซ้ ็ฝฎๆฃไนฑ
- ๆๆ้ ็ฝฎ้ไธญๅ ๅฏ็ฎก็
- ๅฎๆด็ๅฎก่ฎก่ฟฝๆบฏ่ฝๅ
- ็ฐๅบฆๅๅธ้ไฝๅๆด้ฃ้ฉ
- ้ ็ฝฎไธๆๅก่งฃ่ฆ๏ผๆด็ตๆดป
- ๆฏๆๅค็ฏๅขใๅค็งๆท
- ไธบ Feature ToggleใA/B Test ๆไพๅบ็ก่ฝๅ
- ๅฎน้ๆบๅถ๏ผๅฎขๆท็ซฏๆฌๅฐ็ผๅญใๆๅก็ซฏ้็พค้จ็ฝฒใๆฐๆฎๅฑไธปไปๅคๅถ
- ็ผๅญ็ญ็ฅ๏ผๅค็บง็ผๅญๆๅๆง่ฝ๏ผ้็บง็ญ็ฅไฟ่ฏๅฏ็จๆง
- ๅฎๆถๆจ้๏ผๆจๆ็ปๅๆจกๅผ๏ผ3 ็งๅ ้ ็ฝฎ็ๆ
- ๅฎๅ จไผๅ ๏ผไผ ่พๅ ๅฏใๅญๅจๅ ๅฏใๆ้ๆงๅถใๅฎก่ฎกๆฅๅฟ
้ ็ฝฎไธญๅฟไธไป ไป ๆฏไธไธชๅทฅๅ ท๏ผๆดๆฏไธ็ง้ ็ฝฎ็ฎก็็็ๅฟตใๅฎ่ฎฉ้ ็ฝฎๆไธบไธ็ญๅ ฌๆฐ๏ผ่ฎฉ้ ็ฝฎ็ฎก็ไป"ๆๅทฅๆถไปฃ"่ฟๅๅฐ"ๅทฅไธๆถไปฃ"ใ
- ็ณปๅไธ ยท ็ฌฌ1็ฏ๏ผๆๅกๆณจๅไธๅ็ฐ
- ็ณปๅไธ ยท ็ฌฌ2็ฏ๏ผ่ด่ฝฝๅ่กก
- ็ณปๅไธ ยท ็ฌฌ3็ฏ๏ผ้ ็ฝฎไธญๅฟ๏ผๆฌๆ๏ผ
- ็ณปๅไธ ยท ็ฌฌ4็ฏ๏ผๆๅก็ฝๅ ณ๏ผๅพ ็ปญ๏ผ
- ใๅพฎๆๅก่ฎพ่ฎกใ- Sam Newman ่ฑ็ฃ้พๆฅ
- ใRelease It!ใ- Michael T. Nygard
- ้ ็ฝฎไธญๅฟ่ฎพ่ฎกๆไฝณๅฎ่ทต๏ผๅ ้จๆๆฏๆๆกฃ๏ผ
๐ฌ ่ฏ่ฎบ (0)