postgresql高可用_Postgresql高可用实现方案
本文主要讲解一种Postgresql高可用实现方案。由于项目需要,我们需要在短时间实现底层Postgresql DB的高可用,而网络上大多数的解决方案比较复杂,需要深入了解Postgresql。
背景
我们原先只是使用Postgresql DB来存放一些kong的配置信息,作为单实例以k8s pod的形式进行部署使用。这样,在项目中postgresql DB就存在单点故障的隐患。为了解决这个问题,我们需要实现高可用的Postgresql。本文中,利用PostDock来实现master-slave架构的Postgresql高可用集群。
下面是实现后的架构图。
PostDock
PostDock项目的代码参见GitHub项目PostDock,该项目在云和docker环境中提供高可用和自愈作用的Postgresql集群。
项目中使用PostDock提供的k8s中的样例example2-single-statefulset并对其进行了简化。简化内容如下:
- 使用hostPath来替换样例中的PVC;
- 去除样例中的configMap和secret,将其直接配置在SatatefulSet和Deployment中;
- 将所有k8s资源创建在default namespace,不单独创建其他namespace;
简化后的PostDock样例包含2个service,一个具有3个Pod的Postgresql StatefulSet和一个具有2个Pod的Pgpool Deployment。postgresql service为headless service,对应一个postgresql statefuleset,其中包含3个Postgresql Pod,1个master DB,2个slave DB; pgpool service对应一个pgpool deploy,其中包含2个pgpool pod。
-Postgresql Service
apiVersion: v1
kind: Service
metadata:namespace: defaultname: postgresql-db-servicelabels:name: databasesystem: postgresql
spec:clusterIP: Noneports:- port: 5432targetPort: 5432selector:name: databasesystem: postgresql
-Postgresql SatetfulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:namespace: defaultname: postgresql-db-nodelabels:name: databasesystem: postgresqlapp: postgresql
spec:replicas: 3serviceName: "postgresql-db-service"selector:matchLabels:name: databasesystem: postgresqltemplate:metadata:labels:name: databasesystem: postgresqlspec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchLabels:name: databasesystem: postgresqlnamespaces:- defaulttopologyKey: kubernetes.io/hostnamecontainers:- name: db-nodeimage: postdock/postgres:latest-postgres11-repmgr40livenessProbe:exec:command: ['bash', '-c', '/usr/local/bin/cluster/healthcheck/is_major_master.sh']initialDelaySeconds: 600timeoutSeconds: 10periodSeconds: 30successThreshold: 1failureThreshold: 3imagePullPolicy: Alwaysresources:requests:memory: "100Mi"cpu: "100m"env:- name: MY_POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name- name: "REPMGR_WAIT_POSTGRES_START_TIMEOUT"value: "600"- name: "REPLICATION_PRIMARY_HOST"value: "postgresql-db-node-0.postgresql-db-service"- name: "PARTNER_NODES"value: "postgresql-db-node-0.postgresql-db-service,postgresql-db-node-1.postgresql-db-service,postgresql-db-node-2.postgresql-db-service"- name: "NODE_NAME"value: "$(MY_POD_NAME)"- name: "CLUSTER_NODE_NETWORK_NAME"value: "$(MY_POD_NAME).postgresql-db-service"- name: "CONFIGS"value: "wal_keep_segments:250,shared_buffers:300MB,archive_command:'/bin/true'"- name: "POSTGRES_DB"value: "postgresql.postgresql"- name: "POSTGRES_USER"value: "wide"- name: "POSTGRES_PASSWORD"value: "pass"- name: "CLUSTER_NAME"value: "postgresql_cluster"- name: "REPLICATION_DB"value: "rain"- name: "REPLICATION_USER"value: "rain"- name: "REPLICATION_PASSWORD"value: "passwd"ports:- containerPort: 5432volumeMounts:- name: postgresql-datamountPath: /var/lib/postgresql/datavolumes:- hostPath:path: /var/project_deps/postgresql-datatype: ""name: postgresql-data
-pgpool service
apiVersion: v1
kind: Service
metadata:namespace: defaultname: postgresql-pgpool-servicelabels:name: database-balancernode: pgpoolsystem: postgresql
spec:ports:- port: 5432targetPort: 5432selector:name: database-balancernode: pgpoolsystem: postgresql
-pgpool Deployment
apiVersion: apps/v1
kind: Deployment
metadata:namespace: defaultname: postgresql-database-pgpoollabels:name: database-balancernode: pgpoolsystem: postgresqlapp: postgresql
spec:replicas: 2revisionHistoryLimit: 5selector:matchLabels:name: database-balancernode: pgpoolsystem: postgresqlapp: postgresqltemplate:metadata:name: database-pgpoollabels:name: database-balancernode: pgpoolsystem: postgresqlapp: postgresqlspec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchLabels:name: database-balancernode: pgpoolsystem: postgresqlapp: postgresqlnamespaces:- defaulttopologyKey: kubernetes.io/hostnamecontainers:- name: database-pgpoolimage: postdock/pgpool:latest-pgpool37-postgres11livenessProbe:exec:command: ['bash', '-c', '/usr/local/bin/pgpool/has_write_node.sh && /usr/local/bin/pgpool/has_enough_backends.sh']initialDelaySeconds: 600timeoutSeconds: 10periodSeconds: 30successThreshold: 1failureThreshold: 3imagePullPolicy: Alwaysresources:requests:memory: "100Mi"cpu: "100m"ports:- containerPort: 5432env:- name: "CONFIGS"value: "num_init_children:60,max_pool:4,client_idle_limit:900,connection_life_time:300"- name: "PCP_USER"value: "pcp_user"- name: "PCP_PASSWORD"value: "pcp_pass"- name: "CHECK_USER"value: "rain"- name: "CHECK_PASSWORD"value: "passwd"- name: "DB_USERS"value: "wide:pass"- name: "BACKENDS"value: "0:postgresql-db-node-0.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER,1:postgresql-db-node-1.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER,2:postgresql-db-node-2.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER"
将上面的yaml文件使用kubectl命令执行后生成像一个的k8s resource。
kubectl apply -f *.yaml
连接Postgresql高可用集群
创建高可用集群后,我们需要验证高可用集群的可用性,即在master, slave DB挂掉后,Postgresql集群是否能正确竞选出新的master DB并继续提供DB服务。
-安装Postgresql client
为了验证Postgresql高可用集群,我们需要赞centos7上安装postgresql client来连接DB。
centos7上安装postgresql: 详情参见安装Postgresql。注意:需要安装相同版本的Postgresql Client才能访问DB。
yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm -y
yum install postgresql11-server -y
注意:若安装过程中出现证书问题,请检查机器的系统时间date,因为证书有时效性,系统时间不对可能导致证书验证不通过。关于如何修改和同步集群上的时间,请参考之前的文章服务集群时间同步 - Chrony。
卸载Postgresql DB,使用下面的命令。
yum remove postgresql*
-获取DB及Service信息
为了访问创建的Postgresql集群,需要获得我们之前创建的k8s资源信息。访问Postgresql集群,可以通过Service postgresql-pgpool-service来访问,也可以直接访问postgresql-db-node pod来访问,只是直接访问pod会因为访问的不是master DB时而不能执行写操作。
kubectl get svc | grep postgresql
postgresql-db-service-sophondeps2 ClusterIP None <none> 5432/TCP 20h
postgresql-pgpool-service-sophondeps2 ClusterIP 10.10.10.129 <none> 5432/TCP 20h$ kubectl get po -o wide | grep postgresql
postgresql-database-pgpool-sophondeps2-5b4945fd77-qbj8x 1/1 Running 0 20h 10.11.24.17 node43
postgresql-database-pgpool-sophondeps2-5b4945fd77-xbhm6 1/1 Running 0 20h 10.11.73.18 node44
postgresql-db-node-sophondeps2-0 1/1 Running 0 20h 10.11.50.15 node45
postgresql-db-node-sophondeps2-1 1/1 Running 0 20h 10.11.24.18 node43
postgresql-db-node-sophondeps2-2 1/1 Running 0 12s 10.11.73.20 node44
通过上面信息,我们可以通过下面命令来连接Postgresql集群。在输入密码passwd后以user rain的身份连接到DB rain。当然,也可以通过pod的IP作为这里的host来分别访问集群下的指定DB,master DB和slave DB,只是访问slave DB时不能执行写操作。
$ psql -h 10.10.10.129 -p 5432 -U rain rain
用户 sophon 的口令:passwd
注意:Postgresql DB通常都会创建user postgres及其相应DB,有时需要使用下面命令切换到该用户来连接DB。
sudo -i -u postgres
创建Postgresql cluster后,我们需要连接DB后执行下面的命令来获得高可用cluster的架构信息。由下面的信息可知,3个Postgresql Pod正在执行,其中postgresql-db-node-sophondeps2-0 为master DB,其他2个为slave DB。
查看master-slave关系:
sophon=# select * from repmgr.nodes;node_id | upstream_node_id | active | node_name | type | location | priority | conninfo | repluser | slot_name | config_file
---------+------------------+--------+----------------------------------+---------+----------+----------+---------------------------------------------------------------------------------------------------
--------------------------------------------+----------+------------------+------------------1000 | | t | postgresql-db-node-sophondeps2-0 | primary | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-0.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1000 | /etc/repmgr.conf1001 | 1000 | t | postgresql-db-node-sophondeps2-1 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-1.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1001 | /etc/repmgr.conf1002 | 1000 | t | postgresql-db-node-sophondeps2-2 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-2.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1002 | /etc/repmgr.conf
(3 行记录)查看relica db的情况:只在master上可以查看
select * from pg_stat_replication;
sophon=# select * from pg_stat_replication;pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn |
replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state
-----+----------+---------+----------------------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+-
-----------+-----------+-----------+------------+---------------+------------170 | 16386 | sophon | postgresql-db-node-sophondeps2-1 | 10.11.24.18 | | 56320 | 2020-07-09 11:40:19.057691+00 | | streaming | 0/4178EC0 | 0/4178EC0 | 0/4178EC0 |
0/4178EC0 | | | | 0 | async177 | 16386 | sophon | postgresql-db-node-sophondeps2-2 | 10.11.73.19 | | 42984 | 2020-07-09 11:40:21.062225+00 | | streaming | 0/4178EC0 | 0/4178EC0 | 0/4178EC0 |
0/4178EC0 | | | | 0 | async
(2 行记录)master启动正常的情况下查看:
show pool_nodes;
sophon=# show pool_nodes;node_id | hostname | port | status | lb_weight | role | select_cnt | load_balance_node | replication_delay
---------+--------------------------------------------------------------------+------+--------+-----------+---------+------------+-------------------+-------------------0 | postgresql-db-node-sophondeps2-0.postgresql-db-service-sophondeps2 | 5432 | up | 0.333333 | primary | 4872 | false | 01 | postgresql-db-node-sophondeps2-1.postgresql-db-service-sophondeps2 | 5432 | up | 0.333333 | standby | 7327 | false | 02 | postgresql-db-node-sophondeps2-2.postgresql-db-service-sophondeps2 | 5432 | up | 0.333333 | standby | 7188 | true | 0
(3 行记录)
-使用DB
在master-slave架构中,master负责读写,而slave只负责读取。下面时操作Postgresql DB的一些常规操作,SQL跟其他DB一样。
查看所有DB:
list查看当前连接的DB信息:
conninfo切换当前DB到DBNAME:
c DBNAME查看DB中table:
d查看表结构,相当于desc tblname,show columns from tbname
d tblname
di 查看索引 退出 q
验证Postgresql Cluster高可用
高可用验证当master DB或slave DB挂掉时,cluster仍然能够继续使用。
-slave DB挂掉
我们使用下面命令来停止slave DB. 我们发现slave DB所在Pod挂掉后,马上会被重新创建(k8s中statefulset中的pod会被重建),新创建的Pod具有相同的名称,并被作为slave DB重新添加进cluster。而且,在slave DB挂掉重启的整个过程,Postgresql cluster仍然能够正常访问。
kubectl delete po postgresql-db-node-sophondeps2-2
-master DB挂掉
我们使用下面命令来停止master DB.
kubectl delete po postgresql-db-node-sophondeps2-0
master DB挂掉后,Postgresql Cluster会在剩下的2个slave DB中选取master DB, 新master DB来负责读写,而且pgpool service会自动使用新master。旧master DB会因为重启失败进入Error状态,不会重新回到Postgresql Cluster。
此时,Postgresql cluster的架构如下。发现postgresql-db-node-0为down状态,cluster只有2个DB,其中postgresql-db-node-1为master DB,而postgresql-db-node-2为slave DB.
sophon=# select * from repmgr.nodes;node_id | upstream_node_id | active | node_name | type | location | priority | conninfo | repluser | slot_name | config_file
---------+------------------+--------+----------------------+---------+----------+----------+---------------------------------------------------------------------------------------------------------------
--------+----------+------------------+------------------1001 | | t | postgresql-db-node-1 | primary | default | 100 | user=sophon password=passwd host=postgresql-db-node-1.postgresql-db-service dbname=sophon port=5432 connect_ti
meout=2 | sophon | repmgr_slot_1001 | /etc/repmgr.conf1002 | 1001 | t | postgresql-db-node-2 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-2.postgresql-db-service dbname=sophon port=5432 connect_ti
meout=2 | sophon | repmgr_slot_1002 | /etc/repmgr.confsophon=# select * from pg_stat_replication;pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_ls
n | write_lag | flush_lag | replay_lag | sync_priority | sync_state
-----+----------+---------+----------------------+----------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+----------
--+-----------+-----------+------------+---------------+------------682 | 16386 | sophon | postgresql-db-node-2 | 100.111.40.224 | | 57974 | 2020-06-23 08:45:59.437912+00 | | streaming | 0/50415E0 | 0/50415E0 | 0/50415E0 | 0/50415E0| | | | 0 | asyncsophon=# show pool_nodes;node_id | hostname | port | status | lb_weight | role | select_cnt | load_balance_node | replication_delay
---------+--------------------------------------------+------+--------+-----------+---------+------------+-------------------+-------------------0 | postgresql-db-node-0.postgresql-db-service | 5432 | down | 0.333333 | standby | 3 | false | 01 | postgresql-db-node-1.postgresql-db-service | 5432 | up | 0.333333 | primary | 6 | true | 02 | postgresql-db-node-2.postgresql-db-service | 5432 | up | 0.333333 | standby | 3 | false | 0
-将旧master DB重新添加进cluster
将旧master DB重新添加进cluster需要人工干预。干预过程如下:
- 将另一个slave所在节点的/var/project-deps/postgresql-data中所有文件使用ssh拷贝到旧master DB所在host对应的位置(先删除原来所有文件)。
下面命令在另一个slave DB所在host上执行,其中10.11.50.15为旧master DB所在host的IP。
$ cd /var/project_deps/postgresql-data/
$ scp -r ./* root@10.11.50.15:/var/project_deps/postgresql-data/
- 修改拷贝过来的recovery.conf文件
Master DB下recovery.conf会被修改为recovery.done,recovery.conf文件只出现在slave DB中。将该文件中的primary_slot_name配置删除,并修改primary_conninfo中 的application_name=''postgresql-db-node-0'' 为合适的node name。
修改完,重启该节点的postgresql pod即可。
- 验证旧master DB作为slave DB添加进cluster
连接新的master DB并执行下面的命令来验证旧master DB已经添加进集群。可知,postgresql-db-node-sophondeps2-0作为slave DB重新添加进cluster。
psql -h 10.11.24.18 -p 5432 -U rain rain 连接主node
sophon=# select * from pg_stat_replication;pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn
| replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state
-------+----------+---------+----------------------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------
+------------+-----------+-----------+------------+---------------+------------19046 | 16386 | sophon | postgresql-db-node-sophondeps2-0 | 10.11.50.12 | | 50384 | 2020-07-08 07:34:06.353302+00 | 630 | streaming | 0/50480D0 | 0/50480D0 | 0/50480D0
| 0/50480D0 | | | | 0 | async13678 | 16386 | sophon | postgresql-db-node-sophondeps2-2 | 10.11.24.12 | | 38052 | 2020-07-08 07:07:40.862402+00 | | streaming | 0/50480D0 | 0/50480D0 | 0/50480D0
| 0/50480D0 | | | | 0 | async
(2 行记录)sophon=# select * from repmgr.nodes;node_id | upstream_node_id | active | node_name | type | location | priority | conninfo | repluser | slot_name | config_file
---------+------------------+--------+----------------------------------+---------+----------+----------+---------------------------------------------------------------------------------------------------
--------------------------------------------+----------+------------------+------------------1001 | | t | postgresql-db-node-sophondeps2-1 | primary | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-1.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1001 | /etc/repmgr.conf1002 | 1001 | t | postgresql-db-node-sophondeps2-2 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-2.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1002 | /etc/repmgr.conf1000 | 1001 | t | postgresql-db-node-sophondeps2-0 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-0.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1000 | /etc/repmgr.conf
(3 行记录)
数据迁移
高可用的Postgresql cluster创建好之后,我们需要将之前的单个postgresql DB中的老数据迁移到新创建的Postgresql Cluster中。
数据迁移使用pg_dump和psql命令来完成。使用pg_dump命令来将旧数据库中的老数据dump到文件中,然后在新数据库中使用psql来导入老数据。
注意:pg_dump需要使用相同版本,否则不能dump。同时,数据导入应该使用postgresql cluster的master DB进行导入,slave DB不能执行写操作。
-老数据库的数据导出
下面示例,连接到老数据库并导出数据库rain中的所有数据到文件rain.sql中。
$ pg_dump "host=100.120.8.201 port=5432 user=rain password=passwd dbname=rain" -f rain.sql
-新数据库导入数据
我们需要连接新数据库并导入所有数据。
$ psql -h 10.96.91.35 -p 5432 -U rain rain < rain.sql
至此,我们完成了Postgresql DB的高可用架构并完成老数据迁移。
postgresql高可用_Postgresql高可用实现方案相关推荐
- postgresql 重启记录_PostgreSQL 高可用:PostgreSQL复制和自动故障转移
原文:PostgreSQL Replication and Automatic Failover Tutorial[1] 作者:Abbas Butt 翻译整理:alitrack 1.什么是 Postg ...
- PostgreSQL HA集群高可用方案介绍 pgpool-II+PostgreSQL HA方案部署
PostgreSQL HA集群高可用方案介绍 & pgpool-II+PostgreSQL HA方案部署 一.PostgreSQL HA集群高可用方案介绍 二.pgpool-II+Postgr ...
- LVS+keepalived 实现高可用与负载均衡实施方案
LVS+keepalived 实现高可用与负载均衡实施方案 <Alvin-zeng:孤独0-1> 目录 一.安装LVS1 1.1.环境IP描述:1 1.2.所需软件2 1.3.安装步骤主备 ...
- 高可用系统架构设计 技术方案
背景 可靠的系统是业务稳定.快速发展的基石. 那么,如何做到系统高可靠.高可用呢? 高可用方法论 下面的表格里,列出了高可用常见的问题和应对措施. 可扩展 扩展是最常见的提升系统可靠性的方法,系统的扩 ...
- mysql 升级高可用_MySQL高可用方案升级规划
这是学习笔记的第2035篇文章 这两天在梳理MySQL高可用方案的升级计划,发现要做的事情还真不少. 我们目前有新系统和老系统,老系统因为历史原因使用的是MySQL 5.5版本,新系统有了整体的规划, ...
- SAP云上自适应跨可用区高可用方案
SAP云上跨可用区高可用架构现状 在SAP云上官方架构设计指引中,展示了如何利用其多可用区的技术优势.诸如Route 53.NLB等基础设施服务以及SAP或操作系统的高可用技术实现端到端的跨可用区 ...
- clickhouse高可用-节点宕机数据一致性方案-热扩容
clickhouse高可用-节点宕机数据一致性方案-热扩容 1. 集群节点及服务分配 说明: 1.1. 在每个节点上启动两个clickhouse服务(后面会详细介绍如何操作这一步),一个数据分片,一个 ...
- mysql性能调优 高可用_MySQL性能调优与架构设计——第 17 章 高可用设计之思路及方案...
第 17 章 高可用设计之思路及方案 前言: 数据库系统是一个应用系统的核心部分,要想系统整体可用性得到保证,数据库系统就不能出现任何问题.对于一个企业级的系统来说,数据库系统的可用性尤为重要.数据库 ...
- mysql高可用cmha_mysql高可用方案
A.普通的主从复制――――客户端通过master对数据库进行读/写操作,Slave端作为备机,可用来进行一些查询,备份等操作. 优点:部署简单,易于扩展,能提供一定的数据保护. 缺点:如果master ...
最新文章
- Android简单手势滑动的识别
- IDEA 一直不停的scanning files to index解决办法
- js基础教程学习笔记
- 为基于spring-boot的应用添加根据运行时操作系统环境来提示用户选择active profile的功能...
- android4.0教程,图文教程现身,在Win7等系统里跑起Android4.0
- ORACLE RAC 手动建库
- 青鸟云课堂_青鸟云课堂
- tcpdump进行IP抓包
- Android编译内核并刷入
- python扇贝每日一句api_【扇贝批量添加单词到词库】利用python调用扇贝API (oauth2)...
- 用于自动驾驶的实时联合目标检测和语义分割网络
- 制作论坛发帖页面(操作节点的方式)(JavaScript)
- 汇编语言学习-DOSBox-MASM-安装及使用教程
- 【数据库】聊一下数据库的锁机制
- 美国亚马逊图片打不开
- Mac常用解压缩软件是哪个?
- 深入理解pdf.js,PDFObject, iframe 三种方式来打开PDF文件的区别
- gitlab 私有化管理npm包
- 手把手教你提交Jar包到Maven公共仓库
- 一图读懂丨「云信派对」一站式娱乐社交解决方案
热门文章
- Comprehensive anticancer drug response prediction based on a simple cell line drug complex network m
- 在神经网络中使用dropout
- java的connect和http_java发起HttpURLConnection和HttpsURLConnection请求 | 学步园
- 98年建模a题论文_2019年第九届APMCM亚太地区大学生数学建模竞赛 A 题(中英版)...
- java bufferedwrite_Java BufferedWriter BufferedReader 源码分析
- 2020-10-29Ubuntu20.04将软件添加至桌面
- php显示网卡信息,linux如何查看网卡信息
- 怎么在手机上下载python模块_python下载模块然后怎么安装
- 高效办公,如何利用Python自动发送邮件
- tof摄像头手势识别_行业深度光学行业研究:CIS、光学元件、指纹识别、镜头模组...