数据库Operator开发实战:以PostgreSQL为例

1. 为什么需要数据库Operator?

在云原生时代,数据库管理面临三大挑战:

  1. 状态管理复杂:数据库是有状态应用,需要持久化存储、数据一致性保证
  2. 高可用要求高:生产环境需要99.95%以上的可用性,自动故障转移是刚需
  3. 运维自动化不足:传统脚本管理方式难以满足弹性伸缩、备份恢复等需求

Kubernetes Operator模式正是为了解决这些问题而生。通过自定义资源和控制器,Operator将领域知识编码为软件,实现数据库的声明式管理。

1.1 数据库Operator的核心价值

结合我在电商平台数据库运维的实战经验,数据库Operator带来的价值包括:

  • 降低认知负荷:DBA无需记住复杂的命令行参数,通过YAML声明即可管理集群
  • 提升运维效率:自动化的备份、恢复、升级操作,减少人为失误
  • 增强可靠性:基于Kubernetes的自我修复能力,确保数据库高可用
  • 标准化部署:统一配置模板,确保开发、测试、生产环境的一致性

2. 设计PostgreSQL自定义资源

2.1 CRD设计原则

在设计数据库CRD时,我遵循以下原则:

  1. 最小化暴露细节:隐藏PostgreSQL内部配置复杂性,提供高层抽象
  2. 向后兼容:新版本CRD能兼容旧版本资源的升级
  3. 可观测性:在status字段暴露集群健康状态、副本数量等关键信息
  4. 安全性:敏感信息(密码、密钥)通过Secret引用,而非明文存储

2.2 PostgreSQLCluster CRD示例

复制代码
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: postgresqlclusters.database.example.com
spec:
  group: database.example.com
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                # 数据库版本
                postgresVersion:
                  type: string
                  enum: ["14", "15", "16"]
                  default: "15"
                
                # 实例数量
                instances:
                  type: integer
                  minimum: 1
                  maximum: 10
                  default: 3
                
                # 存储配置
                storage:
                  type: object
                  properties:
                    size:
                      type: string
                      pattern: '^[0-9]+Gi$'
                    storageClassName:
                      type: string
                
                # 备份配置
                backup:
                  type: object
                  properties:
                    enabled:
                      type: boolean
                    schedule:
                      type: string
                    retention:
                      type: object
                      properties:
                        fullBackups:
                          type: integer
                          minimum: 1
                          maximum: 30
                
                # 高可用配置
                highAvailability:
                  type: object
                  properties:
                    enabled:
                      type: boolean
                    maxFailoverDelay:
                      type: integer
                      description: "最大故障转移延迟(秒)"
                
                # 资源配置
                resources:
                  type: object
                  properties:
                    requests:
                      type: object
                      properties:
                        memory:
                          type: string
                        cpu:
                          type: string
            status:
              type: object
              properties:
                phase:
                  type: string
                  enum: ["Pending", "Creating", "Running", "Failed", "Upgrading"]
                primary:
                  type: string
                  description: "当前主节点Pod名称"
                replicas:
                  type: integer
                  description: "正常运行副本数量"
                conditions:
                  type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                      status:
                        type: string
                      lastTransitionTime:
                        type: string
                      reason:
                        type: string
                      message:
                        type: string

2.3 相关资源定义

除了主CRD,还需要定义相关的子资源:

复制代码
# PostgreSQLUser CRD - 管理数据库用户
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: postgresqlusers.database.example.com
spec:
  # ... 用户管理相关字段

# PostgreSQLBackup CRD - 管理按需备份
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: postgresqlbackups.database.example.com
spec:
  # ... 备份管理相关字段

3. 实现Operator核心逻辑

3.1 控制器架构设计

基于我在微服务架构中的经验,数据库Operator采用多控制器架构:

复制代码
# src/operator/controllers/__init__.py
"""
数据库Operator控制器架构
采用多控制器模式,每个控制器负责特定资源类型
"""

class PostgreSQLClusterController:
    """主集群控制器"""
    def __init__(self, k8s_client):
        self.k8s_client = k8s_client
        self.reconcile_interval = 30  # 秒
        
    def reconcile(self, cluster):
        """调谐循环核心逻辑"""
        # 1. 检查当前状态
        current_state = self.get_current_state(cluster)
        
        # 2. 计算期望状态
        desired_state = self.compute_desired_state(cluster)
        
        # 3. 执行差异操作
        if current_state != desired_state:
            self.apply_changes(cluster, desired_state)
            
    def get_current_state(self, cluster):
        """获取当前集群状态"""
        # 检查Pod状态
        # 检查Service状态
        # 检查PVC状态
        # 检查配置状态
        pass
        
    def compute_desired_state(self, cluster):
        """计算期望状态"""
        # 基于CRD spec计算
        pass
        
    def apply_changes(self, cluster, desired_state):
        """应用变更"""
        # 创建/更新/删除资源
        pass


class PostgreSQLUserController:
    """用户管理控制器"""
    pass


class PostgreSQLBackupController:
    """备份管理控制器"""
    pass

3.2 数据库实例创建流程

复制代码
# src/operator/controllers/cluster_creation.py
"""
数据库实例创建核心逻辑
包含状态机管理,确保创建过程可重入、可恢复
"""

class ClusterCreationManager:
    def __init__(self, k8s_client, db_client):
        self.k8s_client = k8s_client
        self.db_client = db_client
        self.creation_steps = [
            'validate_spec',
            'create_secrets',
            'create_persistent_volumes',
            'create_statefulset',
            'create_services',
            'initialize_database',
            'configure_replication',
            'start_backup_schedule'
        ]
    
    def create_cluster(self, cluster):
        """创建数据库集群"""
        current_step = self.get_current_step(cluster)
        
        for step in self.creation_steps[self.creation_steps.index(current_step):]:
            try:
                getattr(self, f'step_{step}')(cluster)
                self.update_step(cluster, step)
            except Exception as e:
                self.handle_creation_error(cluster, step, e)
                raise
    
    def step_create_statefulset(self, cluster):
        """创建StatefulSet - 实战踩坑案例"""
        # 踩坑点1:初始化顺序问题
        # 早期版本中,Pod并行启动导致数据目录竞争
        # 解决方案:设置podManagementPolicy为OrderedReady
        
        # 踩坑点2:存储卷声明模板
        # 动态生成PVC名称,确保每个Pod有独立存储
        spec = {
            'serviceName': f'{cluster.metadata.name}-pg',
            'podManagementPolicy': 'OrderedReady',
            'replicas': cluster.spec.instances,
            'selector': {
                'matchLabels': {
                    'app': 'postgresql',
                    'cluster': cluster.metadata.name
                }
            },
            'template': {
                'metadata': {
                    'labels': {
                        'app': 'postgresql',
                    }
                },
                'spec': {
                    'containers': [{
                        'name': 'postgresql',
                        'image': f'postgres:{cluster.spec.postgresVersion}',
                        'ports': [{'containerPort': 5432}],
                        'volumeMounts': [{
                            'name': 'data',
                            'mountPath': '/var/lib/postgresql/data'
                        }]
                    }]
                }
            },
            'volumeClaimTemplates': [{
                'metadata': {'name': 'data'},
                'spec': {
                    'accessModes': ['ReadWriteOnce'],
                    'resources': {
                        'requests': {
                            'storage': cluster.spec.storage.size
                        }
                    },
                    'storageClassName': cluster.spec.storage.storageClassName
                }
            }]
        }
        
        self.k8s_client.create_statefulset(
            namespace=cluster.metadata.namespace,
            name=f'{cluster.metadata.name}-statefulset',
            spec=spec
        )
    
    def step_configure_replication(self, cluster):
        """配置复制 - 集成Patroni"""
        # 使用Patroni管理PostgreSQL高可用
        # 通过环境变量配置Patroni
        patroni_config = {
            'scope': cluster.metadata.name,
            'name': f'{cluster.metadata.name}-{{{{ replica_index }}}}',
            'ttl': 30,
            'loop_wait': 10,
            'retry_timeout': 10,
            'maximum_lag_on_failover': 1048576
        }
        
        # 更新StatefulSet,添加Patroni sidecar容器
        self.update_statefulset_with_patroni(cluster, patroni_config)

3.3 配置管理实现

复制代码
# src/operator/controllers/configuration_manager.py
"""
PostgreSQL配置管理
支持动态配置更新,无需重启服务
"""

class PostgreSQLConfigManager:
    def __init__(self, k8s_client):
        self.k8s_client = k8s_client
        
    def update_configuration(self, cluster, new_config):
        """更新数据库配置"""
        # 踩坑点:直接更新postgresql.conf可能导致配置不一致
        # 解决方案:使用ALTER SYSTEM命令,并通过pg_reload_conf()生效
        
        # 生成配置更新SQL
        config_sql = self.generate_config_sql(new_config)
        
        # 通过kubectl exec执行SQL
        primary_pod = self.get_primary_pod(cluster)
        
        # 执行ALTER SYSTEM命令
        for key, value in new_config.items():
            sql = f"ALTER SYSTEM SET {key} = '{value}';"
            self.execute_sql_on_pod(primary_pod, sql)
        
        # 重新加载配置
        self.execute_sql_on_pod(primary_pod, "SELECT pg_reload_conf();")
        
        # 验证配置生效
        self.verify_configuration(cluster, new_config)
    
    def generate_config_sql(self, config):
        """生成配置SQL"""
        # 过滤无效参数
        valid_params = self.get_valid_postgres_params()
        filtered_config = {
            k: v for k, v in config.items() 
            if k in valid_params
        }
        
        return filtered_config

4. 集成生产级工具

4.1 集成Patroni实现高可用

Patroni是PostgreSQL高可用的行业标准,在Operator中集成时需要关注:

复制代码
# src/operator/integrations/patroni_integration.py
"""
Patroni集成模块
处理故障检测、主从切换、配置同步
"""

class PatroniIntegration:
    def __init__(self, k8s_client):
        self.k8s_client = k8s_client
        
    def setup_patroni_cluster(self, cluster):
        """设置Patroni集群"""
        # 1. 创建ConfigMap存储Patroni配置
        config_map = {
            'apiVersion': 'v1',
            'kind': 'ConfigMap',
            'metadata': {
                'name': f'{cluster.metadata.name}-patroni-config',
                'labels': {
                    'app': 'postgresql',
                    'cluster': cluster.metadata.name
                }
            },
            'data': {
                'patroni.yml': self.generate_patroni_config(cluster)
            }
        }
        
        # 2. 创建Endpoints用于leader选举
        # 使用Kubernetes原语替代etcd
        endpoints = {
            'apiVersion': 'v1',
            'kind': 'Endpoints',
            'metadata': {
                'name': f'{cluster.metadata.name}-leader',
                'labels': {
                    'app': 'postgresql',
                    'cluster': cluster.metadata.name
                }
            }
        }
        
        # 3. 更新StatefulSet,挂载ConfigMap
        self.update_statefulset_config(cluster, config_map)
    
    def monitor_failover(self, cluster):
        """监控故障转移状态"""
        # 实时监控Patroni健康状态
        # 检测网络分区、脑裂问题
        # 自动修复异常状态
        
        # 踩坑案例:网络分区导致双主
        # 解决方案:配置Patroni的failover_priority
        # 设置节点优先级,确保只有一个节点能提升为主
        pass

4.2 集成pgBackRest实现备份恢复

复制代码
# src/operator/integrations/pgbackrest_integration.py
"""
pgBackRest集成模块
处理全量备份、增量备份、时间点恢复
"""

class PgBackRestIntegration:
    def __init__(self, k8s_client, storage_client):
        self.k8s_client = k8s_client
        self.storage_client = storage_client
        
    def configure_backup(self, cluster):
        """配置备份策略"""
        backup_spec = cluster.spec.backup
        
        # 生成pgBackRest配置文件
        config = {
            'global': {
                'repo1-retention-full': backup_spec.retention.fullBackups,
                'repo1-retention-diff': backup_spec.retention.diffBackups or 2,
                'repo1-retention-archive': backup_spec.retention.archiveSets or 2
            },
            f'{cluster.metadata.name}': {
                'pg1-path': '/var/lib/postgresql/data',
                'pg1-port': 5432
            },
            'repo1': {
                'type': 's3',
                's3-endpoint': self.storage_client.endpoint,
                's3-bucket': backup_spec.s3.bucket,
                's3-region': backup_spec.s3.region
            }
        }
        
        # 创建Secret存储S3凭证
        self.create_backup_secret(cluster, backup_spec.s3.credentials)
        
        # 添加pgBackRest sidecar容器到StatefulSet
        self.add_backup_sidecar(cluster, config)
    
    def perform_backup(self, cluster, backup_type='full'):
        """执行备份"""
        # 通过CronJob调度定期备份
        # 支持全量、增量、差异备份
        
        # 踩坑案例:大数据库备份超时
        # 解决方案:配置并行备份、调整超时时间
        # 使用pgBackRest的--archive-timeout参数
        
        cmd = [
            'pgbackrest',
            f'--stanza={cluster.metadata.name}',
            f'--type={backup_type}',
            'backup'
        ]
        
        if backup_type == 'full':
            cmd.append('--parallel=4')  # 并行备份加速
        
        return cmd
    
    def restore_cluster(self, cluster, backup_id=None, target_time=None):
        """恢复集群"""
        # 支持完整恢复和时间点恢复
        
        cmd = ['pgbackrest', f'--stanza={cluster.metadata.name}']
        
        if backup_id:
            cmd.extend(['--delta', f'--set={backup_id}'])
        elif target_time:
            cmd.extend(['--type=time', f'--target={target_time}'])
        else:
            cmd.append('--type=immediate')
        
        cmd.append('restore')
        
        # 执行恢复流程
        # 1. 停止现有数据库实例
        # 2. 清理数据目录
        # 3. 执行pgBackRest恢复
        # 4. 启动新实例
        # 5. 验证数据一致性
        
        return cmd

4.3 监控与告警集成

复制代码
# src/operator/integrations/monitoring_integration.py
"""
监控集成模块
集成Prometheus、Grafana、AlertManager
"""

class MonitoringIntegration:
    def setup_monitoring(self, cluster):
        """设置监控"""
        # 1. 部署PostgreSQL Exporter
        # 2. 配置ServiceMonitor
        # 3. 设置Grafana仪表板
        # 4. 配置告警规则
        
        # 关键监控指标:
        # - 连接数
        # - 查询延迟
        # - 复制延迟
        # - 磁盘使用率
        # - WAL生成速率
        
        pass

5. 实战踩坑案例与解决方案

5.1 案例一:初始化竞争条件

问题描述:早期版本中,StatefulSet的多个Pod同时启动,都尝试初始化数据目录,导致文件锁冲突。

根本原因:podManagementPolicy使用默认的Parallel模式,没有考虑数据库初始化的顺序性要求。

解决方案

复制代码
apiVersion: apps/v1
kind: StatefulSet
spec:
  podManagementPolicy: OrderedReady  # 改为顺序启动
  serviceName: {cluster-name}-pg

5.2 案例二:备份存储凭证泄露

问题描述:将S3访问密钥硬编码在ConfigMap中,存在安全风险。

根本原因:对Kubernetes Secret的使用理解不足。

解决方案

  1. 创建Secret存储敏感信息

  2. 通过环境变量或volume挂载方式注入

  3. 定期轮换凭证

    apiVersion: v1
    kind: Secret
    metadata:
    name: pgbackrest-s3-credentials
    type: Opaque
    data:
    aws-access-key-id: <base64编码>
    aws-secret-access-key: <base64编码>

5.3 案例三:故障转移时数据丢失

问题描述:网络分区导致脑裂,两个节点同时成为主节点,写入冲突数据。

根本原因:Patroni配置没有正确设置故障转移优先级和仲裁机制。

解决方案

复制代码
# patroni.yml配置
patroni:
  ttl: 30
  loop_wait: 10
  retry_timeout: 10
  maximum_lag_on_failover: 1048576
  postgresql:
    use_pg_rewind: true
  failover_priority:
    - node1: 100
    - node2: 50
    - node3: 0

6. 完整示例:简单的PostgreSQL Operator

以下是一个简化版的PostgreSQL Operator示例,展示核心架构:

复制代码
# src/operator/main.py
"""
PostgreSQL Operator主程序
"""

import kopf
import kubernetes
import logging

logging.basicConfig(level=logging.INFO)

@kopf.on.create('database.example.com', 'v1alpha1', 'postgresqlclusters')
def create_postgresql_cluster(spec, name, namespace, logger, **kwargs):
    """创建PostgreSQL集群"""
    logger.info(f"开始创建PostgreSQL集群: {name}")
    
    # 1. 创建Secret
    create_secret(name, namespace, spec)
    
    # 2. 创建ConfigMap
    create_configmap(name, namespace, spec)
    
    # 3. 创建StatefulSet
    create_statefulset(name, namespace, spec)
    
    # 4. 创建Service
    create_service(name, namespace, spec)
    
    logger.info(f"PostgreSQL集群创建完成: {name}")
    return {'message': 'Cluster creation initiated'}


@kopf.on.delete('database.example.com', 'v1alpha1', 'postgresqlclusters')
def delete_postgresql_cluster(spec, name, namespace, logger, **kwargs):
    """删除PostgreSQL集群"""
    logger.info(f"开始删除PostgreSQL集群: {name}")
    
    # 执行清理操作
    # 注意:需要谨慎处理数据持久化卷
    
    logger.info(f"PostgreSQL集群删除完成: {name}")


@kopf.timer('database.example.com', 'v1alpha1', 'postgresqlclusters', interval=30.0)
def reconcile_cluster(spec, status, name, namespace, logger, **kwargs):
    """定期调谐集群状态"""
    logger.info(f"调谐PostgreSQL集群: {name}")
    
    # 检查集群健康状态
    health_status = check_cluster_health(name, namespace)
    
    # 更新status字段
    patch = {
        'status': {
            'phase': health_status['phase'],
            'primary': health_status['primary'],
            'replicas': health_status['replicas']
        }
    }
    
    return patch


def create_statefulset(name, namespace, spec):
    """创建StatefulSet"""
    api = kubernetes.client.AppsV1Api()
    
    statefulset = {
        'apiVersion': 'apps/v1',
        'kind': 'StatefulSet',
        'metadata': {
            'name': f'{name}-postgresql',
            'namespace': namespace,
            'labels': {
                'app': 'postgresql',
                'cluster': name
            }
        },
        'spec': {
            'serviceName': f'{name}-postgresql',
            'podManagementPolicy': 'OrderedReady',
            'replicas': spec.get('instances', 3),
            'selector': {
                'matchLabels': {
                    'app': 'postgresql',
                    'cluster': name
                }
            },
            'template': {
                'metadata': {
                    'labels': {
                        'app': 'postgresql',
                        'cluster': name
                    }
                },
                'spec': {
                    'containers': [{
                        'name': 'postgresql',
                        'image': f'postgres:{spec.get("postgresVersion", "15")}',
                        'ports': [{'containerPort': 5432}],
                        'env': [
                            {
                                'name': 'POSTGRES_DB',
                                'value': 'app'
                            },
                            {
                                'name': 'POSTGRES_USER',
                                'valueFrom': {
                                    'secretKeyRef': {
                                        'name': f'{name}-postgresql-secret',
                                        'key': 'username'
                                    }
                                }
                            },
                            {
                                'name': 'POSTGRES_PASSWORD',
                                'valueFrom': {
                                    'secretKeyRef': {
                                        'name': f'{name}-postgresql-secret',
                                        'key': 'password'
                                    }
                                }
                            }
                        ],
                        'volumeMounts': [{
                            'name': 'data',
                            'mountPath': '/var/lib/postgresql/data'
                        }]
                    }]
                }
            },
            'volumeClaimTemplates': [{
                'metadata': {'name': 'data'},
                'spec': {
                    'accessModes': ['ReadWriteOnce'],
                    'resources': {
                        'requests': {
                            'storage': spec.get('storage', {}).get('size', '10Gi')
                        }
                    }
                }
            }]
        }
    }
    
    api.create_namespaced_stateful_set(
        namespace=namespace,
        body=statefulset
    )


def create_service(name, namespace, spec):
    """创建Service"""
    api = kubernetes.client.CoreV1Api()
    
    service = {
        'apiVersion': 'v1',
        'kind': 'Service',
        'metadata': {
            'name': f'{name}-postgresql',
            'namespace': namespace,
            'labels': {
                'app': 'postgresql',
                'cluster': name
            }
        },
        'spec': {
            'selector': {
                'app': 'postgresql',
                'cluster': name
            },
            'ports': [{
                'name': 'postgresql',
                'port': 5432,
                'targetPort': 5432
            }]
        }
    }
    
    api.create_namespaced_service(
        namespace=namespace,
        body=service
    )


if __name__ == '__main__':
    kopf.run()

6.1 部署和使用

复制代码
# 部署Operator
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql-operator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgresql-operator
  template:
    metadata:
      labels:
        app: postgresql-operator
    spec:
      serviceAccountName: postgresql-operator
      containers:
      - name: operator
        image: myregistry/postgresql-operator:v1.0.0
        env:
        - name: KUBECONFIG
          value: /root/.kube/config

# 创建PostgreSQL集群
apiVersion: database.example.com/v1alpha1
kind: PostgreSQLCluster
metadata:
  name: my-db-cluster
spec:
  postgresVersion: "15"
  instances: 3
  storage:
    size: "20Gi"
    storageClassName: "fast-ssd"
  backup:
    enabled: true
    schedule: "0 2 * * *"
    retention:
      fullBackups: 7
  resources:
    requests:
      memory: "4Gi"
      cpu: "2"

7. 总结与最佳实践

7.1 开发数据库Operator的关键要点

基于我多年的后端开发经验,总结以下最佳实践:

  1. **设计优先 **:花足够时间设计CRD,确保API简洁、直观、可扩展
  2. **状态管理 **:实现健壮的状态机,处理各种异常情况
  3. **工具集成 **:充分利用成熟工具(Patroni、pgBackRest),避免重复造轮子
  4. **安全考虑 **:从设计之初就考虑安全,使用Secret、RBAC、网络策略
  5. **可观测性 **:内置监控、日志、指标收集,便于运维

7.2 性能优化建议

  1. **连接池 **:集成pgBouncer,管理数据库连接,避免连接风暴
  2. **存储优化 **:根据IOPS需求选择合适的存储类,使用本地SSD提升性能
  3. **内存管理 **:合理配置shared_buffers、work_mem等参数
  4. **查询优化 **:内置慢查询分析,自动识别性能瓶颈

7.3 未来发展趋势

  1. **AI驱动的自治数据库 **:通过机器学习自动优化配置、预测故障
  2. **多云数据库管理 **:统一管理跨云、跨地域的数据库集群
  3. **Serverless数据库 **:基于使用量的自动扩缩容,进一步降低运维成本

8. 结语

开发数据库Operator是一项系统工程,需要深厚的数据库知识和Kubernetes经验。通过本文的实战案例和代码示例,希望能为你在数据库Operator开发道路上提供有价值的参考。

记住,好的Operator不仅在于功能完整,更在于易用性、可靠性和可维护性。在实际项目中,建议从小规模开始,逐步迭代,结合业务需求不断完善。

如果你有任何问题或想分享你的经验,欢迎在评论区交流讨论!

相关推荐
qq_334563552 小时前
html标签怎么表示用户输入_kbd标签键盘快捷键标注【介绍】.txt
jvm·数据库·python
weixin_586061462 小时前
SQL报表星型模型优化_事实表索引设计
jvm·数据库·python
耿雨飞2 小时前
Python 后端开发技术博客专栏 | 第 07 篇 元类与类的创建过程 -- Python 最深层的魔法
开发语言·python
慕涯AI2 小时前
Agent 30 课程开发指南 - 第21课
人工智能·python
qq_12084093712 小时前
Three.js AnimationMixer 工程实战:骨骼动画、剪辑切换与时间缩放
开发语言·javascript·ecmascript
源码之家2 小时前
计算机毕业设计:Python城市天气数据挖掘与预测系统 Flask框架 随机森林 K-Means 可视化 数据分析 大数据 机器学习 深度学习(建议收藏)✅
人工智能·爬虫·python·深度学习·机器学习·数据挖掘·课程设计
Dxy12393102162 小时前
Python在图片上画多边形:从简单轮廓到复杂区域标注
开发语言·python
数智化管理手记2 小时前
零基础认知精益生产——核心本质与必避误区
大数据·数据库·人工智能·低代码·制造
weixin_381288182 小时前
MongoDB备节点无法读取数据怎么解决_rs.slaveOk()与Secondary读取权限
jvm·数据库·python