• Redis Cluster 3.0搭建与使用


    最近redis已经比较火了,有关redis的详细介绍,网上有一大堆,我这里只作简单的介绍,然后跟大家一起学习Redis Cluster 3.0的搭建与使用。Redis是一款开源的、网络化的、基于内存的、可进行数据持久化的Key-Value存储系统。它的数据模型建立在外层,类似于其它结构化存储系统,是通过Key映射Value的方式来建立字典以保存数据,有别于其它结构化存储系统的是,它支持多类型存储,包括String、List、Set、Sort set和Hash等,你可以在这些数据类型上做很多原子性操作,比如使用Push/Pop/Add/Remove、交并集、差集等,同时在此基础之上可以支持不同方式的排序。

        reids-cluster计划在redis3.0中推出,相信很多小伙伴们听到都有点小鸡动吧,哈哈,下面我们一起来玩玩reids-cluster

    一、Redis集群介绍

        1)Redis集群是一个可以在多个Redis节点之间进行数据共享的设施(installation)。

        2)Redis集群不支持那些需要同时处理多个键的Redis命令,因为执行这些命令需要在多个Redis节点之间移动数据,并且在高负载的情况下,这些命令将降低Redis集群的性能,并导致不可预测的行为。

        3)Redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部份分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

    Redis集群提供了以下两个好处:

        1、将数据自动切分(split)到多个节点的能力。

        2、当集群中的一部份节点失效或者无法进行通讯时,仍然可以继续处理命令请求的能力。

    二、Redis集群数据共享

    Redis集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现:一个Redis集群包含16384个哈希槽(hash slot),数据库中的每个键都属于这个16384个哈希槽的其中一个,集群使用公式CRC16(key) % 16384来计算键key属于哪个槽,其中CRC16(key)语句用于计算键key的CRC16校验和。

    集群中的每个节点负责处理一部分哈希槽。举个例子,一个集群有三个哈希槽,其中:

         节点 A 负责处理0号至于5500号哈希槽。

         节点 B 负责处理5501号至11000号哈希槽。

         节点 C 负责处理11001号至16384号哈希槽。

    这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。比如说:

    • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
    • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

     因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞,所以无论是添加新节点还是移除已经存在的节点,又或者改变某个节点包含的哈希数量,都不会造成集群下线。

    三、Redis集群中的主从复制

    为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作,Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

    在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000 号的哈希槽。

    另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。

    不过如果节点 B 和 B1 都下线的话,Redis 集群还是会停止运作。

    1)redis-cluster架构图:

    架构细节:

    (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

    (2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

    (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

    (4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

    2.redis-cluster选举:容错图:

    (1)领着选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉。

    (2):什么时候整个集群不可用(cluster_state:fail)?

        a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态。 (redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败。)

        b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态。(当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误)

    四、Redis Cluster搭建使用

    要让集群正常运作至少需要三个主节点, 不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点:(为了方便,我只有一台机子演示),分别是:

    127.0.0.1:7000

    127.0.0.1:7001

    127.0.0.1:7002

    127.0.0.1:7003

    127.0.0.1:7004

    127.0.0.1:7005

    1、下载软件包:

    [root ~]$ wget http://download.redis.io/releases/redis-3.0.0.tar.gz

    2. 解压,安装

    [root ~]$ tar xf redis-3.0.0.tar.gz 
    [root ~]$ cd redis-3.0.0
    [root redis-3.0.0]$ make && make install

    3.创建存放多个实例的目录

    [root redis-3.0.0]$ mkdir /data/cluster -p
    [root redis-3.0.0]$ cd /data/cluster
    [root cluster]$ mkdir 7000 7001 7002 7003 7004 7005

    4.修改配置文件

    [root redis-3.0.0]$ pwd
    /root/redis-3.0.0
    [root redis-3.0.0]$ cp redis.conf /data/cluster/7000/

    修改配置文件中下面选项

    port 7000

    daemonize yes

    cluster-enabled yes

    cluster-config-file nodes.conf

    cluster-node-timeout 5000

    appendonly yes

    选项简单介绍:

       cluster-enabled:用于开实例的集群模式

       cluster-conf-file:选项则设定了保存节点配置文件的路径,默认值为nodes.conf

    节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新

    修改完成后,把修改好的redis.conf复制到7001-7005目录下,并修改其端口

    复制代码
    [root 7000]$ cp redis.conf /data/cluster/7001/
    [root 7000]$ cp redis.conf /data/cluster/7002/
    [root 7000]$ cp redis.conf /data/cluster/7003/
    [root 7000]$ cp redis.conf /data/cluster/7004/
    [root 7000]$ cp redis.conf /data/cluster/7005/
    [root 7000]$ sed -i "s#7000#7001#g" /data/cluster/7001/redis.conf 
    [root 7000]$ sed -i "s#7000#7002#g" /data/cluster/7002/redis.conf   
    [root 7000]$ sed -i "s#7000#7003#g" /data/cluster/7003/redis.conf   
    [root 7000]$ sed -i "s#7000#7004#g" /data/cluster/7004/redis.conf   
    [root 7000]$ sed -i "s#7000#7005#g" /data/cluster/7005/redis.conf
    复制代码

    5.分别启动6个redis实例。

    复制代码
    [root ~]$ cd /data/cluster/7000
    [root 7000]$ redis-server redis.conf
    
    [root 7000]$ cd /data/cluster/7001 [root 7001]$ redis-server redis.conf
    [root 7001]$ cd /data/cluster/7002 [root 7002]$ redis-server redis.conf
    [root 7002]$ cd /data/cluster/7003 [root 7003]$ redis-server redis.conf
    [root 7003]$ cd /data/cluster/7004 [root 7004]$ redis-server redis.conf
    [root 7004]$ cd /data/cluster/7005 [root 7005]$ redis-server redis.conf
    复制代码

    查看进程是否都正常启动了:

    复制代码
    [root 7005]$ ps -ef | grep redis
     root     16403     1  0 01:38 ?        00:00:00 redis-server *:7000 [cluster]
     root     16408     1  0 01:39 ?        00:00:00 redis-server *:7001 [cluster]
     root     16412     1  0 01:39 ?        00:00:00 redis-server *:7002 [cluster]
     root     16416     1  0 01:39 ?        00:00:00 redis-server *:7003 [cluster]
     root     16420     1  0 01:39 ?        00:00:00 redis-server *:7004 [cluster]
     root     16424     1  0 01:40 ?        00:00:00 redis-server *:7005 [cluster]
     root     16428  9516  0 01:43 pts/0    00:00:00 grep redis
    [root 7005]$
    复制代码

    6.执行命令创建集群,首先安装依赖,否则创建集群失败。

    [root ~]$ yum install ruby rubygems -y

    安装gem-redis

    下载地址:https://rubygems.org/gems/redis/versions/3.0.0

    [root ~]$ gem install -l redis-3.0.0.gem 
    Successfully installed redis-3.0.0
    1 gem installed
    Installing ri documentation for redis-3.0.0...
    Installing RDoc documentation for redis-3.0.0...

    复制集群管理程序到/usr/local/bin

    [root ~]$ cp redis-3.0.0/src/redis-trib.rb /usr/local/bin/redis-trib

    创建集群:

    [root ~]$ redis-trib create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

    命令的意义如下:

    • 给定 redis-trib.rb 程序的命令是 create , 这表示我们希望创建一个新的集群。
    • 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
    • 之后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。

    简单来说, 以上命令的意思就是让 redis-trib 程序创建一个包含三个主节点和三个从节点的集群。

    如果配置都OK,会打印如下信息,然后你觉得OK,没问题后,就可以输入yes

    复制代码
    >>> Creating cluster
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    Connecting to node 127.0.0.1:7005: OK
    >>> Performing hash slots allocation on 6 nodes...
    Using 3 masters:
    127.0.0.1:7000
    127.0.0.1:7001
    127.0.0.1:7002
    Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
    Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
    Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
    M: d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000
       slots:0-5460 (5461 slots) master
    M: ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001
       slots:5461-10922 (5462 slots) master
    M: a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002
       slots:10923-16383 (5461 slots) master
    S: c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003
       replicates d8a0120620d3563623190ba214a9c717f19b66c0
    S: 7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004
       replicates ef3361880fa6f88a27415a8f57d7c6dfd848b500
    S: 873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005
       replicates a33e7add3a1561ebd740c910e538cd1debde0aee
    Can I set the above configuration? (type 'yes' to accept): yes
    >>> Nodes configuration updated
    >>> Assign a different config epoch to each node
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join...
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000
       slots:0-5460 (5461 slots) master
    M: ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001
       slots:5461-10922 (5462 slots) master
    M: a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002
       slots:10923-16383 (5461 slots) master
    M: c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003
       slots: (0 slots) master
       replicates d8a0120620d3563623190ba214a9c717f19b66c0
    M: 7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004
       slots: (0 slots) master
       replicates ef3361880fa6f88a27415a8f57d7c6dfd848b500
    M: 873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005
       slots: (0 slots) master
       replicates a33e7add3a1561ebd740c910e538cd1debde0aee
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    复制代码

    输入 yes 并按下回车确认之后, 集群就会将配置应用到各个节点, 并连接起(join)各个节点 ——也即是, 让各个节点开始互相通讯,一切正常输出的信息如下:

    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.


    五、集群客户端

    Redis 集群现阶段的一个问题是客户端实现很少。 以下是一些我知道的实现:

    • redis-rb-cluster 是我(@antirez)编写的 Ruby 实现, 用于作为其他实现的参考。 该实现是对 redis-rb 的一个简单包装, 高效地实现了与集群进行通讯所需的最少语义(semantic)。
    • redis-py-cluster 看上去是 redis-rb-cluster 的一个 Python 版本, 这个项目有一段时间没有更新了(最后一次提交是在六个月之前), 不过可以将这个项目用作学习集群的起点。
    • 流行的 Predis 曾经对早期的 Redis 集群有过一定的支持, 但我不确定它对集群的支持是否完整, 也不清楚它是否和最新版本的 Redis 集群兼容 (因为新版的 Redis 集群将槽的数量从 4k 改为 16k 了)。
    • Redis unstable 分支中的 redis-cli 程序实现了非常基本的集群支持, 可以使用命令 redis-cli -c 来启动。

    测试 Redis 集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli , 接下来我们将使用 redis-cli 为例来进行演示:

    复制代码
    [root ~]$ redis-cli -c -p 7001
    127.0.0.1:7001> set name test
    OK
    127.0.0.1:7001> set user root
    OK-> Redirected to slot [741] located at 127.0.0.1:7000 OK 127.0.0.1:7000> get name -> Redirected to slot [5798] located at 127.0.0.1:7001 "test" 127.0.0.1:7000>
    复制代码

    我们可以看看还有哪些命令可以用:

    复制代码
    [root ~]$ redis-trib help
    Usage: redis-trib <command> <options> <arguments ...>
    
      help            (show this help)
      set-timeout     host:port milliseconds
      del-node        host:port node_id
      import          host:port
                      --from <arg>
      add-node        new_host:new_port existing_host:existing_port
                      --master-id <arg>
                      --slave
      reshard         host:port
                      --to <arg>
                      --from <arg>
                      --slots <arg>
                      --yes
      check           host:port
      call            host:port command arg arg .. arg
      create          host1:port1 ... hostN:portN
                      --replicas <arg>
      fix             host:port
    
    For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
    复制代码

     按字面意思,相信大家都能看懂,add-node添加节点,del-node删除节点,check检查节点,set-timeout设置超时时间,意思是多久没返回状态,就认为它挂了。

    复制代码
    [root ~]$ redis-cli -p 7000 cluster nodes 
    d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
    7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004 slave ef3361880fa6f88a27415a8f57d7c6dfd848b500 0 1428476589001 5 connected
    873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005 slave a33e7add3a1561ebd740c910e538cd1debde0aee 0 1428476587997 6 connected
    a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002 master - 0 1428476581989 3 connected 10923-16383
    c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003 slave d8a0120620d3563623190ba214a9c717f19b66c0 0 1428476585992 4 connected
    ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001 master - 0 1428476586995 2 connected 5461-10922
    [root ~]$
    复制代码

    可以看到7000、7001、7002是master,7003-7005是slave,同时也可以看到每个master管理的哈希槽,7000管理的哈希槽是0-5460,7001管理的哈希槽是5461-10922,7002管理的哈希槽是10923-16383

    六、故障转移测试:

    127.0.0.1:7001> KEYS *
    1) "user"
    2) "name"
    127.0.0.1:7001> 

    模拟master节点7001挂了:

    [root ~]$ ps -ef | grep 7001
    root     16408     1  0 01:39 ?        00:00:04 redis-server *:7001 [cluster]
    root     18743  9516  0 15:19 pts/0    00:00:00 grep 7001
    [root ~]$ kill -9 16408

    查看一下新的master转移到那个节点上了:

    复制代码
    [root ~]$ redis-cli -c -p 7000 cluster nodes 
    d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
    7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004 master - 0 1428477751718 7 connected 5461-10922
    873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005 slave a33e7add3a1561ebd740c910e538cd1debde0aee 0 1428477755719 6 connected
    a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002 master - 0 1428477754719 3 connected 10923-16383
    c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003 slave d8a0120620d3563623190ba214a9c717f19b66c0 0 1428477749716 4 connected
    ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001 master,fail - 1428477608909 1428477606404 2 disconnected
    复制代码

    可以看到7001 master,fail,原来的7004是slave,现在自动提升为master,测试一下取值:

    复制代码
    [root ~]$ redis-cli -c -p 7004
    127.0.0.1:7004> keys *
    1) "user"
    2) "name"
    127.0.0.1:7004> get name
    "test"
    127.0.0.1:7004> get user
    "root"
    127.0.0.1:7004> 
    复制代码

    依然正常获取键值。
    同理,在7000里设置几个键值,然后kill掉7000节点:

    127.0.0.1:7000> keys *
    1) "aa"
    2) "age"
    127.0.0.1:7000> 
    [root ~]$ ps -ef | grep 7000
     root     16403     1  0 01:38 ?        00:00:04 redis-server *:7000 [cluster]
     root     18775  9516  0 15:34 pts/0    00:00:00 grep 7000
    [root ~]$ kill -9 16403

    然后查一下,可以发现7003提升为master了,其实对应关系是7000(master)->7003(slave),7001(master)->7004(slave),7002(master)->7005(slave),当master死掉,会自动把slave提升为master

    复制代码
    [root ~]$ redis-cli -c -p 7002 cluster nodes
    873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005 slave a33e7add3a1561ebd740c910e538cd1debde0aee 0 1428478631146 6 connected
    c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003 master - 0 1428478630145 8 connected 0-5460
    a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002 myself,master - 0 0 3 connected 10923-16383
    ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001 master,fail - 1428477609001 1428477607099 2 disconnected
    d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000 master,fail - 1428478495767 1428478491866 1 disconnected
    7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004 master - 0 1428478632146 7 connected 5461-10922
    复制代码

    登录7003节点测试下:

    复制代码
    [root ~]$ redis-cli -c -p 7003
    127.0.0.1:7003> keys *
    1) "aa"
    2) "age"
    127.0.0.1:7003> get age
    "24"
    127.0.0.1:7003> get aa
    "abc"
    127.0.0.1:7003> 
    复制代码

    还是正常的。
    接着我们测试下,如果刚刚从slave节点升为master节点的节点也挂了,集群还能不能正常工作呢?我们把7004 kill掉:

    [root ~]$ ps -ef | grep 7004
     root     16420     1  0 01:39 ?        00:00:04 redis-server *:7004 [cluster]
     root     18785  9516  0 15:44 pts/0    00:00:00 grep 7004
    [root ~]$ kill -9 16420

    检查一下有没有新的节点代替7004成为新的master:

    复制代码
    [root ~]$ redis-cli -c -p 7002 cluster nodes
    873a82deae732b73b9377e992174a7d52db9fcc9 127.0.0.1:7005 slave a33e7add3a1561ebd740c910e538cd1debde0aee 0 1428479109253 6 connected
    c48d68f0c44a2e242b5695909aee629005c1da60 127.0.0.1:7003 master - 0 1428479108253 8 connected 0-5460
    a33e7add3a1561ebd740c910e538cd1debde0aee 127.0.0.1:7002 myself,master - 0 0 3 connected 10923-16383
    ef3361880fa6f88a27415a8f57d7c6dfd848b500 127.0.0.1:7001 master,fail - 1428477609001 1428477607099 2 disconnected
    d8a0120620d3563623190ba214a9c717f19b66c0 127.0.0.1:7000 master,fail - 1428478495767 1428478491866 1 disconnected
    7bf071948c90ab64e650a7a76347f25556350336 127.0.0.1:7004 master,fail - 1428479059546 1428479058145 7 disconnected 5461-10922
    [root ~]$ 
    复制代码

    可以看到7005还是slave,没有提升为master。所以当master节点和候选master节点都挂了,整个集群都将会无法再正常工作。我们登录还活着的master 7003验证下:

    复制代码
    [root ~]$ redis-cli -c -p 7003
    127.0.0.1:7003> keys *
    1) "aa"
    2) "age"
    127.0.0.1:7003> get aa
    (error) CLUSTERDOWN The cluster is down
    127.0.0.1:7003> 
    复制代码

    可以看到The cluster is down,这集群已经down了,说明集群已经不可用了
    关于更多的在线添加节点,删除节点,以及对集群进行重新分片请参考官方文档。

    总结:

    一、redis-cluster是个好东西,而且配置非常简单,当master节点挂了后,slave会自动提升为master,还是挺不错的

    二、由于redis-cluster才刚刚出来不久,而且现在使用的人比较少,如果想在生产环境搭建,要慎重考虑,需要进行严格的测试,不过相信它会慢慢强大起来的。

    三、现在比较成熟redis的集群可以考虑使用Twitter开源的twemproxy,以及豌豆荚开源的codis,这两个项目都比较成熟,现在使用的公司很多

    参考资料:

    http://redis.readthedocs.org/en/latest/topic/cluster-tutorial.html

    http://hot66hot.iteye.com/blog/2050676

    http://www.cnblogs.com/gomysql/p/4395504.html

  • 相关阅读:
    生日小助手源码运行的步骤
    关于生日小助手跨平台兼容性的临时解决方案
    生日小助手V3.0——跨平台的农历生日提醒软件
    生日小助手V3.1——跨平台多语言的农历生日提醒软件
    有关生日小助手的内容,请浏览生日小助手官方网站……
    生日小助手的详细规划——本博文随时更新,持续有效
    生日小助手V2.0发布了——可以正式投入使用!
    前端开发入门的几本推荐书籍
    多想一想,JS中函数声明和函数表达式的区别
    table固定宽度大小
  • 原文地址:https://www.cnblogs.com/hujihon/p/6429255.html
Copyright © 2020-2023  润新知