本文主要讲解一种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高可用实现方案相关推荐

  1. postgresql 重启记录_PostgreSQL 高可用:PostgreSQL复制和自动故障转移

    原文:PostgreSQL Replication and Automatic Failover Tutorial[1] 作者:Abbas Butt 翻译整理:alitrack 1.什么是 Postg ...

  2. PostgreSQL HA集群高可用方案介绍 pgpool-II+PostgreSQL HA方案部署

    PostgreSQL HA集群高可用方案介绍 & pgpool-II+PostgreSQL HA方案部署 一.PostgreSQL HA集群高可用方案介绍 二.pgpool-II+Postgr ...

  3. LVS+keepalived 实现高可用与负载均衡实施方案

    LVS+keepalived 实现高可用与负载均衡实施方案 <Alvin-zeng:孤独0-1> 目录 一.安装LVS1 1.1.环境IP描述:1 1.2.所需软件2 1.3.安装步骤主备 ...

  4. 高可用系统架构设计 技术方案

    背景 可靠的系统是业务稳定.快速发展的基石. 那么,如何做到系统高可靠.高可用呢? 高可用方法论 下面的表格里,列出了高可用常见的问题和应对措施. 可扩展 扩展是最常见的提升系统可靠性的方法,系统的扩 ...

  5. mysql 升级高可用_MySQL高可用方案升级规划

    这是学习笔记的第2035篇文章 这两天在梳理MySQL高可用方案的升级计划,发现要做的事情还真不少. 我们目前有新系统和老系统,老系统因为历史原因使用的是MySQL 5.5版本,新系统有了整体的规划, ...

  6. SAP云上自适应跨可用区高可用方案

      SAP云上跨可用区高可用架构现状 在SAP云上官方架构设计指引中,展示了如何利用其多可用区的技术优势.诸如Route 53.NLB等基础设施服务以及SAP或操作系统的高可用技术实现端到端的跨可用区 ...

  7. clickhouse高可用-节点宕机数据一致性方案-热扩容

    clickhouse高可用-节点宕机数据一致性方案-热扩容 1. 集群节点及服务分配 说明: 1.1. 在每个节点上启动两个clickhouse服务(后面会详细介绍如何操作这一步),一个数据分片,一个 ...

  8. mysql性能调优 高可用_MySQL性能调优与架构设计——第 17 章 高可用设计之思路及方案...

    第 17 章 高可用设计之思路及方案 前言: 数据库系统是一个应用系统的核心部分,要想系统整体可用性得到保证,数据库系统就不能出现任何问题.对于一个企业级的系统来说,数据库系统的可用性尤为重要.数据库 ...

  9. mysql高可用cmha_mysql高可用方案

    A.普通的主从复制――――客户端通过master对数据库进行读/写操作,Slave端作为备机,可用来进行一些查询,备份等操作. 优点:部署简单,易于扩展,能提供一定的数据保护. 缺点:如果master ...

最新文章

  1. Android简单手势滑动的识别
  2. IDEA 一直不停的scanning files to index解决办法
  3. js基础教程学习笔记
  4. 为基于spring-boot的应用添加根据运行时操作系统环境来提示用户选择active profile的功能...
  5. android4.0教程,图文教程现身,在Win7等系统里跑起Android4.0
  6. ORACLE RAC 手动建库
  7. 青鸟云课堂_青鸟云课堂
  8. tcpdump进行IP抓包
  9. Android编译内核并刷入
  10. python扇贝每日一句api_【扇贝批量添加单词到词库】利用python调用扇贝API (oauth2)...
  11. 用于自动驾驶的实时联合目标检测和语义分割网络
  12. 制作论坛发帖页面(操作节点的方式)(JavaScript)
  13. 汇编语言学习-DOSBox-MASM-安装及使用教程
  14. 【数据库】聊一下数据库的锁机制
  15. 美国亚马逊图片打不开
  16. Mac常用解压缩软件是哪个?
  17. 深入理解pdf.js,PDFObject, iframe 三种方式来打开PDF文件的区别
  18. gitlab 私有化管理npm包
  19. 手把手教你提交Jar包到Maven公共仓库
  20. 一图读懂丨「云信派对」一站式娱乐社交解决方案

热门文章

  1. Comprehensive anticancer drug response prediction based on a simple cell line drug complex network m
  2. 在神经网络中使用dropout
  3. java的connect和http_java发起HttpURLConnection和HttpsURLConnection请求 | 学步园
  4. 98年建模a题论文_2019年第九届APMCM亚太地区大学生数学建模竞赛 A 题(中英版)...
  5. java bufferedwrite_Java BufferedWriter BufferedReader 源码分析
  6. 2020-10-29Ubuntu20.04将软件添加至桌面
  7. php显示网卡信息,linux如何查看网卡信息
  8. 怎么在手机上下载python模块_python下载模块然后怎么安装
  9. 高效办公,如何利用Python自动发送邮件
  10. tof摄像头手势识别_行业深度光学行业研究:CIS、光学元件、指纹识别、镜头模组...