drbd的简介、同步机制和安装见另一篇文章:drbd(一):简介、同步机制和安装

本文所述为drbd8.4的配置,和8.4版本之前的版本,以及drbd9版本的差别都非常大。

1.drbd配置文件

drbd的主配置文件/etc/drbd.conf,为了管理的便捷性,在此文件中使用了include指令指定了包含的配置文件段,默认的是在/etc/drbd.d/目录下。在此目录有全局配置文件global_common.conf和其他配置文件*.res文件。其中在主配置文件中include全局配置文件的指令只能出现一个,且必须出现在最前面。

两个节点的配置文件应尽量完全一致。

/usr/share/doc/drbd-版本/下有drbd.conf的样例配置文件。

以下是global_common.conf的结构。

  1. global {
  2. usage-count yes; # 是否参加drbd的使用者统计,默认此选项为YES
  3. }
  4. common { # common段定义每一个资源从此继承的参数,非必须,但建议将多个资源共享的参数定义在此以降低配置文件的复杂度
  5. handlers {
  6. }
  7. startup {
  8. }
  9. options {
  10. }
  11. disk {
  12. }
  13. net {
  14. }
  15. }

全局配置修改如下:

  1. global {
  2. usage-count no;
  3. }
  4. common {
  5. handlers{ # 定义出现以下问题(如splitbrain或out-of-sync错误)时处理策略
  6. pri-on-incon-degr "/usr/lib/drbd/notify-pri-on-incon-degr.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
  7. pri-lost-after-sb "/usr/lib/drbd/notify-pri-lost-after-sb.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
  8. local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo o > /proc/sysrq-trigger ; halt -f";
  9. split-brain "/usr/lib/drbd/notify-split-brain.sh root";
  10. out-of-sync "/usr/lib/drbd/notify-out-of-sync.sh root";
  11. }
  12. disk {
  13. on-io-error detach; # 当发生io错误时,直接拔除备节点设备
  14. resync-rate 600M;
  15. }
  16. # syncer { # 注意,8.4版本已不支持该选项,要配置相关项,参见https://docs.linbit.com/docs/users-guide-8.4/#s-recent-changes-config
  17. # rate 10M; # re-sync速率,官方建议设置为网络IO和磁盘IO能力最小者的30%
  18. # verify-alg crc32c; # 用于校验block是否一致
  19. }
  20. protocol C; # 定义使用C协议,即同步复制。可不定义,此为默认
  21. }

再新建一个配置文件data1.res,里面定义资源。

  1. resource data1 { # 定义资源名称
  2. on drbd1.longshuai.com { # 指定在drbd1.longshuai.com节点上,节点名要和uname -n一致
  3. device /dev/drbd0; # 指定drbd设备,0是其此设备号
  4. disk /dev/sdb5; # 指定要同步的数据分区
  5. address 192.168.100.51:7788; # 指定监听用来同步数据的地址和端口,此处指定为数据同步专用地址eth1
  6. meta-disk /dev/sdb1;
  7. }
  8. on drbd2.longshuai.com {
  9. device /dev/drbd0;
  10. disk /dev/sdb5;
  11. address 192.168.100.52:7788;
  12. meta-disk /dev/sdb1;
  13. }
  14. }

或者简化为如下:

  1. resource data1 {
  2. device /dev/drbd0;
  3. disk /dev/sdb5;
  4. meta-disk /dev/sdb1[0];
  5. on drbd1.longshuai.com {
  6. address 192.168.100.51:7788;
  7. }
  8. on drbd2.longshuai.com {
  9. address 192.168.100.52:7788;
  10. }
  11. }

resource段用于定义drbd资源,每个资源通常定义在一个单独的位于/etc/drbd.d目录中的以.res结尾的文件中。资源在定义时必须为其命名,名字可以由非空白的ASCII字符组成。每一个资源段的定义中至少要包含两个节点,其它参数均可以从common段或drbd的默认中进行继承。

其中上述配置文件的meta-disk有三种记录方式:internal/device/device[index_num]。其中不管是哪种方式,metadata存放的分区不能格式化,哪怕使用internal时metadata和一般data在同一个分区也不能格式化该分区。

internal是将元数据也写入到数据分区的尾部,即数据和元数据同分区。如果指定的device没有给定index时,则表示元数据存储到该设备中。如果某节点指定device[index_num],那么指定几次元数据分区索引就必须大于128M的几倍,例如上述文件中drbd1.longshuai.com节点指定了/dev/sdb1[0],那么sdb1就必须大于128M,如果此时其他资源的节点也指定了同一台服务器的/dev/sdb1[1],则指定了两次就必须大于256M。指定为internal和device时,元数据区的大小是drbd自行计算的。

on hostname段中,hostname必须和uname -n一致,因为drbd默认是基于主机名进行解析的。但是,drbd支持基于ip地址的解析,这时drbd根据ip地址进行漂移,ip地址在哪个主机上,就解析到哪个主机上。具体内容见官方手册Floating peers

2.创建metadata块(初始化)并计算metadata区的大小

  1. drbdadm create-md [all|resource_names]

两节点都初始化。

  1. [root@drbd1 drbd.d]# drbdadm create-md data1
  2. initializing activity log
  3. NOT initializing bitmap
  4. Writing meta data...
  5. New drbd meta data block successfully created.

初始化成功后,可以使用下面几个命令来获取drbd的metadata区信息。

  1. [root@drbd1 ~]# drbdadm show-gi data1
  2. +--< Current data generation UUID >-
  3. | +--< Bitmap's base data generation UUID >-
  4. | | +--< younger history UUID >-
  5. | | | +-< older history >-
  6. V V V V
  7. 0000000000000004:0000000000000000:0000000000000000:0000000000000000:0:0:0:0:0:0:0
  8. ^ ^ ^ ^ ^ ^ ^
  9. -< Data consistency flag >--+ | | | | | |
  10. -< Data was/is currently up-to-date >--+ | | | | |
  11. -< Node was/is currently primary >--+ | | | |
  12. -< Node was/is currently connected >--+ | | |
  13. -< Node was in the progress of setting all bits in the bitmap >--+ | |
  14. -< The peer's disk was out-dated or inconsistent >--+ |
  15. -< This node was a crashed primary, and has not seen its peer since >--+
  16. flags: Secondary, StandAlone, Inconsistent
  17. meta-data: clean
  18. zero size device -- never seen peer yet?

从上面命令的结果中,可以看出数据的代数,还能获取一些节点状态信息。

关于drbd的代数,它在drbd的内部机制中,用于实现:
(1).判断两节点是否是同一个集群的节点。也就是说,对方节点是不是自己的对端节点。因为有可能出现意外连接,却不是自己对端的情况。
(2).判断节点设备是否需要全部重新同步(re-sync)或者部分重新同步。
(3).判断重新同步的方向。即是从节点1重新同步到节点2还是从节点2重新同步到节点1。
(3).标识节点是否处于脑裂(brain split)。

  1. [root@drbd1 ~]# drbdadm dump-md data1
  2. # DRBD meta data dump
  3. # 2018-03-29 22:53:52 +0800 [1522335232]
  4. # drbd1.longshuai.com> drbdmeta 0 v08 /dev/sdb1 flex-external dump-md
  5. #
  6. version "v08";
  7. # md_size_sect 1951744
  8. # md_offset 0
  9. # al_offset 4096
  10. # bm_offset 36864
  11. uuid {
  12. 0x0000000000000004; 0x0000000000000000; 0x0000000000000000; 0x0000000000000000;
  13. flags 0x00000000;
  14. }
  15. # al-extents 257;
  16. la-size-sect 0;
  17. bm-byte-per-bit 4096;
  18. device-uuid 0x0A293D126547895D;
  19. la-peer-max-bio-size 0;
  20. al-stripes 1;
  21. al-stripe-size-4k 8;
  22. # bm-bytes 0;
  23. bm {
  24. }
  25. # bits-set 0;

从此命令中可以获知不同标记代数的uuid值,以及metadata的元数据信息,例如md_size_sect=1951744表示元数据所在分区占用了1951744个扇区。注意,该命令不要在drbd设备已启动的情况下执行。

知道这两个命令可以获取一些信息后,现在我们要做的是计算metadata部分的数据大小。这个大小在"修改drbd设备空间大小"时有用。

首先获取元数据所在分区的扇区数。即上面结果中的"md_size_sect"。不过也可以使用块设备工具blockdev来获取。

  1. [root@drbd1 ~]# blockdev --getsz /dev/sdb1
  2. 1951744

有了该值,根据计算公式:Size = (md_size_sect/2^18)*8*N+72进行计算。其中md_size_sect如上计算,N是对端的数量,一般情况下drbd实现的是双节点,因此N=1,可以不用考虑。计算的结果是扇区数。

因此,此处计算的结果为:1951744/1024/256*8+72=131.5625共132扇区。如果要转换为KB大小,则再除以2即可(假设扇区大小为512bytes)。

3.在节点上启动drbd

可使用如下命令来管理资源的启动、停止。

  1. drbdadm {up|down} {all|resource_names}

例如:

  1. drbdadm up data1

如果启动报以下错误,则可能是因为对metadata进行了格式化。解决方法是将其删除再重新创建但不要格式化。

  1. 0: Failure: (119) No valid meta-data signature found.

此时可以查看/proc/drbd文件来查看drbd中配置的资源的状态,也可以使用命令drbd-overview RESOURCEdrbdadm status RESOURCE来查看。如果角色ro是从/从,则表明配置成功,若有一端是unknown,则表明和对方不能通信,可能是主机路由的问题,这样会导致脑裂的问题。

  1. cat /proc/drbd
  2. version: 8.4.9-1 (api:1/proto:86-101)
  3. GIT-hash: 9976da086367a2476503ef7f6b13d4567327a280 build by mockbuild@Build64R6, 2016-12-13 18:38:15
  4. 0: cs:Connected ro:Secondary/Secondary ds:Inconsistent/Inconsistent C r-----
  5. ns:0 nr:0 dw:0 dr:0 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:6297452

状态信息极其重要,其中:

  • cs:connection state,必须是connected状态才表示连接成功。
  • ro:roles,各节点是是primary还是secondary。
  • ds:disk state,数据的状态,由此判断是否要同步、重新同步、正在同步、同步完成、是否一致等等信息。

关于drbd的状态信息,见另一篇文章:drbd的状态说明

实际上,drbdadm up启动drbd设备时做了很多工作,如果细致划分的话,可以将"drbdadm up"拆分为以下几个动作:

  1. 将drbd的资源关联到底层设备(metadata和data区)上,使之能通过底层设备存、取数据。该过程调用的是drbdsetup程序。

    1. drbdadm attach data1
  2. 加载drbd资源的同步参数。
    1. drbdadm syncer data1
  3. 连接对端。
    1. drbdadm connect data1

    这些命令在drbdadm中部分已失效,放在这里只是为了说明"up"时所执行的几个步骤。

到目前为止,drbd的资源已经关联完成,也已经准备好进行同步,所不知道的仅仅只是谁作为同步的源端,谁做为同步的目标端,也就是primary和secondary的角色。

4.实现drbd的主从同步

在需要设置为主机点的机器上执行:

  1. drbdadm primary --force data1 # 第一次初始化同步只能执行这个
  2. # 或者
  3. drbdsetup /dev/drbd0 primary

其中"--force"表示强制升级为primary,这会导致本节点的数据强制同步到对端上。

在drbd8.4之前,没有"--force"选项,而是使用等价的"--overwrite-data-of-peer",由于它调用的是drbdsetup,所以同时还要在选项前加上"--"表示drbdadm将此选项传递给drbdsetup,即:

  1. drbdadm -- --overwrite-data-of-peer primary data1

由于是第一次执行同步,因此该过程会同步整个分区进行初始化。

  1. [root@drbd1 ~]# cat /proc/drbd
  2. version: 8.4.10-1 (api:1/proto:86-101)
  3. GIT-hash: a4d5de01fffd7e4cde48a080e2c686f9e8cebf4c build by mockbuild@, 2017-09-15 14:23:22
  4. 0: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
  5. ns:812136 nr:0 dw:0 dr:813048 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:4069272
  6. [==>.................] sync'ed: 16.7% (3972/4764)M
  7. finish: 0:02:24 speed: 28,216 (17,652) K/sec

可以看到本端已经设置为主节点,且ds的一端是uptodate状态,最后还看到了镜像到对方的进度信息。

当镜像完成后,再查看资源的信息。可以发现两端的ds都变成uptodate状态了。说明镜像完成了。

  1. [root@drbd1 ~]# cat /proc/drbd
  2. version: 8.4.10-1 (api:1/proto:86-101)
  3. GIT-hash: a4d5de01fffd7e4cde48a080e2c686f9e8cebf4c build by mockbuild@, 2017-09-15 14:23:22
  4. 0: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
  5. ns:4881408 nr:0 dw:0 dr:4882320 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0

5.数据同步和主从角色切换

现在已经有一端是主节点了。drbd的主从节点中,只有主节点是可以挂载并进行读写的。所以在主节点上挂载drbd设备(可能需要重新格式化data分区,而且如果metadata使用的是internal模式,则需要格式化分区才OK)。

挂载drbd到mnt,然后向其中拷贝一个文件。

  1. [root@drbd1 ~]# mount /dev/drbd0 /mnt
  2. [root@drbd1 ~]# cp /etc/inittab /mnt
  3. [root@drbd1 ~]# ls /mnt
  4. inittab lost+found

如果对端也有这个文件则说明同步成功了,但是对端是从节点,无法读取数据,所以只能切换主从角色再查看。不过在,实验环境下直接把从节点的drbd给down掉,再直接挂载/dev/sdb5也是可以的。

首先将主节点切换为从节点,必须先卸载挂载点才行。

  1. [root@drbd1 ~]# umount /mnt
  2. [root@drbd1 ~]# drbdadm secondary data1
  3. [root@drbd1 ~]# drbd-overview
  4. 0:data1/0 Connected Secondary/Secondary UpToDate/UpToDate

再在从节点上将自己设置为主节点,然后挂载drbd设备,再查看是否有文件同步过来了。

  1. [root@drbd2 ~]# drbdadm primary data1
  2. [root@drbd2 ~]# mount /dev/drbd0 /mnt
  3. [root@drbd2 ~]# ls /mnt
  4. inittab lost+found

这说明实现了数据同步。但是这样手动切换来切换去的很麻烦,一般会将其交给heartbeat或者corosync来管理,实现drbd的自动切换。

6.drbd脑裂的解决方法

当DRBD的两节点都发现对方后,并都交换了初始化握手协议后,发现双方都是primary角色,就会出现脑裂。出现脑裂后,由于两端节点都可以挂载、写数据,会导致数据的混乱。

当检测到出现脑裂时,drbd会立即中断双方的连接,并在日志中记录

  1. Split-Brain detected, dropping connection!

在出现脑裂的时候,一定有一端的连接状态处于StandAlone,另一端的状态可能是StandAlone(当双方同时检测到脑裂),也可能是Connecting(当一端先检测到脑裂立即中断连接后使得另一端无法再检测出脑裂)。

出现脑裂的时候,如果没有配置drbd自动从脑裂状态恢复,那么必须进行人为的手动干涉。干涉的方法是放弃一端的数据,这一端称为"脑裂的受害者",另一端保存数据的节点则称为"脑裂的幸存者"。

处理方法如下:

(1).在脑裂的受害者节点上执行:

  1. drbdadm disconnect {resource | all} # 先中断连接,防止再次写入数据
  2. drbdadm secondary {resource | all} # 设置为secondary
  3. drbdadm connect --discard-my-data {resource | all} # 丢弃数据,并再次连接

(2).如果脑裂的幸存者节点也是StandAlone状态,则执行下面的命令重新连接,如果仍然保持Connecting状态,则不需任何操作:

  1. drbdadm disconnect {resource | all}
  2. drbdadm connect {resource | all}

当干涉完两边后,两边重新建立连接,重新握手交换初始化协议信息。此时,脑裂的受害者端的状态将变为SyncTarget,表示同步的目标端,它将从另一节点上获取数据。

但注意,受害者在SyncTarget状态下同步数据时,并不会从幸存者节点获取所有数据,而是按照自己节点上的事务信息进行回滚,再从幸存者节点上获取回滚后还缺少的数据。因此,drbd脑裂后完成数据同步也是很快的。

7.drbd多卷组配置

前文实验中使用的配置文件如下:

  1. [root@drbd2 ~]# drbdadm dump data1
  2. # resource data1 on drbd2.longshuai.com: not ignored, not stacked
  3. # defined at /etc/drbd.d/data1.res:1
  4. resource data1 {
  5. on drbd1.longshuai.com {
  6. device /dev/drbd0 minor 0;
  7. disk /dev/sdb5;
  8. meta-disk /dev/sdb1;
  9. address ipv4 192.168.100.51:7788;
  10. }
  11. on drbd2.longshuai.com {
  12. device /dev/drbd0 minor 0;
  13. disk /dev/sdb5;
  14. meta-disk /dev/sdb1;
  15. address ipv4 192.168.100.52:7788;
  16. }
  17. }

它等价于:

  1. resource data1 {
  2. device /dev/drbd0;
  3. disk /dev/sdb5;
  4. meta-disk /dev/sdb1;
  5. on drbd1.longshuai.com {
  6. address 192.168.100.51:7788;
  7. }
  8. on drbd2.longshuai.com {
  9. address 192.168.100.52:7788;
  10. }
  11. }

其实它还等价于:

  1. resource data1 {
  2. volume 0 {
  3. device /dev/drbd0;
  4. disk /dev/sdb5;
  5. meta-disk /dev/sdb1;
  6. }
  7. on drbd1.longshuai.com {
  8. address 192.168.100.51:7788;
  9. }
  10. on drbd2.longshuai.com {
  11. address 192.168.100.52:7788;
  12. }
  13. }

drbd会为没有分卷组的资源使用默认卷组"volume 0"。

如果需要多个底层设备(磁盘、分区、LVM、RAID等)提供drbd的同步功能,可以设置多个卷组。

例如,除了上面使用的/dev/sdb{5,1}外,添加/dev/sdc1(data区)、/dev/sdc2(metadata区)到另一个卷组。

  1. resource data1 {
  2. volume 0 {
  3. device /dev/drbd0;
  4. disk /dev/sdb5;
  5. meta-disk /dev/sdb1;
  6. }
  7. volume 1 {
  8. device /dev/drbd1;
  9. disk /dev/sdc1;
  10. meta-disk /dev/sdc2;
  11. }
  12. on drbd1.longshuai.com {
  13. address 192.168.100.51:7788;
  14. }
  15. on drbd2.longshuai.com {
  16. address 192.168.100.52:7788;
  17. }
  18. }

如果第二个卷组是在drbd已经运行后再添加的完全的新分区,则需要先创建元数据区、调整配置文件、启动资源等过程。

两边节点都执行:

  1. drbdadm create-md data1/1 # data1/1表示data1资源下的卷组1
  2. drbdadm adjust data1 # 调整资源,相当于reload资源配置文件

主节点执行:

  1. drbdadm --force primary data1/1

查看两卷组的状态信息:

  1. [root@drbd1 ~]# cat /proc/drbd
  2. version: 8.4.10-1 (api:1/proto:86-101)
  3. GIT-hash: a4d5de01fffd7e4cde48a080e2c686f9e8cebf4c build by mockbuild@, 2017-09-15 14:23:22
  4. 0: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
  5. ns:76408 nr:0 dw:76408 dr:3441 al:22 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
  6. 1: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
  7. ns:5352 nr:0 dw:0 dr:6264 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:4876056
  8. [>....................] sync'ed: 0.2% (4760/4764)M
  9. finish: 0:43:32 speed: 1,784 (1,784) K/sec

虽然可以通过resource_name/volume_id的方式指定某个资源中的卷组,甚至上面通过data1/1来指定其作为primary。

但一定要注意,资源是有层次的,卷组是资源中的一个子对象,它代表的只是底层设备的一个关联对象。因此,在资源层次上的操作(如资源、连接),必须指定整个资源,而不能指定资源中的某个卷组。例如:

  1. drbadm {up | down | connect | disconnect | primary | secondary} <resource>

如果这些命令指定资源中的某个卷组,则会报告错误,类似如下:

  1. [root@drbd90 ~]# drbdadm down rs0/0
  2. down operates on whole resources, but you specified a specific volume!

但是,在底层设备的关联层次上,可以指定资源中的单个卷组。例如:

  1. drbsetup {attach | detach} <volume_id>

也就是说,每个资源中要么完全是primary,要么完全是secondary,而不能volume 0是primary,volume 1是secondary,因为primary、secondary是资源层次上的概念,而不是设备层次的概念。

再次提醒,两端的配置文件应当尽量保持一致,因此最好不要随意分区。

drbd(二):配置和使用的更多相关文章

  1. Drbd 安装配置

    一.Drbd介绍 Distributed Replicated Block Device(DRBD)是基于块设备在不同的高可用服务器之间同步和镜像数据的软件,通过它可以实现在网络中两台服务器这间基于块 ...

  2. Struts2入门(二)——配置拦截器

    一.前言 之前便了解过,Struts 2的核心控制器是一个Filter过滤器,负责拦截所有的用户请求,当用户请求发送过来时,会去检测struts.xml是否存在这个action,如果存在,服务器便会自 ...

  3. SQL Server 2008 数据库镜像部署实例之二 配置镜像,实施手动故障转移

    SQL Server 2008 数据库镜像部署实例之二 配置镜像,实施手动故障转移 上一篇文章已经为配置镜像数据库做好了准备,接下来就要进入真正的配置阶段 一.在镜像数据库服务器上设置安全性并启动数据 ...

  4. Struts2学习笔记二 配置详解

    Struts2执行流程 1.简单执行流程,如下所示: 在浏览器输入请求地址,首先会被过滤器处理,然后查找主配置文件,然后根据地址栏中输入的/hello去每个package中查找为/hello的name ...

  5. 学以致用二---配置Centos7.2 基本环境

    Centos 7 虚拟机安装好后,接下来该配置环境了. 一.查看系统版本 cat /etc/redhat-release 二.修改主机名 /etc/hostname 注意,hostname里的内容为l ...

  6. Struts2(五)Action二配置

    一.method参数 action package com.pb.web.action; public class HourseAction { public String add(){ System ...

  7. DRBD安装配置、工作原理及故障恢复

    一.DRBD简介 DRBD的全称为:Distributed ReplicatedBlock Device(DRBD)分布式块设备复制,DRBD是由内核模块和相关脚本而构成,用以构建高可用性的集群.其实 ...

  8. Java系列学习(二)-配置开发环境

    1.设置系统环境变量 1.1.设置JDK的Path路径 作用:通过path环境变量,将JDK安装目录下的bin目录配置到path变量下,可使javac指令和java指令在任意目录下运行   方法一:直 ...

  9. 《Hadoop学习之路》学习实践二——配置idea远程调试hadoop

    背景:在上篇文章中按照大神“扎心了老铁”的博客,在服务器上搭建了hadoop的伪分布式环境.大神的博客上是使用eclipse来调试,但是我入门以来一直用的是idea,eclipse已经不习惯,于是便摸 ...

随机推荐

  1. 无法连接MySQL

    1.错误描述 Error No. 2003 Can't connect to MySQL Server on 'localhost' (10061) 2.错误原因       打开任务管理器,关闭了m ...

  2. 远程块存储iSCSI

    /* Border styles */ #table-2 thead, #table-2 tr { border-top-width: 1px; border-top-style: solid; bo ...

  3. h264多slice

    1, h264编码以macroblock为最小单位(简称MB),多个连续的MB组成一个slice,每个slice编码输出一个NALU 2, 划分slice的方式可以按照固定个数MB的方式: 也可以以对 ...

  4. 一个简单的freemark输入输出的案例(二)

    freemarker入门实例 1.设计思路 (1)新建Maven Project (2)生成freemarker模板 (3)写freemarker页面ftl文件 (4)写测试文件 2.新建Maven ...

  5. json省市县数据源

    {cityData,"Code":""},{"Name":"牡丹江市",","Code": ...

  6. Android外部存储

    WeTest 导读 外部存储作为开发中经常接触的一个重要系统组成,在Android历代版本中,有过许许多多重要的变更.我也曾疑惑过,为什么一个简简单单外部存储,会存在存在这么多奇奇怪怪的路径:/sdc ...

  7. JS倒计时特效--JavaScript基础

    1.倒计时特效HTML源码 <!DOCTYPE html><html lang="en"><head> <meta charset=&qu ...

  8. 【BZOJ2152】聪聪可可(点分治)

    [BZOJ2152]聪聪可可(点分治) 题面 Description 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电 ...

  9. 4.2 js没有块级作用域

    JavaScript没有块级作用域.在其他语言上,比如C语言中,有花括号封闭的代码块都有自己的作用域,(如果用ECMAScript的话来讲,就是他们自己的执行环境),因而支持根据条件来定义变量.例如, ...

  10. 项目角度谈矢量切片运用以及Geoserver处理自定义规格矢量切片方案

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1. 背景 矢量切图方案目前已经是很常见的一个方案,在2016年时团队 ...