Nestjs框架: 基于Mongodb的多租戶功能集成和優化

概述

  • 基于前文,我們知道如何集成多租戶的相關功能了, 現在我們繼續集成Monodb的多租戶形式
  • 需要注意的是,MongoDB 在 NestJS 中的使用過程中存在一些“坑點”
  • 如果按照默認方式集成,會發現連接數在不斷增長,即使我們請求的是相同的數據庫地址和租戶信息
  • 這說明每次接口請求都在創建新的數據庫連接,而不是復用已有連接,這個行為與我們預期不符
  • 數據庫連接是有限資源,頻繁創建連接會導致 IO 資源耗盡、性能下降等問題
  • 下面,我們分別來看看如何解決此類問題

開始集成


1 ) 編寫 docker-compose.multi.yaml 文件

services:mongo:image: mongo:8  # 使用最新的 MongoDB 鏡像container_name: mongo_apprestart: alwaysports:- "27018:27017"  # 將容器的 27017 端口映射到主機的 27017 端口environment:- MONGO_INITDB_ROOT_USERNAME=root  # 設置 MongoDB 的 root 用戶名- MONGO_INITDB_ROOT_PASSWORD=123456_mongodb  # 設置 MongoDB 的 root 密碼# 調整日志級別的例子,可選值如 "0"(致命錯誤)、"1"(警告+錯誤)、"2"(信息+警告+錯誤)...- MONGO_LOG_LEVEL=2- MONGO_SYSTEM_LOG_PATH=/data/logs/mongodb.logvolumes:- ./docker-dbconfig/mongo/conf/mongod.conf:/etc/mongod.conf- ./docker-dbconfig/mongo/data/db:/data/db  # 將容器內的 /data/db 目錄映射到本地的 ./data/db 目錄,用于數據持久化- ./docker-dbconfig/mongo/logs:/data/logsnetworks:- light_networkmongo-express:image: mongo-express:latestcontainer_name: mongo_express_apprestart: alwaysenvironment:ME_CONFIG_MONGODB_ADMINUSERNAME: rootME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodbME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo:27017/ME_CONFIG_BASICAUTH: falseports:- 18081:8081networks:- light_networkmongo2:image: mongo:8  # 使用最新的 MongoDB 鏡像container_name: mongo_app2restart: alwaysports:- "27019:27017"  # 將容器的 27017 端口映射到主機的 27017 端口environment:- MONGO_INITDB_ROOT_USERNAME=root  # 設置 MongoDB 的 root 用戶名- MONGO_INITDB_ROOT_PASSWORD=123456_mongodb  # 設置 MongoDB 的 root 密碼# 調整日志級別的例子,可選值如 "0"(致命錯誤)、"1"(警告+錯誤)、"2"(信息+警告+錯誤)...- MONGO_LOG_LEVEL=2- MONGO_SYSTEM_LOG_PATH=/data/logs/mongodb.logvolumes:- ./docker-dbconfig/mongo2/conf/mongod.conf:/etc/mongod.conf- ./docker-dbconfig/mongo2/data/db:/data/db  # 將容器內的 /data/db 目錄映射到本地的 ./data/db 目錄,用于數據持久化- ./docker-dbconfig/mongo2/logs:/data/logsnetworks:- light_networkmongo-express2:image: mongo-express:latestcontainer_name: mongo_express_app2restart: alwaysenvironment:ME_CONFIG_MONGODB_ADMINUSERNAME: rootME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodbME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo2:27017/ME_CONFIG_BASICAUTH: falseports:- 18082:8081networks:- light_networknetworks:light_network:external: true
  • 啟動服務,之后在 UI 管理界面給 2個數據庫加入可識別的數據
  • 下面測試的時候,會看到數據

2 ) 配置 .env

T1_MONGODB_URI="mongodb://root:123456_mongodb@localhost:27018/nestmongodb"
T2_MONGODB_URI="mongodb://root:123456_mongodb@localhost:27019/nestmongodb"

3 ) 編寫 database/mongoose/mongoose.constant.ts

// 這個配置模擬調接口/讀數據庫獲取的
export const tenantMap = new Map([['1', 'T1'],['2', 'T2']
]);
export const defaultTenant = tenantMap.values().next().value; // 第一個

4 ) 編寫 database/mongoose/mongoose-config.service.ts

import { Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import {MongooseModuleOptions,MongooseOptionsFactory,
} from '@nestjs/mongoose';
import { Request } from 'express';
import { tenantMap, defaultTenant } from './mongoose.constant';
import { ConfigService } from '@nestjs/config';export class MongooseConfigService implements MongooseOptionsFactory {constructor(@Inject(REQUEST) private request: Request,private configService: ConfigService,) {}createMongooseOptions():| MongooseModuleOptions| Promise<MongooseModuleOptions> {const headers = this.request.headers;const tenantId = headers['x-tenant-id'] as string;console.log('tenantId: ', tenantId)if (tenantId && !tenantMap.has(tenantId)) {throw new Error('invalid tenantId');}const t_prefix = !tenantId ? defaultTenant : tenantMap.get(tenantId);const uri = this.configService.get<string>(`${t_prefix}_MONGODB_URI`);return { uri } as MongooseModuleOptions;}
}

5 ) 編寫 app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './user/user.schema'
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseConfigService } from './database/mongoose/mongoose-config.service';@Module({imports: [// 1. 下面這個后續可以封裝一個新的模塊,來匹配 .env 和 其他配置ConfigModule.forRoot({  // 配置環境變量模塊envFilePath: '.env', // 指定環境變量文件路徑isGlobal: true, // 全局可用}),MongooseModule.forRootAsync({useClass: MongooseConfigService,}),MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),],controllers: [AppController],providers: []
})export class AppModule {}

6 ) 編寫 app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user/user.entity';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Document as Doc } from 'mongoose';@Controller()
export class AppController {constructor(@InjectModel('User') private userModel: Model<Doc>,) {}@Get('/multi-mongoose')async getMongooseUsers(): Promise<any> {const rs = await this.userModel.find()return rs;}
}

測試


1 ) 測試1

  • 請求

    curl --request GET \--url http://localhost:3000/multi-mongoose \--header 'x-tenant-id: 1'
    
  • 響應

    [{"_id": "6874d4b0d10e36e350dd588d","username": "mongo1","password": "123456"},{"_id": "6874d4d9d10e36e350dd588f","username": "lee","password": "123456"}
    ]
    

2 ) 測試2

  • 請求

    curl --request GET \--url http://localhost:3000/multi-mongoose \--header 'x-tenant-id: 2'
    
  • 響應

    [{"_id": "6874d4b0d10e36e350dd588d","username": "mongo2","password": "123456"},{"_id": "6874d4d9d10e36e350dd588f","username": "lee","password": "123456"}
    ]
    

3 )進入其中一個數據庫,測試連接情況

docker exec -it mongo_app2 mongosh admin -u root # 輸入密碼
use nestmongodb;# 其實上面一個 use 命令 可以不用
db.serverStatus().connections

輸出如下:

{current: 6,available: 838839,totalCreated: 38,rejected: 0,active: 2,threaded: 6,exhaustIsMaster: Long('0'),exhaustHello: Long('0'),awaitingTopologyChanges: Long('1'),loadBalanced: Long('0')
}

目前 active 有2個,當不斷請求同一個接口,在此執行上述命令,可發現這個數字是累加的
這明顯是不對的,訪問相同鏈接應該使用同一個 connection, 而不是創建新的通道
當租戶多起來的時候,會帶來嚴重的性能問題

4 ) 分析原因

@nestjs/mongoose 包的 mongoose-core.module.ts 中 在 使用 factory 方法的時候,會調用 createMongooseConnection

private static async createMongooseConnection(uri: string,mongooseOptions: ConnectOptions,factoryOptions: {lazyConnection?: boolean;onConnectionCreate?: MongooseModuleOptions['onConnectionCreate'];},
): Promise<Connection> {const connection = mongoose.createConnection(uri, mongooseOptions);if (factoryOptions?.lazyConnection) {return connection;}factoryOptions?.onConnectionCreate?.(connection);return connection.asPromise();
}

這個是每次都會創建的根本原因,這個包也沒有提供 類似 datasourceFactory 的功能
現在需要定制 mongoose 的 module 中的 forRootSync 的邏輯

定制 mongoose 的 forRootAsync 方法

現在需要對官方 @nestjs/mongoose 包中的核心模塊的方法進行裁剪和優化
找到對應的文件貼到自己項目中進行修改

1 )新建 src/database/mongoose/mongoose.module.ts

import { Module, DynamicModule } from '@nestjs/common';
import { MongooseModuleAsyncOptions, MongooseModuleOptions, MongooseModule as NestMongooseModule
} from '@nestjs/mongoose';
import { MongooseCoreModule } from './mongoose-core.module';@Module({})
export class MongooseModule extends NestMongooseModule {static forRoot(uri: string,options: MongooseModuleOptions = {},): DynamicModule {return {module: MongooseModule,imports: [MongooseCoreModule.forRoot(uri, options)],}}static forRootAsync(options: MongooseModuleAsyncOptions): DynamicModule {return {module: MongooseModule,imports: [MongooseCoreModule.forRootAsync(options)],}}
}

這是最外層,用于引入 MongooseCoreModule 模塊, 重寫有問題的源碼

2 )新建 src/database/mongoose/mongoose-core.module.ts

import {DynamicModule,Global,Inject,Module,OnApplicationShutdown,Provider,Type,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import * as mongoose from 'mongoose';
import { ConnectOptions, Connection } from 'mongoose';
import { defer, lastValueFrom } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { handleRetry } from './mongoose.utils';
import {MONGOOSE_CONNECTION_NAME,MONGOOSE_MODULE_OPTIONS,
} from './mongoose.constants';import {MongooseModuleOptions,MongooseModuleAsyncOptions,MongooseModuleFactoryOptions,MongooseOptionsFactory,getConnectionToken,
} from '@nestjs/mongoose';@Global()
@Module({})
export class MongooseCoreModule implements OnApplicationShutdown {private static connections: Record<string, mongoose.Connection> = {};constructor(@Inject(MONGOOSE_CONNECTION_NAME) private readonly connectionName: string,private readonly moduleRef: ModuleRef,) {}static forRoot(uri: string,options: MongooseModuleOptions = {},): DynamicModule {const {retryAttempts,retryDelay,connectionName,connectionFactory,connectionErrorFactory,lazyConnection,onConnectionCreate,verboseRetryLog,...mongooseOptions} = options;const mongooseConnectionFactory =connectionFactory || ((connection) => connection);const mongooseConnectionError =connectionErrorFactory || ((error) => error);const mongooseConnectionName = getConnectionToken(connectionName);const mongooseConnectionNameProvider = {provide: MONGOOSE_CONNECTION_NAME,useValue: mongooseConnectionName,};const connectionProvider = {provide: mongooseConnectionName,useFactory: async (): Promise<any> =>await lastValueFrom(defer(async () =>mongooseConnectionFactory(await this.createMongooseConnection(uri, mongooseOptions, {lazyConnection,onConnectionCreate,}),mongooseConnectionName,),).pipe(handleRetry(retryAttempts, retryDelay, verboseRetryLog),catchError((error) => {throw mongooseConnectionError(error);}),),),};return {module: MongooseCoreModule,providers: [connectionProvider, mongooseConnectionNameProvider],exports: [connectionProvider],};}static forRootAsync(options: MongooseModuleAsyncOptions): DynamicModule {const mongooseConnectionName = getConnectionToken(options.connectionName);const mongooseConnectionNameProvider = {provide: MONGOOSE_CONNECTION_NAME,useValue: mongooseConnectionName,};const connectionProvider = {provide: mongooseConnectionName,useFactory: async (mongooseModuleOptions: MongooseModuleFactoryOptions,): Promise<any> => {const {retryAttempts,retryDelay,uri,connectionFactory,connectionErrorFactory,lazyConnection,onConnectionCreate,verboseRetryLog,...mongooseOptions} = mongooseModuleOptions;const mongooseConnectionFactory =connectionFactory || ((connection) => connection);const mongooseConnectionError =connectionErrorFactory || ((error) => error);return await lastValueFrom(defer(async () =>mongooseConnectionFactory(await this.createMongooseConnection(uri as string,mongooseOptions,{ lazyConnection, onConnectionCreate },),mongooseConnectionName,),).pipe(handleRetry(retryAttempts, retryDelay, verboseRetryLog),catchError((error) => {throw mongooseConnectionError(error);}),),);},inject: [MONGOOSE_MODULE_OPTIONS],};const asyncProviders = this.createAsyncProviders(options);return {module: MongooseCoreModule,imports: options.imports,providers: [...asyncProviders,connectionProvider,mongooseConnectionNameProvider,],exports: [connectionProvider],};}private static createAsyncProviders(options: MongooseModuleAsyncOptions,): Provider[] {if (options.useExisting || options.useFactory) {return [this.createAsyncOptionsProvider(options)];}const useClass = options.useClass as Type<MongooseOptionsFactory>;return [this.createAsyncOptionsProvider(options),{provide: useClass,useClass,},];}private static createAsyncOptionsProvider(options: MongooseModuleAsyncOptions,): Provider {if (options.useFactory) {return {provide: MONGOOSE_MODULE_OPTIONS,useFactory: options.useFactory,inject: options.inject || [],};}// `as Type<MongooseOptionsFactory>` is a workaround for microsoft/TypeScript#31603const inject = [(options.useClass || options.useExisting) as Type<MongooseOptionsFactory>,];return {provide: MONGOOSE_MODULE_OPTIONS,useFactory: async (optionsFactory: MongooseOptionsFactory) =>await optionsFactory.createMongooseOptions(),inject,};}private static async createMongooseConnection(uri: string,mongooseOptions: ConnectOptions,factoryOptions: {lazyConnection?: boolean;onConnectionCreate?: MongooseModuleOptions['onConnectionCreate'];},): Promise<Connection> {// 添加這里if (this.connections[uri]) {return this.connections[uri];}const connection = mongoose.createConnection(uri, mongooseOptions);this.connections[uri] = connection; // 這里賦值if (factoryOptions?.lazyConnection) {return connection;}factoryOptions?.onConnectionCreate?.(connection);return connection.asPromise();}async onApplicationShutdown() {const connection = this.moduleRef.get<any>(this.connectionName);if (connection) {await connection.close();}const connectionClients = Object.values(MongooseCoreModule.connections);if (connectionClients.length > 0) {// 銷毀所有 mongoose connectionfor (const client of connectionClients) {client?.close();}}}
}

這里,注意 createMongooseConnection 以及 onApplicationShutdown 中的 處理
對連接進行優化處理,以及在異常關閉時對客戶端進行銷毀

3 )src/database/mongoose/mongoose.constants.ts

// 這個配置模擬調接口/讀數據庫獲取的
export const tenantMap = new Map([['1', 'T1'],['2', 'T2']
]);
export const defaultTenant = tenantMap.values().next().value; // 第一個// 新增如下
export const DEFAULT_DB_CONNECTION = 'DatabaseConnection';
export const MONGOOSE_MODULE_OPTIONS = 'MongooseModuleOptions';
export const MONGOOSE_CONNECTION_NAME = 'MongooseConnectionName';export const RAW_OBJECT_DEFINITION = 'RAW_OBJECT_DEFINITION';

3 )src/database/mongoose/mongoose.utils.ts

import { Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { delay, retryWhen, scan } from 'rxjs/operators';export function handleRetry(retryAttempts = 9,retryDelay = 3000,verboseRetryLog = false,
): <T>(source: Observable<T>) => Observable<T> {const logger = new Logger('MongooseModule');return <T>(source: Observable<T>) =>source.pipe(retryWhen((e) =>e.pipe(scan((errorCount, error) => {const verboseMessage = verboseRetryLog? ` Message: ${error.message}.`: '';const retryMessage =retryAttempts > 0 ? ` Retrying (${errorCount + 1})...` : '';logger.error(['Unable to connect to the database.',verboseMessage,retryMessage,].join(''),error.stack,);if (errorCount + 1 >= retryAttempts) {throw error;}return errorCount + 1;}, 0),delay(retryDelay),),),);
}
  • 工具包的部分函數

4 )測試

  • 重啟項目,連入其中一個mongo 的 docker 容器,進入數據庫,執行 db.serverStatus().connections
  • 調用對應數據庫的租戶標識的接口,再次執行 db.serverStatus().connections
  • 可看到 currentactive 增加了,后面多次調用同一租戶接口則不會再增加
  • 結束程序,則會銷毀客戶端,相應數字會同步減少
  • 這樣就完成了相關功能

總結

  • 問題本質:Mongoose 在 NestJS 中的連接未復用,導致連接數異常增長
  • 核心影響:IO 資源浪費、性能下降、數據庫連接池耗盡
  • 解決思路:
    • 在服務層緩存已有連接
    • 修改 Mongoose 模塊源碼邏輯
    • 使用第三方連接池庫進行封裝
    • 最佳實踐:
      • 多租戶系統中應確保數據庫連接的唯一性與復用性
      • 建議對 Mongoose 模塊進行輕量級封裝以適配業務需求

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93277.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93277.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93277.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何利用機器學習分析篩選生物標記物

在生物信息學中&#xff0c;Lasso回歸、隨機森林&#xff08;Random Forest&#xff09;和XGBoost因其各自的特性和優勢&#xff0c;被廣泛應用于基因組學、蛋白質組學、藥物發現和疾病機制研究等領域。 Lasso回歸 癌癥亞型分類&#xff1a;從TCGA數據中篩選驅動基因&#xf…

計算機網絡(基礎篇)

TCP/IP 網絡模型 應用層&#xff08;Application Layer&#xff09; 應用層只需要專注于為用戶提供應用功能&#xff0c;比如 HTTP、FTP、Telnet、DNS、SMTP等。應用層是工作在操作系統中的用戶態&#xff0c;傳輸層及以下則工作在內核態。傳輸層&#xff08;Transport Layer&a…

全面解析 CSS Flex 布局:從入門到精通的所有屬性詳解

1. Flex 容器屬性 通過 display: flex 或 display: inline-flex 將元素設置為 Flex 容器。以下是所有容器屬性。 1.1 display: flex | inline-flex 作用&#xff1a;定義一個 Flex 容器。可選值&#xff1a; flex&#xff1a;塊級容器&#xff0c;占據整行。inline-flex&#x…

數據結構:對角矩陣(Diagonal Matrix)

目錄 矩陣的傳統表示&#xff1a;二維數組 &#x1f50d; 真正有用的數據是哪些&#xff1f; 從二維數組轉為一維數組 用 C 類實現對角矩陣 1. 對角矩陣真正需要存什么&#xff1f; 2. 對角矩陣允許哪些行為&#xff1f; 3. 為什么要動態分配數組&#xff1f; 接下來推…

Leetcode_349.兩個數組的交集

這道題的意思很明確&#xff0c;就是讓尋找兩個數組中的共同元素&#xff0c;并去重&#xff0c;由此可以聯想到哈希表的特性&#xff0c;注意到題目給的數據范圍&#xff0c;在1000以內&#xff0c;所以本題可以使用 STL 的庫函數&#xff0c;也可以使用數組進行模擬。 本題要…

STM32——寄存器映射

總 &#xff1a;STM32——HAL庫總結-CSDN博客 芯片資料&#xff1a; STM32F1系列參考手冊-V10&#xff08;中&#xff09; STM32F103ZET6(English) 一、寄存器基礎 1.1 簡介 單片機內部的控制機構。 像空氣開關控制電路一樣的原理&#xff0c;打開關閉某個開關&#xff0…

Java響應式編程

Java 響應式編程是一種基于異步數據流處理的編程范式&#xff0c;它強調數據流的聲明式構建和傳播變化的自動響應。Java 9 引入的Flow API為響應式編程提供了標準接口&#xff0c;而 Reactor 和 RxJava 等第三方庫則提供了更豐富的操作符和工具。核心概念Publisher&#xff08;…

【重學數據結構】二叉搜索樹 Binary Search Tree

目錄 二叉搜索樹的數據結構 手寫實現二叉搜索樹 樹節點定義 插入節點 源碼 流程圖 二叉樹插入步驟圖解 第一步: 插入 20 第二步: 插入 10 第三步: 插入 30 第四步: 插入 5 查找節點 源碼 場景一: 查找成功 (search for 25) 第一步: 從根節點開始 第二步:…

四、計算機組成原理——第1章:計算機系統概述

目錄 1.1計算機發展歷程 1.1.1計算機硬件的發展 1.計算機的四代變化 2.計算機元件的更新換代 1.1.2計算機軟件的發展 1.2計算機系統層次結構 1.2.1計算機系統的組成 1.2.2計算機硬件 1.馮諾依曼機基本思想 2.計算機的功能部件 (1)輸入設備 (2)輸出設備 (3)存儲器 (4)運算器 (5)…

flutter TextField 失去焦點事件

在 Flutter 中&#xff0c;處理 TextField 的失去焦點事件&#xff08;即失去焦點時觸發的操作&#xff09;通常有兩種常用方式&#xff1a;使用 FocusNode 或 onEditingComplete 回調。以下是具體實現&#xff1a; import package:flutter/material.dart;class MyTextField e…

Moonlight for ChromeOS 常見問題解決方案

Moonlight for ChromeOS 常見問題解決方案 項目基礎介紹 Moonlight for ChromeOS 是一個開源的 NVIDIA GameStream 客戶端&#xff0c;允許用戶將他們的游戲從高性能的桌面電腦流式傳輸到運行 ChromeOS 的設備上。該項目還支持 Android 和 iOS/tvOS 平臺。Moonlight for Chrome…

SQL語句:讀操作、寫操作、視圖

文章目錄讀操作分類基礎查詢語句示例高級查詢--分組查詢、子查詢、表連接、聯合查詢分組查詢&#xff1a;子查詢&#xff08;嵌套查詢&#xff09;表連接聯合查詢寫操作視圖SQL&#xff1a;結構化查詢語言讀操作 重點是where查詢&#xff0c;即高級查詢部分 分類 DML &#…

Python 機器學習實戰:基于 Scikit-learn

本文圍繞《Python 機器學習實戰&#xff1a;基于 Scikit-learn 的項目開發》展開&#xff0c;先介紹 Scikit-learn 庫的基礎特性與優勢&#xff0c;再闡述機器學習項目開發的完整流程&#xff0c;包括數據收集與預處理、模型選擇與訓練、評估與優化等。通過具體實戰案例&#x…

java里List鏈式編程

java里對list的操作&#xff0c;我們一遍使用for遍歷&#xff0c;輸出或改變里面的內容。單經常在代碼里面我們發現&#xff0c;也可以使用這樣的代碼結構daPaymentActionVo.setApnolist(paymentActionVo.getApnolist().stream().map(PaymentActionVo.Voucher::getApno).collec…

【esp32s3】7 - VSCode + PlatformIO + Arduino + 構建項目

一、PlatformIO 1.1. 概述 官方文檔&#xff1a;What is PlatformIO? PlatformIO 是一個跨平臺的物聯網開發生態系統&#xff0c;專門為嵌入式系統開發設計&#xff0c;支持多種開發板和框架。 1.1.1. 主要特點 跨平臺&#xff1a;支持 Windows、macOS 和 Linux多框架支持&…

LE AUDIO CIS/BIS音頻傳輸時延的計算

LE AUDIO音頻總時延計算方法 按照BAP的規范,LE AUDIO音頻總延時包括三個部分:Audio Processing Time,Transport Latency,Presentation Delay。如下圖所示是播放音樂的示例圖: 這里還有一個麥克風錄音的總時延示例圖: Audio Processing Time:這個就是音頻DSP獲取音頻數…

git 修改 更新

git 修改 更新先更新&#xff0c;后修改# 暫存當前修改 git add . git stash# 獲取最新的 main 分支 git checkout main git pull# 新建開發分支 git checkout -b lbg_0727# ?? 先把 main 的最新代碼合并/變基到當前分支&#xff08;用于消除沖突&#xff09; # 方法1&#x…

飛鶴困局:增長神話的裂痕

增長天花板已然逼近&#xff0c;飛鶴需要探尋新方向。作者|安德魯編輯|文昌龍“飛鶴&#xff0c;更適合中國寶寶體質”——這句曾讓無數媽媽點頭的廣告語&#xff0c;幫飛鶴坐上了中國奶粉市場的頭把交椅。可多年后&#xff0c;時代紅利退潮&#xff0c;故事不好講了。飛鶴的利…

Java設計模式之<建造者模式>

目錄 1、建造者模式 2、建造者模式結構 3、實現 4、工廠模式對比 5、適用場景差異 前言 建造者模式是一種創建型設計模式。用于封裝復雜對象的構建過程&#xff0c;通過步驟構建產品類。它包括產品類、抽象建造者、具體建造者和指揮者角色。 優點在于靈活性、解耦和易擴展…

fchown/fchownat系統調用及示例

55. fchmod - 通過文件描述符改變文件權限 函數介紹 fchmod是一個Linux系統調用&#xff0c;用于通過文件描述符來改變文件的訪問權限。它是chmod函數的文件描述符版本&#xff0c;避免了路徑名解析。 函數原型 #include <sys/stat.h> #include <unistd.h>int fchm…