้…็ฝฎไธญๅฟƒ๏ผš่ฎฉๆ”นๅŠจ"็ง’็บง็”Ÿๆ•ˆ"็š„ๅน•ๅŽ่‹ฑ้›„ ๐Ÿ”ง

่ฟ™้‡Œ็š„"็ง’็บง"๏ผŒๆ˜ฏ็œŸ็š„็ง’็บง๏ผŒไธๆ˜ฏ"็ญ‰ๆˆ‘ๅ–ๆฏๅ’–ๅ•ก้‡ๅฏไธ€ไธ‹"็š„้‚ฃ็ง็ง’็บงใ€‚


ไธ€ใ€ๅ…ˆ่ฎฒไธช"่ก€ๆณช"ๆ•…ไบ‹ ๐Ÿ“–

ๅ‘จไบ”ไธ‹ๅˆ5็‚น๏ผŒ่ฟ่ฅ็ซๆ€ฅ็ซ็‡Žๅœฐ่ท‘่ฟ‡ๆฅ๏ผš"้…‹้•ฟ๏ผๆดปๅŠจๅฅ–ๅŠฑ่ฆ็ฟปๅ€๏ผŒ่€ๆฟ่ฏด็ŽฐๅœจๅฐฑไธŠ๏ผ"

ไฝ ๅนไบ†ๅฃๆฐ”๏ผŒๆ‰“ๅผ€ๆœๅŠกๅ™จ๏ผŒๆ‰พๅˆฐ้…็ฝฎๆ–‡ไปถ๏ผŒๅฐๅฟƒ็ฟผ็ฟผๅœฐๆ”นๅฎŒไฟๅญ˜ใ€‚็„ถๅŽโ€”โ€”

# ้‡ๅฏๆœๅŠกA
ssh server-a "systemctl restart game-service"

# ้‡ๅฏๆœๅŠกB
ssh server-b "systemctl restart game-service"

# ้‡ๅฏๆœๅŠกC...็ญ‰็ญ‰๏ผŒ่ฟ˜ๆœ‰Dใ€Eใ€F๏ผŸ

็ญ‰ไฝ ๅฟ™ๅฎŒ๏ผŒๅคฉ้ƒฝ้ป‘ไบ†ใ€‚่ฟ่ฅๅˆๆฅ้—ฎ๏ผš"ๆ€Žไนˆ่ฟ˜ๆฒก็”Ÿๆ•ˆ๏ผŸ็Žฉๅฎถ้ƒฝๅœจๆŠ•่ฏ‰ไบ†๏ผ"

ไฝ ๆ— ๅฅˆๅœฐ่ฏด๏ผš"ๅœจ้‡ๅฏไบ†,ๅ†็ญ‰็ญ‰...่ฟ˜ๆœ‰3ๅฐๆœๅŠกๅ™จๆฒก้‡ๅฏๅฎŒ..."

ๆ›ดๆƒจ็š„ๆ˜ฏ๏ผŒไฝ ๆ นๆœฌไธ็กฎๅฎš่ฟ˜ๆœ‰ๆฒกๆœ‰ๆผๆމ็š„ๆœๅŠกๅ™จใ€‚้…็ฝฎๆ•ฃ่ฝๅœจๅ„ไธชๆœๅŠกๅ™จ๏ผŒๅƒ่—ๅœจๅไธชๅœฐๆ–น็š„้’ฅๅŒ™๏ผŒไฝ ๅฟ˜ไบ†ๅ…ถไธญไธ‰ไธชๅœจๅ“ชใ€‚

่ฟ™ไธๆ˜ฏๆ•ˆ็އ้—ฎ้ข˜๏ผŒๆ˜ฏๅฟƒๆ€้—ฎ้ข˜โ€”โ€”่ฐๆ„ฟๆ„ๅ› ไธบๆ”นไธช้…็ฝฎๅฐฑๅŠ ็ญ๏ผŸ


ไบŒใ€ไธบไป€ไนˆ้œ€่ฆ้…็ฝฎไธญๅฟƒ๏ผŸ็ต้ญ‚ไธ‰้—ฎ ๐Ÿค”

2.1 ้…็ฝฎๆ–‡ไปถ"ๆปกๅคฉ้ฃž"๏ผŒๆ€Žไนˆ็ฎก๏ผŸ

ไผ ็ปŸๆžถๆž„้‡Œ๏ผŒ้…็ฝฎๆ–‡ไปถๅ’Œไปฃ็ ไธ€ๆ ท๏ผŒ่บบๅœจๅ„ไธชๆœๅŠกๅ™จ็š„่ง’่ฝ๏ผš

/server-a/config/database.yml
/server-b/config/redis.yml
/server-c/config/game-config.json
...่ฟ˜ๆœ‰่ฐ๏ผŸๆฒกไบบ็Ÿฅ้“

ไฝ ๆƒณๆ”นไธชๆ•ฐๆฎๅบ“่ฟžๆŽฅ๏ผŸๆต็จ‹ๆ˜ฏ่ฟ™ๆ ท็š„๏ผš

  1. ่ฎฐๅพ—๏ผˆๆˆ–็Œœ๏ผ‰้…็ฝฎๅœจๅ“ชๅฐๆœๅŠกๅ™จ
  2. SSH ็™ปๅฝ•ไธŠๅŽป
  3. vim config.yml๏ผŒๅฐๅฟƒ็ฟผ็ฟผๅœฐๆ”น
  4. :wq ไฟๅญ˜
  5. ้‡ๅฏๆœๅŠก
  6. ็ฅˆ็ฅทๆฒกๆ”น้”™

่ฟ™ๅฐฑๅƒไฝ ๆŠŠ้’ฅๅŒ™่—ๅœจๅไธชๅœฐๆ–น๏ผŒ็„ถๅŽๅฟ˜ไบ†ๅ…ถไธญไธ‰ไธชๅœจๅ“ชใ€‚็ญ‰ไฝ ้œ€่ฆๅผ€้—จ็š„ๆ—ถๅ€™๏ผŒๅช่ƒฝไธ€ไธชไธช่ฏ•ใ€‚

2.2 ไฟฎๆ”น้…็ฝฎ = ้‡ๅฏๆœๅŠก๏ผŒๅ‡ญไป€ไนˆ๏ผŸ

ๆ”นไปฃ็ ่ฆ้‡ๅฏ๏ผŒๅคงๅฎถ้ƒฝ่ƒฝๆŽฅๅ—โ€”โ€”ๆฏ•็ซŸไปฃ็ ้€ป่พ‘ๅ˜ไบ†ๅ˜›ใ€‚

ไฝ†ๆ”นไธช่ถ…ๆ—ถๆ—ถ้—ดใ€ๅผ€ๅ…ณใ€ๆดปๅŠจๅฅ–ๅŠฑๅ€็އ๏ผŒไนŸ่ฆ้‡ๅฏ๏ผŸ

# ๅฐฑๆ”นไบ†่ฟ™ไธช
timeout: 30  โ†’  timeout: 60

# ็ป“ๆžœ่ฆ่ฟ™ๆ ท
systemctl restart my-service  # ็ญ‰ๅพ…30็ง’...

่ฟ™ๅฐฑๅฅฝๆฏ”ไฝ ่ฐƒไธช็ฉบ่ฐƒๆธฉๅบฆ๏ผŒ้œ€่ฆๆŠŠๆ•ดๆ ‹ๆฅผ็š„็”ต้—ธๆ‹‰ไบ†ๅ†ๅผ€ใ€‚

2.3 ๅคš็Žฏๅขƒ็ฎก็†๏ผŒ่ฐๆฅไฟๅ‘ฝ๏ผŸ

ๅผ€ๅ‘็Žฏๅขƒใ€ๆต‹่ฏ•็Žฏๅขƒใ€้ข„ๅ‘็Žฏๅขƒใ€็”Ÿไบง็Žฏๅขƒ...

ๆฏไธช็Žฏๅขƒ้ƒฝๆœ‰่‡ชๅทฑ็š„ๆ•ฐๆฎๅบ“ใ€็ผ“ๅญ˜ใ€ๅŸŸๅ๏ผš

# ๅผ€ๅ‘็Žฏๅขƒ
database:
  host: dev-mysql.internal
  port: 3306

# ๆต‹่ฏ•็Žฏๅขƒ
database:
  host: test-mysql.internal
  port: 3306

# ็”Ÿไบง็Žฏๅขƒ
database:
  host: prod-mysql.internal  # โš ๏ธ ่ฟ™ไธช็ปๅฏนไธ่ƒฝๆž้”™๏ผ
  port: 3306

ไฝ ๆ€Žไนˆไฟ่ฏ็”Ÿไบง็Žฏๅขƒ็š„้…็ฝฎไธไผšๆ‰‹ๆป‘ๅคๅˆถๅˆฐๆต‹่ฏ•็Žฏๅขƒ๏ผŸ

ๆŸๅคงๅŽ‚็œŸๅฎžๆกˆไพ‹๏ผšๅ‡Œๆ™จ3็‚น๏ผŒๅ€ผ็ญๅŒๅญฆๆŠŠๆต‹่ฏ•็Žฏๅขƒ็š„้…็ฝฎ่ฆ†็›–ๅˆฐไบ†็”Ÿไบง็Žฏๅขƒ๏ผŒๅฏผ่‡ดๅ…จ็ซ™ๆ•ฐๆฎๅบ“่ฟžๆŽฅ้”™่ฏฏ๏ผŒๆœๅŠก้›ชๅดฉใ€‚


ไธ‰ใ€ๆˆ‘ไปฌ็š„้…็ฝฎไธญๅฟƒๆžถๆž„ ๐ŸŽฏ

ๅŸบไบŽๆธธๆˆๅ‘่กŒๅนณๅฐ็š„ไธšๅŠก็‰น็‚น๏ผŒๆˆ‘ไปฌ่‡ช็ ”ไบ† configcenter ้…็ฝฎไธญๅฟƒๆœๅŠกใ€‚

3.1 ๆ•ดไฝ“ๆžถๆž„่ฎพ่ฎก

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   configcenter ๆžถๆž„                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                         โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”‚
โ”‚   โ”‚ Web็ฎก็†ๅฐ โ”‚    โ”‚ API ๆœๅŠก  โ”‚    โ”‚  ๅญ˜ๅ‚จๅฑ‚   โ”‚         โ”‚
โ”‚   โ”‚ (Portal) โ”‚โ”€โ”€โ”€โ–ถโ”‚ (Server) โ”‚โ”€โ”€โ”€โ–ถโ”‚ (MySQL)  โ”‚         โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚
โ”‚        โ”‚               โ”‚                                โ”‚
โ”‚        โ”‚               โ–ผ                                โ”‚
โ”‚        โ”‚         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚        โ”‚         โ”‚ ๅ˜ๆ›ด้€š็Ÿฅ  โ”‚                          โ”‚
โ”‚        โ”‚         โ”‚ (Notify) โ”‚                          โ”‚
โ”‚        โ”‚         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚        โ”‚               โ”‚                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚               โ”‚
         โ–ผ               โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚         ๅฎขๆˆท็ซฏ SDK (Go/Java)        โ”‚
   โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”   โ”‚
   โ”‚  โ”‚ๆธธๆˆๆœโ”‚ โ”‚ๆ”ฏไป˜ๆœโ”‚ โ”‚ๅŒน้…ๆœโ”‚ โ”‚่ฟ่ฅๅฐโ”‚   โ”‚
   โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
็ป„ไปถ ๆŠ€ๆœฏๆ ˆ ่Œ่ดฃ
Web็ฎก็†ๅฐ React + Ant Design ้…็ฝฎ็š„ๅฏ่ง†ๅŒ–็ฎก็†็•Œ้ข
APIๆœๅŠก Go + Gin ้…็ฝฎ็š„ๅขžๅˆ ๆ”นๆŸฅใ€ๆƒ้™ๆŽงๅˆถ
ๅญ˜ๅ‚จๅฑ‚ MySQL 8.0 ้…็ฝฎๆŒไน…ๅŒ–ๅญ˜ๅ‚จ
ๅ˜ๆ›ด้€š็Ÿฅ Go + ้•ฟ่ฝฎ่ฏข ้…็ฝฎๅ˜ๆ›ดๅฎžๆ—ถๆŽจ้€
ๅฎขๆˆท็ซฏSDK Go / Java ๅบ”็”จ้›†ๆˆ๏ผŒๆ‹‰ๅ–ๅ’Œ็›‘ๅฌ้…็ฝฎ

3.2 ๆ ธๅฟƒ่ƒฝๅŠ›

่ƒฝๅŠ›ไธ€๏ผš้›†ไธญๅญ˜ๅ‚จ + ๅ‘ฝๅ็ฉบ้—ด้š”็ฆป

ๆ‰€ๆœ‰้…็ฝฎ็ปŸไธ€ๅญ˜ๅ‚จๅœจ MySQL๏ผŒ้€š่ฟ‡ๅ‘ฝๅ็ฉบ้—ด๏ผˆnamespace๏ผ‰ๅฎž็Žฐๅคš็Žฏๅขƒ้š”็ฆป๏ผš

-- ้…็ฝฎ่กจ็ป“ๆž„
CREATE TABLE config (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    namespace VARCHAR(100) NOT NULL COMMENT 'ๅ‘ฝๅ็ฉบ้—ด๏ผŒๅฆ‚๏ผšdev/test/prod',
    config_key VARCHAR(255) NOT NULL COMMENT '้…็ฝฎ้”ฎ',
    config_value TEXT NOT NULL COMMENT '้…็ฝฎๅ€ผ๏ผˆJSON๏ผ‰',
    version INT NOT NULL DEFAULT 1 COMMENT '็‰ˆๆœฌๅท',
    is_deleted BOOLEAN DEFAULT FALSE COMMENT '้€ป่พ‘ๅˆ ้™ค',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_namespace_key (namespace, config_key),
    KEY idx_namespace (namespace)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# ๅ‘ฝๅ็ฉบ้—ด็คบไพ‹
application:
  dev:      # ๅผ€ๅ‘็Žฏๅขƒ
    game:
      timeout: 30
      debug: true
  test:     # ๆต‹่ฏ•็Žฏๅขƒ
    game:
      timeout: 30
      debug: false
  prod:     # ็”Ÿไบง็Žฏๅขƒ
    game:
      timeout: 60
      debug: false

่ฟ™ๅฐฑๅƒๅ…ฌๅธๅชๆœ‰ไธ€ไธชๆกฃๆกˆๅฎค๏ผŒไฝ†ๅˆ†ไบ†ไธๅŒ็š„ๆŸœๅญ๏ผšๅˆๅŒๆŸœใ€่ดขๅŠกๆŸœใ€ไบบไบ‹ๆŸœใ€‚่ฐ่ฆ็œ‹้ƒฝๅŽป้‚ฃ้‡ŒๆŸฅ๏ผŒไฝ†ๅช่ƒฝ็œ‹่‡ชๅทฑๆœ‰ๆƒ้™็š„ๆŸœๅญใ€‚

่ƒฝๅŠ›ไบŒ๏ผš็‰ˆๆœฌ็ฎก็† + ๅ˜ๆ›ดๅฎก่ฎก

ๆฏๆฌกไฟฎๆ”น้ƒฝๆœ‰่ฎฐๅฝ•๏ผŒ่ฐๆ”น็š„ใ€ไป€ไนˆๆ—ถๅ€™ๆ”น็š„ใ€ๆ”นไบ†ไป€ไนˆ๏ผš

-- ้…็ฝฎๅ˜ๆ›ดๅކๅฒ่กจ
CREATE TABLE config_history (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    config_id BIGINT NOT NULL COMMENT '้…็ฝฎID',
    namespace VARCHAR(100) NOT NULL,
    config_key VARCHAR(255) NOT NULL,
    old_value TEXT COMMENT 'ๆ—งๅ€ผ',
    new_value TEXT COMMENT 'ๆ–ฐๅ€ผ',
    operator VARCHAR(100) NOT NULL COMMENT 'ๆ“ไฝœไบบ',
    change_type VARCHAR(20) NOT NULL COMMENT 'ๅ˜ๆ›ด็ฑปๅž‹๏ผšCREATE/UPDATE/DELETE',
    comment VARCHAR(500) COMMENT 'ๅ˜ๆ›ด่ฏดๆ˜Ž',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    KEY idx_config_id (config_id),
    KEY idx_namespace_key (namespace, config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
// ้…็ฝฎๅ˜ๆ›ดๅކๅฒ็คบไพ‹
{
  "configKey": "game.activity.bonusRate",
  "changes": [
    {
      "version": 5,
      "oldValue": "1.0",
      "newValue": "2.0",
      "operator": "zhangsan",
      "timestamp": "2026-02-28 14:30:00",
      "comment": "ๆ˜ฅ่Š‚ๆดปๅŠจๅฅ–ๅŠฑ็ฟปๅ€"
    }
  ]
}

ๅ‡บไบ†้—ฎ้ข˜๏ผŸไธ€้”ฎๅ›žๆปšๅˆฐไธŠไธ€ไธช็‰ˆๆœฌใ€‚่ฟ™ๅฐฑๅƒ Git ็ฎก็†ไปฃ็ ไธ€ๆ ท๏ผŒ้…็ฝฎไนŸ้œ€่ฆ็‰ˆๆœฌๆŽงๅˆถใ€‚

่ƒฝๅŠ›ไธ‰๏ผš็ƒญๆ›ดๆ–ฐ โšก๏ผˆๆ ธๅฟƒไปทๅ€ผ๏ผ‰

็ƒญๆ›ดๆ–ฐๅฎž็ŽฐๅŽŸ็†๏ผš

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ็ƒญๆ›ดๆ–ฐๆต็จ‹                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                         โ”‚
โ”‚  1. ๆœๅŠกๅฏๅŠจๆ—ถไปŽ้…็ฝฎไธญๅฟƒๆ‹‰ๅ–้…็ฝฎ                          โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    HTTP GET     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
โ”‚     โ”‚ ๆธธๆˆๆœ  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚configcenterโ”‚         โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚                                                         โ”‚
โ”‚  2. ๆœๅŠกๅปบ็ซ‹้•ฟ่ฝฎ่ฏข่ฟžๆŽฅ๏ผŒ็›‘ๅฌ้…็ฝฎๅ˜ๅŒ–                      โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    Long Polling  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚     โ”‚ ๆธธๆˆๆœ  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚configcenterโ”‚         โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚                                                         โ”‚
โ”‚  3. ้…็ฝฎๅ˜ๆ›ดๆ—ถ๏ผŒ้…็ฝฎไธญๅฟƒ็ซ‹ๅˆปๅ“ๅบ”                          โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    ่ฟ”ๅ›žๆ–ฐ้…็ฝฎ     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚     โ”‚ ๆธธๆˆๆœ  โ”‚ โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚configcenterโ”‚         โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚                                                         โ”‚
โ”‚  4. ๆœๅŠกๆ”ถๅˆฐๆ–ฐ้…็ฝฎ๏ผŒๆ›ดๆ–ฐๅ†…ๅญ˜๏ผˆๆ— ้œ€้‡ๅฏ๏ผ‰                   โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                       โ”‚
โ”‚     โ”‚ ๆธธๆˆๆœ  โ”‚  reload config... done! (3็ง’ๅ†…)       โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                       โ”‚
โ”‚                                                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

่ƒฝๅŠ›ๅ››๏ผš็ฐๅบฆๅ‘ๅธƒ

ๆ–ฐ้…็ฝฎไธๆ•ขๅ…จ้‡ไธŠ็บฟ๏ผŸๅ…ˆๆŽจ็ป™้ƒจๅˆ†ๅฎžไพ‹่ฏ•่ฏ•๏ผš

็ฐๅบฆๅ‘ๅธƒๆต็จ‹๏ผš
  5% โ”€โ”€โ–ถ ่ง‚ๅฏŸ1ๅฐๆ—ถ โ”€โ”€โ–ถ ๆ— ๅผ‚ๅธธ โ”€โ”€โ–ถ 20% โ”€โ”€โ–ถ ่ง‚ๅฏŸ2ๅฐๆ—ถ โ”€โ”€โ–ถ 50% โ”€โ”€โ–ถ 100%
   โ”‚                      โ”‚
   โ””โ”€โ”€โ–ถ ๆœ‰้—ฎ้ข˜๏ผŸไธ€้”ฎๅ›žๆปš โ”€โ”€โ”˜

่ฟ™ๅฐฑๅƒๆ–ฐ่ฏๅ…ˆๅšไธดๅบŠ่ฏ•้ชŒ๏ผŒ็กฎ่ฎคๅฎ‰ๅ…จๅ†ๅคง่ง„ๆจกๆŽจๅนฟใ€‚

่ƒฝๅŠ›ไบ”๏ผšๆƒ้™ๆŽงๅˆถ

ๅŸบไบŽ RBAC ็š„ๆƒ้™ๆจกๅž‹๏ผŒ็ฒพ็ป†ๆŽงๅˆถ่ฐ่ƒฝๆ”น้…็ฝฎ๏ผš

ๆƒ้™็Ÿฉ้˜ต๏ผš
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ่ง’่‰ฒ   โ”‚ ๅผ€ๅ‘็Žฏๅขƒโ”‚ ๆต‹่ฏ•็Žฏๅขƒโ”‚ ็”Ÿไบง็Žฏๅขƒโ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๅฎžไน ็”Ÿ  โ”‚  ๅช่ฏป  โ”‚  ๅช่ฏป  โ”‚  ๆ— ๆƒ้™ โ”‚
โ”‚ ๅผ€ๅ‘    โ”‚  ่ฏป+ๅ†™ โ”‚  ่ฏป+ๅ†™ โ”‚  ๅช่ฏป  โ”‚
โ”‚ ่ฟ็ปด    โ”‚  ่ฏป+ๅ†™ โ”‚  ่ฏป+ๅ†™ โ”‚  ่ฏป+ๅ†™ โ”‚
โ”‚ ๆŠ€ๆœฏ่ดŸ่ดฃไบบโ”‚ ่ฏป+ๅ†™ โ”‚  ่ฏป+ๅ†™ โ”‚  ่ฏป+ๅ†™+ๅฎกๆ‰นโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ๅ››ใ€้…็ฝฎๆŽจ้€ๆœบๅˆถ๏ผš้•ฟ่ฝฎ่ฏขๅฎž็Žฐ ๐Ÿ”„

้…็ฝฎๆ›ดๆ–ฐๅŽ๏ผŒๆ€Žไนˆ่ฎฉๆœๅŠก็Ÿฅ้“๏ผŸ่ฟ™ๆ˜ฏ้…็ฝฎไธญๅฟƒ็š„ๆ ธๅฟƒๆŠ€ๆœฏ้šพ็‚นใ€‚

4.1 ไธบไป€ไนˆ้€‰ๆ‹ฉ้•ฟ่ฝฎ่ฏข๏ผŸ

ๆˆ‘ไปฌๅฏนๆฏ”ไบ†ไธ‰็งๆ–นๆกˆ๏ผŒๆœ€็ปˆ้€‰ๆ‹ฉไบ†้•ฟ่ฝฎ่ฏข๏ผš

ๆ–นๆกˆ ๅฎžๆ—ถๆ€ง ๆœๅŠก็ซฏๅŽ‹ๅŠ› ๅฎž็Žฐๅคๆ‚ๅบฆ ๆœ€็ปˆ้€‰ๆ‹ฉ
ๆŽจๆจกๅผ ๆฏซ็ง’็บง ้ซ˜๏ผˆ็ปดๆŠคๅคง้‡่ฟžๆŽฅ๏ผ‰ ๅคๆ‚ โŒ
ๆ‹‰ๆจกๅผ ็ง’็บงๅปถ่ฟŸ๏ผˆ่ฝฎ่ฏข้—ด้š”๏ผ‰ ไธญ๏ผˆ้ข‘็นๆ— ๆ•ˆ่ฏทๆฑ‚๏ผ‰ ็ฎ€ๅ• โŒ
้•ฟ่ฝฎ่ฏข 3็ง’ๅ†… ไฝŽ๏ผˆๆŒ‚่ตท็ญ‰ๅพ…๏ผ‰ ไธญ็ญ‰ โœ…

4.2 ้•ฟ่ฝฎ่ฏขๅฎž็Žฐ็ป†่Š‚

ๅฎขๆˆท็ซฏๅฎž็Žฐ๏ผˆGo SDK๏ผ‰

// ๅฎขๆˆท็ซฏ้•ฟ่ฝฎ่ฏขๆ ธๅฟƒ้€ป่พ‘
func (c *ConfigClient) startLongPolling() {
    for {
        // ๅ‘่ตท้•ฟ่ฝฎ่ฏข่ฏทๆฑ‚๏ผŒ่ถ…ๆ—ถๆ—ถ้—ด30็ง’
        resp, err := c.httpClient.Get(
            c.serverURL + "/api/v1/configs/watch",
            func(req *http.Request) {
                // ๆบๅธฆๅฝ“ๅ‰้…็ฝฎ็‰ˆๆœฌๅท
                req.Header.Set("X-Config-Version", c.getCurrentVersion())
                // ่ฎพ็ฝฎ30็ง’่ถ…ๆ—ถ
                req.Header.Set("X-Long-Polling-Timeout", "30")
            },
        )

        if err != nil {
            // ็ฝ‘็ปœ้”™่ฏฏ๏ผŒ็ญ‰ๅพ…ๅŽ้‡่ฏ•
            time.Sleep(5 * time.Second)
            continue
        }

        if resp.StatusCode == 200 {
            // ๆœ‰้…็ฝฎๅ˜ๆ›ด
            var changes []ConfigChange
            json.NewDecoder(resp.Body).Decode(&changes)

            // ๅบ”็”จๆ–ฐ้…็ฝฎ
            c.applyConfigChanges(changes)
        } else if resp.StatusCode == 304 {
            // ๆ— ๅ˜ๆ›ด๏ผˆNot Modified๏ผ‰๏ผŒ็ปง็ปญไธ‹ไธ€่ฝฎ่ฝฎ่ฏข
        }

        // ็ซ‹ๅณๅ‘่ตทๆ–ฐ็š„้•ฟ่ฝฎ่ฏข
    }
}

// ๅบ”็”จ้…็ฝฎๅ˜ๆ›ด
func (c *ConfigClient) applyConfigChanges(changes []ConfigChange) {
    for _, change := range changes {
        // ๆ›ดๆ–ฐๅ†…ๅญ˜ไธญ็š„้…็ฝฎ
        c.configCache.Store(change.Key, change.Value)

        // ่งฆๅ‘ๅ›ž่ฐƒๅ‡ฝๆ•ฐ
        if listener, ok := c.listeners[change.Key]; ok {
            listener.onChange(change)
        }
    }
}

ๆœๅŠก็ซฏๅฎž็Žฐ๏ผˆGo API๏ผ‰

// ้•ฟ่ฝฎ่ฏขๆŽฅๅฃ
func (h *ConfigHandler) WatchConfig(c *gin.Context) {
    namespace := c.Query("namespace")
    currentVersion := c.GetHeader("X-Config-Version")
    timeout := c.GetHeader("X-Long-Polling-Timeout")

    // ้ป˜่ฎค30็ง’่ถ…ๆ—ถ
    pollTimeout := 30 * time.Second
    if timeout != "" {
        if seconds, err := strconv.Atoi(timeout); err == nil {
            pollTimeout = time.Duration(seconds) * time.Second
        }
    }

    ctx, cancel := context.WithTimeout(c.Request.Context(), pollTimeout)
    defer cancel()

    // ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰้…็ฝฎๅ˜ๆ›ด
    changes := h.checkConfigChanges(namespace, currentVersion)

    if len(changes) > 0 {
        // ๆœ‰ๅ˜ๆ›ด๏ผŒ็ซ‹ๅณ่ฟ”ๅ›ž
        c.JSON(http.StatusOK, changes)
        return
    }

    // ๆ— ๅ˜ๆ›ด๏ผŒ็ญ‰ๅพ…้…็ฝฎๅ˜ๆ›ดไบ‹ไปถๆˆ–่ถ…ๆ—ถ
    watcher := h.notifyService.Watch(namespace)
    defer watcher.Stop()

    select {
    case <-watcher.Changed():
        // ้…็ฝฎๅ˜ๆ›ดไบ†๏ผŒ่ฟ”ๅ›žๆ–ฐ้…็ฝฎ
        changes := h.checkConfigChanges(namespace, currentVersion)
        c.JSON(http.StatusOK, changes)

    case <-ctx.Done():
        // ่ถ…ๆ—ถ๏ผŒ่ฟ”ๅ›ž 304 Not Modified
        c.Status(http.StatusNotModified)
    }
}

// ๆฃ€ๆŸฅ้…็ฝฎๅ˜ๆ›ด
func (h *ConfigHandler) checkConfigChanges(namespace, currentVersion string) []ConfigChange {
    // ๆŸฅ่ฏขๆœ€ๆ–ฐ็‰ˆๆœฌ
    latestVersion := h.configService.GetLatestVersion(namespace)

    if latestVersion <= currentVersion {
        return nil
    }

    // ๆŸฅ่ฏขๅ˜ๆ›ด็š„้…็ฝฎ
    return h.configService.GetChangedConfigs(namespace, currentVersion)
}

้…็ฝฎๅ˜ๆ›ด้€š็ŸฅๆœๅŠก

// ้…็ฝฎๅ˜ๆ›ด้€š็ŸฅๆœๅŠก
type NotifyService struct {
    mu            sync.RWMutex
    watchers      map[string][]*Watcher
    eventChan     chan ConfigChangeEvent
}

type Watcher struct {
    Changed chan struct{}
    stopped chan struct{}
}

func (s *NotifyService) Start() {
    // ๅฏๅŠจไบ‹ไปถๅค„็†ๅพช็Žฏ
    go func() {
        for event := range s.eventChan {
            s.notifyWatchers(event.Namespace)
        }
    }()
}

// ้…็ฝฎๅ˜ๆ›ดๆ—ถ่ฐƒ็”จ
func (s *NotifyService) NotifyChange(namespace string) {
    s.eventChan <- ConfigChangeEvent{Namespace: namespace}
}

// ้€š็Ÿฅๆ‰€ๆœ‰็ญ‰ๅพ…็š„ watcher
func (s *NotifyService) notifyWatchers(namespace string) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    if watchers, ok := s.watchers[namespace]; ok {
        for _, watcher := range watchers {
            select {
            case watcher.Changed <- struct{}{}:
            default:
                // ้˜ฒๆญข้˜ปๅกž
            }
        }
    }
}

4.3 ๆ€ง่ƒฝไผ˜ๅŒ–

ไธบไบ†ๆ”ฏๆŒๅคง่ง„ๆจกๆœๅŠกๅฎžไพ‹๏ผˆ1000+๏ผ‰๏ผŒๆˆ‘ไปฌๅšไบ†ไปฅไธ‹ไผ˜ๅŒ–๏ผš

ไผ˜ๅŒ–ไธ€๏ผš่ฟžๆŽฅๆฑ ๅค็”จ

// ๅฎขๆˆท็ซฏไฝฟ็”จ่ฟžๆŽฅๆฑ 
client := &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
    },
    Timeout: 35 * time.Second, // ๆฏ”้•ฟ่ฝฎ่ฏข่ถ…ๆ—ถๅคš5็ง’
}

ไผ˜ๅŒ–ไบŒ๏ผšๆ‰น้‡ๅ˜ๆ›ด้€š็Ÿฅ

// ๆ‰น้‡ๆ”ถ้›†ๅ˜ๆ›ด๏ผŒๅ‡ๅฐ‘้€š็Ÿฅ้ข‘็އ
type BatchNotifier struct {
    timer       *time.Timer
    pendingChan chan string
    batchChan   chan []string
}

func (n *BatchNotifier) Start() {
    batch := make([]string, 0, 10)

    for {
        select {
        case namespace := <-n.pendingChan:
            batch = append(batch, namespace)

            if len(batch) >= 10 {
                // ่พพๅˆฐๆ‰น้‡ๅคงๅฐ๏ผŒ็ซ‹ๅณๅ‘้€
                n.batchChan <- batch
                batch = make([]string, 0, 10)
                n.timer.Reset(100 * time.Millisecond)
            } else {
                // ้‡็ฝฎๅฎšๆ—ถๅ™จ๏ผŒ100msๅŽๆ‰น้‡ๅ‘้€
                n.timer.Reset(100 * time.Millisecond)
            }

        case <-n.timer.C:
            // ๅฎšๆ—ถๅ™จๅˆฐๆœŸ๏ผŒๅ‘้€ๆ‰น้‡้€š็Ÿฅ
            if len(batch) > 0 {
                n.batchChan <- batch
                batch = make([]string, 0, 10)
            }
        }
    }
}

ไผ˜ๅŒ–ไธ‰๏ผš้…็ฝฎ็‰ˆๆœฌๅทๅฟซ้€Ÿๆฏ”ๅฏน

// ไฝฟ็”จ็‰ˆๆœฌๅทๅฟซ้€Ÿๅˆคๆ–ญๆ˜ฏๅฆๆœ‰ๅ˜ๆ›ด
func (s *ConfigService) GetLatestVersion(namespace string) string {
    // ไปŽ็ผ“ๅญ˜ไธญ่Žทๅ–
    if version, ok := s.versionCache.Get(namespace); ok {
        return version.(string)
    }

    // ไปŽๆ•ฐๆฎๅบ“ๆŸฅ่ฏขๆœ€ๆ–ฐ็‰ˆๆœฌ
    version := s.configDao.GetLatestVersion(namespace)
    s.versionCache.Set(namespace, version, 5*time.Minute)

    return version
}

ไบ”ใ€็ฐๅบฆๅ‘ๅธƒไธŽ็‰ˆๆœฌ็ฎก็† ๐Ÿ“Š

5.1 ็ฐๅบฆๅ‘ๅธƒๅฎž็Žฐ

ๆˆ‘ไปฌๆ”ฏๆŒๆŒ‰ๅฎžไพ‹่ฟ›่กŒ็ฐๅบฆๅ‘ๅธƒ๏ผš

-- ็ฐๅบฆ้…็ฝฎ่กจ
CREATE TABLE config_gray (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    config_id BIGINT NOT NULL COMMENT '้…็ฝฎID',
    gray_value TEXT NOT NULL COMMENT '็ฐๅบฆ้…็ฝฎๅ€ผ',
    gray_type VARCHAR(20) NOT NULL COMMENT '็ฐๅบฆ็ฑปๅž‹๏ผšinstance/percent',
    gray_rule TEXT NOT NULL COMMENT '็ฐๅบฆ่ง„ๅˆ™๏ผšๅฎžไพ‹IDๅˆ—่กจๆˆ–็™พๅˆ†ๆฏ”',
    status VARCHAR(20) NOT NULL COMMENT '็Šถๆ€๏ผšgray/publish/rollback',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    KEY idx_config_id (config_id),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

็ฐๅบฆๅ‘ๅธƒๆต็จ‹

// ็ฐๅบฆๅ‘ๅธƒๆœๅŠก
type GrayReleaseService struct{}

// ่Žทๅ–้…็ฝฎ๏ผˆ่‡ชๅŠจๅˆคๆ–ญๆ˜ฏๅฆ็ฐๅบฆ๏ผ‰
func (s *GrayReleaseService) GetConfig(namespace, key, instanceID string) (string, error) {
    // 1. ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰่ฏฅๅฎžไพ‹็š„็ฐๅบฆ้…็ฝฎ
    grayConfig := s.grayDao.GetGrayConfig(namespace, key, instanceID)
    if grayConfig != nil {
        return grayConfig.GrayValue, nil
    }

    // 2. ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰ๆŒ‰็™พๅˆ†ๆฏ”็š„็ฐๅบฆ้…็ฝฎ
    percentConfig := s.grayDao.GetPercentGrayConfig(namespace, key)
    if percentConfig != nil && s.shouldApplyGray(instanceID, percentConfig) {
        return percentConfig.GrayValue, nil
    }

    // 3. ่ฟ”ๅ›žๆญฃๅผ้…็ฝฎ
    return s.configDao.GetConfig(namespace, key)
}

// ๅˆคๆ–ญๅฎžไพ‹ๆ˜ฏๅฆๅบ”่ฏฅๅบ”็”จ็ฐๅบฆ้…็ฝฎ
func (s *GrayReleaseService) shouldApplyGray(instanceID string, config *GrayConfig) bool {
    // ๅฎžไพ‹IDๅ“ˆๅธŒๅ€ผๅ–ๆจก
    hash := crc32.ChecksumIEEE([]byte(instanceID))
    percent := config.GrayPercent

    return int(hash%100) < percent
}

5.2 ็‰ˆๆœฌ็ฎก็†

็‰ˆๆœฌๅ›žๆปšๅฎž็Žฐ

// ็‰ˆๆœฌๆœๅŠก
type ConfigVersionService struct{}

// ๅ›žๆปšๅˆฐๆŒ‡ๅฎš็‰ˆๆœฌ
func (s *ConfigVersionService) Rollback(configID int64, targetVersion int) error {
    // 1. ๆŸฅ่ฏข็›ฎๆ ‡็‰ˆๆœฌ็š„้…็ฝฎๅ€ผ
    history, err := s.historyDao.GetByVersion(configID, targetVersion)
    if err != nil {
        return err
    }

    // 2. ๅผ€ๅฏไบ‹ๅŠก
    tx := s.db.Begin()

    // 3. ๆ›ดๆ–ฐๅฝ“ๅ‰้…็ฝฎ
    err = s.configDao.UpdateValue(tx, configID, history.NewValue)
    if err != nil {
        tx.Rollback()
        return err
    }

    // 4. ่ฎฐๅฝ•ๅ›žๆปšๆ“ไฝœ
    err = s.historyDao.Save(tx, &ConfigHistory{
        ConfigID:   configID,
        OldValue:   s.getCurrentValue(configID),
        NewValue:   history.NewValue,
        Operator:   "system",
        ChangeType: "ROLLBACK",
        Comment:    fmt.Sprintf("ๅ›žๆปšๅˆฐ็‰ˆๆœฌ %d", targetVersion),
    })
    if err != nil {
        tx.Rollback()
        return err
    }

    tx.Commit()
    return nil
}

ๅ…ญใ€ๆธธๆˆ่กŒไธš็š„้…็ฝฎไธญๅฟƒๅฎž่ทต ๐ŸŽฎ

ๆธธๆˆ่กŒไธšๅฏน้…็ฝฎไธญๅฟƒ็š„้œ€ๆฑ‚ๆ›ด่ฟซๅˆ‡โ€”โ€”ๆดปๅŠจ้ข‘็นใ€ๅผ€ๅ…ณๅคšใ€ๆ—ถๆ•ˆๆ€งๅผบใ€‚

6.1 ๆธธๆˆ้…็ฝฎ็š„็‰นๆฎŠๆ€ง

ๆธธๆˆ้…็ฝฎๅ’Œๆ™ฎ้€šไธšๅŠก้…็ฝฎๆœ‰ๅพˆๅคงๅŒบๅˆซ๏ผš

็‰นๆ€ง ๆ™ฎ้€šไธšๅŠก้…็ฝฎ ๆธธๆˆ้…็ฝฎ
ๅ˜ๆ›ด้ข‘็އ ๆฏๅคฉ 1-10 ๆฌก ๆฏๅฐๆ—ถๅฏ่ƒฝๅคšๆฌก
ๆ—ถๆ•ˆๆ€ง่ฆๆฑ‚ ๅˆ†้’Ÿ็บง ็ง’็บง๏ผˆๆดปๅŠจๅ‡†็‚นๅผ€ๅฏ๏ผ‰
้…็ฝฎ็ฑปๅž‹ ้”ฎๅ€ผๅฏนไธบไธป ๅคๆ‚ๅฏน่ฑก๏ผˆ่ฃ…ๅค‡ใ€ๆŠ€่ƒฝใ€ๆดปๅŠจ๏ผ‰
ๆ•ฐๆฎ้‡ KB ็บง MB ็”š่‡ณ GB ็บง
็ฐๅบฆ้œ€ๆฑ‚ ๆŒ‰ๅฎžไพ‹ ๆŒ‰็Žฉๅฎถใ€ๅŒบๆœใ€ๆธ ้“

6.2 ๆธธๆˆ้…็ฝฎๅˆ†ๅฑ‚่ฎพ่ฎก

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   ๆธธๆˆ้…็ฝฎๅˆ†ๅฑ‚ๆžถๆž„                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                      โ”‚
โ”‚  Layer 1: ็ณป็ปŸ้…็ฝฎ๏ผˆSystem Config๏ผ‰                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ ๆ•ฐๆฎๅบ“่ฟžๆŽฅใ€Redis ๅœฐๅ€ใ€ๆ—ฅๅฟ—็บงๅˆซ              โ”‚    โ”‚
โ”‚  โ”‚ ๅ˜ๆ›ด้ข‘็އ๏ผšๆžไฝŽ                                โ”‚    โ”‚
โ”‚  โ”‚ ๆ˜ฏๅฆ็ƒญๆ›ด๏ผš้€šๅธธไธ้œ€่ฆ                          โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                      โ”‚
โ”‚  Layer 2: ไธšๅŠก้…็ฝฎ๏ผˆBusiness Config๏ผ‰               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ ๅŠŸ่ƒฝๅผ€ๅ…ณใ€ๆดปๅŠจๅผ€ๅ…ณใ€ๆŽจ่็ฎ—ๆณ•ๅ‚ๆ•ฐ              โ”‚    โ”‚
โ”‚  โ”‚ ๅ˜ๆ›ด้ข‘็އ๏ผšๆฏๅคฉ                                โ”‚    โ”‚
โ”‚  โ”‚ ๆ˜ฏๅฆ็ƒญๆ›ด๏ผš้œ€่ฆ                                โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                      โ”‚
โ”‚  Layer 3: ๆธธๆˆๆ•ฐๆฎ๏ผˆGame Data๏ผ‰                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ ่ฃ…ๅค‡ๅฑžๆ€งใ€ๆŠ€่ƒฝ้…็ฝฎใ€ๆดปๅŠจ่ง„ๅˆ™                  โ”‚    โ”‚
โ”‚  โ”‚ ๅ˜ๆ›ด้ข‘็އ๏ผš้š็‰ˆๆœฌๆ›ดๆ–ฐ                          โ”‚    โ”‚
โ”‚  โ”‚ ๆ˜ฏๅฆ็ƒญๆ›ด๏ผš้œ€่ฆ๏ผˆ็ดงๆ€ฅไฟฎๅค๏ผ‰                    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

6.3 ๅฎž้™…ๆกˆไพ‹๏ผšๆดปๅŠจ้…็ฝฎ็ƒญๆ›ดๆ–ฐ

  1. ๆๅ‰ๆ”น้…็ฝฎๆ–‡ไปถ
  2. ๅ†™ๅฎšๆ—ถไปปๅŠก๏ผŒ20:00 ้‡ๅฏๆœๅŠก
  3. ็ฅˆ็ฅท้‡ๅฏๆˆๅŠŸ
// ๆดปๅŠจ้…็ฝฎ็ป“ๆž„
type ActivityConfig struct {
    ActivityID   string    `json:"activityId"`
    BonusRate    float64   `json:"bonusRate"`
    StartTime    time.Time `json:"startTime"`
    EndTime      time.Time `json:"endTime"`
    Enabled      bool      `json:"enabled"`
}

// ้…็ฝฎๅ˜ๆ›ด็›‘ๅฌๅ™จ
func (s *ActivityService) OnConfigChange(change *ConfigChange) {
    var newConfig ActivityConfig
    json.Unmarshal([]byte(change.Value), &newConfig)

    // ๆฃ€ๆŸฅๆ˜ฏๅฆ้œ€่ฆ็ซ‹ๅณ็”Ÿๆ•ˆ
    if newConfig.Enabled && time.Now().After(newConfig.StartTime) {
        // ๆดปๅŠจๅทฒๅผ€ๅง‹๏ผŒ็ซ‹ๅณ็”Ÿๆ•ˆ
        s.UpdateBonusRate(newConfig.BonusRate)
        log.Infof("ๆดปๅŠจ้…็ฝฎ็ƒญๆ›ดๆ–ฐๆˆๅŠŸ๏ผš%+v", newConfig)
    }
}
  1. ๆๅ‰ๅœจ configcenter ๅˆ›ๅปบ้…็ฝฎ๏ผˆenabled=false๏ผ‰
  2. 19:59:30๏ผŒ่ฟ่ฅไฟฎๆ”น้…็ฝฎ๏ผˆenabled=true, bonusRate=2.0๏ผ‰
  3. 20:00:00๏ผŒ้…็ฝฎๆŽจ้€ๅˆฐๆ‰€ๆœ‰ๆœๅŠก๏ผŒๆดปๅŠจ็”Ÿๆ•ˆ
  4. ๅ…จ็จ‹ๆ— ้œ€้‡ๅฏ๏ผŒ็Žฉๅฎถๆ— ๆ„Ÿ็Ÿฅ

6.4 ๅŠŸ่ƒฝๅผ€ๅ…ณ๏ผš็บฟไธŠๆ•‘็”Ÿๅœˆ

ๆ–ฐๅŠŸ่ƒฝไธŠ็บฟ๏ผŒๆœ€ๆ€•ๅ‡บ้—ฎ้ข˜ใ€‚ๅŠŸ่ƒฝๅผ€ๅ…ณ่ฎฉไฝ ่ƒฝๅฟซ้€Ÿๆญข่ก€ใ€‚

// ๅŠŸ่ƒฝๅผ€ๅ…ณ้…็ฝฎ
type FeatureToggle struct {
    FeatureName string   `json:"featureName"`
    Enabled     bool     `json:"enabled"`
    Whitelist   []string `json:"whitelist"`
    GrayPercent int      `json:"grayPercent"`
}

// ไธšๅŠกไปฃ็ ไธญไฝฟ็”จๅŠŸ่ƒฝๅผ€ๅ…ณ
func (s *NewFeatureService) Execute(player *Player) {
    toggle := s.configCenter.GetConfig("feature.new-battle-system")

    // ๆฃ€ๆŸฅๅผ€ๅ…ณ็Šถๆ€
    if !toggle.Enabled {
        // ๅŠŸ่ƒฝๅ…ณ้—ญ๏ผŒ่ตฐ่€้€ป่พ‘
        s.ExecuteOld(player)
        return
    }

    // ๆฃ€ๆŸฅ็™ฝๅๅ•
    if contains(toggle.Whitelist, player.ID) {
        s.ExecuteNew(player)
        return
    }

    // ๆฃ€ๆŸฅ็ฐๅบฆๆฏ”ไพ‹
    if hash(player.ID)%100 < toggle.GrayPercent {
        s.ExecuteNew(player)
    } else {
        s.ExecuteOld(player)
    }
}

ๆŸๆธธๆˆไธŠ็บฟๆ–ฐๆˆ˜ๆ–—็ณป็ปŸ๏ผŒๅ‘็Žฐๆˆ˜ๆ–—่ฎก็ฎ—ๆœ‰ Bug๏ผŒๅฏผ่‡ด้ƒจๅˆ†็Žฉๅฎถๆˆ˜ๅŠ›ๅผ‚ๅธธใ€‚

  1. ่ฟ็ปดๅœจ configcenter ๅ…ณ้—ญๅŠŸ่ƒฝๅผ€ๅ…ณ๏ผˆenabled=false๏ผ‰
  2. ้…็ฝฎ3็ง’ๅ†…ๆŽจ้€ๅˆฐๆ‰€ๆœ‰ๆœๅŠก
  3. ๆ–ฐๆˆ˜ๆ–—็ณป็ปŸ็ซ‹ๅˆปๅœ็”จ๏ผŒๅˆ‡ๅ›ž่€็ณป็ปŸ
  4. ๅ…จ็จ‹ๆ— ้œ€้‡ๅฏ๏ผŒ็Žฉๅฎถๅ‡ ไนŽๆ— ๆ„Ÿ็Ÿฅ

่ฟ™ๆฏ”็ดงๆ€ฅๅ›žๆปšไปฃ็ ไผ˜้›…ๅคšไบ†โ€”โ€”ๅ›žๆปšไปฃ็ ้œ€่ฆ้‡ๆ–ฐ้ƒจ็ฝฒ๏ผŒ่‡ณๅฐ‘ๅ‡ ๅˆ†้’Ÿ๏ผ›ๅ…ณๅผ€ๅ…ณๅช้œ€่ฆ3็ง’ใ€‚

6.5 ๅคš็Žฏๅขƒ้š”็ฆป๏ผšๆต‹่ฏ•ๆœ็‚ธไบ†ไธๅฝฑๅ“ๆญฃๅผๆœ

ๆธธๆˆๅผ€ๅ‘้€šๅธธๆœ‰๏ผšๅผ€ๅ‘ๆœใ€ๆต‹่ฏ•ๆœใ€ไฝ“้ชŒๆœใ€ๆญฃๅผๆœใ€‚

็Žฏๅขƒ้š”็ฆปๆžถๆž„๏ผš
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    configcenter                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ ๅผ€ๅ‘็Žฏๅขƒ โ”‚  โ”‚ ๆต‹่ฏ•็Žฏๅขƒ โ”‚  โ”‚ ไฝ“้ชŒ็Žฏๅขƒ โ”‚  โ”‚ ๆญฃๅผ็Žฏๅขƒ โ”‚โ”‚
โ”‚  โ”‚  (dev)  โ”‚  โ”‚ (test)  โ”‚  โ”‚ (stage) โ”‚  โ”‚ (prod)  โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚       โ”‚            โ”‚            โ”‚            โ”‚      โ”‚
โ”‚       โ–ผ            โ–ผ            โ–ผ            โ–ผ      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ ๅผ€ๅ‘ๆœ  โ”‚  โ”‚ ๆต‹่ฏ•ๆœ  โ”‚  โ”‚ ไฝ“้ชŒๆœ  โ”‚  โ”‚ ๆญฃๅผๆœ  โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
# ๅผ€ๅ‘็Žฏๅขƒ (dev)
game:
  activity:
    bonusRate: 10.0      # ๅผ€ๅ‘็Žฏๅขƒๅฅ–ๅŠฑ10ๅ€๏ผŒๆ–นไพฟๆต‹่ฏ•
    debugMode: true

# ๆต‹่ฏ•็Žฏๅขƒ (test)
game:
  activity:
    bonusRate: 2.0       # ๆต‹่ฏ•็Žฏๅขƒๆญฃๅธธๅ€็އ
    debugMode: true

# ๆญฃๅผ็Žฏๅขƒ (prod)
game:
  activity:
    bonusRate: 1.0       # ๆญฃๅผ็Žฏๅขƒ้ป˜่ฎคไธ็ฟปๅ€
    debugMode: false     # ๅ…ณ้—ญ่ฐƒ่ฏ•ๆจกๅผ
// ๅฎขๆˆท็ซฏๅฏๅŠจๆ—ถๆŒ‡ๅฎšๅ‘ฝๅ็ฉบ้—ด๏ผˆ็Žฏๅขƒ๏ผ‰
func main() {
    env := os.Getenv("ENV") // dev / test / prod

    client := configcenter.NewClient(&configcenter.Config{
        ServerURL:  "http://config-center:8080",
        Namespace:  env,     // ๅ…ณ้”ฎ๏ผšๆŒ‡ๅฎš็Žฏๅขƒ
        AppID:      "game-server",
    })

    client.Start()
}

ไธƒใ€configcenter ้ƒจ็ฝฒๆžถๆž„ ๐Ÿ—๏ธ

7.1 ็”Ÿไบง็Žฏๅขƒ้ƒจ็ฝฒ

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚   SLB / Nginx   โ”‚
                    โ”‚   (่ดŸ่ฝฝๅ‡่กก)     โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚              โ”‚              โ”‚
              โ–ผ              โ–ผ              โ–ผ
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚configcenterโ”‚  โ”‚configcenterโ”‚  โ”‚configcenterโ”‚
       โ”‚  Node 1  โ”‚   โ”‚  Node 2  โ”‚   โ”‚  Node 3  โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
             โ”‚              โ”‚              โ”‚
             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
                            โ–ผ
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  MySQL ้›†็พค   โ”‚
                    โ”‚ (ไธปไปŽๅคๅˆถ)    โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

7.2 ้ซ˜ๅฏ็”จไฟ้šœ

// ๅฅๅบทๆฃ€ๆŸฅๆŽฅๅฃ
func (h *HealthHandler) Check(c *gin.Context) {
    // ๆฃ€ๆŸฅๆ•ฐๆฎๅบ“่ฟžๆŽฅ
    if err := h.db.Ping(); err != nil {
        c.JSON(503, gin.H{"status": "unhealthy", "error": "database"})
        return
    }

    // ๆฃ€ๆŸฅๅ†…ๅญ˜ไฝฟ็”จ
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    if m.Alloc > 2*1024*1024*1024 { // 2GB
        c.JSON(503, gin.H{"status": "unhealthy", "error": "memory"})
        return
    }

    c.JSON(200, gin.H{"status": "healthy"})
}

7.3 ็›‘ๆŽงๆŒ‡ๆ ‡

// ๅ…ณ้”ฎ็›‘ๆŽงๆŒ‡ๆ ‡
type Metrics struct {
    // QPS
    RequestQPS int64

    // ๅปถ่ฟŸ
    P50Latency  time.Duration
    P99Latency  time.Duration

    // ้•ฟ่ฝฎ่ฏข
    ActivePollingCount int64

    // ้…็ฝฎๅ˜ๆ›ด
    ConfigChangeCount int64

    // ้”™่ฏฏ็އ
    ErrorRate float64
}

ๅ…ซใ€ๆœ€ไฝณๅฎž่ทต ๐Ÿ“‹

8.1 ้…็ฝฎ่ฎพ่ฎกๅŽŸๅˆ™

// โŒ ้”™่ฏฏ๏ผš้…็ฝฎๅ†™ๆญปๅœจไปฃ็ ้‡Œ
const MaxLevel = 100
const BonusRate = 1.5

// โœ… ๆญฃ็กฎ๏ผš้…็ฝฎไปŽ้…็ฝฎไธญๅฟƒ่ฏปๅ–
maxLevel := configCenter.GetInt("game.player.maxLevel", 100)
bonusRate := configCenter.GetFloat("game.activity.bonusRate", 1.5)
# โŒ ๅคช็ฒ—๏ผšไธ€ไธช้…็ฝฎๅŒ…ๅซๆ‰€ๆœ‰
game: '{"maxLevel":100,"bonusRate":1.5,"timeout":30}'

# โŒ ๅคช็ป†๏ผšๆฏไธชๅญ—ๆฎตไธ€ไธช้…็ฝฎ
game.maxLevel: 100
game.bonusRate: 1.5
game.timeout: 30

# โœ… ้€‚ไธญ๏ผšๆŒ‰ๅŠŸ่ƒฝๆจกๅ—ๅˆ†็ป„
game:
  player:
    maxLevel: 100
  activity:
    bonusRate: 1.5
  system:
    timeout: 30
// ๆ•ๆ„Ÿ้…็ฝฎๅŠ ๅฏ†ๅญ˜ๅ‚จ
type SecureConfig struct {
    DatabasePassword string `json:"databasePassword" encrypt:"true"`
    APIKey           string `json:"apiKey" encrypt:"true"`
}

// ๅญ˜ๅ‚จๆ—ถ่‡ชๅŠจๅŠ ๅฏ†
func (s *ConfigService) SaveConfig(config *SecureConfig) error {
    // ๅŠ ๅฏ†ๆ•ๆ„Ÿๅญ—ๆฎต
    config.DatabasePassword = s.encrypt(config.DatabasePassword)
    config.APIKey = s.encrypt(config.APIKey)

    // ไฟๅญ˜ๅˆฐๆ•ฐๆฎๅบ“
    return s.configDao.Save(config)
}

// ่ฏปๅ–ๆ—ถ่‡ชๅŠจ่งฃๅฏ†
func (s *ConfigService) GetConfig() (*SecureConfig, error) {
    config := s.configDao.Get()

    // ่งฃๅฏ†ๆ•ๆ„Ÿๅญ—ๆฎต
    config.DatabasePassword = s.decrypt(config.DatabasePassword)
    config.APIKey = s.decrypt(config.APIKey)

    return config, nil
}

8.2 ๅฎขๆˆท็ซฏ้›†ๆˆ็คบไพ‹

// ๆธธๆˆๆœๅŠกๅ™จ้›†ๆˆ configcenter
package main

import (
    "github.com/ourcompany/configcenter-go"
)

func main() {
    // ๅˆๅง‹ๅŒ–้…็ฝฎไธญๅฟƒๅฎขๆˆท็ซฏ
    client := configcenter.NewClient(&configcenter.Config{
        ServerURL:  "http://config-center:8080",
        Namespace:  os.Getenv("ENV"), // dev/test/prod
        AppID:      "game-server",
        InstanceID: getInstanceID(),
    })

    // ๅฏๅŠจๅฎขๆˆท็ซฏ
    if err := client.Start(); err != nil {
        log.Fatal("ๅฏๅŠจ้…็ฝฎไธญๅฟƒๅคฑ่ดฅ๏ผš", err)
    }

    // ็›‘ๅฌ้…็ฝฎๅ˜ๆ›ด
    client.Watch("game.activity.bonusRate", func(change *configcenter.ConfigChange) {
        newRate := change.Value.(float64)
        log.Infof("ๆดปๅŠจๅ€็އๅ˜ๆ›ด๏ผš%f โ†’ %f", change.OldValue, newRate)

        // ๆ›ดๆ–ฐๅ†…ๅญ˜ไธญ็š„้…็ฝฎ
        activityService.UpdateBonusRate(newRate)
    })

    // ่Žทๅ–้…็ฝฎๅ€ผ
    bonusRate := client.GetFloat("game.activity.bonusRate", 1.0)
    log.Infof("ๅฝ“ๅ‰ๆดปๅŠจๅ€็އ๏ผš%f", bonusRate)

    // ๅฏๅŠจๆธธๆˆๆœๅŠก
    gameServer.Run()
}

8.3 ็›‘ๆŽงไธŽๅ‘Š่ญฆ

้…็ฝฎๅ˜ๆ›ด้œ€่ฆ็›‘ๆŽง๏ผŒ้˜ฒๆญข่ฏฏๆ“ไฝœ๏ผš

// ้…็ฝฎๅ˜ๆ›ดๅฎก่ฎกๆ—ฅๅฟ—
type ConfigAudit struct {
    Operator  string
    ConfigKey string
    OldValue  string
    NewValue  string
    Timestamp time.Time
}

// ๆ•ๆ„Ÿ้…็ฝฎๅ˜ๆ›ดๅ‘Š่ญฆ
func (s *ConfigService) UpdateConfig(key, value, operator string) error {
    // ๆฃ€ๆŸฅๆ˜ฏๅฆไธบๆ•ๆ„Ÿ้…็ฝฎ
    if s.isSensitiveConfig(key) {
        // ๅ‘้€ๅ‘Š่ญฆ้€š็Ÿฅ
        s.alertService.SendAlert(
            "ๆ•ๆ„Ÿ้…็ฝฎๅ˜ๆ›ดๅ‘Š่ญฆ",
            fmt.Sprintf("้…็ฝฎ %s ่ขซ %s ไฟฎๆ”น", key, operator),
        )
    }

    // ่ฎฐๅฝ•ๅฎก่ฎกๆ—ฅๅฟ—
    s.auditService.Log(&ConfigAudit{
        Operator:  operator,
        ConfigKey: key,
        OldValue:  s.getCurrentValue(key),
        NewValue:  value,
        Timestamp: time.Now(),
    })

    return nil
}

ไนใ€ๆ€ป็ป“๏ผš้…็ฝฎไธญๅฟƒ็š„ไปทๅ€ผ ๐Ÿ“Œ

ๆ ธๅฟƒ่ฆ็‚นๅ›ž้กพ

  1. ้…็ฝฎไธญๅฟƒ็š„ๆœฌ่ดจ๏ผšๆŠŠๆ•ฃ่ฝ็š„้…็ฝฎ้›†ไธญ็ฎก็†๏ผŒ่ฎฉๆ”นๅŠจๅฟซ้€Ÿ็”Ÿๆ•ˆ๏ผŒ่ฎฉๅ˜ๆ›ดๆœ‰่ฟนๅฏๅพชใ€‚
  1. ็ƒญๆ›ดๆ–ฐๆ˜ฏๆ ธๅฟƒไปทๅ€ผ๏ผšๆ”น้…็ฝฎไธ้‡ๅฏ๏ผŒ่ฟ™ๆ‰ๆ˜ฏ้…็ฝฎไธญๅฟƒๅญ˜ๅœจ็š„ๆ„ไน‰ใ€‚ๆˆ‘ไปฌ็š„ configcenter ๆ”ฏๆŒ 3็ง’ๅ†…้…็ฝฎ็”Ÿๆ•ˆใ€‚
  1. ๆŽจ้€ๆœบๅˆถ้€‰้•ฟ่ฝฎ่ฏข๏ผšๅ…ผ้กพๅฎžๆ—ถๆ€งๅ’Œๆ€ง่ƒฝ๏ผŒ้…็ฝฎๅ˜ๆ›ดๅŽ3็ง’ๅ†…็”Ÿๆ•ˆ๏ผŒๆœๅŠก็ซฏๅŽ‹ๅŠ›ๅฏๆŽงใ€‚
  1. ็ฐๅบฆๅ‘ๅธƒๆ˜ฏๅฎ‰ๅ…จ็ฝ‘๏ผšๆ–ฐ้…็ฝฎๅ…ˆๅฐ่Œƒๅ›ด้ชŒ่ฏ๏ผŒๅ†ๅ…จ้‡ๅ‘ๅธƒใ€‚
  1. ็‰ˆๆœฌ็ฎก็†ๆ˜ฏๆ—ถๅ…‰ๆœบ๏ผšๅ‡บไบ†้—ฎ้ข˜ไธ€้”ฎๅ›žๆปš๏ผŒๆŠŠๆŸๅคฑ้™ๅˆฐๆœ€ไฝŽใ€‚
  1. ๆธธๆˆ่กŒไธšๆ›ด้œ€่ฆ๏ผšๆดปๅŠจ้ข‘็นใ€ๆ—ถๆ•ˆๆ€งๅผบ๏ผŒ้…็ฝฎไธญๅฟƒๅ‡ ไนŽๆ˜ฏๅˆš้œ€ใ€‚
  1. ่‡ช็ ”็š„ไผ˜ๅŠฟ๏ผšๅฎŒๅ…จ่‡ชไธปๅฏๆŽง๏ผŒ้’ˆๅฏนๆธธๆˆไธšๅŠกๅœบๆ™ฏไผ˜ๅŒ–๏ผŒ่ฝป้‡้ซ˜ๆ•ˆใ€‚

configcenter ๅธฆๆฅ็š„ๆ”นๅ˜


ๅใ€ๅ†™ๅœจๆœ€ๅŽ ๐Ÿ’ญ

้…็ฝฎไธญๅฟƒ็œ‹่ตทๆฅๆ˜ฏไธชๅฐไธœ่ฅฟโ€”โ€”ไธๅฐฑๆ˜ฏๅญ˜ไธช้…็ฝฎๅ—๏ผŸ

ไฝ†็œŸๆญฃ็”จ่ฟ‡ไน‹ๅŽ๏ผŒไฝ ไผšๅ‘็Žฐ๏ผšๅฎƒๆ”นๅ˜ไบ†ไฝ ๅ’Œ้…็ฝฎ็š„ๅ…ณ็ณปใ€‚

ไปฅๅ‰๏ผŒๆ”น้…็ฝฎๆ˜ฏไปถ"ๅคงไบ‹"โ€”โ€”่ฆ็™ปๅฝ•ๆœๅŠกๅ™จใ€่ฆๆ”นๆ–‡ไปถใ€่ฆ้‡ๅฏใ€่ฆ็ฅˆ็ฅทใ€‚ๆ‰€ไปฅ่ƒฝไธๆ”นๅฐฑไธๆ”น๏ผŒ่ƒฝๆ‹–ๅฐฑๆ‹–ใ€‚

็Žฐๅœจ๏ผŒๆ”น้…็ฝฎๅ˜ๆˆ"ๅฐไบ‹"โ€”โ€”ๆ‰“ๅผ€็ฝ‘้กตใ€ๆ”นๅฎŒไฟๅญ˜ใ€3็ง’็”Ÿๆ•ˆใ€‚้‡ๅˆฐ้—ฎ้ข˜่ฟ˜่ƒฝไธ€้”ฎๅ›žๆปšใ€‚

ไบŽๆ˜ฏ๏ผŒไฝ ๆ›ดๆ„ฟๆ„่ฐƒๆ•ดๅ‚ๆ•ฐใ€ไผ˜ๅŒ–้…็ฝฎใ€ๅฟซ้€Ÿ่ฏ•้”™ใ€‚

ๅœจๅฟซ้€Ÿ่ฟญไปฃ็š„ไบ’่”็ฝ‘ๆ—ถไปฃ๏ผŒ่ฐๅ˜ๅพ—ๅฟซ๏ผŒ่ฐๅฐฑ่ตขใ€‚้…็ฝฎไธญๅฟƒ๏ผŒๅฐฑๆ˜ฏ่ฎฉไฝ ๅ˜ๅฟซ็š„้‚ฃไธชๅทฅๅ…ทใ€‚


ไธ‹ๆœŸ้ข„ๅ‘Š๏ผšๆถˆๆฏ้˜Ÿๅˆ—โ€”โ€”่ฎฉ็ณป็ปŸ"ๅผ‚ๆญฅ่งฃ่€ฆ"็š„็ง˜ๅฏ†ๆญฆๅ™จ๏ผŒๆ•ฌ่ฏทๆœŸๅพ…๏ผ


๐Ÿ’ฌ ่ฏ„่ฎบ (0)

0/500
ๆŽ’ๅบ๏ผš