本頁面提供 Memorystore for Redis 的最佳使用指南。本頁也會指出應避免的潛在問題。
如需疑難排解情境清單,請參閱「疑難排解」。
高可用性和資料耐用性最佳做法
建議使用標準級做為高可用性 (HA) 的主要機制。Standard 級別提供具有多個副本的高可用性執行個體,並在主要執行個體故障時,使用自動容錯移轉功能快速復原。此外,在標準級執行個體上啟用 RDB 快照,可進一步防範節點故障。
在某些情況下,您可能也需要確保能從快照備份復原資料。在這些情況下,備份和從 RDB 快照還原資料的功能,可進一步防止資料遺失。
如果啟用 RDB 快照,且需要復原資料,Memorystore for Redis 可從最新快照復原資料。這些快照僅供內部系統復原使用,您無法存取。
耐用性限制和最佳做法
雖然標準級透過複製和自動容錯移轉提供高可用性,但高可用性無法保證完整的資料耐用性。即使在某些災難性事件 (例如暫時性區域故障) 期間啟用高可用性,Memorystore for Redis 也無法提供完整的資料耐用性。
為保護資料並提供基準層級的持久性,當您使用 Google Cloud 控制台建立 Memorystore for Redis 執行個體時,系統會預設啟用 RDB 快照。
如果是正式環境工作負載,建議採用下列最佳做法,進一步提升資料韌性:
- 災難復原:Memorystore for Redis 不支援跨區域複製。如果災難復原策略需要跨區域複製,才能真正具備區域韌性,建議使用 Memorystore for Valkey 執行個體。如要進一步瞭解如何設定及管理跨區域次要執行個體,請參閱 Memorystore for Valkey 說明文件中的「使用跨區域複製功能」。
- 最高耐用性:如果應用程式需要最高等級的資料耐用性,建議使用 Memorystore for Valkey 執行個體,並設定跨區域複製、每日自動備份和僅附加檔案 (AOF) 持續性。這種組合可提供最完善的資料遺失防護。詳情請參閱 Memorystore for Valkey 說明文件的「持續性總覽」一節。
容量規劃最佳做法
為確保您能成功佈建資源並建構彈性架構,建議您採用下列容量規劃最佳做法:
- 在多個位置部署:如果因暫時性資源限制而無法在偏好的可用區建立執行個體,請嘗試在同一區域的其他可用區或不同區域建立執行個體。
- 使用跨區域存取權:設定用戶端,以便存取位於不同區域的執行個體。如果因當地資源限制,而需要在不同區域佈建執行個體,用戶端可以連線至該執行個體。
- 選擇其他容量級別:如果因執行個體大小而遇到資源限制,請選取其他容量級別或調整執行個體大小。詳情請參閱「容量級別效能」。
匯出 RDB
匯出 RDB 備份時,請遵循下列指南:
- 在寫入率較低時匯出。
- 如果在寫入率偏高的期間匯出,請暫時將
maxmemory設定降低至執行個體容量的 50%,提供足夠的額外資源,確保作業順利完成。
需要大量資源的作業
如果是標準級 Redis 執行個體,下列作業在執行期間會使用額外記憶體:
由於複製作業,版本升級、資源調度及手動容錯移轉會使用額外記憶體 (適用於標準級執行個體)。這些作業會遵循「標準級執行個體升級行為」一節所述的複製程序。
匯入和匯出作業需要額外的記憶體,因為這些作業會產生分支 Redis 程序和寫入時複製資料管理。
為降低資源密集作業的缺點,建議您採取下列做法:
- 在作業期間,將 maxmemory 設定降低至執行個體容量的 80%。這可提供足夠的額外空間,確保作業順利完成。
- 監控系統記憶體用量比率指標,並確保這項指標低於 80%,再執行上述任一作業。
- 請在執行個體流量較低的時段 (例如夜間或週末) 執行這些作業。
- 執行這些作業前,請先設定重試邏輯,並採用指數輪詢。
需要重試連線的操作和情境
下列作業和情境會中斷網路與 Redis 執行個體之間的網路連線:
這些作業會修改執行個體,因此需要暫時中斷連線。執行這些作業前,您必須具備採用指數輪詢的重試邏輯,讓應用程式自動重新連線並繼續正常運作。
例行維護
Memorystore for Redis 執行個體會定期進行維護。詳情請參閱 Memorystore for Redis 維護政策。
請採用下列最佳做法,為例行維護做好準備:
- 設定維護期間,指定可執行維護更新的時間。
- 請在執行個體流量較低且記憶體負載充足時,安排維護期間。詳情請參閱「維護更新的影響」。
- 開啟維護期間通知,在維護作業即將開始時收到通知。
- 採用指數輪詢重試邏輯。
- 如果是標準級執行個體,您可以使用手動容錯移轉模擬維護事件,瞭解維護作業造成的容錯移轉對應用程式有何影響。
- 如果是基本級執行個體,您可以暫時將執行個體擴充至較大容量,模擬維護更新的影響。觀察影響後,您可以縮回原始大小。
記憶體管理
開放原始碼 Redis 會發生眾所皆知的記憶體片段問題,因此記憶體管理可能是一大挑戰。建議您降低執行個體的 maxmemory 設定,以便在記憶體壓力過高時預留空間。
如要監控 Memorystore 執行個體的記憶體壓力,最佳方式是使用系統記憶體用量比率指標。如要進一步瞭解如何管理 Memorystore for Redis 的記憶體,請參閱「記憶體管理最佳做法」。
管理閒置連線
如果連線未正確終止,Memorystore 執行個體的連線數量可能會隨著時間增加。這可能會對效能造成負面影響,尤其是使用傳輸中資料加密時,系統會根據容量層級限制連線數量上限。為降低這類情況,建議您使用 timeout Redis 設定參數,設定閒置用戶端連線自動終止前的秒數。
資料存取透明化控管機制資源名稱
請勿將機密資料儲存在 Memorystore for Redis 資源名稱中。資源名稱是指 Memorystore for Redis 執行個體名稱,以及執行個體中繼資料,例如標記。儲存在資源名稱中的資料不一定會受到 Google Cloud 資料存取透明化控管機制保護,且可能與貴組織的資料存取透明化控管機制法規遵循要求相衝突。
部分無伺服器環境需要無伺服器 VPC 存取連接器
部分無伺服器環境需要無伺服器 VPC 存取連接器,才能連線至 Memorystore for Redis。如要使用其中一種環境連線,請為專案設定無伺服器 VPC 存取連接器。
網路
建議您使用私人服務存取權 連線模式。Memorystore for Redis 採用兩種連線模式:私人服務存取權和直接對接。私人服務存取權連線模式可簡化 IP 範圍管理作業,並可視需要使用 Shared VPC。
執行個體建立後,即無法變更連線模式。
詳情請參閱「網路」。
監控與警告
建議您使用監控和快訊功能,因為這些功能可提供 Redis 執行個體記憶體用量的關鍵信號。此外,您也可以透過這些指標,瞭解 Redis 執行個體回應傳入快取要求的效率。
建議您設定下列預設快訊:
CPU 使用率最佳做法
不當使用昂貴的 Redis 指令,會導致延遲時間長、無回應或連線問題。Standard 級執行個體會在災難復原期間提供高可用性,並依賴主要節點和副本節點之間的非同步複製。如果其中一個節點有耗費大量資源的指令處理作業,導致 Redis 主執行緒遭到封鎖,複製作業可能會受到影響。如果問題持續發生,且位置發生中斷,則在發生中斷的位置寫入的最新資料可能無法在其他位置使用。
建議您使用 Cloud Monitoring,為主執行緒 CPU 秒數 (redis.googleapis.com/stats/cpu_utilization_main_thread) 指標設定快訊,確保 CPU 使用率不會超過主要節點的 0.8 秒,或每個副本節點的 0.5 秒 (副本指定為唯讀副本時)。
如果 Redis 執行個體超過建議值,建議您將執行個體擴充至較高的容量級別,或按照疑難排解操作說明,避免執行 CPU 密集作業。
如果執行個體的 CPU 使用率偏高,或執行個體的資源耗盡 (例如連線過多),執行個體可能會發生異常行為,且外部指標可能會遺失。
需要大量資源的指令
強烈建議您避免使用會耗用大量資源的 Redis 指令。使用這些指令可能會導致下列效能問題:
- 高延遲和用戶端逾時
- 增加記憶體用量的指令導致記憶體壓力
- Redis 主執行緒遭到封鎖,導致節點複製和同步處理期間資料遺失
- 健康狀態檢查、觀測能力和複製功能受限
下表列出耗用大量資源的 Redis 指令範例,並提供節省資源的替代方案。
| 類別 | 資源密集型指令 | 資源效率替代方案 |
|---|---|---|
| 針對整個鍵空間執行 | KEYS |
SCAN |
| 執行可變長度金鑰集 | LRANGE |
限制查詢使用的範圍大小。 |
ZRANGE |
限制查詢使用的範圍大小。 | |
HGETALL |
HSCAN |
|
SMEMBERS |
SSCAN |
|
| 封鎖指令碼執行 | EVAL |
確認指令碼不會無限期執行。 |
EVALSHA |
確認指令碼不會無限期執行。 | |
| 移除檔案和連結 | DEL |
UNLINK |
| 發布及訂閱 | PUBLISH |
SPUBLISH |
SUBSCRIBE |
SSUBSCRIBE |
Redis 用戶端最佳做法
本節提供 Redis 用戶端的最佳使用指南。
偵測及處理無回應的連線
強烈建議您設定用戶端應用程式,偵測 Memorystore for Redis 連線是否無回應。偵測到無回應的連線時,用戶端必須重設連線。為建構彈性應用程式,建議您採用下列用戶端設定:
- 設定 TCP 保持運作參數:設定
TCP keepalive time、TCP keepalive interval和TCP keepalive probes參數,讓用戶端主動偵測並捨棄無回應的連線,即使連線處於閒置狀態也一樣。舉例來說,如果將TCP keepalive time參數設為 30 秒、TCP keepalive interval設為 10 秒,並將TCP keepalive probes設為 3,用戶端就會在一分鐘內重設無回應的閒置連線。 - 設定 TCP 使用者逾時:在用戶端設定這項逾時,重設有未完成要求且停止回應的連線。舉例來說,如果將逾時時間設為 15 秒,用戶端會在 15 秒後重設有未完成要求且無回應的連線。
客戶專屬最佳做法
如果水平擴展應用程式,寫入指令可能會發生 READONLY 錯誤。Memorystore for Redis 採用非 Sentinel 部署,並使用靜態端點,用戶端不會預先收到動態角色資訊。如果您同時使用單一用戶端連線連至主要和副本端點,用戶端可能會不小心將寫入指令傳送至唯讀副本。副本會傳回 READONLY 錯誤,因為無法處理寫入指令。
為避免寫入作業轉送錯誤,請勿同時使用單一連線進行讀取和寫入作業。請改為建立下列獨立用戶端執行個體,藉此分割作業:
- 主要用戶端:僅連線至主要端點
- 副本用戶端:僅連線至副本端點
下列分頁說明如何使用 Go、Java、Node.js 和 Python 設定個別範本。
Go
package main import ( "context" "fmt" "github.com/redis/go-redis/v9" ) func main() { ctx := context.Background() // Initialize the primary client connecting only to the primary endpoint primaryClient := redis.NewClient(&redis.Options{ Addr: "PRIMARY_HOST:6379", Password: "YOUR_PASSWORD", DB: 0, }) defer primaryClient.Close() // Initialize the replica client connecting only to the replica endpoint replicaClient := redis.NewClient(&redis.Options{ Addr: "REPLICA_HOST:6379", Password: "YOUR_PASSWORD", DB: 0, }) defer replicaClient.Close() // Use the primary client for all mutating commands err := primaryClient.Set(ctx, "example_key", "example_value", 0).Err() if err != nil { fmt.Printf("Failed to write to primary: %v\n", err) } // Use the replica client for all read-only commands val, err := replicaClient.Get(ctx, "example_key").Result() if err != nil { fmt.Printf("Failed to read from replica: %v\n", err) } else { fmt.Printf("Successfully read value: %s\n", val) } }
Java
@Bean public RedisConnectionFactory primaryConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("PRIMARY_HOST", 6379); config.setPassword(RedisPassword.of("YOUR_PASSWORD")); return new LettuceConnectionFactory(config); } @Bean public RedisConnectionFactory replicaConnectionFactory() { RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration("PRIMARY_HOST", 6379); config.addNode("REPLICA_HOST", 6379); config.setPassword(RedisPassword.of("YOUR_PASSWORD")); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .readFrom(ReadFrom.REPLICA_PREFERRED) .build(); return new LettuceConnectionFactory(config, clientConfig); } @Bean public RedisTemplate<String, Object> primaryRedisTemplate( @Qualifier("primaryConnectionFactory") RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); return template; } @Bean public RedisTemplate<String, Object> replicaRedisTemplate( @Qualifier("replicaConnectionFactory") RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); return template; }
Node.js
import { createClient } from 'redis'; async function main() { // Initialize the primary client connecting only to the primary endpoint const primaryClient = createClient({ url: 'redis://PRIMARY_HOST:6379', password: 'YOUR_PASSWORD' }); // Initialize the replica client connecting only to the replica endpoint const replicaClient = createClient({ url: 'redis://REPLICA_HOST:6379', password: 'YOUR_PASSWORD' }); primaryClient.on('error', (err) => console.error('Primary Client Error', err)); replicaClient.on('error', (err) => console.error('Replica Client Error', err)); await primaryClient.connect(); await replicaClient.connect(); // Use the primary client for all mutating commands try { await primaryClient.set('example_key', 'example_value'); console.log('Successfully wrote to primary'); } catch (err) { console.error('Failed to write to primary:', err); } // Use the replica client for all read-only commands try { const val = await replicaClient.get('example_key'); console.log(`Successfully read value: ${val}`); } catch (err) { console.error('Failed to read from replica:', err); } await primaryClient.disconnect(); await replicaClient.disconnect(); } main();
Python
import redis def main(): # Initialize the primary client connecting only to the primary endpoint primary_client = redis.Redis( host='PRIMARY_HOST', port=6379, password='YOUR_PASSWORD', decode_responses=True ) # Initialize the replica client connecting only to the replica endpoint replica_client = redis.Redis( host='REPLICA_HOST', port=6379, password='YOUR_PASSWORD', decode_responses=True ) # Use the primary client for all mutating commands try: primary_client.set('example_key', 'example_value') print('Successfully wrote to primary') except redis.RedisError as e: print(f'Failed to write to primary: {e}') # Use the replica client for all read-only commands try: val = replica_client.get('example_key') print(f'Successfully read value: {val}') except redis.RedisError as e: print(f'Failed to read from replica: {e}') if __name__ == '__main__': main()