• Redis持久化实践及灾难恢复模拟

    目前Redis持久化的方式有两种: RDB 和 AOF

    Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。

    RDB就是Snapshot快照存储,是默认的持久化方式。
    可理解为半持久化模式,即按照一定的策略周期性的将数据保存到磁盘。
    对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。
    下面是默认的快照设置:

    save 900 1    #当有一条Keys数据被改变时,900秒刷新到Disk一次
    save 300 10   #当有10条Keys数据被改变时,300秒刷新到Disk一次
    save 60 10000 #当有10000条Keys数据被改变时,60秒刷新到Disk一次

    第一次Slave向Master同步的实现是:
    Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。
    第二次以及以后的同步实现是:
    Master将变量的快照直接实时依次发送给各个Slave。
    但不管什么原因导致Slave和Master断开重连都会重复以上两个步骤的过程。
    Redis的主从复制是建立在内存快照的持久化基础上的,只要有Slave就一定会有内存快照发生。

    RDB的不足:从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。

    ————————————————–

    AOF(Append-Only File)比RDB方式有更好的持久化性。
    由于在使用AOF持久化方式时,Redis会将每一个收到的写命令都通过Write函数追加到文件中,类似于MySQL的binlog。
    当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
    对应的设置参数为:
    $ vim /opt/redis/etc/redis_6379.conf

    appendonly yes       #启用AOF持久化方式
    appendfilename appendonly.aof #AOF文件的名称,默认为appendonly.aof
    # appendfsync always #每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。
    appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。
    # appendfsync no     #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐。

    AOF的完全持久化方式同时也带来了另一个问题,持久化文件会变得越来越大。
    比如我们调用INCR test命令100次,文件中就必须保存全部的100条命令,但其实99条都是多余的。
    因为要恢复数据库的状态其实文件中保存一条SET test 100就够了。
    为了压缩AOF的持久化文件,Redis提供了bgrewriteaof命令。
    收到此命令后Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件,以此来实现控制AOF文件的增长。
    由于是模拟快照的过程,因此在重写AOF文件时并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件。
    对应的设置参数为:
    $ vim /opt/redis/etc/redis_6379.conf

    no-appendfsync-on-rewrite yes   #在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
    auto-aof-rewrite-percentage 100 #当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
    auto-aof-rewrite-min-size 64mb  #当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。

    到底选择什么呢?下面是来自官方的建议:
    通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。
    如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用RDB。
    很多用户仅使用了AOF,但是我们建议,既然RDB可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用RDB。
    因此,我们希望可以在未来(长远计划)统一AOF和RDB成一种持久化模式。

    在数据恢复方面:
    RDB的启动时间会更短,原因有两个:
    一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。
    另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在CPU消耗上要远小于AOF日志的加载。

    ————————————————–

    灾难恢复模拟
    既然持久化的数据的作用是用于重启后的数据恢复,那么我们就非常有必要进行一次这样的灾难恢复模拟了。
    据称如果数据要做持久化又想保证稳定性,则建议留空一半的物理内存。因为在进行快照的时候,fork出来进行dump操作的子进程会占用与父进程一样的内存,真正的copy-on-write,对性能的影响和内存的耗用都是比较大的。
    目前,通常的设计思路是利用Replication机制来弥补aof、snapshot性能上的不足,达到了数据可持久化。
    即Master上Snapshot和AOF都不做,来保证Master的读写性能,而Slave上则同时开启Snapshot和AOF来进行持久化,保证数据的安全性。

    首先,修改Master上的如下配置:
    $ sudo vim /opt/redis/etc/redis_6379.conf

    #save 900 1 #禁用Snapshot
    #save 300 10
    #save 60 10000
    
    appendonly no #禁用AOF

    接着,修改Slave上的如下配置:
    $ sudo vim /opt/redis/etc/redis_6379.conf

    save 900 1 #启用Snapshot
    save 300 10
    save 60 10000
    
    appendonly yes #启用AOF
    appendfilename appendonly.aof #AOF文件的名称
    # appendfsync always
    appendfsync everysec #每秒钟强制写入磁盘一次
    # appendfsync no  
    
    no-appendfsync-on-rewrite yes   #在日志重写时,不进行命令追加操作
    auto-aof-rewrite-percentage 100 #自动启动新的日志重写过程
    auto-aof-rewrite-min-size 64mb  #启动新的日志重写过程的最小值

    分别启动Master与Slave

    假设master当掉了

    在Slave上复制数据文件:appendonly.aof,dump.rdb  拷贝到master相应的位置

    启动Master上的Redis

    不出意外,恢复成功

    在此次恢复的过程中,我们同时复制了AOF与RDB文件,那么到底是哪一个文件完成了数据的恢复呢?
    实际上,当Redis服务器挂掉时,重启时将按照以下优先级恢复数据到内存:
    1. 如果只配置AOF,重启时加载AOF文件恢复数据;
    2. 如果同时 配置了RDB和AOF,启动是只加载AOF文件恢复数据;
    3. 如果只配置RDB,启动是将加载dump文件恢复数据。
    AOF的优先级要高于RDB
  • Redis持久化

    本文内容来源于Redis 作者博文,Redis作者说,他看到的所有针对Redis的讨论中,对Redis持久化 的误解是最大的,于是他写了一篇长文来对Redis的持久化进行了系统性的论述。

    什么是持久化,简单来讲就是将数据放到断电后数据不会丢失的设备中。也就是我们通常理解的硬盘上。

    写操作的流程

    首先我们来看一下数据库在进行写操作时到底做了哪些事,主要有下面五个过程。

    1. 客户端向服务端发送写操作(数据在客户端的内存中)
    2. 数据库服务端接收到写请求的数据(数据在服务端的内存中)
    3. 服务端调用write(2) 这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)
    4. 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)
    5. 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)

    写操作大致有上面5个流程,下面我们结合上面的5个流程看一下各种级别的故障。

    • 当数据库系统故障时,这时候系统内核还是OK的,那么此时只要我们执行完了第3步,那么数据就是安全的,因为后续操作系统会来完成后面几步,保证数据最终会落到磁盘上。
    • 当系统断电,这时候上面5项中提到的所有缓存都会失效,并且数据库和操作系统都会停止工作。所以只有当数据在完成第5步后,机器断电才能保证数据不丢失,在上述四步中的数据都会丢失。

    通过上面5步的了解,可能我们会希望搞清下面一些问题:

    • 数据库多长时间调用一次write(2),将数据写到内核缓冲区
    • 内核多长时间会将系统缓冲区中的数据写到磁盘控制器
    • 磁盘控制器又在什么时候把缓存中的数据写到物理介质上

    对于第一个问题,通常数据库层面会进行全面控制。而对第二个问题,操作系统有其默认的策略,但是我们也可以通过POSIX API提供的fsync系列命令强制操作系统将数据从内核区写到磁盘控制器上。对于第三个问题,好像数据库已经无法触及,但实际上,大多数情况下磁盘缓存是被设置关闭的。或者是只开启为读缓存,也就是写操作不会进行缓存,直接写到磁盘。建议的做法是仅仅当你的磁盘设备有备用电池时才开启写缓存。

    所谓数据损坏,就是数据无法恢复,上面我们讲的都是如何保证数据是确实写到磁盘上去,但是写到磁盘上可能并不意味着数据不会损坏。比如我们可能一次写请求会进行两次不同的写操作,当意外发生时,可能会导致一次写操作安全完成,但是另一次还没有进行。如果数据库的数据文件结构组织不合理,可能就会导致数据完全不能恢复的状况出现。

    这里通常也有三种策略来组织数据,以防止数据文件损坏到无法恢复的情况:

    1. 第一种是最粗糙的处理,就是不通过数据的组织形式保证数据的可恢复性。而是通过配置数据同步备份的方式,在数据文件损坏后通过数据备份来进行恢复。实际上MongoDB在不开启journaling日志,通过配置Replica Sets时就是这种情况。
    2. 另一种是在上面基础上添加一个操作日志,每次操作时记一下操作的行为,这样我们可以通过操作日志来进行数据恢复。因为操作日志是顺序追加的方式写的,所以不会出现操作日志也无法恢复的情况。这也类似于MongoDB开启了journaling日志的情况。
    3. 更保险的做法是数据库不进行老数据的修改,只是以追加方式去完成写操作,这样数据本身就是一份日志,这样就永远不会出现数据无法恢复的情况了。实际上CouchDB就是此做法的优秀范例。

    RDB快照

    下面我们说一下Redis的第一个持久化策略,RDB快照。Redis支持将当前数据的快照存成一个数据文件的持久化机制。而一个持续写入的数据库如何生成快照呢。Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。

    我们可以通过Redis的save指令来配置RDB快照生成的时机,比如你可以配置当10分钟以内有100次写入就生成快照,也可以配置当1小时内有1000次写入就生成快照,也可以多个规则一起实施。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。

    Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。

    同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。

    但是,我们可以很明显的看到,RDB有他的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍受的,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不高。但是对于另外一些对数据安全性要求极高的应用,无法容忍数据丢失的应用,RDB就无能为力了,所以Redis引入了另一个重要的持久化机制:AOF 日志。

    AOF日志

    aof日志的全称是append only file,从名字上我们就能看出来,它是一个追加写入的日志文件。与一般数据库的binlog不同的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。比如我们进行如下实验,使用Redis2.6版本,在启动命令参数中设置开启aof功能:

    ./redis-server --appendonly yes

    然后我们执行如下的命令:

    redis 127.0.0.1:6379> set key1 Hello
    OK
    redis 127.0.0.1:6379> append key1 " World!"
    (integer) 12
    redis 127.0.0.1:6379> del key1
    (integer) 1
    redis 127.0.0.1:6379> del non_existing_key
    (integer) 0

    这时我们查看AOF日志文件,就会得到如下内容:

    $ cat appendonly.aof
    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    set
    $4
    key1
    $5
    Hello
    *3
    $6
    append
    $4
    key1
    $7
     World!
    *2
    $3
    del
    $4
    key1

    可以看到,写操作都生成了一条相应的命令作为日志。其中值得注意的是最后一个del命令,它并没有被记录在AOF日志中,这是因为Redis判断出这个命令不会对当前数据集做出修改。所以不需要记录这个无用的写命令。另外AOF日志也不是完全按客户端的请求来生成日志的,比如命令INCRBYFLOAT在记AOF日志时就被记成一条SET记录,因为浮点数操作可能在不同的系统上会不同,所以为了避免同一份日志在不同的系统上生成不同的数据集,所以这里只将操作后的结果通过SET来记录。

    AOF重写

    你可以会想,每一条写命令都生成一条日志,那么AOF文件是不是会很大?答案是肯定的,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的AOF文件取代老的AOF文件。

    从上面的流程我们能够看到,RDB和AOF操作都是顺序IO操作,性能都很高。而同时在通过RDB文件或者AOF日志进行数据库恢复的时候,也是顺序的读取数据加载到内存中。所以也不会造成磁盘的随机读。

    AOF可靠性设置

    AOF是一个写文件操作,其目的是将操作日志写到磁盘上,所以它也同样会遇到我们上面说的写操作的5个流程。那么写AOF的操作安全性又有多高呢。实际上这是可以设置的,在Redis中对AOF调用write(2)写入后,何时再调用fsync将其写到磁盘上,通过appendfsync 选项来控制,下面appendfsync的三个设置项,安全强度逐渐变强。

    appendfsync no

    当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。

    appendfsync everysec

    当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。 所以,结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。

    这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。

    appednfsync always

    当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。

    对于pipelining有什么不同

    对于pipelining的操作,其具体过程是客户端一次性发送N个命令,然后等待这N个命令的返回结果被一起返回。通过采用pipilining就意味着放弃了对每一个命令的返回值确认。由于在这种情况下,N个命令是在同一个执行过程中执行的。所以当设置appendfsync为everysec时,可能会有一些偏差,因为这N个命令可能执行时间超过1秒甚至2秒。但是可以保证的是,最长时间不会超过这N个命令的执行时间和。

    与postgreSQL和MySQL的比较

    这一块就不多说了,由于上面操作系统层面的数据安全已经讲了很多,所以其实不同的数据库在实现上都大同小异。 总之最后的结论就是,在Redis开启AOF的情况下,其单机数据安全性并不比这些成熟的SQL数据库弱。

    这些持久化的数据有什么用,当然是用于重启后的数据恢复。 Redis是一个内存数据库,无论是RDB还是AOF,都只是其保证数据恢复的措施。 所以Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。 相对于MySQL等数据库的启动时间来说,会长很多,因为MySQL本来是不需要将数据加载到内存中的。

    但是相对来说,MySQL启动后提供服务时,其被访问的热数据也会慢慢加载到内存中,通常我们称之为预热,而在预热完成前,其性能都不会太高。而Redis的好处是一次性将数据加载到内存中,一次性预热。这样只要Redis启动完成,那么其提供服务的速度都是非常快的。

    而在利用RDB和利用AOF启动上,其启动时间有一些差别。RDB的启动时间会更短,原因有两个,一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作。在CPU消耗上要远小于AOF日志的加载。

    好了,大概内容就说到这里。更详细完整的版本请看Redis作者的博文:Redis persistence demystified。本文如有描述不周之处,就大家指正。

  • jquery datepicker 中文显示

        var initDatepicker = function() {
            $('input[type=date]').each(function() {
                var $input = $(this);
                $.datepicker.regional['zh-CN'] = {
                    clearText: '清除',
                    clearStatus: '清除已选日期',
                    closeText: '关闭',
                    closeStatus: '不改变当前选择',
                    prevText: '<上月',
                    prevStatus: '显示上月',
                    prevBigText: '<<',
                    prevBigStatus: '显示上一年',
                    nextText: '下月>',
                    nextStatus: '显示下月',
                    nextBigText: '>>',
                    nextBigStatus: '显示下一年',
                    currentText: '今天',
                    currentStatus: '显示本月',
                    monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
                    monthNamesShort: ['一','二','三','四','五','六', '七','八','九','十','十一','十二'],
                    monthStatus: '选择月份',
                    yearStatus: '选择年份',
                    weekHeader: '周',
                    weekStatus: '年内周次',
                    dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
                    dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
                    dayNamesMin: ['日','一','二','三','四','五','六'],
                    dayStatus: '设置 DD 为一周起始',
                    dateStatus: '选择 m月 d日, DD',
                    dateFormat: 'yy-mm-dd',
                    firstDay: 1,
                    initStatus: '请选择日期',
                    isRTL: false
                };
                $.datepicker.setDefaults($.datepicker.regional['zh-CN']);
                $input.datepicker({
                    numberOfMonths: 2,
                    showButtonPanel: true,
                    dateFormat: 'yy-mm-dd'
                });
            });
        };
        $(document).ready(initDatepicker);
    

    http://jqueryui.com/datepicker/

  • 百度地图 JavaScript API 说明

    http://developer.baidu.com/map/jsdemo.htm

    基础应用

    1.创建地图: var map = new BMap.Map(“divid”);

    2.创建坐标点:var point = new BMap.Point(“经度”,”纬度”);

    3.设置视图中心点:map.centerAndZoom(point,size);

    4.激活滚轮调整大小功能:map.enableScrollWheelZoom();

    5.添加控件:map.addControl(new BMap.Xxx());

    6.添加覆盖物:map.addOverlay();

    控件介绍

    1.NavigationControl:缩放地图的控件,默认在左上角;

    2.OverviewMapControl:地图的缩略图的控件,默认在右下方;

    3.ScaleControl:地图显示比例的控件,默认在左下方;

    4.MapTypeControl:地图类型控件,默认在右上方;

    map.addControl()方法添加控件;

    1.标注:Marker

    (1)在point处添加标注:var marker = new BMap.Marker(point);

    (2)添加覆盖物:map.addOverlay(marker);

    (3)激活标注的拖拽功能:marker.enableDragging();

    (4)为标注添加事件:marker.addEventListener(“名称”,function(){

    //点击标注后的事件

    });

    (5)删除覆盖物:map.removeOverlay(marker);

    (6)销毁标注:marker.dispose();

    2.信息窗口:InfoWindow

    (1)在某个特定的位置创建一个信息窗口:var infowindow = new BMap.InfoWindow(“内容”,{width:250,height:100,title:”hello”});

    (2)在地图中央打开信息窗口:map.openInfoWindow(infoWindow,map.getCenter());

    获取坐标:
    http://developer.baidu.com/map/static/doc/Reference_of_Baidu_JavaScript_API_v1.4.pdf
    http://developer.baidu.com/map/static/doc/Guide_for_Baidu_JavaScript_API_v1.4.pdf

    http://api.map.baidu.com/lbsapi/getpoint/index.html
    http://api.map.baidu.com/lbsapi/creatmap/index.html

    test code:

            
    
    function initialize() {
                var map = new BMap.Map('map');
                var point = new BMap.Point(112.550864,37.890277);
                map.centerAndZoom(point, 12);
                /*
                *  缩放控制按钮
                *  BMAP_NAVIGATION_CONTROL_LARGE 表示显示完整的平移缩放控件。
                *  BMAP_NAVIGATION_CONTROL_SMALL 表示显示小型的平移缩放控件。
                *  BMAP_NAVIGATION_CONTROL_PAN 表示只显示控件的平移部分功能。
                *  BMAP_NAVIGATION_CONTROL_ZOOM 表示只显示控件的缩放部分功能。
                * */
                var opts = {type: BMAP_NAVIGATION_CONTROL_ZOOM}
                map.addControl(new BMap.NavigationControl(opts));
                /*
                 *  那个破尺子  可以调整它所在的位置
                 * */
                //var opts = {offset: new BMap.Size(15, 5)}
                //map.addControl(new BMap.ScaleControl(opts));
                /*
                *   此类表示缩略地图控件  就是右下角的那个小地图
                 */
                map.addControl(new BMap.OverviewMapControl());
                /*
                 * 显示其它类型的地图  三维 卫星
                 */
                //map.addControl(new BMap.MapTypeControl());
                //map.setCurrentCity("北京");
    
    /*            map.addEventListener('click', function(e){
                    alert(e.point);
                });*/
    
                /*
                * 从这里我们标注地图
                 */
               var icon = new BMap.Icon('pin.png', new BMap.Size(20, 32), {
                    anchor: new BMap.Size(10, 30)
                });
                var infowindow = new BMap.InfoWindow("内容",{width:250,height:100,title:"hello"});
                //map.openInfoWindow(infowindow,map.getCenter());
    
                var myLabel = new BMap.Label("海辉房产 21000元",     //为lable填写内容
                        {offset:new BMap.Size(-60,-60),                  //label的偏移量,为了让label的中心显示在点上
                            position:point});                                //label的位置
    
                myLabel.setTitle("我是文本标注label");               //为label添加鼠标提示
                map.addOverlay(myLabel);                             //把label添加到地图上
                var circle = new BMap.Circle(point,5000);
                map.addOverlay(circle);
                var mkr =new BMap.Marker(point, {
                    //icon: icon,
                    enableDragging: true,
                    raiseOnDrag: true
                });
                map.addOverlay(mkr);
                mkr.addEventListener('dragend', function(e){
                    //这里可以把获取到的经纬度存到数据库里去
                    console.log(e.point.lng +', '+e.point.lat);
                })
    
    
            }
    
            function loadScript() {
                var script = document.createElement("script");
                script.src = "http://api.map.baidu.com/api?v=1.4&callback=initialize";
                document.body.appendChild(script);
            }
    
            window.onload = loadScript;
     
    
  • 省市数据 json格式 mysql格式

    两种数据

    {"北京":["西城","东城","崇文","宣武","朝阳","海淀","丰台","石景山","门头沟","房山","通州","顺义","大兴","昌平","平谷","怀柔","密云","延庆"],"天津":["青羊","河东","河西","南开","河北","红桥","塘沽","汉沽","大港","东丽","西青","北辰","津南","武清","宝坻","静海","宁河","蓟县","开发区"],"河北":["石家庄","秦皇岛","廊坊","保定","邯郸","唐山","邢台","衡水","张家口","承德","沧州","衡水"],"山西":["太原","大同","长治","晋中","阳泉","朔州","运城","临汾"],"内蒙古":["呼和浩特","赤峰","通辽","锡林郭勒","兴安"],"辽宁":["大连","沈阳","鞍山","抚顺","营口","锦州","丹东","朝阳","辽阳","阜新","铁岭","盘锦","本溪","葫芦岛"],"吉林":["长春","吉林","四平","辽源","通化","延吉","白城","辽源","松原","临江","珲春"],"黑龙江":["哈尔滨","齐齐哈尔","大庆","牡丹江","鹤岗","佳木斯","绥化"],"上海":["浦东","杨浦","徐汇","静安","卢湾","黄浦","普陀","闸北","虹口","长宁","宝山","闵行","嘉定","金山","松江","青浦","崇明","奉贤","南汇"],"江苏":["南京","苏州","无锡","常州","扬州","徐州","南通","镇江","泰州","淮安","连云港","宿迁","盐城","淮阴","沐阳","张家港"],"浙江":["杭州","金华","宁波","温州","嘉兴","绍兴","丽水","湖州","台州","舟山","衢州"],"安徽":["合肥","马鞍山","蚌埠","黄山","芜湖","淮南","铜陵","阜阳","宣城","安庆"],"福建":["福州","厦门","泉州","漳州","南平","龙岩","莆田","三明","宁德"],"江西":["南昌","景德镇","上饶","萍乡","九江","吉安","宜春","鹰潭","新余","赣州"],"山东":["青岛","济南","淄博","烟台","泰安","临沂","日照","德州","威海","东营","荷泽","济宁","潍坊","枣庄","聊城"],"河南":["郑州","洛阳","开封","平顶山","濮阳","安阳","许昌","南阳","信阳","周口","新乡","焦作","三门峡","商丘"],"湖北":["武汉","襄樊","孝感","十堰","荆州","黄石","宜昌","黄冈","恩施","鄂州","江汉","随枣","荆沙","咸宁"],"湖南":["长沙","湘潭","岳阳","株洲","怀化","永州","益阳","张家界","常德","衡阳","湘西","邵阳","娄底","郴州"],"广东":["广州","深圳","东莞","佛山","珠海","汕头","韶关","江门","梅州","揭阳","中山","河源","惠州","茂名","湛江","阳江","潮州","云浮","汕尾","潮阳","肇庆","顺德","清远"],"广西":["南宁","桂林","柳州","梧州","来宾","贵港","玉林","贺州"],"海南":["海口","三亚"],"重庆":["渝中","大渡口","江北","沙坪坝","九龙坡","南岸","北碚","万盛","双桥","渝北","巴南","万州","涪陵","黔江","长寿"],"四川":["成都","达州","南充","乐山","绵阳","德阳","内江","遂宁","宜宾","巴中","自贡","康定","攀枝花"],"贵州":["贵阳","遵义","安顺","黔西南","都匀"],"云南":["昆明","丽江","昭通","玉溪","临沧","文山","红河","楚雄","大理"],"西藏":["拉萨","林芝","日喀则","昌都"],"陕西":["西安","咸阳","延安","汉中","榆林","商南","略阳","宜君","麟游","白河"],"甘肃":["兰州","金昌","天水","武威","张掖","平凉","酒泉"],"青海":["黄南","海南","西宁","海东","海西","海北","果洛","玉树"],"宁夏":["银川","吴忠"],"新疆":["乌鲁木齐","哈密","喀什","巴音郭楞","昌吉","伊犁","阿勒泰","克拉玛依","博尔塔拉"],"香港":["中西区","湾仔区","东区","南区","九龙-油尖旺区","九龙-深水埗区","九龙-九龙城区","九龙-黄大仙区","九龙-观塘区","新界-北区","新界-大埔区","新界-沙田区","新界-西贡区","新界-荃湾区","新界-屯门区","新界-元朗区","新界-葵青区","新界-离岛区"],"澳门":["花地玛堂区","圣安多尼堂区","大堂区","望德堂区","风顺堂区","嘉模堂区","圣方济各堂区","路氹城"]}
    

    中国省市区三级数据库表 txt