Chemmy's Blog

chengming0916@outlook.com

一、下载链接https://portal.influxdata.com/downloads,选windows版

二、解压到安装盘,目录如下

三、修改conf文件,代码如下,直接复制粘贴(1.4.2版本),注意修改路径,带D盘的改为你的安装路径就好,一共三个,注意网上有配置admin进行web管理,但新版本配置文件里没有admin因为官方给删除了,需下载Chronograf,后文会介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
### Welcome to the InfluxDB configuration file.

# The values in this file override the default values used by the system if
# a config option is not specified. The commented out lines are the configuration
# field and the default value used. Uncommenting a line and changing the value
# will change the value used at runtime when the process is restarted.

# Once every 24 hours InfluxDB will report usage data to usage.influxdata.com
# The data includes a random ID, os, arch, version, the number of series and other
# usage data. No data from user databases is ever transmitted.
# Change this option to true to disable reporting.
# reporting-disabled = false

# Bind address to use for the RPC service for backup and restore.
# bind-address = "127.0.0.1:8088"

###
### [meta]
###
### Controls the parameters for the Raft consensus group that stores metadata
### about the InfluxDB cluster.
###

[meta]
# Where the metadata/raft database is stored
dir = "D:/influxdb-1.4.2-1/meta"

# Automatically create a default retention policy when creating a database.
retention-autocreate = true

# If log messages are printed for the meta service
logging-enabled = true

###
### [data]
###
### Controls where the actual shard data for InfluxDB lives and how it is
### flushed from the WAL. "dir" may need to be changed to a suitable place
### for your system, but the WAL settings are an advanced configuration. The
### defaults should work for most systems.
###

[data]
# The directory where the TSM storage engine stores TSM files.
dir = "D:/influxdb-1.4.2-1/data"

# The directory where the TSM storage engine stores WAL files.
wal-dir = "D:/influxdb-1.4.2-1/wal"

# The amount of time that a write will wait before fsyncing. A duration
# greater than 0 can be used to batch up multiple fsync calls. This is useful for slower
# disks or when WAL write contention is seen. A value of 0s fsyncs every write to the WAL.
# Values in the range of 0-100ms are recommended for non-SSD disks.
# wal-fsync-delay = "0s"


# The type of shard index to use for new shards. The default is an in-memory index that is
# recreated at startup. A value of "tsi1" will use a disk based index that supports higher
# cardinality datasets.
# index-version = "inmem"

# Trace logging provides more verbose output around the tsm engine. Turning
# this on can provide more useful output for debugging tsm engine issues.
# trace-logging-enabled = false

# Whether queries should be logged before execution. Very useful for troubleshooting, but will
# log any sensitive data contained within a query.
query-log-enabled = true

# Settings for the TSM engine

# CacheMaxMemorySize is the maximum size a shard's cache can
# reach before it starts rejecting writes.
# Valid size suffixes are k, m, or g (case insensitive, 1024 = 1k).
# Vaues without a size suffix are in bytes.
# cache-max-memory-size = "1g"

# CacheSnapshotMemorySize is the size at which the engine will
# snapshot the cache and write it to a TSM file, freeing up memory
# Valid size suffixes are k, m, or g (case insensitive, 1024 = 1k).
# Values without a size suffix are in bytes.
# cache-snapshot-memory-size = "25m"

# CacheSnapshotWriteColdDuration is the length of time at
# which the engine will snapshot the cache and write it to
# a new TSM file if the shard hasn't received writes or deletes
# cache-snapshot-write-cold-duration = "10m"

# CompactFullWriteColdDuration is the duration at which the engine
# will compact all TSM files in a shard if it hasn't received a
# write or delete
# compact-full-write-cold-duration = "4h"

# The maximum number of concurrent full and level compactions that can run at one time. A
# value of 0 results in 50% of runtime.GOMAXPROCS(0) used at runtime. Any number greater
# than 0 limits compactions to that value. This setting does not apply
# to cache snapshotting.
# max-concurrent-compactions = 0

# The maximum series allowed per database before writes are dropped. This limit can prevent
# high cardinality issues at the database level. This limit can be disabled by setting it to
# 0.
# max-series-per-database = 1000000

# The maximum number of tag values per tag that are allowed before writes are dropped. This limit
# can prevent high cardinality tag values from being written to a measurement. This limit can be
# disabled by setting it to 0.
# max-values-per-tag = 100000

###
### [coordinator]
###
### Controls the clustering service configuration.
###

[coordinator]
# The default time a write request will wait until a "timeout" error is returned to the caller.
# write-timeout = "10s"

# The maximum number of concurrent queries allowed to be executing at one time. If a query is
# executed and exceeds this limit, an error is returned to the caller. This limit can be disabled
# by setting it to 0.
# max-concurrent-queries = 0

# The maximum time a query will is allowed to execute before being killed by the system. This limit
# can help prevent run away queries. Setting the value to 0 disables the limit.
# query-timeout = "0s"

# The time threshold when a query will be logged as a slow query. This limit can be set to help
# discover slow or resource intensive queries. Setting the value to 0 disables the slow query logging.
# log-queries-after = "0s"

# The maximum number of points a SELECT can process. A value of 0 will make
# the maximum point count unlimited. This will only be checked every second so queries will not
# be aborted immediately when hitting the limit.
# max-select-point = 0

# The maximum number of series a SELECT can run. A value of 0 will make the maximum series
# count unlimited.
# max-select-series = 0

# The maxium number of group by time bucket a SELECT can create. A value of zero will max the maximum
# number of buckets unlimited.
# max-select-buckets = 0

###
### [retention]
###
### Controls the enforcement of retention policies for evicting old data.
###

[retention]
# Determines whether retention policy enforcement enabled.
enabled = true

# The interval of time when retention policy enforcement checks run.
check-interval = "30m"

###
### [shard-precreation]
###
### Controls the precreation of shards, so they are available before data arrives.
### Only shards that, after creation, will have both a start- and end-time in the
### future, will ever be created. Shards are never precreated that would be wholly
### or partially in the past.

[shard-precreation]
# Determines whether shard pre-creation service is enabled.
enabled = true

# The interval of time when the check to pre-create new shards runs.
check-interval = "10m"

# The default period ahead of the endtime of a shard group that its successor
# group is created.
advance-period = "30m"

###
### Controls the system self-monitoring, statistics and diagnostics.
###
### The internal database for monitoring data is created automatically if
### if it does not already exist. The target retention within this database
### is called 'monitor' and is also created with a retention period of 7 days
### and a replication factor of 1, if it does not exist. In all cases the
### this retention policy is configured as the default for the database.

[monitor]
# Whether to record statistics internally.
store-enabled = true

# The destination database for recorded statistics
store-database = "_internal"

# The interval at which to record statistics
store-interval = "10s"

###
### [http]
###
### Controls how the HTTP endpoints are configured. These are the primary
### mechanism for getting data into and out of InfluxDB.
###

[http]
# Determines whether HTTP endpoint is enabled.
enabled = true

# The bind address used by the HTTP service.
bind-address = ":8086"

# Determines whether user authentication is enabled over HTTP/HTTPS.
# auth-enabled = false

# The default realm sent back when issuing a basic auth challenge.
# realm = "InfluxDB"

# Determines whether HTTP request logging is enabled.
# log-enabled = true

# Determines whether detailed write logging is enabled.
# write-tracing = false

# Determines whether the pprof endpoint is enabled. This endpoint is used for
# troubleshooting and monitoring.
# pprof-enabled = true

# Determines whether HTTPS is enabled.
# https-enabled = false

# The SSL certificate to use when HTTPS is enabled.
# https-certificate = "/etc/ssl/influxdb.pem"

# Use a separate private key location.
# https-private-key = ""

# The JWT auth shared secret to validate requests using JSON web tokens.
# shared-secret = ""

# The default chunk size for result sets that should be chunked.
# max-row-limit = 0

# The maximum number of HTTP connections that may be open at once. New connections that
# would exceed this limit are dropped. Setting this value to 0 disables the limit.
# max-connection-limit = 0

# Enable http service over unix domain socket
# unix-socket-enabled = false

# The path of the unix domain socket.
# bind-socket = "/var/run/influxdb.sock"

# The maximum size of a client request body, in bytes. Setting this value to 0 disables the limit.
# max-body-size = 25000000


###
### [ifql]
###
### Configures the ifql RPC API.
###

[ifql]
# Determines whether the RPC service is enabled.
# enabled = true

# Determines whether additional logging is enabled.
# log-enabled = true

# The bind address used by the ifql RPC service.
# bind-address = ":8082"


###
### [subscriber]
###
### Controls the subscriptions, which can be used to fork a copy of all data
### received by the InfluxDB host.
###

[subscriber]
# Determines whether the subscriber service is enabled.
# enabled = true

# The default timeout for HTTP writes to subscribers.
# http-timeout = "30s"

# Allows insecure HTTPS connections to subscribers. This is useful when testing with self-
# signed certificates.
# insecure-skip-verify = false

# The path to the PEM encoded CA certs file. If the empty string, the default system certs will be used
# ca-certs = ""

# The number of writer goroutines processing the write channel.
# write-concurrency = 40

# The number of in-flight writes buffered in the write channel.
# write-buffer-size = 1000


###
### [[graphite]]
###
### Controls one or many listeners for Graphite data.
###

[[graphite]]
# Determines whether the graphite endpoint is enabled.
# enabled = false
# database = "graphite"
# retention-policy = ""
# bind-address = ":2003"
# protocol = "tcp"
# consistency-level = "one"

# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.

# Flush if this many points get buffered
# batch-size = 5000

# number of batches that may be pending in memory
# batch-pending = 10

# Flush at least this often even if we haven't hit buffer limit
# batch-timeout = "1s"

# UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
# udp-read-buffer = 0

### This string joins multiple matching 'measurement' values providing more control over the final measurement name.
# separator = "."

### Default tags that will be added to all metrics. These can be overridden at the template level
### or by tags extracted from metric
# tags = ["region=us-east", "zone=1c"]

### Each template line requires a template pattern. It can have an optional
### filter before the template and separated by spaces. It can also have optional extra
### tags following the template. Multiple tags should be separated by commas and no spaces
### similar to the line protocol format. There can be only one default template.
# templates = [
# "*.app env.service.resource.measurement",
# # Default template
# "server.*",
# ]

###
### [collectd]
###
### Controls one or many listeners for collectd data.
###

[[collectd]]
# enabled = false
# bind-address = ":25826"
# database = "collectd"
# retention-policy = ""
#
# The collectd service supports either scanning a directory for multiple types
# db files, or specifying a single db file.
# typesdb = "/usr/local/share/collectd"
#
# security-level = "none"
# auth-file = "/etc/collectd/auth_file"

# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.

# Flush if this many points get buffered
# batch-size = 5000

# Number of batches that may be pending in memory
# batch-pending = 10

# Flush at least this often even if we haven't hit buffer limit
# batch-timeout = "10s"

# UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
# read-buffer = 0

# Multi-value plugins can be handled two ways.
# "split" will parse and store the multi-value plugin data into separate measurements
# "join" will parse and store the multi-value plugin as a single multi-value measurement.
# "split" is the default behavior for backward compatability with previous versions of influxdb.
# parse-multivalue-plugin = "split"
###
### [opentsdb]
###
### Controls one or many listeners for OpenTSDB data.
###

[[opentsdb]]
# enabled = false
# bind-address = ":4242"
# database = "opentsdb"
# retention-policy = ""
# consistency-level = "one"
# tls-enabled = false
# certificate= "/etc/ssl/influxdb.pem"

# Log an error for every malformed point.
# log-point-errors = true

# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Only points
# metrics received over the telnet protocol undergo batching.

# Flush if this many points get buffered
# batch-size = 1000

# Number of batches that may be pending in memory
# batch-pending = 5

# Flush at least this often even if we haven't hit buffer limit
# batch-timeout = "1s"

###
### [[udp]]
###
### Controls the listeners for InfluxDB line protocol data via UDP.
###

[[udp]]
# enabled = false
# bind-address = ":8089"
# database = "udp"
# retention-policy = ""

# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.

# Flush if this many points get buffered
# batch-size = 5000

# Number of batches that may be pending in memory
# batch-pending = 10

# Will flush at least this often even if we haven't hit buffer limit
# batch-timeout = "1s"

# UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
# read-buffer = 0

###
### [continuous_queries]
###
### Controls how continuous queries are run within InfluxDB.
###

[continuous_queries]
# Determines whether the continuous query service is enabled.
# enabled = true

# Controls whether queries are logged when executed by the CQ service.
# log-enabled = true

# Controls whether queries are logged to the self-monitoring data store.
# query-stats-enabled = false

# interval for how often continuous queries will be checked if they need to run
# run-interval = "1s"

四、使配置生效并打开数据库连接,双击influxd.exe就好,然后双击influx.exe进行操作,网上有操作教程,注意操作数据库时不能关闭influxd.exe,我不知道为什么总有这么个提示:There was an error writing history file: open : The system cannot find the file specified.不过好像没啥影响

五、要使用web管理需要下载Chronograf,https://portal.influxdata.com/downloads第三个就是,下载完直接解压,双击exe程序,在浏览器输入http://localhost:8888/,一开始登录要账户密码,我都用admin就进去了

这个是查看建立的数据库

这个是查看数据库的数据

没了

关于子仓库或者说是仓库共用,git官方推荐的工具是git subtree。 我自己也用了一段时间的git subtree,感觉比git submodule好用,但是也有一些缺点,在可接受的范围内。
所以对于仓库共用,在git subtree 与 git submodule之中选择的话,我推荐git subtree。

git subtree 可以实现一个仓库作为其他仓库的子仓库。

使用git subtree 有以下几个原因:

  • 旧版本的git也支持(最老版本可以到 v1.5.2).
  • git subtree与git submodule不同,它不增加任何像.gitmodule这样的新的元数据文件.
  • git subtree对于项目中的其他成员透明,意味着可以不知道git subtree的存在.

当然,git subtree也有它的缺点,但是这些缺点还在可以接受的范围内:

  • 必须学习新的指令(如:git subtree).
  • 子仓库的更新与推送指令相对复杂。

git subtree的主要命令有:

1
2
3
4
5
6
git subtree add   --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
git subtree split --prefix=<prefix> [OPTIONS] [<commit>]

准备

我们先准备一个仓库叫photoshop,一个仓库叫libpng,然后我们希望把libpng作为photoshop的子仓库。
photoshop的路径为https://github.com/test/photoshop.git,仓库里的文件有:

1
2
3
4
5
6
photoshop
|
|-- photoshop.c
|-- photoshop.h
|-- main.c
\-- README.md

libPNG的路径为https://github.com/test/libpng.git,仓库里的文件有:

1
2
3
4
5
libpng
|
|-- libpng.c
|-- libpng.h
\-- README.md

以下操作均位于父仓库的根目录中。

在父仓库中新增子仓库

我们执行以下命令把libpng添加到photoshop中:

1
git subtree add --prefix=sub/libpng https://github.com/test/libpng.git master --squash

(--squash参数表示不拉取历史信息,而只生成一条commit信息。)

执行git status可以看到提示新增两条commit:

image

git log查看详细修改:

image

执行git push把修改推送到远端photoshop仓库,现在本地仓库与远端仓库的目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
photoshop
|
|-- sub/
| |
| \--libpng/
| |
| |-- libpng.c
| |-- libpng.h
| \-- README.md
|
|-- photoshop.c
|-- photoshop.h
|-- main.c
\-- README.md

注意,现在的photoshop仓库对于其他项目人员来说,可以不需要知道libpng是一个子仓库。什么意思呢?
当你git clone或者git pull的时候,你拉取到的是整个photoshop(包括libpng在内,libpng就相当于photoshop里的一个普通目录);当你修改了libpng里的内容后执行git push,你将会把修改push到photoshop上。
也就是说photoshop仓库下的libpng与其他文件无异。

从源仓库拉取更新

如果源libpng仓库更新了,photoshop里的libpng如何拉取更新?使用git subtree pull,例如:

1
git subtree pull --prefix=sub/libpng https://github.com/test/libpng.git master --squash

推送修改到源仓库

如果在photoshop仓库里修改了libpng,然后想把这个修改推送到源libpng仓库呢?使用git subtree push,例如:

1
git subtree push --prefix=sub/libpng https://github.com/test/libpng.git master

简化git subtree命令

我们已经知道了git subtree 的命令的基本用法,但是上述几个命令还是显得有点复杂,特别是子仓库的源仓库地址,特别不方便记忆。
这里我们把子仓库的地址作为一个remote,方便记忆:

1
git remote add -f libpng https://github.com/test/libpng.git

然后可以这样来使用git subtree命令:

1
2
3
git subtree add --prefix=sub/libpng libpng master --squash
git subtree pull --prefix=sub/libpng libpng master --squash
git subtree push --prefix=sub/libpng libpng master

InfluxDB是一个开源的时序数据库,使用GO语言开发,特别适合用于处理和分析资源监控数据这种时序相关数据。而InfluxDB自带的各种特殊函数如求标准差,随机取样数据,统计数据变化比等,使数据统计和实时分析变得十分方便。在我们的容器资源监控系统中,就采用了InfluxDB存储cadvisor的监控数据。本文对InfluxDB的基本概念和一些特色功能做一个详细介绍,内容主要是翻译整理自官网文档,如有错漏,请指正。

这里说一下使用docker容器运行influxdb的步骤,物理机安装请参照官方文档。拉取镜像文件后运行即可,当前最新版本是1.3.5。启动容器时设置挂载的数据目录和开放端口。InfluxDB的操作语法InfluxQL与SQL基本一致,也提供了一个类似mysql-client的名为influx的CLI。InfluxDB本身是支持分布式部署多副本存储的,本文介绍都是针对的单节点单副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


f216e9be15bff545befecb30d1d275552026216a939cc20c042b17419e3bde31

root@f216e9be15bf:/
Connected to http:
InfluxDB shell version: 1.3.5
> create database cadvisor
> show databases
name: databases
name
----
_internal
cadvisor
> CREATE USER testuser WITH PASSWORD 'testpwd'
> GRANT ALL PRIVILEGES ON cadvisor TO testuser
> CREATE RETENTION POLICY "cadvisor_retention" ON "cadvisor" DURATION 30d REPLICATION 1 DEFAULT

influxdb里面有一些重要概念:database,timestamp,field key, field value, field set,tag key,tag value,tag set,measurement, retention policy ,series,point。结合下面的例子数据来说明这几个概念:

1
2
3
4
5
6
7
8
9
10
11
name: census
-————————————
time butterflies honeybees location scientist
2015-08-18T00:00:00Z 12 23 1 langstroth
2015-08-18T00:00:00Z 1 30 1 perpetua
2015-08-18T00:06:00Z 11 28 1 langstroth
2015-08-18T00:06:00Z 3 28 1 perpetua
2015-08-18T05:54:00Z 2 11 2 langstroth
2015-08-18T06:00:00Z 1 10 2 langstroth
2015-08-18T06:06:00Z 8 23 2 perpetua
2015-08-18T06:12:00Z 7 22 2 perpetua

timestamp

既然是时间序列数据库,influxdb的数据都有一列名为time的列,里面存储UTC时间戳。

field key,field value,field set

butterflies和honeybees两列数据称为字段(fields),influxdb的字段由field key和field value组成。其中butterflies和honeybees为field key,它们为string类型,用于存储元数据。

而butterflies这一列的数据12-7为butterflies的field value,同理,honeybees这一列的23-22为honeybees的field value。field value可以为string,float,integer或boolean类型。field value通常都是与时间关联的。

field key和field value对组成的集合称之为field set。如下:

1
2
3
4
5
6
7
8
butterflies = 12 honeybees = 23
butterflies = 1 honeybees = 30
butterflies = 11 honeybees = 28
butterflies = 3 honeybees = 28
butterflies = 2 honeybees = 11
butterflies = 1 honeybees = 10
butterflies = 8 honeybees = 23
butterflies = 7 honeybees = 22

在influxdb中,字段必须存在。注意,字段是没有索引的。如果使用字段作为查询条件,会扫描符合查询条件的所有字段值,性能不及tag。类比一下,fields相当于SQL的没有索引的列。

tag key,tag value,tag set

location和scientist这两列称为标签(tags),标签由tag key和tag value组成。location这个tag key有两个tag value:1和2,scientist有两个tag value:langstroth和perpetua。tag key和tag value对组成了tag set,示例中的tag set如下:

1
2
3
4
location = 1, scientist = langstroth
location = 2, scientist = langstroth
location = 1, scientist = perpetua
location = 2, scientist = perpetua

tags是可选的,但是强烈建议你用上它,因为tag是有索引的,tags相当于SQL中的有索引的列。tag value只能是string类型 如果你的常用场景是根据butterflies和honeybees来查询,那么你可以将这两个列设置为tag,而其他两列设置为field,tag和field依据具体查询需求来定。

measurement

measurement是fields,tags以及time列的容器,measurement的名字用于描述存储在其中的字段数据,类似mysql的表名。如上面例子中的measurement为census。measurement相当于SQL中的表,本文中我在部分地方会用表来指代measurement。

retention policy

retention policy指数据保留策略,示例数据中的retention policy为默认的autogen。它表示数据一直保留永不过期,副本数量为1。你也可以指定数据的保留时间,如30天。

series

series是共享同一个retention policy,measurement以及tag set的数据集合。示例中数据有4个series,如下:

Arbitrary series number

Retention policy

Measurement

Tag set

series 1

autogen

census

location = 1,scientist = langstroth

series 2

autogen

census

location = 2,scientist = langstroth

series 3

autogen

census

location = 1,scientist = perpetua

series 4

autogen

census

location = 2,scientist = perpetua

point

point则是同一个series中具有相同时间的field set,points相当于SQL中的数据行。如下面就是一个point:

1
2
3
4
name: census
-----------------
time butterflies honeybees location scientist
2015-08-18T00:00:00Z 1 30 1 perpetua

database

上面提到的结构都存储在数据库中,示例的数据库为my_database。一个数据库可以有多个measurement,retention policy, continuous queries以及user。influxdb是一个无模式的数据库,可以很容易的添加新的measurement,tags,fields等。而它的操作却和传统的数据库一样,可以使用类SQL语言查询和修改数据。

influxdb不是一个完整的CRUD数据库,它更像是一个CR-ud数据库。它优先考虑的是增加和读取数据而不是更新和删除数据的性能,而且它阻止了某些更新和删除行为使得创建和读取数据更加高效。

influxdb函数分为聚合函数,选择函数,转换函数,预测函数等。除了与普通数据库一样提供了基本操作函数外,还提供了一些特色函数以方便数据统计计算,下面会一一介绍其中一些常用的特色函数。

  • 聚合函数:FILL(), INTEGRAL()SPREAD()STDDEV()MEAN(), MEDIAN()等。
  • 选择函数: SAMPLE(), PERCENTILE(), FIRST(), LAST(), TOP(), BOTTOM()等。
  • 转换函数: DERIVATIVE(), DIFFERENCE()等。
  • 预测函数:HOLT_WINTERS()

先从官网导入测试数据(注:这里测试用的版本是1.3.1,最新版本是1.3.5):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl https://s3.amazonaws.com/noaa.water-database/NOAA_data.txt -o NOAA_data.txt
$ influx -import -path=NOAA_data.txt -precision=s -database=NOAA_water_database
$ influx -precision rfc3339 -database NOAA_water_database
Connected to http://localhost:8086 version 1.3.1
InfluxDB shell 1.3.1
> show measurements
name: measurements
name
----
average_temperature
distincts
h2o_feet
h2o_pH
h2o_quality
h2o_temperature

> show series from h2o_feet;
key
---
h2o_feet,location=coyote_creek
h2o_feet,location=santa_monica

下面的例子都以官方示例数据库来测试,这里只用部分数据以方便观察。measurement为h2o_feet,tag key为location,field key有level descriptionwater_level两个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> SELECT * FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z'
name: h2o_feet
time level description location water_level
---- ----------------- -------- -----------
2015-08-18T00:00:00Z between 6 and 9 feet coyote_creek 8.12
2015-08-18T00:00:00Z below 3 feet santa_monica 2.064
2015-08-18T00:06:00Z between 6 and 9 feet coyote_creek 8.005
2015-08-18T00:06:00Z below 3 feet santa_monica 2.116
2015-08-18T00:12:00Z between 6 and 9 feet coyote_creek 7.887
2015-08-18T00:12:00Z below 3 feet santa_monica 2.028
2015-08-18T00:18:00Z between 6 and 9 feet coyote_creek 7.762
2015-08-18T00:18:00Z below 3 feet santa_monica 2.126
2015-08-18T00:24:00Z between 6 and 9 feet coyote_creek 7.635
2015-08-18T00:24:00Z below 3 feet santa_monica 2.041
2015-08-18T00:30:00Z between 6 and 9 feet coyote_creek 7.5
2015-08-18T00:30:00Z below 3 feet santa_monica 2.051

GROUP BY,FILL()

如下语句中GROUP BY time(12m),* 表示以每12分钟和tag(location)分组(如果是GROUP BY time(12m)则表示仅每12分钟分组,GROUP BY 参数只能是time和tag)。然后fill(200)表示如果这个时间段没有数据,以200填充,mean(field_key)求该范围内数据的平均值(注意:这是依据series来计算。其他还有SUM求和,MEDIAN求中位数)。LIMIT 7表示限制返回的point(记录数)最多为7条,而SLIMIT 1则是限制返回的series为1个。

注意这里的时间区间,起始时间为整点前包含这个区间第一个12m的时间,比如这里为 2015-08-17T:23:48:00Z,第一条为 2015-08-17T23:48:00Z <= t < 2015-08-18T00:00:00Z这个区间的location=coyote_creekwater_level的平均值,这里没有数据,于是填充的200。第二条为 2015-08-18T00:00:00Z <= t < 2015-08-18T00:12:00Z区间的location=coyote_creekwater_level平均值,这里为 (8.12+8.005)/ 2 = 8.0625,其他以此类推。

GROUP BY time(10m)则表示以10分钟分组,起始时间为包含这个区间的第一个10m的时间,即 2015-08-17T23:40:00Z。默认返回的是第一个series,如果要计算另外那个series,可以在SQL语句后面加上 SOFFSET 1

那如果时间小于数据本身采集的时间间隔呢,比如GROUP BY time(10s)呢?这样的话,就会按10s取一个点,没有数值的为空或者FILL填充,对应时间点有数据则保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## GROUP BY time(12m)
> SELECT mean("water_level") FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m),* fill(200) LIMIT 7 SLIMIT 1
name: h2o_feet
tags: location=coyote_creek
time mean
---- ----
2015-08-17T23:48:00Z 200
2015-08-18T00:00:00Z 8.0625
2015-08-18T00:12:00Z 7.8245
2015-08-18T00:24:00Z 7.5675

## GROUP BY time(10m),SOFFSET设置为1
> SELECT mean("water_level") FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(10m),* fill(200) LIMIT 7 SLIMIT 1 SOFFSET 1
name: h2o_feet
tags: location=santa_monica
time mean
---- ----
2015-08-17T23:40:00Z 200
2015-08-17T23:50:00Z 200
2015-08-18T00:00:00Z 2.09
2015-08-18T00:10:00Z 2.077
2015-08-18T00:20:00Z 2.041
2015-08-18T00:30:00Z 2.051

INTEGRAL(field_key, unit)

计算数值字段值覆盖的曲面的面积值并得到面积之和。测试数据如下:

1
2
3
4
5
6
7
8
9
10
11
> SELECT "water_level" FROM "h2o_feet" WHERE "location" = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z'

name: h2o_feet
time water_level
---- -----------
2015-08-18T00:00:00Z 2.064
2015-08-18T00:06:00Z 2.116
2015-08-18T00:12:00Z 2.028
2015-08-18T00:18:00Z 2.126
2015-08-18T00:24:00Z 2.041
2015-08-18T00:30:00Z 2.051

使用INTERGRAL计算面积。注意,这个面积就是这些点连接起来后与时间围成的不规则图形的面积,注意unit默认是以1秒计算,所以下面语句计算结果为3732.66=2.028*1800+分割出来的梯形和三角形面积。如果unit改为1分,则结果为3732.66/60 = 62.211。unit为2分,则结果为3732.66/120 = 31.1055。以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
# unit为默认的1秒
> SELECT INTEGRAL("water_level") FROM "h2o_feet" WHERE "location" = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z'
name: h2o_feet
time integral
---- --------
1970-01-01T00:00:00Z 3732.66

# unit为1分
> SELECT INTEGRAL("water_level", 1m) FROM "h2o_feet" WHERE "location" = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z'
name: h2o_feet
time integral
---- --------
1970-01-01T00:00:00Z 62.211

SPREAD(field_key)

计算数值字段的最大值和最小值的差值。

1
2
3
4
5
6
7
8
> SELECT SPREAD("water_level") FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m),* fill(18) LIMIT 3 SLIMIT 1 SOFFSET 1
name: h2o_feet
tags: location=santa_monica
time spread
---- ------
2015-08-17T23:48:00Z 18
2015-08-18T00:00:00Z 0.052000000000000046
2015-08-18T00:12:00Z 0.09799999999999986

STDDEV(field_key)

计算字段的标准差。influxdb用的是贝塞尔修正的标准差计算公式 ,如下:

  • mean=(v1+v2+…+vn)/n;
  • stddev = math.sqrt(
    ((v1-mean)2 + (v2-mean)2 + …+(vn-mean)2)/(n-1)
    )
1
2
3
4
5
6
7
8
9
> SELECT STDDEV("water_level") FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m),* fill(18) SLIMIT 1;
name: h2o_feet
tags: location=coyote_creek
time stddev
---- ------
2015-08-17T23:48:00Z 18
2015-08-18T00:00:00Z 0.08131727983645186
2015-08-18T00:12:00Z 0.08838834764831845
2015-08-18T00:24:00Z 0.09545941546018377

PERCENTILE(field_key, N)

选取某个字段中大于N%的这个字段值。

如果一共有4条记录,N为10,则10%*4=0.4,四舍五入为0,则查询结果为空。N为20,则 20% * 4 = 0.8,四舍五入为1,选取的是4个数中最小的数。如果N为40,40% * 4 = 1.6,四舍五入为2,则选取的是4个数中第二小的数。由此可以看出N=100时,就跟MAX(field_key)是一样的,而当N=50时,与MEDIAN(field_key)在字段值为奇数个时是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> SELECT PERCENTILE("water_level",20) FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m)
name: h2o_feet
time percentile
---- ----------
2015-08-17T23:48:00Z
2015-08-18T00:00:00Z 2.064
2015-08-18T00:12:00Z 2.028
2015-08-18T00:24:00Z 2.041

> SELECT PERCENTILE("water_level",40) FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m)
name: h2o_feet
time percentile
---- ----------
2015-08-17T23:48:00Z
2015-08-18T00:00:00Z 2.116
2015-08-18T00:12:00Z 2.126
2015-08-18T00:24:00Z 2.051

SAMPLE(field_key, N)

随机返回field key的N个值。如果语句中有GROUP BY time(),则每组数据随机返回N个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> SELECT SAMPLE("water_level",2) FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z';
name: h2o_feet
time sample
---- ------
2015-08-18T00:00:00Z 2.064
2015-08-18T00:12:00Z 2.028

> SELECT SAMPLE("water_level",2) FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z' GROUP BY time(12m);
name: h2o_feet
time sample
---- ------
2015-08-18T00:06:00Z 2.116
2015-08-18T00:06:00Z 8.005
2015-08-18T00:12:00Z 7.887
2015-08-18T00:18:00Z 7.762
2015-08-18T00:24:00Z 7.635
2015-08-18T00:30:00Z 2.051

CUMULATIVE_SUM(field_key)

计算字段值的递增和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> SELECT CUMULATIVE_SUM("water_level") FROM "h2o_feet" WHERE time >= '2015-08-17T23:48:00Z' AND time <= '2015-08-18T00:30:00Z';
name: h2o_feet
time cumulative_sum
---- --------------
2015-08-18T00:00:00Z 8.12
2015-08-18T00:00:00Z 10.184
2015-08-18T00:06:00Z 18.189
2015-08-18T00:06:00Z 20.305
2015-08-18T00:12:00Z 28.192
2015-08-18T00:12:00Z 30.22
2015-08-18T00:18:00Z 37.982
2015-08-18T00:18:00Z 40.108
2015-08-18T00:24:00Z 47.742999999999995
2015-08-18T00:24:00Z 49.78399999999999
2015-08-18T00:30:00Z 57.28399999999999
2015-08-18T00:30:00Z 59.334999999999994

DERIVATIVE(field_key, unit) 和 NON_NEGATIVE_DERIVATIVE(field_key, unit)

计算字段值的变化比。unit默认为1s,即计算的是1秒内的变化比。

如下面的第一个数据计算方法是 (2.116-2.064)/(6*60) = 0.00014..,其他计算方式同理。虽然原始数据是6m收集一次,但是这里的变化比默认是按秒来计算的。如果要按6m计算,则设置unit为6m即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> SELECT DERIVATIVE("water_level") FROM "h2o_feet" WHERE "location" = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z'
name: h2o_feet
time derivative
---- ----------
2015-08-18T00:06:00Z 0.00014444444444444457
2015-08-18T00:12:00Z -0.00024444444444444465
2015-08-18T00:18:00Z 0.0002722222222222218
2015-08-18T00:24:00Z -0.000236111111111111
2015-08-18T00:30:00Z 0.00002777777777777842

> SELECT DERIVATIVE("water_level", 6m) FROM "h2o_feet" WHERE "location" = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z'
name: h2o_feet
time derivative
---- ----------
2015-08-18T00:06:00Z 0.052000000000000046
2015-08-18T00:12:00Z -0.08800000000000008
2015-08-18T00:18:00Z 0.09799999999999986
2015-08-18T00:24:00Z -0.08499999999999996
2015-08-18T00:30:00Z 0.010000000000000231

而DERIVATIVE结合GROUP BY time,以及mean可以构造更加复杂的查询,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> SELECT DERIVATIVE(mean("water_level"), 6m) FROM "h2o_feet" WHERE time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z' group by time(12m), *
name: h2o_feet
tags: location=coyote_creek
time derivative
---- ----------
2015-08-18T00:12:00Z -0.11900000000000022
2015-08-18T00:24:00Z -0.12849999999999984

name: h2o_feet
tags: location=santa_monica
time derivative
---- ----------
2015-08-18T00:12:00Z -0.00649999999999995
2015-08-18T00:24:00Z -0.015499999999999847

这个计算其实是先根据GROUP BY time求平均值,然后对这个平均值再做变化比的计算。因为数据是按12分钟分组的,而变化比的unit是6分钟,所以差值除以2(12/6)才得到变化比。如第一个值是 (7.8245-8.0625)/2 = -0.1190

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> SELECT mean("water_level") FROM "h2o_feet" WHERE time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z' group by time(12m), *
name: h2o_feet
tags: location=coyote_creek
time mean
---- ----
2015-08-18T00:00:00Z 8.0625
2015-08-18T00:12:00Z 7.8245
2015-08-18T00:24:00Z 7.5675

name: h2o_feet
tags: location=santa_monica
time mean
---- ----
2015-08-18T00:00:00Z 2.09
2015-08-18T00:12:00Z 2.077
2015-08-18T00:24:00Z 2.0460000000000003

NON_NEGATIVE_DERIVATIVEDERIVATIVE不同的是它只返回的是非负的变化比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> SELECT DERIVATIVE(mean("water_level"), 6m) FROM "h2o_feet" WHERE location='santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z' group by time(6m), *
name: h2o_feet
tags: location=santa_monica
time derivative
---- ----------
2015-08-18T00:06:00Z 0.052000000000000046
2015-08-18T00:12:00Z -0.08800000000000008
2015-08-18T00:18:00Z 0.09799999999999986
2015-08-18T00:24:00Z -0.08499999999999996
2015-08-18T00:30:00Z 0.010000000000000231

> SELECT NON_NEGATIVE_DERIVATIVE(mean("water_level"), 6m) FROM "h2o_feet" WHERE location='santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time <= '2015-08-18T00:30:00Z' group by time(6m), *
name: h2o_feet
tags: location=santa_monica
time non_negative_derivative
---- -----------------------
2015-08-18T00:06:00Z 0.052000000000000046
2015-08-18T00:18:00Z 0.09799999999999986
2015-08-18T00:30:00Z 0.010000000000000231

4.1 基本语法

连续查询(CONTINUOUS QUERY,简写为CQ)是指定时自动在实时数据上进行的InfluxQL查询,查询结果可以存储到指定的measurement中。基本语法格式如下:

1
2
3
4
5
6
7
8
CREATE CONTINUOUS QUERY <cq_name> ON <database_name>
BEGIN
<cq_query>
END

cq_query格式:
SELECT <function[s]> INTO <destination_measurement> FROM <measurement> [WHERE <stuff>] GROUP BY time(<interval>)[,<tag_key[s]>]

CQ操作的是实时数据,它使用本地服务器的时间戳、GROUP BY time()时间间隔以及InfluxDB预先设置好的时间范围来确定什么时候开始查询以及查询覆盖的时间范围。注意CQ语句里面的WHERE条件是没有时间范围的,因为CQ会根据GROUP BY time()自动确定时间范围。

CQ执行的时间间隔和GROUP BY time()的时间间隔一样,它在InfluxDB预先设置的时间范围的起始时刻执行。如果GROUP BY time(1h),则单次查询的时间范围为 now()-GROUP BY time(1h)now(),也就是说,如果当前时间为17点,这次查询的时间范围为 16:00到16:59.99999。

下面看几个示例,示例数据如下,这是数据库transportation中名为bus_data的measurement,每15分钟统计一次乘客数和投诉数。数据文件bus_data.txt如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# DDL
CREATE DATABASE transportation

# DML
# CONTEXT-DATABASE: transportation

bus_data,complaints=9 passengers=5 1472367600
bus_data,complaints=9 passengers=8 1472368500
bus_data,complaints=9 passengers=8 1472369400
bus_data,complaints=9 passengers=7 1472370300
bus_data,complaints=9 passengers=8 1472371200
bus_data,complaints=7 passengers=15 1472372100
bus_data,complaints=7 passengers=15 1472373000
bus_data,complaints=7 passengers=17 1472373900
bus_data,complaints=7 passengers=20 1472374800

导入数据,命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@f216e9be15bf:/# influx -import -path=bus_data.txt -precision=s
root@f216e9be15bf:/# influx -precision=rfc3339 -database=transportation
Connected to http://localhost:8086 version 1.3.5
InfluxDB shell version: 1.3.5
> select * from bus_data
name: bus_data
time complaints passengers
---- ---------- ----------
2016-08-28T07:00:00Z 9 5
2016-08-28T07:15:00Z 9 8
2016-08-28T07:30:00Z 9 8
2016-08-28T07:45:00Z 9 7
2016-08-28T08:00:00Z 9 8
2016-08-28T08:15:00Z 7 15
2016-08-28T08:30:00Z 7 15
2016-08-28T08:45:00Z 7 17
2016-08-28T09:00:00Z 7 20

示例1 自动缩小取样存储到新的measurement中

对单个字段自动缩小取样并存储到新的measurement中。

1
2
3
4
CREATE CONTINUOUS QUERY "cq_basic" ON "transportation"
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(1h)
END

这个CQ的意思就是对bus_data每小时自动计算取样数据的平均乘客数并存储到 average_passengers中。那么在2016-08-28这天早上会执行如下流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
At 8:00 cq_basic 执行查询,查询时间范围 time >= '7:00' AND time < '08:00'.
cq_basic写入一条记录到 average_passengers:
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 7
At 9:00 cq_basic 执行查询,查询时间范围 time >= '8:00' AND time < '9:00'.
cq_basic写入一条记录到 average_passengers:
name: average_passengers
------------------------
time mean
2016-08-28T08:00:00Z 13.75

# Results
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 7
2016-08-28T08:00:00Z 13.75

示例2 自动缩小取样并存储到新的保留策略(Retention Policy)中

1
2
3
4
CREATE CONTINUOUS QUERY "cq_basic_rp" ON "transportation"
BEGIN
SELECT mean("passengers") INTO "transportation"."three_weeks"."average_passengers" FROM "bus_data" GROUP BY time(1h)
END

与示例1类似,不同的是保留的策略不是autogen,而是改成了three_weeks(创建保留策略语法 CREATE RETENTION POLICY "three_weeks" ON "transportation" DURATION 3w REPLICATION 1)。

1
2
3
4
5
6
> SELECT * FROM "transportation"."three_weeks"."average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 7
2016-08-28T08:00:00Z 13.75

示例3 使用后向引用(backreferencing)自动缩小取样并存储到新的数据库中

1
2
3
4
CREATE CONTINUOUS QUERY "cq_basic_br" ON "transportation"
BEGIN
SELECT mean(*) INTO "downsampled_transportation"."autogen".:MEASUREMENT FROM /.*/ GROUP BY time(30m),*
END

使用后向引用语法自动缩小取样并存储到新的数据库中。语法 :MEASUREMENT 用来指代后面的表,而 /.*/则是分别查询所有的表。这句CQ的含义就是每30分钟自动查询transportation的所有表(这里只有bus_data一个表),并将30分钟内数字字段(passengers和complaints)求平均值存储到新的数据库 downsampled_transportation中。

最终结果如下:

1
2
3
4
5
6
7
8
> SELECT * FROM "downsampled_transportation."autogen"."bus_data"
name: bus_data
--------------
time mean_complaints mean_passengers
2016-08-28T07:00:00Z 9 6.5
2016-08-28T07:30:00Z 9 7.5
2016-08-28T08:00:00Z 8 11.5
2016-08-28T08:30:00Z 7 16

示例4 自动缩小取样以及配置CQ的时间范围

1
2
3
4
CREATE CONTINUOUS QUERY "cq_basic_offset" ON "transportation"
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(1h,15m)
END

与前面几个示例不同的是,这里的GROUP BY time(1h, 15m)指定了一个时间偏移,也就是说 cq_basic_offset执行的时间不再是整点,而是往后偏移15分钟。执行流程如下:

1
2
3
4
5
6
7
8
9
10
At 8:15 cq_basic_offset 执行查询的时间范围 time >= '7:15' AND time < '8:15'.
name: average_passengers
------------------------
time mean
2016-08-28T07:15:00Z 7.75
At 9:15 cq_basic_offset 执行查询的时间范围 time >= '8:15' AND time < '9:15'.
name: average_passengers
------------------------
time mean
2016-08-28T08:15:00Z 16.75

最终结果:

1
2
3
4
5
6
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T07:15:00Z 7.75
2016-08-28T08:15:00Z 16.75

4.2 高级语法

InfluxDB连续查询的高级语法如下:

1
2
3
4
5
CREATE CONTINUOUS QUERY <cq_name> ON <database_name>
RESAMPLE EVERY <interval> FOR <interval>
BEGIN
<cq_query>
END

与基本语法不同的是,多了RESAMPLE关键字。高级语法里CQ的执行时间和查询时间范围则与RESAMPLE里面的两个interval有关系。

高级语法中CQ以EVERY interval的时间间隔执行,执行时查询的时间范围则是FOR interval来确定。如果FOR interval为2h,当前时间为17:00,则查询的时间范围为15:00-16:59.999999RESAMPLE的EVERY和FOR两个关键字可以只有一个

示例的数据表如下,比之前的多了几条记录为了示例3和示例4的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: bus_data
--------------
time passengers
2016-08-28T06:30:00Z 2
2016-08-28T06:45:00Z 4
2016-08-28T07:00:00Z 5
2016-08-28T07:15:00Z 8
2016-08-28T07:30:00Z 8
2016-08-28T07:45:00Z 7
2016-08-28T08:00:00Z 8
2016-08-28T08:15:00Z 15
2016-08-28T08:30:00Z 15
2016-08-28T08:45:00Z 17
2016-08-28T09:00:00Z 20

示例1 只配置执行时间间隔

1
2
3
4
5
CREATE CONTINUOUS QUERY "cq_advanced_every" ON "transportation"
RESAMPLE EVERY 30m
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(1h)
END

这里配置了30分钟执行一次CQ,没有指定FOR interval,于是查询的时间范围还是GROUP BY time(1h)指定的一个小时,执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
At 8:00, cq_advanced_every 执行时间范围 time >= '7:00' AND time < '8:00'.
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 7
At 8:30, cq_advanced_every 执行时间范围 time >= '8:00' AND time < '9:00'.
name: average_passengers
------------------------
time mean
2016-08-28T08:00:00Z 12.6667
At 9:00, cq_advanced_every 执行时间范围 time >= '8:00' AND time < '9:00'.
name: average_passengers
------------------------
time mean
2016-08-28T08:00:00Z 13.75

需要注意的是,这里的 8点到9点这个区间执行了两次,第一次执行时时8:30,平均值是 (8+15+15)/ 3 = 12.6667,而第二次执行时间是9:00,平均值是 (8+15+15+17) / 4=13.75,而且最后第二个结果覆盖了第一个结果。InfluxDB如何处理重复的记录可以参见这个文档

最终结果:

1
2
3
4
5
6
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 7
2016-08-28T08:00:00Z 13.75

示例2 只配置查询时间范围

1
2
3
4
5
CREATE CONTINUOUS QUERY "cq_advanced_for" ON "transportation"
RESAMPLE FOR 1h
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(30m)
END

只配置了时间范围,而没有配置EVERY interval。这样,执行的时间间隔与GROUP BY time(30m)一样为30分钟,而查询的时间范围为1小时,由于是按30分钟分组,所以每次会写入两条记录。执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
At 8:00 cq_advanced_for 查询时间范围:time >= '7:00' AND time < '8:00'.
写入两条记录。
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 6.5
2016-08-28T07:30:00Z 7.5
At 8:30 cq_advanced_for 查询时间范围:time >= '7:30' AND time < '8:30'.
写入两条记录。
name: average_passengers
------------------------
time mean
2016-08-28T07:30:00Z 7.5
2016-08-28T08:00:00Z 11.5
At 9:00 cq_advanced_for 查询时间范围:time >= '8:00' AND time < '9:00'.
写入两条记录。
name: average_passengers
------------------------
time mean
2016-08-28T08:00:00Z 11.5
2016-08-28T08:30:00Z 16

需要注意的是,cq_advanced_for每次写入了两条记录,重复的记录会被覆盖。

最终结果:

1
2
3
4
5
6
7
8
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T07:00:00Z 6.5
2016-08-28T07:30:00Z 7.5
2016-08-28T08:00:00Z 11.5
2016-08-28T08:30:00Z 16

示例3 同时配置执行时间间隔和查询时间范围

1
2
3
4
5
CREATE CONTINUOUS QUERY "cq_advanced_every_for" ON "transportation"
RESAMPLE EVERY 1h FOR 90m
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(30m)
END

这里配置了执行间隔为1小时,而查询范围90分钟,最后分组是30分钟,每次插入了三条记录。执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
At 8:00 cq_advanced_every_for 查询时间范围 time >= '6:30' AND time < '8:00'.
插入三条记录
name: average_passengers
------------------------
time mean
2016-08-28T06:30:00Z 3
2016-08-28T07:00:00Z 6.5
2016-08-28T07:30:00Z 7.5
At 9:00 cq_advanced_every_for 查询时间范围 time >= '7:30' AND time < '9:00'.
插入三条记录
name: average_passengers
------------------------
time mean
2016-08-28T07:30:00Z 7.5
2016-08-28T08:00:00Z 11.5
2016-08-28T08:30:00Z 16

最终结果:

1
2
3
4
5
6
7
8
9
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T06:30:00Z 3
2016-08-28T07:00:00Z 6.5
2016-08-28T07:30:00Z 7.5
2016-08-28T08:00:00Z 11.5
2016-08-28T08:30:00Z 16

示例4 配置查询时间范围和FILL填充

1
2
3
4
5
CREATE CONTINUOUS QUERY "cq_advanced_for_fill" ON "transportation"
RESAMPLE FOR 2h
BEGIN
SELECT mean("passengers") INTO "average_passengers" FROM "bus_data" GROUP BY time(1h) fill(1000)
END

在前面值配置查询时间范围的基础上,加上FILL填充空的记录。执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
At 6:00, cq_advanced_for_fill 查询时间范围:time >= '4:00' AND time < '6:00',没有数据,不填充。

At 7:00, cq_advanced_for_fill 查询时间范围:time >= '5:00' AND time < '7:00'. 写入两条记录,没有数据的时间点填充1000。
------------------------
time mean
2016-08-28T05:00:00Z 1000 <------ fill(1000)
2016-08-28T06:00:00Z 3 <------ average of 2 and 4

[…] At 11:00, cq_advanced_for_fill 查询时间范围:time >= '9:00' AND time < '11:00'.写入两条记录,没有数据的点填充1000。
name: average_passengers
------------------------
2016-08-28T09:00:00Z 20 <------ average of 20
2016-08-28T10:00:00Z 1000 <------ fill(1000)

At 12:00, cq_advanced_for_fill 查询时间范围:time >= '10:00' AND time < '12:00'。没有数据,不填充。

最终结果:

1
2
3
4
5
6
7
8
9
10
> SELECT * FROM "average_passengers"
name: average_passengers
------------------------
time mean
2016-08-28T05:00:00Z 1000
2016-08-28T06:00:00Z 3
2016-08-28T07:00:00Z 7
2016-08-28T08:00:00Z 13.75
2016-08-28T09:00:00Z 20
2016-08-28T10:00:00Z 1000

方式一

通过udev规则监听设备事件,编写/etc/udev/rules.d/99-udev-mount.rules规则实现U盘插入捕获U盘插入事件

1
2
3
4
5
# 插入U盘自动挂载
ACTION=="add", KERNEL=="sd[a-z]*", RUN+="/bin/mkdir -p /media/udev-%k", RUN+="/bin/mount /dev/%k /media/udev-%k"

# 移除U盘自动卸载
ACTION=="remove", KERNEL=="sd[a-z]*", RUN+="/bin/umount /media/udev-%k"

规则编辑完成后执行以下命令使规则生效

1
sudo udevadm control --reload

我们在开发嵌入式产品时,会需要U盘读写功能,但是Linux系统不像Windows系统那样能默认配置U盘自动挂载,所以我们需要在Linux系统中手动添加rule规则,实现U盘自动挂载功能。

Udev规则

是什么?

udev 是Linux 系统的一个设备管理器,它主要的功能是管理/dev目录底下的设备节点。

特色功能是对设备有极端需求的站点(比如需要控制上千个硬盘)和热插拔设备(比如USB摄像头和MP3播放器)有着较完善的处理能力。

比如在处理热插拔设备时,udev在用户空间,根据用户设置的规则,在监测到设备被插入后,在/dev/下自动创建并命名设备文件节点 或者 自动设置设备属性。

溯源历史

早期:通过mknod静态添加

中期:devfs基于内核的动态设备文件系统

现在:udev 一个用户空间程序

Udev和Devfs的区别

1.devfs运行在内核空间(使用devfs_register等API在内核空间注册设备节点),而udev运行在用户空间。

2.当用户访问/dev下的某个节点时,devfs会自动加载对应的驱动;而udev则并不负责自动加载驱动的工作。

Udev的热插拔工作机制

Udev工作在用户空间,当一个设备被插入或者移除时,内核会通过netlink套接字发送设备详细信息到用户空间,udev获取到设备信息,根据信息内容在/dev下创建并命名设备节点 或者 配置属性。

Udev的配置规则

1.配置文件

主配置文件: /etc/udev/udev.conf

udev_root=”/dev/“ 设备目录,默认是/dev

udev_rules=”/etc/udev/rules.d/“ udev规则存储的目录

udev_log=”err” 日志等级(表示严重程度),跟 syslog 一致,例如: err, info, debug

2.规则文件

udev的规则文件一般位于 /lib/udev/rules.d/,有的位于 /etc/udev/rules.d/下。

规则文件是按照字母顺序处理的,不管放在哪个目录。对于相同名字的规则文件, /etc/udev/rules.d 比 /lib/udev/rules.d 优先。

规则文件必须以.rules 作为扩展名,否则不被当作规则文件。

规则文件的每一行都是 key=value 格式。 key 有两个类型:1)匹配型 key 2)赋值型 key

当所有匹配型 key 都匹配时,该规则即被采用,赋值型 key就会获得相应的值。

一条规则由多个key=value 组成,以英文逗号隔开。 每个key 有一个操作,取决于操作符,有效的操作符如下:

== 比较是否相等
!= 比较是否不相等
= 给一个key 赋值。表示一个列表的key会被重置,并且把这个唯一的值传给它
+= 将一个值增加到key中
:= 将一个值传给一个key,并且不允许再修改这个key。

3.匹配型Key

ACTION:           事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )。
KERNEL:           内核设备名称,例如:sda, cdrom。
DEVPATH:        设备的 devpath 路径。
SUBSYSTEM:        设备的子系统名称,例如:sda 的子系统为 block。
BUS:            设备在 devpath 里的总线名称,例如:usb。
DRIVER:           设备在 devpath 里的设备驱动名称,例如:ide-cdrom。
ID:              设备在 devpath 里的识别号。
SYSFS{filename}:      设备的 devpath 路径下,设备的属性文件“filename”里的内容。例如:SYSFS{model}==“ST936701SS”表示:如果设备的型号为 ST936701SS,则该设备匹配该 匹配键。在一条规则中,可以设定最多五条 SYSFS 的 匹配键。
ENV{key}:          环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。
PROGRAM:        调用外部命令。
RESULT:          外部命令 PROGRAM 的返回结果。

通配符 * 代表 [ 0 个到无穷多个 ] 任意字符
通配符 ? 代表 [一定有一个] 任意字符
通配符 [ ] 代表 [一定有一个在括号内] 的字符(非任意字符)

4.赋值型 Key

NAME:            在 /dev下产生的设备文件名。只有第一次对某个设备的 NAME 的赋值行为生效,之后匹配的规则再对该设备的 NAME 赋值行为将被忽略。如果没有任何规则对设备的 NAME 赋值,udev 将使用内核设备名称来产生设备文件。

SYMLINK:          为 /dev/下的设备文件产生符号链接。由于 udev 只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接。

LABEL GOTO 可以跳到的地方

GOTO 跳到下一个带有匹配名字的 LABEL 处。

ENV{key}:          导入一个环境变量。

OWNER, GROUP, MODE:   为设备设定权限。

$kernel, %k: 该设备的内核名字(%k 替换$kernel)
$number, %n:该设备的内核号码。例如 sda3 的内核号码是 3。
$devpath, %p: 该设备的 devpath
$id, %b:当向上搜索devpath,寻找 SUBSYSTEMS, KERNELS, DRIVERS 和 ATTRS 时,被匹配的设备名字
$driver: 当向上搜索devpath,寻找 SUBSYSTEMS, KERNELS, DRIVERS 和 ATTRS 时,被匹配的驱动名字
$attr { file }, %s { file }: 一个被发现的设备的sysfs 属性的值。如果该设备没有该属性,且前面的 KERNELS, SUBSYSTEMS, DRIVERS或 ATTRS 测试选择的是一个父设备,那么就用父设备的属性。如果属性是一个符号链,符号链的最后一个元素作为返回值。
$env { key }, %E { key }: 一个设备属性值
$major, %M: 该设备的内核主号码
$minor, %m: 该设备的内核次号码
$result, %c: 由 PROGRAM 调用的外部程序返回的字符串。如果这个字符串包含空格,可以用 %c{N} 选中第N个字段。如果这个数字N,后面有一个 + 字符, 则表示选中从这个字段开始的所有后面的字符串 %c { N + }
$parent, %p:父设备的节点名字
$name:设备节点的名字,用一个空格作为分隔符。该值只有在前面的规则赋值之后才存在,或者是remove事件。
$links:当前符号链的列表,用空格隔开。该值只有在前面的规则赋值之后才存在,或者是remove事件。
$root, %r:udev_root 的值
$sys, %S:sysfs 挂载点
$tempnode, %N:在真正的设备节点创建之前,创建的一个临时的设备节点的名字,这个临时设备节点供外部程序使用。
$$: ‘$’字符自己
%%: ‘%’ 字符自己

Udev的应用举例

前提是系统里存在udev工具

在/etc/udev/rules.d/下添加usb-mount.rules规则,参考如下:

KERNEL!= "sd[a-z][0-9]"``,GOTO= "automount_exit"

ACTION== "add"``, SUBSYSTEMS== "usb"``, SUBSYSTEM== "block"``,RUN+= "/bin/mkdir /mnt/usb"``,RUN+= "/bin/mount --no-block --automount=yes $devnode /mnt/usb"

ACTION== "remove"``, RUN+= "/bin/umount /mnt/usb"``,RUN+= "/bin/rmdir  /mnt/usb"

LABEL= "automount_exit"

以上例子中的udev规则中匹配sd[a-z][0-9]的设备, 当系统中检查usb设备插入时, 就会执行add中的操作, 当检查到usb设备拔出后, 就会执行remove中的操作。

规则中的remove操作是在拔出U盘后执行的, 为了保证写入U盘的数据不丢失, 在拔出U盘之前需要执行sync命令把数据写入U盘中。

在有的Linux里,存在systemd服务的话,尽量还是用systemd的mount和umount为好。


欢迎转载,欢迎指正,更欢迎点赞。转载请附原文链接。

posted @ Aaron看世界 阅读(3264)  评论(0) 收藏 举报

公告

<2025年8月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

字节旗下的 AI IDE

AI 搜索

使用netlink捕获USB插拔事件

最新推荐文章于 2024-06-14 14:01:01 发布

SlamDunk31598 于 2019-02-20 20:26:28 发布

CC 4.0 BY-SA版权

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接: https://blog.csdn.net/yangchao315/article/details/87820406

使用netlink捕获USB插拔事件

Linux 中使用udev来管理热插拔,使用netlink监听udev的消息可以为用户空间提供管理策略,通过解析消息中的字符串来完成控制逻辑。

USB的热插拔应用广泛,比如U盘、手机、USB网卡等等,netlink捕获消息后,解析消息中的字符串可以进一步识别是哪种设备,从而在用户空间完成逻辑控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define UEVENT_BUFFER_SIZE 2048

int main(void)
{
char buf[UEVENT_BUFFER_SIZE] = { 0 };
struct sockaddr_nl netlink;

int hotplug_sock, rcvlen, ret;
int buffersize = 2048;

hotplug_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
memset(&netlink, 0, sizeof(netlink));
netlink.nl_family = AF_NETLINK;
netlink.nl_pid = getpid();
netlink.nl_groups = 1; /* receive broadcast message*/

setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
bind(hotplug_sock, (struct sockaddr*)&netlink, sizeof(netlink));

while (1) {
memset(buf, 0, sizeof(buf));
/* receive data */
rcvlen = recv(hotplug_sock, &buf, sizeof(buf), 0);
if (rcvlen > 0) {
/*You can do something here to make the program more perfect!!!*/
printf("%s\n", buf);
#if 0
if(strstr(buf, "add@")){
//printf("%s\n", buf);
}else if(strstr(buf, "remove@")){
printf("%s\n", buf);
system("echo 0 > /sys/bus/i2c/drivers/e52241/0-0011/set_en");
sleep(1);
system("echo 1 > /sys/bus/i2c/drivers/e52241/0-0011/set_en");
}
#endif
}
}

close(hotplug_sock);
return 0;
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
```[Linux C高级技巧:利用 *Netlink* 机制检测嵌入式设备的 *USB* 热 *插拔*](https://blog.csdn.net/CodeSageX/article/details/133131520)

[CodeSageX的博客](https://blog.csdn.net/CodeSageX)

09-21 796[*USB* 热 *插拔* 是在运行中插入或拔出 *USB* 设备的过程。在嵌入式系统中,通过 *使用* Linux的 *Netlink* 机制,我们可以实现对 *USB* 设备的热 *插拔* *事件* 进行检测和处理。我们将 *使用* *Netlink* Socket来监听内核 *事件* ,并接收有关 *USB* 设备热 *插拔* 的通知消息。接下来,我们需要解析接收到的 *Netlink* 消息,以 *获取* 有关 *USB* 热 *插拔* *事件* 的信息。在上述代码中的循环中,我们 *使用* recvmsg()函数接收消息,并 *使用* NLMSG\_DATA宏来 *获取* 消息数据。根据接收到的消息,我们可以判断是 *USB* 设备的插入还是拔出 *事件* 。](https://blog.csdn.net/CodeSageX/article/details/133131520)[通过 *Netlink* 检测 *USB* 设备的 *插拔*](https://blog.csdn.net/guochongxin/article/details/140906880)

[

最新发布

](https://blog.csdn.net/guochongxin/article/details/140906880)

[阿xin\*的嵌入式AI](https://blog.csdn.net/guochongxin)

08-06 869[Android/Linux通过 *netlink* 检测 *USB* *插拔* 。](https://blog.csdn.net/guochongxin/article/details/140906880)[*netlink* 加异步套接字select函数 *捕获* *USB* *插拔* *事件* \_ *netlink* *usb*](https://blog.csdn.net/qq_45649553/article/details/144449886)

7-13[Linux中 *使用* udev来管理热 *插拔*,*使用* *netlink* 监听udev的消息可以为用户空间提供管理策略,通过解析消息中的字符串来完成控制逻辑。 *USB* 的热 *插拔* 应用广泛,比如U盘、手机、 *USB* 网卡等等,*netlink* *捕获* 消息后,解析消息中的字符串可以进一步识别是哪种设备,从而在用户空间完成逻辑控制 一、整体框架 socket s;...fd\_set set;while...](https://blog.csdn.net/qq_45649553/article/details/144449886)[go语言实现通过 *netlink* 监控 *usb* 热 *插拔* (附完整源码)\_go语言如何做到实时...](https://blog.csdn.net/it_xiangqiang/article/details/147076117)

7-14[go语言实现通过 *netlink* 监控 *usb* 热 *插拔* 监控 *USB* 热 *插拔* *事件* 在 Linux 系统中通常通过监听 *Netlink* 套接字上的 udev *事件* 来实现。以下是一个 *使用* Go 语言通过 *Netlink* 监听 *USB* 热 *插拔* *事件* 的完整示例代码。这个程序会持续运行,并在检测到 *USB* 设备的插入或移除时输出相关信息。](https://blog.csdn.net/it_xiangqiang/article/details/147076117)[通过 *netlink* *获取* 内核信息捕捉 *USB* 设备 *插拔* 消息](https://blog.csdn.net/qq_44488261/article/details/120022844)

[qq\_44488261的博客](https://blog.csdn.net/qq_44488261)

08-31 1080[Linux中,通过 *netlink* 接收内核消息,获得设备变化的消息,并输出 #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <linux/ *netlink*.h> #include <unistd.h> static int init\_hotplug\_sock(void) { struct sockaddr\_nl snl; const int](https://blog.csdn.net/qq_44488261/article/details/120022844)[Linux 下 *使用* *netlink* 检测设备的热 *插拔*](https://blog.csdn.net/qq_41630102/article/details/107307680)

[SauryN的博客](https://blog.csdn.net/qq_41630102)

07-13 1979[*使用* *netlink* 实现设备热 *插拔*](https://blog.csdn.net/qq_41630102/article/details/107307680)[linux- c检测 *USB* /SD卡热 *插拔* (*netlink*)\_ *netlink* sd卡](https://blog.csdn.net/ding283595861/article/details/107661121)

7-24[linux- c检测 *USB* /SD卡热 *插拔* (*netlink*) 可以参考: \*\* https://blog.csdn.net/u013566722/article/details/78806217 拔插 *USB* 或者SD卡,系统会打印相关的log信息,recvmsg 就会 *捕获* 到,然后解析相应的关键字段信息来判断 *usb* 或者sd卡是否拔掉或者插入。 比如:sd卡拔插 *事件* 收到后,可以解析buf,解析方法如下:...](https://blog.csdn.net/ding283595861/article/details/107661121)[*USB* 热 *插拔* 消息 *获取* 与 *NetLink* 机制](https://blog.csdn.net/martingang/article/details/8194535)

7-23[*netlink* 机制-实现系统调用 *netlink* 机制-总结 PF\_ *NETLINK* 应用实例 *NETLINK* \_KOBJECT\_UEVENT具体实现--udev实现原理 linux 内核与用户空间通信之 *netlink* *使用* 方法 如下是我实现的从内核中 *获取* *USB* 热 *插拔* 消息的代码,可根据对消息字符串的解析来定义对应的操作。事实上这种方法并不足够好(胜在简便)。比较好的做法是实现点对点...](https://blog.csdn.net/martingang/article/details/8194535)[在.NET中探测U盘的插入/拔出](https://blog.csdn.net/weixin_30731287/article/details/95704068)

[weixin\_30731287的博客](https://blog.csdn.net/weixin_30731287)

07-25 136  [有同学向我问这个问题,于是就Google了一下找到答案,不过是C下的,我将其改编成了C#的。   当设备被插入/拔出的时候,WINDOWS会向每个窗体发送WM\_DEVICECHANGE 消息,当消息的wParam 值等于 DBT\_DEVICEARRIVAL 时,表示Media设备被插入并且已经可用;如果wParam值等于DBT\_DEVICEREMOVECOMPLETE,表示Media设备已经...](https://blog.csdn.net/weixin_30731287/article/details/95704068)[...从内核 *获取* PCI主板上的 *USB* 热 *插拔* 信息\_ *netlink* *获取* pci](https://blog.csdn.net/qq_16542775/article/details/88553527)

8-3[本文详细介绍了在Linux操作系统中如何利用内核功能实现对PCI主板上 *USB* 设备的热 *插拔* 管理,重点探讨了如何 *获取* 并处理 *USB* 设备的热 *插拔* *事件* 。 摘要生成于C知道,由 DeepSeek-R1 满血版支持,前往体验 > Linux下热 *插拔* 功能: #include<stdio.h> #include<stdlib.h>...](https://blog.csdn.net/qq_16542775/article/details/88553527)[linux下检测U盘插入并读取文件\_ *netlink* u盘插入](https://blog.csdn.net/mianhuantang848989/article/details/46379267/)

7-28[获得U盘的插入或者拔取得信息的传统方法是在内核级运行hotplug程序,相关参数通过环境变量传递过来,再由hotplug通知其他关注hotplug的应用程序。这样的做法效率有些低,现在通过一种特殊类型的socket *netlink* 实现 *获取* U盘拔插的信息。 *netlink* 专门用于内核空间和用户空间的异步通信。](https://blog.csdn.net/mianhuantang848989/article/details/46379267/)[Linux *使用* *Netlink* *捕获* *USB* 热 *插拔* *事件*](https://blog.csdn.net/zt18786458319/article/details/139673452)

[zt18786458319的博客](https://blog.csdn.net/zt18786458319)

06-14 742[在 Qt 中 *使用* *Netlink* 来 *捕获* *USB* 热 *插拔* *事件* 需要结合 Qt 的 *事件* 循环机制和 *Netlink* 的底层接口。 *Netlink* 是 Linux 内核和用户空间之间的一种通信机制,适用于 *捕获* 内核 *事件* (例如 *USB* 设备的 *插拔* *事件* )。以下是一个 *使用* Qt 和 *Netlink* *捕获* *USB* 热 *插拔* *事件* 的示例。](https://blog.csdn.net/zt18786458319/article/details/139673452)[RTL8198D 网口 *插拔* *事件* *捕获* 功能实现](https://blog.csdn.net/wgl307293845/article/details/118785261)

[wgl307293845的博客](https://blog.csdn.net/wgl307293845)

07-16 1040[功能描述 路由设备网口有网线接入或者拔掉的时候通知应用层,然后在应用层做响应的处理 应用层修改 应用层去 *捕获* KOBJ\_ADD和KOBJ\_REMOVE *事件* 即可,实例代码如下 参考链接: *Netlink* 实现热拔插监控\_不积跬步,无以至千里-CSDN博客 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include...](https://blog.csdn.net/wgl307293845/article/details/118785261)[linux 监测 设备热拔插](https://turingevo.blog.csdn.net/article/details/106736416)

[TuringEvo专栏](https://blog.csdn.net/WMX843230304WMX)

06-13 553[linux 监测 设备热拔插 这个示例是 *USB* 设备热拔插,可以稍微修改代码,符合你想要监测的设备 原理 这里是通过用户空间,接收内核的消息,提取消息有效字符,匹配想要的结果,监测 *USB* 设备热拔插!!! 编译需要 启用 c++11 支持,因为 *使用* 了正则表达式匹配 编译命令:g++ *USB* -monitor.cpp -std=c++11 -o *usb* *USB* -monitor.cpp: //--编译: // g++ *USB* -monitor.cpp -std=c++11 -o *usb* // /\* Linux](https://turingevo.blog.csdn.net/article/details/106736416)[linux查看 *USB* 运行状态,Linux下 *获取* *USB* 设备 *插拔* 状态的通知](https://blog.csdn.net/weixin_29038155/article/details/116622997)

[weixin\_29038155的博客](https://blog.csdn.net/weixin_29038155)

04-30 1504[最近遇到一个需求,要求浏览器的内嵌keyboard与 *USB* keyboard状态同步。主要就是几个特殊的功能键,shift、alt、ctrl、caps、tab等等,借鉴了一个博客:http://www.voidcn.com/article/p-sjjkjzze-cy.html,其中提供了一段 *获取* *USB* 设备的代码,自己运行过后,有一定的帮助,具体的还需要深究。代码如下:#include #inclu...](https://blog.csdn.net/weixin_29038155/article/details/116622997)[*netlink* read *事件* 的处理](https://blog.csdn.net/oHeHui1/article/details/129258106)

[我的博客](https://blog.csdn.net/oHeHui1)

02-28 181[*netlink* read *事件* 的处理](https://blog.csdn.net/oHeHui1/article/details/129258106)[sonic处理 *netlink* *事件*](https://blog.csdn.net/weixin_39094034/article/details/115325287)

[weixin\_39094034的博客](https://blog.csdn.net/weixin_39094034)

03-30 1746[sonic处理 *netlink* *事件* sonic在处理路由,接口up/down,接口地址变化,team等 *事件* 上极大的依赖内核。sonic通过监听rtnl *事件* 来响应linux *事件* 。从而感知相关信息变化。 libnl sonic *使用* libnl库来操作 *netlink* *事件* ,详细内容可以访问http://www.infradead.org/~tgr...。sonic在libnl库基础上封装了类 *NetLink* 进行 *netlink* 操作。 *NetLink* class *NetLink*: public Select](https://blog.csdn.net/weixin_39094034/article/details/115325287)[linux下 监控 *USB* 插入 *事件*](https://blog.csdn.net/sno_guo/article/details/38853341)

[

热门推荐

](https://blog.csdn.net/sno_guo/article/details/38853341)

[蓝松SDK](https://blog.csdn.net/sno_guo)

08-26 1万+[linux下 监控 *USB* #include #include #include #include #include #include #include #include #include #include static int init\_hotplug\_sock(void) { struct sockaddr\_nl snl; const int buffers](https://blog.csdn.net/sno_guo/article/details/38853341)[网络子系统63\_路由子系统处理 *netlink* *事件*](https://blog.csdn.net/u012259202/article/details/12841467)

[奔跑的Linerdx的专栏](https://blog.csdn.net/u012259202)

10-17 2466[// 路由子系统 *netlink* 控制块 1.1 static struct rt *netlink* \_link inet\_rt *netlink* \_table\[RTM\_MAX - RTM\_BASE + 1\] = { \[4\] = {.doit = inet\_rtm\_newaddr, }, \[5\] = {.doit = inet\_rtm\_deladdr, }, \[6\] = {.dumpit](https://blog.csdn.net/u012259202/article/details/12841467)[*netlink* 判断 *USB* 热 *插拔* *事件*](https://blog.csdn.net/newnewman80/article/details/8766657)

[newnewman](https://blog.csdn.net/newnewman80)

04-07 7734[做嵌入式开发,尤其在网关、路由器或者其他支持 *USB* 设备的终端上,为了提高用户体验,我们常常需要支持自动识别并挂载 *USB* 设备功能。某些应用程序,在 *使用* *USB* 设备的过程中,也希望能够侦测到 *USB* 断开 *事件* ,不至于某些工作因为 *USB* 已经不存在而白做。在Linux下,我们主要有两种办法检测 *USB* 热 *插拔* 。 第一种便是定时检查/proc/scsi/scsi文件,该文件内会按照标准格式保存着当前设备](https://blog.csdn.net/newnewman80/article/details/8766657)[linux *获取* 优盘信息](https://blog.csdn.net/ZHANG_TIMI/article/details/114745599)

[1252661442的博客](https://blog.csdn.net/ZHANG_TIMI)

03-13 562[这段时间做liveCD优盘管控,需要对优盘信息进行注册管理,所以就要拿到优盘的指纹信息 cat /proc/scsi/ *usb* -storage/\* 例子: root@deepin:~# cat /proc/scsi/ *usb* -storage/\* Host scsi3: *usb* -storage Vendor: General Product: *USB* Flash Disk Serial Number: 04YRD8F50M4ONAPE Protocol: Tr...](https://blog.csdn.net/ZHANG_TIMI/article/details/114745599)[linux *usb* *事件*,Linux下实现 *USB* 口的热 *插拔* *事件* 触发](https://blog.csdn.net/weixin_39658716/article/details/116911904)

[weixin\_39658716的博客](https://blog.csdn.net/weixin_39658716)

05-13 439[目前要做一个在嵌入式平台上的 *USB* 口的热 *插拔* *事件* 。经过我现在的分析总结目前有如下方法:1,定时检查/proc/scsi/scsi文件此方法只能在PC上,但在嵌入式平台上不可用。2, *netlink* 方式 *使用* *netlink*.#include #include #include #include #include #include #include #include #include #include #...](https://blog.csdn.net/weixin_39658716/article/details/116911904)[通过 *NETLINK* \_KOBJECT\_UEVENT *捕获* udev的uevent](https://blog.csdn.net/wq897387/article/details/124188153)

[wq897387的专栏](https://blog.csdn.net/wq897387)

04-15 1148[测试代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <sys/un.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <linux/types.h> #include <linux/ *netlink*..](https://blog.csdn.net/wq897387/article/details/124188153)[*netlink* *usb* *插拔*](https://wenku.csdn.net/answer/67815mn9ju)

07-29[*Netlink* 是一个用于Linux内核中的通信机制。通过 *Netlink* 机制,用户空间进程可以与内核空间进行通信,实现内核与用户空间之间的数据交换。而 *USB* *插拔* 则是指插入或拔出 *USB* 设备。 在Linux系统中,当用户将 *USB* 设备插入计算机时,内核会检测到 *USB* 设备的插入 *事件* ,并将此 *事件* 通过 *Netlink* 机制发送给用户空间。用户空间的 *Netlink* 监听进程可以接收到这个 *插拔* *事件* ,并进行相应的处理。 例如,当用户插入一个 *USB* 存储设备时,内核可以通过 *Netlink* 发送一个消息给用户空间的监听进程,消息中包含有关插入设备的信息,例如设备的路径、设备的供应商ID和产品ID等。用户空间的监听进程可以根据这些信息执行特定的操作,例如自动挂载存储设备,或更新相关设备列表等。 同样,当用户将 *USB* 设备拔出时,内核也会检测到拔出 *事件* ,并通过 *Netlink* 发送消息给用户空间的监听进程。监听进程可以根据收到的消息进行相应的操作,例如卸载存储设备、更新设备列表等。 总之, *Netlink* 机制为 *USB* 设备的 *插拔* 提供了一种可靠的通信机制,使内核和用户空间能够及时地进行信息交换,实现对 *USB* 设备 *插拔* *事件* 的处理。](https://wenku.csdn.net/answer/67815mn9ju)

实付 元

[使用余额支付](https://blog.csdn.net/yangchao315/article/details/)

点击重新获取

扫码支付

钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

[余额充值](https://i.csdn.net/#/wallet/balance/recharge)

举报

[![](https://csdnimg.cn/release/blogv2/dist/pc/img/toolbar/Group.png) 点击体验
DeepSeekR1满血版](https://ai.csdn.net/?utm_source=cknow_pc_blogdetail&spm=1001.2101.3001.10583) ![程序员都在用的中文IT技术交流社区](https://g.csdnimg.cn/side-toolbar/3.6/images/qr_app.png)

程序员都在用的中文IT技术交流社区

![专业的中文 IT 技术社区,与千万技术人共成长](https://g.csdnimg.cn/side-toolbar/3.6/images/qr_wechat.png)

专业的中文 IT 技术社区,与千万技术人共成长

![关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!](https://g.csdnimg.cn/side-toolbar/3.6/images/qr_video.png)

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回顶部

[![](https://upload.jianshu.io/users/upload_avatars/2286722/963827d773bf.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/100/h/100/format/webp)](https://www.jianshu.com/u/184d9cf3e988)

[苍蝇的梦](https://www.jianshu.com/u/184d9cf3e988) IP属地: 福建

字数 805 阅读 7,082

2019-06-10 遇到的一点小问题
新需求是在U盘插入设备时实现自动挂载功能,一般都是推荐使用 [udev](https://links.jianshu.com/go?to=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2FUdev) 。记录一下第一次使用出现的问题。

###### .

刚开始先按 [网上找的方法](https://links.jianshu.com/go?to=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Flinux%2Fl-cn-udev%2F)
,在 `/etc/udev/rules.d/` 创建个*.rules* 文件,再根据插入动作调用*.sh* 文件来执行 *[mount](https://www.jianshu.com/p/3abfdf7dd21f)* 操作:

```bash
KERNEL=="sd[a-z]*",ACTION=="add",RUN+="/srv/wannoo.sh %k"

语法挺简单的,就是容易遇坑:有一次 KERNELACTION 顺序弄反,然后就执行失败;还有一U盘只能读到sdd,所以 KERNEL==”sd[a-z][1-9]“ 读不到,为了兼容只能改成 KERNEL==”sd[a-z] “*;然后换行也要注意;还有就是一行命令数限制五个,多了后面的不生效。
不过虽然把语法弄懂了,但mount还是出错了:

1
2
3
4
5
6
7
8
mount: /tmp/sdc1: permission denied.
echo $? == 32 #分区不支持挂载是返回这个,随便输个地址也返回这个,服了。

FUSE exfat 1.3.0 #在命令行mount也有提示这句,返回值为0,能正常mount。
fusermount: mount failed: Operation not permitted
echo $? == 1 #这边多了上面那句返回,然后返回值是1,挂载失败。

WARN: volume was not unmounted cleanly. #偶尔出现这句,可以忽略了。
.

测了几次,依然没有解决。只好再到 ArchLinux的wiki 上面仔细看看 udev (简体中文) 。按说明修改了 /etc/systemd/system/systemd-udevd.service 文件,也按要求 把挂载动作放在 udev 规则内部 ,可惜测试了还是不行。也按这篇 Auto-mounting USB storage with udev 的示例试了一下,依旧失败返回 fusermount: mount failed: Operation not permitted

1
KERNEL=="sd[a-z]*",ACTION=="add",RUN+="/bin/mkdir /tmp/%k",RUN+="bin/mount -r /dev/%k /tmp/%k"
.

因为 udev (简体中文) 写的翻译时间是 2017-10-19 ,有点旧了,所以又仔细看了看 udev英文文档 。发现里面使用的示例不是 bin/mount 而是使用 /usr/bin/systemd-mount 去挂载。

1
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=yes --collect $devnode /media"

在控制台试了下 systemd-mount命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@archlinux ~]# /usr/bin/systemd-mount /dev/sdb2 /media/sdb2
Failed to start transient mount unit: Unit media-sdb2.mount already exists. #之前未卸载
# echo $? == 1
[root@archlinux ~]# systemd-mount -u /media/sdb2
Stopped unit media-sdb2.mount for mount point: /media/sdb2 #卸载成功
# echo $? == 0
[root@archlinux ~]# systemd-mount /dev/sdb2 /media/sdb2
Started unit media-sdb2.mount for mount point: /media/sdb2 #任务执行成功,不一定mount成功。
# echo $? == 0
[root@archlinux ~]# systemctl status media-sdb2.mount
* media-sdb2.mount - /media/sdb2
Loaded: loaded (/run/systemd/transient/media-sdb2.mount; transient)
Transient: yes
Active: active (mounted) since Tue 2019-06-11 08:55:28 UTC; 32s ago
Where: /media/sdb2
What: /dev/sdb2
Tasks: 1 (limit: 4915)
Memory: 2.5M
CGroup: /system.slice/media-sdb2.mount
\`-10153 /usr/bin/mount.exfat /dev/sdb2 /media/sdb2 -o rw

Jun 11 08:55:27 archlinux systemd[1]: Mounting /media/sdb2...
Jun 11 08:55:28 archlinux systemd[1]: Mounted /media/sdb2.
# echo $? == 0

测试中发现加上 --automount=yes 选项之后,如果使用 umount 命令 去卸载, df 能看出成功,但是再 cd 进入之前挂载的文件夹又会自动挂载起来,所以卸载动作需要使用 systemd-umount 命令systemd-mount -u 命令 去卸载。

最后,在**.rules** 文件里使用 systemd-mount 命令试一下挂载U盘。

1
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", RUN{program}+="/bin/mkdir /media/%k" ,RUN{program}+="/usr/bin/systemd-mount --no-block --collect $devnode /media/%k"

有一个U盘成功了,但有一个U盘一直没看到挂载成功,所以又用控制台试了一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@archlinux ~]# /usr/bin/systemd-mount --no-block --collect /dev/sdb2 /media/sdb2
Started unit media-sdb2.mount for mount point: /media/sdb2 #任务执行成功,不一定mount成功。
[root@archlinux ~]# systemd-mount /dev/sdb2 /media/sdb2
Job for media-sdb2.mount failed. #不使用--no-block选项能等到结果出现
See "systemctl status media-sdb2.mount" and "journalctl -xe" for details. #根据提示去看状态
[root@archlinux media]# systemctl status media-sdb2.mount
* media-sdb2.mount - /media/sdb2
Loaded: loaded (/run/systemd/transient/media-sdb2.mount; transient)
Transient: yes
Active: failed (Result: exit-code) since Tue 2019-06-11 08:48:38 UTC; 5s ago
Where: /media/sdb2
What: /dev/sdb2

Jun 11 08:48:38 archlinux systemd[1]: Mounting /media/sdb2...
Jun 11 08:48:38 archlinux mount[5851]: mount: /media/sdb2: unknown filesystem type 'exfat'.
Jun 11 08:48:38 archlinux systemd[1]: media-sdb2.mount: Mount process exited, code=exited, status=32/n/a
Jun 11 08:48:38 archlinux systemd[1]: media-sdb2.mount: Failed with result 'exit-code'.
Jun 11 08:48:38 archlinux systemd[1]: Failed to mount /media/sdb2.
[root@archlinux ~]# pacman -Sy exfat-utils
... #成功安装 exfat-utils

加上 --no-block 可以异步等待结果,但现在并不需要,所以去掉后重试。马上返回失败信息了,然后根据提示使用 systemctl status media-sdb2.mount 查看状态及记录,才知道是缺少 exfat 支持,这个问题之前在其他设备上 monut 时有遇到过,使用 pacman -Sy exfat-utils 安装后,重新拔插U盘再试了一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@archlinux media]# systemctl status media-sdb2.mount
* media-sdb2.mount - /media/sdb2
Loaded: loaded (/run/systemd/transient/media-sdb2.mount; transient)
Transient: yes
Active: activating (mounting) since Tue 2019-06-11 08:50:36 UTC; 93ms ago
Where: /media/sdb2
What: /dev/sdb2
Cntrl PID: 7177 (mount)
Tasks: 2 (limit: 4915)
Memory: 2.5M
CGroup: /system.slice/media-sdb2.mount
|-7177 /usr/bin/mount /dev/sdb2 /media/sdb2
\`-7178 /usr/bin/mount.exfat /dev/sdb2 /media/sdb2 -o rw

Jun 11 08:50:36 archlinux systemd[1]: Mounting /media/sdb2...
# echo $? == 3

不懂为什么返回值是3。使用 df 看了下,已经正常挂载了。先不理会。

.

网上随便搜了几篇文章参考,这边记录一下:

使用 udev 高效、动态地管理 Linux 设备文件
在 Linux 中如何编写基本的 udev 规则
An introduction to Udev: The Linux subsystem for managing device events
udev学习笔记汇总

关于 udevadm命令 ,有用到几个先记录一下:

1
2
3
4
5
6
#实时监听U盘拔插,在控制台显示动态:
udevadm monitor
#模拟插拔动作:
udevadm test --action="add" /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2.3/1-2.3:1.0/host3/target3:0:0/3:0:0:0/block/sdd
#规则修改后,重载生效:
udevadm control --reload

然后有几个会用到的位置也记一下:

1
2
3
4
/usr/lib/udev/rules.d    #系统定义的规则
/etc/udev/rules.d/ #自定义的规则
/usr/lib/systemd/system/systemd-udevd.service #系统定义的配置文件
/etc/systemd/system/systemd-udevd.service #将系统定义的配置文件复制过来修改

https://wiki.archlinux.org/index.php/Udisks_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

0人点赞

Linux

更多精彩内容,就在简书APP

“小礼物走一走,来简书关注我”

还没有人赞赏,支持一下

苍蝇的梦 路走对了,就不怕远

总资产9 共写了4.4W字 获得127个赞 共30个粉丝

赞 1赞

赞赏

手机看全文

写在开头:
1看这里的时候,请确保你已将熟悉JavaScript以及了解Vue的语法, Django的语法也略懂一二。
如果不是很了解,请点击这里查看学习文档VueDjango,否则下文可能有些不好理解。
2文章有点长 ,因为包含了一个Index.vue页面。
3第一次写长文章,所以排版很尴尬,请指正。

  1. 安装Vue环境
  2. 安装element-ui组件 使用其组件美化界面
1
npm i element-ui -S ||  npm install element-ui --save
1
2
3
4
5
main.js 
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI)
  1. 安装axios 使用其完成前端到后端的请求

由于axios 使用Vue.use(无效),所以要将其绑定在Vue原型上

1
npm install axios --save |  brew install axios --save
1
2
3
4
5
import axios from 'axios'

axios.defaults.baseURL = 'http://localhost:8000'

Vue.prototype.$axios = axios
  1. 安装Django及配置环境
  2. 配置mysql数据库,使用sqlite3的 跳过此步骤无需配置
1
2
3
4
5
6
7
8
9
10
11
settings.py
DATABASES = {
'default': { #
'ENGINE': 'django.db.backends.mysql', # 不同库有不同的殷勤
'NAME': 'python_use', # 使用的库名
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}

配置完成后请查看django是否报错,不报错即连接成功

  1. 安装 pipdjango-cors-headers
1
2

pip install django-cors-headers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
settings.py

INSTALLED_APPS = {
...
'corsheaders',
...
}


MIDDLEWARE = [
...

'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]


CORS_ORIGIN_ALLOW_ALL = True





至此,已将Vue和Django安装并配置好,接下来写一个简单的CRUD操作。
请确认你的整个项目目录与此类似

![](Vue + Django/2064404-d3a828d4530715b4.png)

项目目录结构

以下使用的目录均为此图所示


  1. 配置路由
1
2
3
4
5
first/urls.py 
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'', include('crud.urls')),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
crud/urls.py




from django.conf.urls import url
from . import views

urlpatterns = [
url('create/', views.create, name = 'create'),
url('read', views.read, name = 'read'),
url('update/', views.update, name = 'update'),
url('delete/', views.delete, name = 'delete'),
url('search', views.search, name = 'search')
]
  1. 创建models,即在数据库中创建表
1
2
3
4
5
from django.db import models
class Books ( models.Model ):
book_name = models.CharField( max_length = 255 )
book_price = models.DecimalField( max_digits = 5, decimal_places = 2 )
book_time = models.DateTimeField( '保存日期', auto_now_add = True )

Models创建完成后运行命令 将其应用到数据库中并创建表
如果不懂 请返回顶部阅读Django文档

1
2
python manage.py makemigrations
python manage.py migrate
  1. 编写views.py 完成增删改查的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
# 1 获取前端传递来的参数
# 1.1 get方法发送的参数
request.GET['content']
# 1.2 post方法发送的参数
obj = json.loads(request.body)
name = obj['name']
# 2 由于使用Books.objects下的方法,获取到的数据为Query Set类型,
# 所以需要使用serializers.serialize("json", books)
# 将查询到的数据进行序列化,变成可读的对象。
# 3 向前端返回处理结果
return HttpResponse(json.dumps(res), content_type="application/json")
# 将res变成json字符串返回给前端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

from __future__ import unicode_literals
from django.shortcuts import render
from django.http import HttpResponse
import json
from django.core import serializers
from django.utils import timezone
from crud.models import Books

def search(request):
content = request.GET['content']
try:
books = serializers.serialize("json",Books.objects.filter(book_name__contains=content))
res = {
"code": 200,
"data": books
}
print(books)
except Exception,e:
res = {
"code": 0,
"errMsg": e
}
return HttpResponse(json.dumps(res), content_type="application/json")

def create(request):
print('create')
obj = json.loads(request.body)
name = obj['name']
price = obj['price']
try:
book = Books(book_name=name, book_price=price, book_time=timezone.now())
book.save()
res = {
"code": 200,
}
except Exception,e:
res = {
"code": 0,
"errMsg": e
}
return HttpResponse(json.dumps(res), content_type="application/json")

def read(request):
print('read')
try:
res = {
"code": 200,
"data": serializers.serialize("json",Books.objects.filter())
}
except Exception,e:
res = {
"code": 0,
"errMsg": e
}
return HttpResponse(json.dumps(res), content_type="application/json")

def update(request):
print('update')
obj = json.loads(request.body)
pid = obj['id']
name = obj['name']
price = obj['price']
try:
Books.objects.filter(id=pid).update(book_price=price, book_name=name)
res = {
"code": 200
}
except Exception,e:
res = {
"code": 0,
"errMsg": e
}
return HttpResponse(json.dumps(res), content_type="application/json")

def delete(request):
print('delete')
obj = json.loads(request.body)
print(obj)
pid = obj['id']
try:
Books.objects.filter(id=pid).delete()
res = {
"code": 200
}
except Exception,e:
res = {
"code": 0,
"errMsg": e
}
return HttpResponse(json.dumps(res), content_type="application/json")

  1. 配置路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
frontend/src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'index',
component: Index
}
]
})
  1. 编写路由中使用到的组件 与上面import所用名称和路径需要一致,请耐心看完注释。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21








this.$axios.get('/search', {
params: {
content: this.search
}
}).then(res => {
console.log(res)
})
this.$axios.post('/delete/', JSON.stringify(row)).then(res => {

console.log(res)


})

以下为Index.vue的全部页面,包含增删改查的基本操作,以及更改和新增时的弹出框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
frontend/src/components/Index.vue
<template>
<div>
<el-button type="primary" round @click="handleShowCreate">增加书籍</el-button>
<el-input v-model="search" placeholder="请输入内容" style="width: 200px" @keyup.enter.native="handleSearch"/>
<el-button type="primary" round @click="handleSearch">搜索</el-button>
<el-table :data="booksData" height="250" border style="width: 600px; margin: 40px auto;" v-loading="loading">
<el-table-column
prop="book_name"
label="书名"
align="center"
width="200">
</el-table-column>
<el-table-column
prop="book_price"
label="价格"
align="center"
width="200">
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="修改书籍" :visible.sync="dialogUpdateVisible">
<el-form :model="updateData">
<el-form-item label="书籍名称">
<el-input auto-complete="off" v-model="updateData.name"></el-input>
</el-form-item>
<el-form-item label="书籍价格">
<el-input-number v-model="updateData.price" :precision="2" :step="0.01" :max="9999"></el-input-number>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel('dialogUpdateVisible')">Cancel</el-button>
<el-button type="primary" @click="handleConfirm('dialogUpdateVisible')">Submit</el-button>
</div>
</el-dialog>
<el-dialog title="增加书籍" :visible.sync="dialogCreateVisible">
<el-form :model="createData">
<el-form-item label="书籍名称">
<el-input auto-complete="off" v-model="createData.name"></el-input>
</el-form-item>
<el-form-item label="书籍价格">
<el-input-number v-model="createData.price" :precision="2" :step="0.01" :max="9999"></el-input-number>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel('dialogCreateVisible')">Cancel</el-button>
<el-button type="primary" @click="handleCreate('dialogCreateVisible')">Submit</el-button>
</div>
</el-dialog>
</div>
</template>

<script>
export default {
name: 'index',
data () {
return {
search: '',
booksData: [],
oldData: {},
updateData: {},
createData: {
name: '',
price: 0
},
dialogUpdateVisible: false,
dialogCreateVisible: false,
loading: true
}
},
methods: {
handleShowCreate () {
this.dialogCreateVisible = true
},
handleCreate () {
if (this.createData.name === '') {
this.$message.error('please input book name')
return
}
if (this.createData.price === 0) {
this.$message.error('please input book price')
return
}
this.$axios.post('/create/', JSON.stringify(this.createData)).then(res => {
if (res.data.code === 200) {
this.$message.success(`create ${this.createData.name} success`)
this.dialogCreateVisible = false
this.handleRead()
} else {
this.$message.error("can't read books database")
}
})
console.log(this.createData)
},
handleRead () {
this.booksData = []
this.$axios.get('/read').then(res => {
this.loading = false
if (res.data.code === 200) {
let books = JSON.parse(res.data.data)
for (let i in books) {
books[i].fields.id = books[i].pk
books[i].fields.book_price = Number(books[i].fields.book_price)
this.booksData.push(books[i].fields)
}
console.log(this.booksData)
} else {
this.$message.console.error("can't read books database")
}
}).catch((res) => {
console.log(res)
})
},
handleUpdate (index, row) {
this.dialogUpdateVisible = true
this.updateData = Object.assign({}, {
id: row.id,
name: row.book_name,
price: row.book_price,
time: row.book_time
})
this.oldData = Object.assign({}, {
id: row.id,
name: row.book_name,
price: row.book_price,
time: row.book_time
})
},
handleDelete (index, row) {
this.$confirm(`are you sure to delete ${this.updateData.name} ?`, '', {
confirmButtonText: 'submit',
cancelButtonText: 'cancel',
type: 'warning'
}).then(() => {
this.$axios.post('/delete/', JSON.stringify(row)).then(res => {
if (res.data.code === 200) {
this.$message.success(`delete ${this.updateData.name} success`)
this.handleRead()
} else {
this.$message.error("can't read books database")
}
})
}).catch(() => {
this.$message.info('cancel delete')
})
},
handleCancel (arg) {
this.$message.info('cancel')
this[arg] = false
},
handleConfirm (arg) {
if (this.updateData.name === this.oldData.name && this.updateData.price === this.oldData.price) {
this.$message.error('please update something or cancel')
return
}
this[arg] = false
this.$axios.post('/update/', JSON.stringify(this.updateData)).then(res => {
if (res.data.code === 200) {
this.$message.success(`update ${this.updateData.name} success`)
this.handleRead()
} else {
this.$message.error("can't read books database")
}
})
},
handleSearch () {
this.$axios.get('search', {
params: {
content: this.search
}
}).then(res => {
if (res.data.code === 200) {
if (res.data.data && JSON.parse(res.data.data).length > 0) {
this.booksData = []
let books = JSON.parse(res.data.data)
for (let i in books) {
let obj = {
id: books[i].pk,
book_name: books[i].fields.book_name,
book_price: Number(books[i].fields.book_price),
book_time: books[i].fields.book_time
}
this.booksData.push(obj)
}
} else {
this.$message.error(`can't search contains of '${this.search}' in database`)
}
} else {
this.$message.error(`can't search books in database`)
}
})
}
},
mounted () {
this.handleRead()
}
}
</script>

到这里,一个增删改查基本操作的页面就写完了,如果哪里有问题可以留言指正。 git源码以上传, 没事可以star/fork 更新将在以下附注后增加。

https://github.com/RogersLei/django-vue


附注 :

  1. Vue添加事件所用到的修饰符:

    ![](Vue + Django/2064404-1aa984b701bf3e11.png)

    Vue事件绑定修饰符

  2. Django中模糊查询用到的语法:

YourModels.objects.filter(headline__contains=str)
字段名__contains / __icontains 忽略大小写

更多精彩内容,就在简书APP

“小礼物走一走,来简书关注我”

还没有人赞赏,支持一下

总资产23共写了2.2W字获得33个赞共22个粉丝

推荐阅读更多精彩内容

  • 一.前言 最近接手了一个项目,后端是django,前端是django自带的模板,用的是jinja2,写了一段时间发…

  • 组织文章借鉴 ——培训师的21项修炼 书籍结构:错误的案例情景重现-抛出问题,传道受业解惑也 我们假设一个场景,大…

  • 每天总是忙忙碌碌,感觉时间完全不够用,更不要说是学习了,可是忙忙碌碌到最后感觉收获也很小,就像大家说的,瞎忙活。…

  • 和姑姑聊起当时借钱给已故父亲治病时的场景,我依稀记得当时我和涛古,妈妈给厂里老板下跪借那三万块的场景。这辈子希望以…

本文整合Django和Vue.js  并引入elementUi 实现前后端分离项目环境

最终的成品是设计出一个ElementUi风格的页面可以添加和显示学生信息.

Django作为Python 三大Web框架之一,因其集成了大量的组件(例如: Models Admin Form 等等)而大受欢迎,但是他本身自带的template模板实在是有点弱.于是考虑整合Vue.js同时引入ElementUI 组件,可以更加快速高效的开发出美观实用的Web页面.

Python

本文版本:Python 3.5

安装教程: https://www.runoob.com/python3/python3-install.html

Pycharm

本文版本:2019.1.3

PyCharm 2019.1.3 (Community Edition)

安装教程:https://www.runoob.com/w3cnote/pycharm-windows-install.html

Django

本文版本:2.2.3

安装教程:https://www.runoob.com/django/django-install.html

node.js

本文版本:10.16.3

安装教程:https://www.runoob.com/nodejs/nodejs-install-setup.html

MySQL

本文版本: 8.0.13 for Win64

安装教程:https://www.runoob.com/mysql/mysql-install.html

本文的Pycharm为社区版,如果为专业版则字段Django项目的创建选项,创建项目将更加简单.

1.创建django项目:DjangoElementUI

创建文件夹E:\PycharmProjects:

在项目文件夹目录输入Windows 命令行如下

1
django-admin.py startproject DjangoElementUI

成功创建项目完成后文件夹结构如下图:

进入项目文件夹目录,在目录中输入命令

1
python manage.py runserver 0.0.0.0:8000

看到如下提示则为项目创建成功

在浏览器输入你服务器的 ip(这里我们输入本机 IP 地址: 127.0.0.1:8000) 及端口号,如果正常启动,输出结果如下:

2.数据库配置

Django 对各种数据库提供了很好的支持,包括:PostgreSQL、MySQL、SQLite、Oracle。

Django 为这些数据库提供了统一的调用API。 我们可以根据自己业务需求选择不同的数据库。

MySQL 是 Web 应用中最常用的数据库。

本文采用MySQL

第一次使用MySQL需要安装 MySQL驱动,在项目文件夹目录下执行以下命令安装:

1
pip install pymysql

Django无法直接创建数据库(只能操作到数据表层),我们需要手工创建MySQL数据库.

以下通过命令行创建 MySQL 数据库:Django_ElementUI

登录数据库:

数据库安装文件夹bin文件夹下输入命令

1
mysql -u root -p 

创建数据库:

1
create DATABASE Django_ElementUI DEFAULT CHARSET utf8;

Django配置数据库

在项目的 settings.py 文件中找到 DATABASES 配置项,将其信息修改为:

1
'ENGINE': 'django.db.backends.mysql',  'NAME': 'Django_ElementUI',  

在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置 (告诉 Django 使用 pymysql 模块连接 mysql 数据库)

1
pymysql.install_as_MySQLdb()

3.利用Django模型设计数据库表

Django 规定,如果要使用模型,必须要创建一个 app。

创建Django APP:myApp

我们使用以下命令创建一个Django app:myApp

1
django-admin.py startapp myApp

成功后的项目文件夹目录如下:

设计数据库表

在myApp下的models.py设计表:

这里我们设计一个Student表,用来存储学生信息.

表字段

字段类型

含义

student_name

Varchar类型

学生姓名

student_sex

Varchar类型

学生性别

create_time

Datetime类型

创建日期时间

1
from django.db import modelsclass Student(models.Model):    student_name = models.CharField(max_length=64)    student_sex = models.CharField(max_length=3)    create_time = models.DateTimeField(auto_now=True)

在 settings.py 中找到INSTALLED_APPS这一项,如下:

1
'django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',

生成数据库迁移文件

在命令行中运行:

1
python manage.py makemigrations myApp

执行成功后结果:

执行迁移文件来完成数据库表的创建

在命令行中运行:

1
python manage.py migrate myApp

执行成功后结果:

查看数据库中数据库表已经生成成功

(django默认在makemigrations会为表对象创建主键id,id = models.AutoField(primary_key=True))

4.Django创建新增和查询学生信息接口

在myApp目录下的views.py中创建两个视图函数

1
from __future__ import unicode_literalsfrom django.http import JsonResponsefrom django.core import serializersfrom django.shortcuts import renderfrom django.views.decorators.http import require_http_methodsfrom myApp.models import Student@require_http_methods(["GET"])def add_student(request):        student = Student(student_name=request.GET.get('student_name'))        response['msg'] = 'success'        response['error_num'] = 0        response['error_num'] = 1return JsonResponse(response)@require_http_methods(["GET"])def show_students(request):        students = Student.objects.filter()        response['list'] = json.loads(serializers.serialize("json", students))        response['msg'] = 'success'        response['error_num'] = 0        response['error_num'] = 1return JsonResponse(response)

5.配置路由

1.在myApp目录下,新增一个urls.py文件,用于创建此APP下的分支路由,把新增的两个视图函数添加到路由里面.

1
from django.conf.urls import url    url(r'^add_book/', views.add_book),    url(r'^show_books/', views.show_books),

2.把上面创建的myApp下的分支路由加到DjangoElementUI下的主路由中urls.py.

1
from django.contrib import adminfrom django.urls import pathfrom django.conf.urls import urlfrom django.conf.urls import include    url(r'^admin/', admin.site.urls),    url(r'^api/', include(urls)),

至此Django部分已经完成,总结下我们利用Django完成了数据库的创建,并创建了两个视图函数作为接口给前端调用.

1.安装vue-cli脚手架

在DjangoElementUI根目录下输入命令:

1
npm install -g vue-cli

2.安装好后,新建一个前端工程目录:appfront

在DjangoElementUI项目根目录下输入命令:

1
vue-init webpack appfront

3.进入appfront目录安装vue所需要的依赖

1
npm install

4.安装ElementUI

1
npm i element-ui -S

5.创建新vue页面

在src/component文件夹下新建一个名为Studengt.vue的组件,通过调用之前在Django上写好的api,实现添加学生和展示学生信息的功能.

1
<el-row display="margin-top:10px"><el-input v-model="input" placeholder="请输入学生姓名" style="display:inline-table; width: 30%; float:left"></el-input><el-button type="primary" @click="addStudent()" style="float:left; margin: 2px;">新增</el-button><el-table :data="studentList" style="width: 100%" border><el-table-column prop="id" label="编号" min-width="100"><template scope="scope"> {{ scope.row.pk }} </template><el-table-column prop="student_name" label="姓名" min-width="100"><template scope="scope"> {{ scope.row.fields.student_name }} </template><el-table-column prop="student_sex" label="性别" min-width="100"><template scope="scope"> {{ scope.row.fields.student_sex }} </template><el-table-column prop="add_time" label="添加时间" min-width="100"><template scope="scope"> {{ scope.row.fields.create_time }} </template>this.$http.get('http://127.0.0.1:8000/api/add_student?student_name=' + this.input)var res = JSON.parse(response.bodyText)if (res.error_num === 0) {this.$message.error('新增学生失败,请重试')this.$http.get('http://127.0.0.1:8000/api/show_students')var res = JSON.parse(response.bodyText)if (res.error_num === 0) {this.studentList = res['list']this.$message.error('查询学生失败')<!-- Add "scoped" attribute to limit CSS to this component only -->

6.配置路由

appfront/router文件夹下的index.js中增加页面路由.

1
import Router from 'vue-router'import HelloWorld from '@/components/HelloWorld'import Student from '@/components/Student'export default new Router({

appfront文件夹下的main.js中引入ElementUI并注册.

1
import router from './router'import '../node_modules/element-ui/lib/theme-chalk/index.css'import ElementUI from 'element-ui'Vue.config.productionTip = false

7.打包并启动前端项目

打包vue项目

1
npm run build

启动前端项目

1
npm run dev

出现下面信息则说明我们前端项目已经构建成功.

去浏览器访问页面地址:http://localhost:8080/#/student

出现如下页面说明我们的页面已经成功.

截止到目前,我们已经成功通过Django创建了一个后端服务,通过Vue.js + ElementUI 实现了前端页面的构建,但是他们运行在各自的服务器,而且前端页面还无法调用后端的接口.

接下来我们需要将两个项目真正的整合到一个成一个项目.

1.引入用于HTTP解析的vue-resource

前端vue项目调用后端需要引入vue-resource

在appfront文件下运行命令:

1
npm install 

安装完成后在main.js中引入vue-resource

1
import router from './router'import '../node_modules/element-ui/lib/theme-chalk/index.css'import ElementUI from 'element-ui'import VueResource from 'vue-resource'Vue.config.productionTip = false

2.在Django层注入header

为了让后端可以识别前端需求,我们须要在Django层注入header,用Django的第三方包django-cors-headers来解决跨域问题:

在DjangoElementUI根目录下输入命令:

1
pip install django-cors-headers

在settings.py中增加相关中间件代码

1
'django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','corsheaders.middleware.CorsMiddleware',     'django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',CORS_ORIGIN_ALLOW_ALL = True   

3.修改Django路由

这一步我们通过Django路由配置连接前后端资源.

首先我们把Django的TemplateView指向我们刚才生成的前端dist文件

在DjangoElementUI目录下的urls.py中增加代码:

1
from django.conf.urls import urlfrom django.contrib import adminfrom django.conf.urls import includefrom django.views.generic import TemplateView    url(r'^admin/', admin.site.urls),    url(r'^api/', include(urls)),    url( r'^vue/', TemplateView.as_view( template_name="index.html" ) )

接着修改静态资源文件路径也指向前端appfront 相关文件

在DjangoElementUI目录下的setting.py中增加代码:

1
'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [os.path.join(BASE_DIR, 'appfront/dist')],  'django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',    os.path.join(BASE_DIR, "appfront/dist/static")

3.重新构建前端项目

appfront目录下输入命令:

1
npm run build

重新启动Django项目

1
python manage.py runserver

输入地址:http://localhost:8000/vue/#/student

添加一条记录

至此,大功告成!

此份指南在配置的过程踩过不少坑,以下是踩的印象较深的坑.

1.数据库创建的过程中务必注意大小写的问题,数据库字段和Django的Models页面,View页面和Vue中的组件页面都有关联.很容易一个大小写不注意,导致整个接口无法使用.

2.连接MySQL需要按照对应的包,同时需要在根目录的_ini_.py中引入pymysql

3.在整个环境的搭建过程中VUE环境的搭建需要耗费较长的npm安装时间,需要耐心等待.

4.前后台连接需要在前端引入vue-resource,Django需要引入django-cors-headers

引言

大U的技术课堂 的新年第一课,祝大家新的一年好好学习,天天向上:)

本篇将手把手教你如何快速而优雅的构建前后端分离的项目,想直接上手请往后翻!

目录:

  1. 我为什么要选择Django与VueJS?

  2. Django和VueJS是如何结合起来的?

  3. 实操

  4. 创建 Django 项目

  5. 创建 Django App 做为后端

  6. 创建 VueJS 项目作为前端

  7. 使用 Webpack 处理前端代码

  8. 配置 Django 模板的搜索路径

  9. 配置 Django 静态文件搜索路径

  10. 开发环境

  11. 生产环境(部署到 UCloud)

正文:

我为什么要选择Django与VueJS?

首先介绍一下我看重的点:

Django (MVC框架) - The Web framework for perfectionists with deadlines

  • Python

  • ORM

  • 简单、清晰的配置

  • Admin app

Django 仅因为 Python 的血统,就已经站在了巨人的肩膀上,配置管理( SaltStack、Ansible )
,数据分析( Pandas ),任务队列( Celery ),Restful API( Django REST framework ),HTTP请求( requests ),再加上高度抽象的ORM,功能强大的 Query Expressions,简单清晰的配置,着重提一下堪称神器的自带App: Admin,有了它你再也不用将一些经常变化的配置写在文件里面,每次增删改都重新发布一次,你只需要定义出配置的 data scheme ,只需要几行代码,Django Admin便为你提供美观,并带有权限控制的增删改查界面,而且可以通过ORM为它生成的API来做到定制化的更新,比如直接读某个wiki上的配置,自动的写入数据库,伪代码如下:

1
2
3
4
import pandas as pd
settings = pd.read_html('http://某个gitlab的README 或者 某个redmine wiki')
settings = clean(settings)
update(settings)

最后还可以使用 django-celery 的 celery-beat 按 Interval/crontab 的方式扔更新配置的任务到 celery 队列里面,最最重要的是,这些都可以在Django Admin后台直接配置哦,还不够优雅?请联系我

VueJS (MVVM框架) - Vue.js

  • 数据双向绑定
  • 单文件组件
  • 清晰的生命周期
  • 学习曲线平滑
  • vue-cli

前端是DevOps的弱项,我需要一个 MVVM 框架来提升交互和节约时间,在试过 AngularJS ,ReactJS,VueJS之后我选择了VueJS,因为我觉得写 VueJS 代码的感觉最接近写 Python

着重提一下单文件组件:

特别清晰,一个文件包含且仅包含三块

  1. 前端渲染的模板
  2. 专为此模板写渲染逻辑的
  3. 专为此模板写样式的

这样可以达到什么效果呢?一个文件一个组件,每个组件有它自己的逻辑与样式,你不用关心什么 local 什么 global ,CSS样式加载先后、覆盖问题,因为它是『闭包』的,而且『自给自足』,不知道这样说好不好理解

当然组件之间也是可以通信的,举个例子,我有一个组件叫 ListULB ,使用表格展示了我拥有的所有 ULB (负载均衡),ListULB 做了一件事,从 API 获取 ULB 对象列表并 for 循环展现出来, ListULB 可以放到某个页面里,可以放到弹框里,放到模态框里,任何地方都可以,因为这个组件对外交互的只有API

如果我现在要写一个组件叫 AddVServer ,功能是可以为任意一个 ULB 对象添加VServer,我的写法是将在 AddVServer 组件创建的时候,将 ULB 对象传给 AddVServer 组件,这样AddVServer 组件拿到这个对象,就可以直接根据对象的ID等,创建出当前行的ULB的VServer了,伪代码如下:

1
2
3
4
5
6
<ListULB>
for **ulb_object** in ulbs_list:
{{ ulb_object.name }}
{{ ulb_object.id }}
<AddVServer :current_ulb='**ulb_object**'></AddVServer>
</ListULB>

注意双星号包着的对象,在 ListULB 组件里面是每行的ULB,传给AddServer组件之后,变成了 current_ulb 对象,拿到id为 current_ulb.id 尽情的为它创建 VServer 吧

如果我要为指定 VServer 创建 RServer 呢,一样的

看出来了吧,进行开发之前,前端组件的结构与数据的结构对应起来可以省好多时间,数据驱动前端组件,棒吗?

谁不喜欢优雅的代码呢, 『Data drive everything』 多么的省脑细胞

以上就是我选择Python与VueJS的原因

Django与VueJS是如何结合起来?

  • 首先我选择了VueJS的前端渲染,自然放弃了Django的后端模板引擎渲染
  • 然后业务逻辑放到了前端,放弃了Django的View(其实也就是前后端分离必要的条件)
  • 保留了Django的 Controller (URLconf) 来实现前端路由的父级路由,可以达到不同页面使用不同的前端框架, 页面内部使用各自独有的前端路由的效果,万一老大给你配了前端呢,万一前端只想写 ReactJS 呢
  • 保留了Django的 Model ,前面说了Django的ORM太好用了,而且可以配合Django Admin

所以综合来说就是:

M(Django) + C(Django) + MVVM (VueJS) = M + MVVM + C = MMVVMC

(为了容易理解,并没有使用Django自称的MTV模式理解,感兴趣看看我画的图)

总结:作为以改变世界为己任的 DevOps ,MVC框架后端渲染的柔弱表现力与繁杂的交互已经不能满足我们了,…..省略1000子…..,所以我选择这样构建项目,嗯…

好吧,也该开始了

代码块中的修改都会用爽星号括起来,比如: **changed**

本文为了精简篇幅,默认您已经安装了必要的 命令行界面(CLI),比如 vue-cli等

1. 创建Django项目

命令:

1
django-admin startproject ulb_manager

结构:

1
2
3
4
5
6
7
.
├── manage.py
└── ulb_manager
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

2. 进入项目根目录,创建一个 app 作为项目后端

命令:

1
2
cd ulb_manager
python manage.py startapp backend

即:app 名叫做 backend

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── backend
│ ├── __init__.py
│ ├── admin.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── ulb_manager
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

3. 使用vue-cli创建一个vuejs项目作为项目前端

命令:

1
vue-init webpack frontend

即:项目名叫 frontend

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.
├── backend
│ ├── __init__.py
│ ├── admin.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── frontend
│ ├── README.md
│ ├── build
│ │ └── ....
│ ├── config
│ │ ├── dev.env.js
│ │ ├── index.js
│ │ ├── prod.env.js
│ │ └── test.env.js
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── App.vue
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ └── Hello.vue
│ │ └── main.js
│ ├── static
│ └── test
│ └── ...
├── manage.py
└── ulb_manager
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

结构总结:

可以看到项目根目录有两个新文件夹,一个叫 backend ,一个叫 frontend,分别是:

  • backend Django的一个app
  • frontend Vuejs项目

4. 接下来我们使用 webpack 打包Vusjs项目

命令:

1
2
3
cd frontend
npm install
npm run build

结构:

我引入了一些包,比如element-ui等,你的static里面的内容会不同,没关系 index.html 和 static 文件夹相同就够了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dist
├── index.html
└── static
├── css
│ ├── app.42b821a6fd065652cb86e2af5bf3b5d2.css
│ └── app.42b821a6fd065652cb86e2af5bf3b5d2.css.map
├── fonts
│ ├── element-icons.a61be9c.eot
│ └── element-icons.b02bdc1.ttf
├── img
│ └── element-icons.09162bc.svg
└── js
├── 0.8750b01fa7ffd70f7ba6.js
├── vendor.804853a3a7c622c4cb5b.js
└── vendor.804853a3a7c622c4cb5b.js.map

构建完成会生成一个 文件夹名字叫dist,里面有一个 index.html 和一个 文件夹static ,

5. 使用Django的通用视图 TemplateView

找到项目根 urls.py (即ulb_manager/urls.py),使用通用视图创建最简单的模板控制器,访问 『/』时直接返回 index.html

1
2
3
4
5
urlpatterns = [
url(r'^admin/', admin.site.urls),
**url(r'^$', TemplateView.as_view(template_name="index.html")),**
url(r'^api/', include('backend.urls', namespace='api'))
]

6. 配置Django项目的模板搜索路径

上一步使用了Django的模板系统,所以需要配置一下模板使Django知道从哪里找到index.html

打开 settings.py (ulb_manager/settings.py),找到TEMPLATES配置项,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'DIRS': [],
**'DIRS': ['frontend/dist']**,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

注意这里的 frontend 是VueJS项目目录,dist则是运行 npm run build 构建出的index.html与静态文件夹 static 的父级目录

这时启动Django项目,访问 / 则可以访问index.html,但是还有问题,静态文件都是404错误,下一步我们解决这个问题

7. 配置静态文件搜索路径

打开 settings.py (ulb_manager/settings.py),找到 STATICFILES_DIRS 配置项,配置如下:

1
2
3
4
# Add for vuejs
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]

这样Django不仅可以将/ulb 映射到index.html,而且还可以顺利找到静态文件

此时访问 /ulb 我们可以看到使用Django作为后端的VueJS helloworld

ALL DONE.

8. 开发环境

因为我们使用了Django作为后端,每次修改了前端之后都要重新构建(你可以理解为不编译不能运行)

除了使用Django作为后端,我们还可以在dist目录下面运行以下命令来看效果:

但是问题依然没有解决,我想过检测文件变化来自动构建,但是构建是秒级的,太慢了,所以我直接使用VueJS的开发环境来调试

毫秒,但是有个新问题,使用VueJS的开发环境脱离了Django环境,访问Django写的API,出现了跨域问题,有两种方法解决,一种是在VueJS层上做转发(proxyTable),另一种是在Django层注入header,这里我使用后者,用Django的第三方包 django-cors-headers 来解决跨域问题

安装

1
pip install django-cors-headers

配置(两步)

1. settings.py 修改

1
2
3
4
5
6
7
8
9
10
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
**'corsheaders.middleware.CorsMiddleware',**
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

这里要注意中间件加载顺序,列表是有序的哦

2. settings.py 添加

1
CORS_ORIGIN_ALLOW_ALL = True

至此,我的开发环境就搭建完成了

9. 生产环境部署(部署到 UCloud )

9.1 创建主机

  1. 注册 UCloud - 专业云计算服务商
  2. 点击左侧的 云主机,然后点击 创建主机
  3. 右侧选择 付费方式,点击 立即购买
  4. 在支付确认页面,点击 确认支付

购买成功后回到主机管理列表,如下所示:

这里注意记住你的外网IP,下面的ip替换成你的

9.2 环境搭建与部署

登录主机,用你刚填写的密码:

ssh root@120.132.**.75

CentOS 系统可以使用 yum 安装必要的包

1
2
3
4
5
6
7
8
# 如果你使用git来托管代码的话
yum install git

# 如果你要在服务器上构建前端
yum install nodejs
yum install npm

yum install nginx

我们使用 uwsgi 来处理 Django 请求,使用 nginx 处理 static 文件(即之前 build 之后 dist 里面的static,这里默认前端已经打包好了,如果在服务端打包前端需要安装nodejs,npm等)

安装uWsgi

1
2
3
yum install uwsgi
# 或者
pip install uwsgi

我们使用配置文件启动uwsgi,比较清楚

uwsgi配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
[uwsgi]
socket = 127.0.0.1:9292
stats = 127.0.0.1:9293
workers = 4
# 项目根目录
chdir = /opt/inner_ulb_manager
touch-reload = /opt/inner_ulb_manager
py-auto-reload = 1
# 在项目跟目录和项目同名的文件夹里面的一个文件
module= inner_ulb_manager.wsgi
pidfile = /var/run/inner_ulb_manager.pid
daemonize = /var/log/inner_ulb_manager.log

nginx 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 8888;
server_name 120.132.**.75;
root /opt/inner_ulb_manager;
access_log /var/log/nginx/access_narwhals.log;
error_log /var/log/nginx/error_narwhals.log;

location / {
uwsgi_pass 127.0.0.1:9292;
include /etc/nginx/uwsgi_params;
}
location /static/ {
root /opt/inner_ulb_manager/;
access_log off;
}
location ^~ /admin/ {
uwsgi_pass 127.0.0.1:9292;
include /etc/nginx/uwsgi_params;
}
}

/opt/inner_ulb_manager/static 即为静态文件目录,那么现在我们静态文件还在 frontend/dist 怎么办,不怕,Django给我们提供了命令:

先去settings里面配置:

1
STATIC_ROOT = os.path.join(BASE_DIR, "static")

然后在存在manage.py的目录,即项目跟目录执行:

1
python manage.py collectstatic

这样frontend/dist/static里面的东西就到了项目根目录的static文件夹里面了

那么为什么不直接手动把构建好的dist/static拷过来呢,因为开始提过Django自带的App:admin 也有一些静态文件(css,js等),它会一并collect过来,毕竟nginx只认项目跟目录的静态文件,它不知道django把它自己的需求文件放到哪了

开头说过Django配置灵活,那么我们专门为Django创建一个生产环境的配置 prod.py

prod.py 与 默认 settings.py 同目录

1
2
3
4
5
6
7
8
9
10
11
# 导入公共配置
from .settings import *

# 生产环境关闭DEBUG模式
DEBUG = False

# 生产环境开启跨域
CORS_ORIGIN_ALLOW_ALL = False

# 特别说明,下面这个不需要,因为前端是VueJS构建的,它默认使用static作为静态文件入口,我们nginx配置static为入口即可,保持一致,没Django什么事
STATIC_URL = '/static/'

如何使用这个配置呢,进入 wisg.py 即uwsgi配置里面的module配置修改为:

1
2
3
4
5
6
7
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "**inner_ulb_manager.prod**")

application = get_wsgi_application()

启动uwsgi

1
uwsgi --ini inner_ulb_manager.ini

启动ngingx

至此,部署就完成了

10. 效果图

List 组件:

传单个 ULB 对象给 Detail 组件使用即可

Detail 组件:

当然里面还实现了前面提到的 ULB 的 VServer 创建,VServer 的 RServer 的创建等。

————————

本文由『UCloud平台产品研发团队』提供。

项目源码文件戳下面链接查看,大家可以马上拿源码上手试起来,操作过程中遇到问题也可直接在github上留言:)https://github.com/tmpbook/django-with-vuejs

现在注册使用UCloud,还免费试用 及 首充返现优惠,最高可返3000元代金券!活动传送门:用UCloud!3000元限量版礼盒等你来拆!

另,欢迎添加UCloud运营小妹个人微信号:Surdur,陪聊很专业:)

关于作者:

星辰(@星辰), UCloud平台产品研发工程师,DevOps一枚。你也可以去他的知乎专栏 《随心DevOps》 上逛逛,干货满满,带你更优雅的改变世界。

相关阅读推荐:

机器学习进阶笔记之八 | TensorFlow与中文手写汉字识别

机器学习进阶笔记之七 | MXnet初体验
机器学习进阶笔记之六 | 深入理解Fast Neural Style
机器学习进阶笔记之五 | 深入理解VGG\Residual Network
机器学习进阶笔记之四 | 深入理解GoogLeNet
机器学习进阶笔记之三 | 深入理解Alexnet
机器学习进阶笔记之二 | 深入理解Neural Style
机器学习进阶笔记之一 | TensorFlow安装与入门

「UCloud机构号」将独家分享云计算领域的技术洞见、行业资讯以及一切你想知道的相关讯息。

欢迎提问&求关注 o(*////▽////*)q~

以上。

npm-check

 npm-check 是一个检查依赖包是否存在过期、不正确、未使用等情况的工具。

 全局安装:

npm  install  -g  npm-check

 使用:

npm-check

上述指令会自动检查当前目录下的依赖包情况。

 这里我们重点关注下未使用的依赖包。npm-check 在检查依赖包是否使用时判断的依据是文件中是否存在 require(package) 这条语句,例如:

const lodash = require(‘lodash’);

只要存在这条语句,即使我并未在其它任何地方使用(也就是说这是个无用的包),但是 npm-check 是不会将其判定为未使用的。

 ESLint

为了解决上述存在的这种情况,我们可以借助 ESLint 先去检查代码是否存在未使用的变量(no-unused-vars),这样就可以检查某个包 require 了但并未在后续使用的情况。

全局安装:

npm install -g eslint

编写 .eslintrc.js 配置文件:

 

eslint  –config  .eslintrc.js  ./

执行上述指令便会检查当前目录下的所有代码是否存在定义了但未使用的变量。删除掉未使用的变量(包含对依赖包的引用)之后,再运行 npm-check 便能正确的找出那些在项目中已不再使用的依赖包了。

通过远程桌面连接windows server服务器时,经常是直接关闭远程桌面程序,而没有注销远程登录的用户,这样导致有很多远程桌面启动的进程依然运行在服务器上,对服务器产生了不必要的开销,其实作为server版操作系统,是可以通过策略控制关闭后自动注销的,步骤如下:

1.运行gpedit.msc

2.找到:本地计算机策略-计算机配置-管理模板-windows组件

3.找到:远程桌面服务-远程桌面会话主机-会话时间限制,右侧,点击设置已中断会话的时间限制

4.选择已启用,在结束已断开连接的会话,选择1分钟

配置用户信息

配置 Git 用户信息是使用 Git 的第一步,这有助于在提交记录中标识作者身份。

1
2
3
# 配置用户名和邮箱
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

配置编码

为避免中文路径和文件名显示乱码,以及确保提交信息的编码正确,可以进行以下配置:

1
2
3
4
5
6
# 解决中文路径和文件名乱码问题
git config --global core.quotePath false

# 设置提交和日志输出的编码为 UTF-8
git config --global i18n.commitEncoding utf-8
git config --global i18n.logOutputEncoding utf-8

配置内网域名证书

当访问使用自签名证书的内网 Git 仓库时,需要配置 Git 信任该证书。

1
2
3
4
5
# 配置特定内网域名的 SSL 证书路径
git config --global http."内网域名".sslCAInfo "证书所在路径"

# 示例:配置 example.io 域名的自签名根证书
git config --global http."https://example.io".sslCAInfo ~/.certs/selfsigned-root-ca.crt

一、计算机视觉库

1. OpenCV

OpenCV 是最古老、最受欢迎的开源计算机视觉库,为计算机视觉应用提供通用底层算法。

特性

  • 跨平台支持(Windows、Linux、Android、macOS)
  • 支持 Python、Java、C++ 等语言
  • 支持 GPU CUDA 加速
  • 包含可转换为 TensorFlow 模型的预训练模型

主要功能

  • 2D/3D 图像工具包
  • 人脸识别、手势识别
  • 运动检测、人机交互
  • 对象检测、图像分割

2. Scikit-Image

Python 视觉库,是 Scikit-Learn 的扩展,支持将 NumPy 数组作为图像对象处理。

1
2
3
4
5
import skimage as ski
image = ski.data.coins()
edges = ski.filters.sobel(image)
ski.io.imshow(edges)
ski.io.show()

3. Pillow (PIL Fork)

Python 图像处理库,支持多种格式图像读写,包含旋转、合并、缩放等基础变换。

4. TorchVision

PyTorch 扩展库,提供常见图像转换功能、数据集和模型架构,支持 Python 和 C++。

5. MMDetection

基于 PyTorch 的目标检测工具箱,支持多种检测模型(Faster R-CNN、YOLO、SSD 等)。


二、深度学习框架

6. TensorFlow

Google Brain 团队开发(2015年11月发布),支持图像分类、人脸识别、目标检测等任务。

扩展组件

  • TensorFlow.js:浏览器和 Node.js
  • TensorFlow Lite:终端设备
  • TensorFlow Hub:模型复用平台

编程接口:Python、C、C++、Java、JavaScript、Go、R

7. PyTorch

Facebook AI 研究小组开发,Python 优先框架,灵活易用,支持 GPU 加速和动态计算图。

应用场景:图像评估、图像分割、图像分类

8. Keras

基于 Python 的开源库,对初学者友好,底层使用 TensorFlow,有强大的社区支持。

应用场景:图像分割分类、手写识别、3D图像分类、语义图像聚类

9. Caffe

加州大学伯克利分校开发,使用 C++ 编写,支持多语言,用于图像分类、分割模型开发。

适用领域:视觉、语音、多媒体

10. MXNet

亚马逊 AWS 默认框架,支持混合编程模型,支持 Python、C++、R、Scala、Julia 等语言。


三、推理部署工具

11. NVIDIA CUDA-X

GPU 加速库集合,包含数学库、并行算法库、图像视频库、深度学习库。

12. OpenVINO

英特尔开发的跨平台框架,功能包括对象检测、人脸识别、图像彩色化、运动识别。

13. NVIDIA TensorRT

NVIDIA 高性能深度学习推理引擎,优化模型推理性能。


四、目标检测专用

14. YOLO 系列

实时目标检测模型,Joseph Redmon 和 Ali Farhadi 于 2016 年开发。

版本:YOLOv1 → YOLOv8

特性:将图像划分为网格,同时预测各网格目标,高效实时

应用:目标检测、实例分割、图像分类、姿态估计

15. Detectron2

Facebook AI Research 开发的 PyTorch 目标检测库。

包含模型:Faster R-CNN、Mask R-CNN、RetinaNet、DensePose、Cascade R-CNN、Panoptic FPN、TensorMask


五、轻量级框架

16. SimpleCV

开源机器视觉框架,轻松访问 OpenCV 等高性能库,无需深入了解底层概念。


六、框架选型建议

场景 推荐框架
入门学习 Keras、PyTorch
生产部署 TensorFlow、Caffe2、OpenVINO
目标检测 YOLO、Detectron2、MMDetection
边缘设备 OpenVINO、TensorRT Lite
研究实验 PyTorch、MXNet
0%