mongodb是目前使用非常广泛的nosql(not only sql)之一,在db engines上排名非常靠前,下图是5月份的排名:

  

  可以看到前面四个都是传统的关系型数据库,而mongodb在nosql中拔得头筹。本文会简单介绍mongodb的一些特性,然后通过在Linux环境下一步步搭建sharded cluster来学习mongodb。本文实验的mongodb是mongodb3.0,可能与最新的版本(mongodb3.4)在细节之处略有差异。

Mongodb特性

   官方一句话就能概括Mongodb的特点:
  MongoDB is an open-source document database that provides high performance, high availability, and automatic scaling.

  开源、基于文档(document oriented)、高性能、高可用、自动伸缩。

  开源

  这个好处就不用多说了,GitHub上有源码。

  面向文档
  文档(document)在很多编程语言都有类似的数据结构,各种table、map、dict,再也不用使用DAO(data access object)。比如在python中,document与dict对应,array与list对应。
      document也支持嵌套的document和array,这样的话也能部分解决关联查询(当然,虽然把相关信息放在一个嵌套的document降低了关联查询的开销,但在某些情况不得不需要关联查询的时候还是有点头疼)
  由于基于document,所以就schema free(模式自由)啦,使用关系型数据的同学都知道,线上修改表结构是多么麻烦的一件事情。但在mongodb中,增该删一个字段太容易了,这个也是最后开发人员喜欢的一点,比如游戏服务器,玩家的持久化数据会不停的变化,每次更新都会增加一些功能,也就回增加一些需要持久化的字段,用mongodb就很合适。
  

  高性能
  支持嵌套的document,在关系型数据库中需要联合查询的耗时操作,在mongodb中一条查询就能搞定
  丰富的索引支持,而且索引还支持嵌套文档和数组
  在使用了sharding机制的情况下,读写操作可以路由到不同的shard,提到了集群的并发性能
 
  高可用
  要想mongodb高可用,那么就得使用mongodb的复制机制:replica set
  replica set通过异步复制(Asynchronous replication)和自动Failover来保证可用性
  后面会专门介绍replica set
 
  自动扩展(水平拆分)
  在关系型数据中,当单个表数据量过大的时候,一般会通过垂直分表或者水平分表的方式来提到数据库吞吐量。在mongodb中,sharding是其核心功能之一,提供了自动的水平扩展(horizontal scalability)来对数据量比较大的集合进行拆分,sharding将同一个集合的不同子集数据放在不同的机器上,当应用程序选择好适当的sharding key,可以将读写操作路由到某一个shard上,大大提高了集群吞吐性能。
  后面会专门介绍sharding cluster的组成
 

预备知识

  本文并不涵盖mongodb的基础知识,但是为了后面介绍sharding知识,以及搭建sharded cluster,在这里介绍一下_id这个特殊的字段(field)
  默认情况下,mongodb会在集合的_id字段上创建unique index。如果没有在持久化的文档中包含_id,那么mongodb会自动添加这个字段,其value是一个ObjectId。
  mongodb官方建议_id使用ObjectId、或者自然唯一标示(unique identifier)、说着自增的数字、或者UUID。_id在sharding相关的CRUD中有特殊性,具体使用的时候可以参加文档。
  另外,为了后文描述方便,这里声明几个在mongodb中的概念
  DB:与mysql的DB对应
  collection、集合: 与mysql的table对应
  document:与mysql的record对应

replica set

  MongoDB中通过replica set(复制集)来提供高可用性:冗余与自动failover。
  复制集是说将同一份数据的多分拷贝放在不同的机器(甚至不同的数据中心)来提高容错。一个典型的replica set由一组mongod实例组成,其中有且仅有一个节点提供写操作,称之为primary,primary也是默认的读节点。同时,replica set中可包含一个到多个secondary节点,secondary节点只提供读操作。如下图所示:
  

  应用程序通过驱动与Primary连接,所有的写操作都在Primary上进行,同时primary会将这些操作写到oplog(operation log)中,secondary通过异步复制oplog,然后在本地数据集上执行oplog中的操作,这样就达到了数据的一致性。从这里可以看到,虽然secondary和primary维护的上同一份数据,但是其变更是要迟于primary的。

  如果应用程序对数据的实时性要求不太高,比如评论数据、聚合数据等,那么可以从secondary读取,这样可以做到读写分离和高并发。如果拷贝放在不同的数据中心,能更好的提供数据局部性(data locality)和分布式服务的可用性。
 
  我们看到,如果一个Secondary不能正常工作了(可能是进程crash、物理损坏、网络故障),对应用程序来说影响并不大。但是如果primary不能工作了呢?这个时候mongodb的automatic failover就开始发挥作用了。
  在replica set中的所用mongod节点之间都会有心跳(heartbeat)存在,如果超过一定时间其他节点没有收到primary的心跳,那么就认为primary挂掉了。可被选举的secondary会投票选举出新的primary。整个过程如下所示:

  自动的failover 虽然保证了mongodb的高可用性,但是在primary到secondary的切换过程中,这一段时间,mongodb是无法提供写操作的。表现就是对于应用程序的数据库操作请求会返回一些错误,这个时候应用程序需要识别这些错误,然后做重试。

  

  除了Primary和Secondary,在replica set中还可以存在存在另外一种节点:Arbiter。Arbiter与Secondary节点的区别在于,Arbiter不持久化数据(do not bearing data),  自然也不可能在Primary挂掉的时候被选举。Arbiter的作用在于投票:为了选出新的primary,secondary投票规则是少数服从多数,如果replica set中的节点数目是偶数,那么就可能出现“平局”的情况,所以加入一个Arbiter就可以以最小的代价解决这个问题。

   Arbiter不持久化数据,所以占用的磁盘空间也很少,对硬件的要求也不高。官方建议,Arbiter不要和primary或者secondary放在同一个物理主机上。
  在后面的演示中,也会在replica set中加入一个Arbiter,减少磁盘占用。

sharded cluster

  所谓sharding就是将同一个集合的不同子集分发存储到不同的机器(shard)上,Mongodb使用sharding机制来支持超大数据量,将不同的CRUD路由到不同的机器上执行,提到了数据库的吞吐性能。由此可见,sharding是非常常见的scale out方法。

  

  如上图所示,一个集合(Collection1)有1T的数据,原本放在一个单独的数据库中,通过sharding,将这个集合的数据放在四个独立的shard中,每一个shard存储这个集合256G的数据。每个shard物理上是独立的数据库,但逻辑上共同组成一个数据库。

  一个sharded cluster由一下三部分组成:config server,shards,router。如图所示:

  

  shards

  存储数据,可以是单个的mongod,也可以是replica set。在生产环境中,为了提高高可用性,都会使用replica set。存储在mongod上的数据以chunk为基本单位,默认的大小为64M,后面会介绍shard上数据的分裂(split)与迁移(migration)

  config server

  存储集群的元数据(metadata),即数据的哪一部分放在哪一个shard上,router将会利用这些元数据将请求分发到对应的shards上,shards上chunk的迁移也是config server来控制的。

  router:

  mongos实例,在一个集群中直接为应用程序提供服务,利用config server上的元数据来制定最佳的查询计划。

  数据分割(data partition)

  从前文知道,MongoDB在collection这个级别进行数据的切块,称之为sharding。块的最小粒度是chunk,其大小(chunkSize)默认为64M。

  当一个集合的数据量超过chunkSize的时候,就会被拆分成两个chunk,这个过程称为splitting。那么按照什么原则将一个chunk上的数据拆分成两个chunk,这就是Sharding key的作用,Sharding key是被索引的字段,通过sharding key,就可以把数据均分到两个chunk,每一个document在哪一个chunk上,这就是元数据信息。元数据信息存放在config server上,方便router使用。

  如果sharding cluster中有多个shard,那么不同shard上的chunk数目可能是不一致的,这个时候会有一个后台进程(balancer)来迁移(migrate)chunk,从chunk数目最多的shard迁移到chunk数目最少的chunk,直到达到均衡的状态。迁移的过程对应用程序来说是透明的。

  如下图所示,迁移之前ShardA ShardB上都有3个chunk,而Shard C上只有一个Chunk。通过从ShardB上迁移一个chunk到ShardC,就达到了一个均衡的状态。  

  splitting和migration 的目的是为了让数据在shards之间均匀分布,其根本目标是为了将对数据的CRUD操作均衡地分发到各个shard,提高集群的并发性能。

  

Sharded cluster搭建

  声明,本章节只是演示Sharded Cluster的搭建过程,与生产环境还是有较大差异,不过我也会在文中尽量指出这些差异。首先需要注意的是,本文的演示不涉及到鉴权(--auth),但在生产环境中鉴权是非常重要的,相信大家都还记得春节期间Mongodb被劫持、被攻击的事件。
  前文已经提到,一个典型的Sharded Cluster包括router(mongos)、config server和shards,其中每个shard都可以是单点(standalone)或者复制集(replica set)。接下来的演示包括一个router, 三个config server,两个shard。每一个shard都是有一个primary、一个secondary和一个arbiter组成的replica set。
  在开始之前,首先预定义好所有需要用到的变量,如下所示:
 #!/bin/bash
export BIN_HOME=/usr/local/mongodb/bin
export DB_PATH=/home/mongo_db/data
export LOG_PATH=/home/mongo_db/log LOCAL=127.0.0.1 #config rs
export RS1_1_DB_PATH=$DB_PATH/rs1_1
export RS1_2_DB_PATH=$DB_PATH/rs1_2
export RS1_3_DB_PATH=$DB_PATH/rs1_3
export RS2_1_DB_PATH=$DB_PATH/rs2_1
export RS2_2_DB_PATH=$DB_PATH/rs2_2
export RS2_3_DB_PATH=$DB_PATH/rs2_3 export RS1_1_DB_LOG=$LOG_PATH/rs1_1.log
export RS1_2_DB_LOG=$LOG_PATH/rs1_2.log
export RS1_3_DB_LOG=$LOG_PATH/rs1_3.log
export RS2_1_DB_LOG=$LOG_PATH/rs2_1.log
export RS2_2_DB_LOG=$LOG_PATH/rs2_2.log
export RS2_3_DB_LOG=$LOG_PATH/rs2_3.log export RS1_1_PORT=
export RS1_2_PORT=
export RS1_3_PORT=
export RS2_1_PORT=
export RS2_2_PORT=
export RS2_3_PORT= export RS1=rs1
export RS2=rs2 #config config_server
export CONF1_DB_PATH=$DB_PATH/db_conf1
export CONF2_DB_PATH=$DB_PATH/db_conf2
export CONF3_DB_PATH=$DB_PATH/db_conf3 export CONF1_DB_LOG=$LOG_PATH/conf1.log
export CONF2_DB_LOG=$LOG_PATH/conf2.log
export CONF3_DB_LOG=$LOG_PATH/conf3.log export CONF1_PORT=
export CONF2_PORT=
export CONF3_PORT= export CONF1_HOST=$LOCAL:$CONF1_PORT
export CONF2_HOST=$LOCAL:$CONF2_PORT
export CONF3_HOST=$LOCAL:$CONF3_PORT #config route_server
export ROUTE_DB_LOG=$LOG_PATH/route.log export ROUTE_PORT=

  可以在会话窗口中将这些命令执行一遍,不过更好的方式是将其保存在一个文件中(如mongodb_define.sh),然后执行这个文件就行了:source mongodb_define.sh

启动shards(replica set)

  在这一部分,会创建两个replica set,分别是rs1, rs2。每个replica set包含三个节点,且其中一个是arbiter。由于两个replica set创建过程没什么区别,因此以rs1为例。关于replica set的搭建,可参见mongodb doc中deploy-replica-set-for-testing部分,讲得比较清楚。
  step1: 首先得创建好存放数据的目录:
  mkdir -p $RS1_1_DB_PATH
  mkdir -p $RS1_2_DB_PATH
  mkdir -p $RS1_3_DB_PATH
  PS: -p means "no error if existing, make parent directories as needed"
 
  step2: 启动组成rs1的三个mongod 

  $BIN_HOME/mongod --port $RS1_1_PORT --dbpath $RS1_1_DB_PATH --fork --logpath $RS1_1_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_2_PORT --dbpath $RS1_2_DB_PATH --fork --logpath $RS1_2_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_3_PORT --dbpath $RS1_3_DB_PATH --fork --logpath $RS1_3_DB_LOG --replSet $RS1 --smallfiles --nojournal

  关于mongod的启动选项,可以通过mongod --help查看,在上面的命令行中,--replSet 指定了replica set的名字, --smallfiles 声明使用更小的默认文件, --nojournal表明不开启journaling机制。注意,在这个地方不开启journaling是因为实验环境磁盘空间有限,而所有的mongod实例都在这个机器上,在生成环境中,一定要开始journaling,这个是mongodb durability的保证。
  
  step3:初始化复制集rs1
  在这一步,需要通过mongdb的客户端mongo连接到复制集的任何一个节点,对复制集初始化,这里连接到RS1_1(端口为27018):
  mongo --port $RS1_1_PORT
  先来看一下现在复制集的状态(PS:下面所有以 > 开头的命令行都表示是在mongo这个交互式客户端输入的指令)
  > rs.status()
  
  可以看到这个复制集还没有初始化
  >config = {

    _id : "rs1",
    members : [
      {_id : 0, host : "127.0.0.1:27018"},
      {_id : 1, host : "127.0.0.1:27019"},
      {_id : 2, host : "127.0.0.1:27020", arbiterOnly: true},
    ]
  }

  >rs.initiate(config)

  

  从config和运行后的复制集状态都可以看到,RS1_3(127.0.0.1:27020)这个mongod为一个Arbiter,即只参与投票,不持久化数据。另外RS1_1为Primary, RS1_2为Secondary。
  到此为止,复制集rs1就启动好了。
 
 
  关于s2的启动,下面也给出所有命令。方便读者实践

  mkdir -p $RS2_1_DB_PATH
  mkdir -p $RS2_2_DB_PATH
  mkdir -p $RS2_3_DB_PATH

  $BIN_HOME/mongod --port $RS1_1_PORT --dbpath $RS1_1_DB_PATH --fork --logpath $RS1_1_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_2_PORT --dbpath $RS1_2_DB_PATH --fork --logpath $RS1_2_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_3_PORT --dbpath $RS1_3_DB_PATH --fork --logpath $RS1_3_DB_LOG --replSet $RS1 --smallfiles --nojournal

  mongo --port $RS2_1_PORT
  >config = {
  _id : "rs2",
  members : [
  {_id : 0, host : "127.0.0.1:27021"},
  {_id : 1, host : "127.0.0.1:27022"},
  {_id : 2, host : "127.0.0.1:27023", arbiterOnly: true},
  ]
  }
  >rs.initiate(config)

启动config servers

  mongodb官方建议config server需要三个mongod实例组成,每一个mongod最好部署在不同的物理机器上。这个三个mongod并不是复制集的关系,

  step1:创建db目录

  mkdir -p $CONF1_DB_PATH
  mkdir -p $CONF2_DB_PATH
  mkdir -p $CONF3_DB_PATH

  step2:启动三个mongod实例:

  $BIN_HOME/mongod --port $CONF1_PORT --dbpath $CONF1_DB_PATH --fork --logpath $CONF1_DB_LOG --configsvr --smallfiles --nojournal
  $BIN_HOME/mongod --port $CONF2_PORT --dbpath $CONF2_DB_PATH --fork --logpath $CONF2_DB_LOG --configsvr --smallfiles --nojournal
  $BIN_HOME/mongod --port $CONF3_PORT --dbpath $CONF3_DB_PATH --fork --logpath $CONF3_DB_LOG --configsvr --smallfiles --nojournal

  同样启动参数中nojournal只是为了节省存储空间,在生产环境中一定要使用journaling。与创建replica set时mongod的启动不同的是,这里有一个configsvr 选项,表明这些节点都是作为config server存在。

  再启动这三个mongod之后,不会有类似replica set那样讲三个mongod绑定之类的操作,也说明了config server之间是相互独立的

启动router

  在Sharded Cluster中,router(mongos)是应用程序连接的对象,一切对mongodb的操作都通过router来路由
  step1:启动mongos
 
  $BIN_HOME/mongos --port $ROUTE_PORT --configdb $CONF1_HOST,$CONF2_HOST,$CONF3_HOST --fork --logpath $ROUTE_DB_LOG --chunkSize 32
  注意这里的可执行程序是mongos,而不是之前的mongod,关于参数,也是可以通过mongos --help查看的。在上面的命令中,--configdb选项指定了三个config server,--chunkSize指定了chunk的大小,单位为M。关于chunksize,默认是64M,虽然可以在初次启动的时候指定chunksize,但mongodb官方推荐按照以下方式修改。 在本文中将chunkSize改小的目的,是为了以后实验的时候更方便观察数据的拆分和迁移。
  chunkSize事实上会持久化到config.setting中,连接到mongos可查看:
  mongo --port $ROUTE_PORT 
  

  在上面截图蓝色框中可以看出,现在还没有任何shard的信息,原因是到现在为止,config servers与replica set还没有任何关系

 
  step2:将在前面创建的两个replica set(rs1 rs2)加入到Sharded Cluster中
  mongo --port $ROUTE_PORT 

  mongos> sh.addShard('rs1/127.0.0.1:27018')
  mongos> sh.addShard('rs2/127.0.0.1:27021')

  PS:为什么需要在rs1后面指定一个mongod的ip port,这个是用来找到对应的mongod,继而找到相应rs

  再次查看结果:

  
  可以看到已经添加了两个shard,每一个都是一个replica set。有意思的是Arbiter(比如RS1_3)并没有显示在查询结果中,可能的原因是Arbiter并不持久化数据,显示在这里也没有什么意义。
 
  到此为止,整个Sharded Cluster就算搭建好了,但是还未进入真正使用阶段,要发挥Sharded Cluster的作用,我们得指定哪些collection可以被sharding,以及如何sharding

创建sharding key

  为了演示,我们假设添加一个db叫test_db, 其中有两个collection,一个是需要sharding的,叫sharded_col;另一个暂时不用sharding,叫non_sharded_col, 当然之后也可以增加新的集合,或者把原来没有sharding的集合改成sharding。

  一下操作都需要登录到router进行: mongo --port $ROUTE_PORT

  step1:首先得告知mongodb test_db这个数据库支持sharding

  mongos> sh.enableSharding("test_db")
  { "ok" : 1 }

  这个时候可以查看数据库的状态,注意,是在config这个db下面的databases集合

  mongos> use config

  mongos> db.databases.find()
  { "_id" : "admin", "partitioned" : false, "primary" : "config" }
  { "_id" : "test_db", "partitioned" : true, "primary" : "rs1" }
  从查询结果可以看到,test_db是支持sharding的("partitioned" : true)。另外上面加粗部分primary: rs1,这个primary与replica set里面的primary不是一回事,这里的primary是primary shard的意思,我们知道即使在sharded cluster环境中,一些数据量比较小的db也是无需分片的,这些db默认就存放在primary shard上面
 
  step2:为需要的collection(即这里的sharded_col)指定sharding key
  前面已经提到了sharding key的作用,关于sharding key的选择,是一个比较复杂的问题,sharding key对索引,对CRUD语句的操作都有诸多限制,这一部分以后再细讲,在这里默认使用_id做sharding key(_id是mongodb默认为每个集合增加的索引)
  mongos> sh.shardCollection('test_db.sharded_col', {'_id': 1})
  接下来看看整个sharded cluster的状态:

  

  sh.status()反应的内容事实上也是来自config整个数据库的内容,只不过做了一定程度的整合。从上面可以看到,有两个shard,rs1, rs2;test_db允许sharding,test_db.sharded_col整个collection的sharding key为{"_id": 1},且目前只有一个chunk在rs1整个shard上。

总结:

  到目前为止,我们已经搭建了一个有三个config server,两个shard的sharded cluster,其中每一个shard包含三个节点的replica set,且都包含一个Arbiter。我们可以查看一下刚创建好之后各个mongod实例持久化的数据大小:

  

  可以看到,两个Arbiter(rs1_3, rs2_3)所占的空间要小得多。

  对于应用程序来说,集群(sharded cluster)和单点(standalone)是有一定差异的,如果需要发挥sharded cluster高性能、高可用的特点,需要根据应用场景精心选择好sharding key,而sharding key的选择跟索引的建立以及CRUD语句息息相关,这一部分以后再聊。对于目前搭建的这个实例,简单测试的话,往sharded_col插入足够多条document就能看到chunks的拆分和迁移。

 

references:

db engines

the-id-field

replication-introduction

deploy-replica-set-for-testing

deploy-shard-cluster

通过一步步创建sharded cluster来认识mongodb的更多相关文章

  1. Java开发环境的搭建以及使用eclipse从头一步步创建java项目

    一.java 开发环境的搭建 这里主要说的是在windows 环境下怎么配置环境. 1.首先安装JDK java的sdk简称JDK ,去其官方网站下载最近的JDK即可..http://www.orac ...

  2. java第一天--Java开发环境的搭建以及使用eclipse从头一步步创建java项目

    一.java 开发环境的搭建 这里主要说的是在windows 环境下怎么配置环境. 1.首先安装JDK java的sdk简称JDK ,去其官方网站下载最近的JDK即可..http://www.orac ...

  3. 搭建一套Java开发环境以及使用eclipse从头一步步创建java项目

    一.java 开发环境的搭建 在windows 环境下怎么配置环境. 1.首先安装JDK java的sdk简称JDK ,去其官方网站下载JDK. https://www.oracle.com/tech ...

  4. IDEA一步步创建Maven管理的Spring入门程序

    目前,做Java开发的很多人都在使用IDEA了,而有些人也选择用Eclipse,我这里介绍一下IDEA一步步创建Maven项目的步骤,并创建一个Spring的入门程序(Java项目,非Web项目),讲 ...

  5. docker下创建redis cluster集群

    概述 在Redis中,集群的解决方案有三种 主从复制 哨兵机制 Cluster Redis Cluster是Redis的分布式解决方案,在 3.0 版本正式推出. 准备工作 1.确定本机IP地址 2. ...

  6. 【Azure Redis 缓存】Windows版创建 Redis Cluster 实验 (精简版)

    简介 学习Redis Cluster的第一步,即本地搭建Redis Cluster.但是在Redis的官方文档中,是介绍在Linux系统中搭建Redis Cluster.本文主要介绍在Windows系 ...

  7. Deploy a Sharded Cluster

    Start the Config Server Database Instances for example :  mongod --configsvr --dbpath <path> - ...

  8. 一步步创建Qt Widget项目+TextFinder案例(摘自笔者2015年将出的《QT5权威指南》,本文为试读篇)

     创建一个基于应用的QtWidget应用程序 这个手册描述了怎样使用QtCreater创建个一个小的Qt应用程序,Text Finder.它是Qt工具Text Finder例子的简写版本.这个应用 ...

  9. 一步步创建第一个Docker App —— 4. 部署应用

    原文:https://docs.docker.com/engine/getstarted-voting-app/deploy-app/ 在这一步中,将会使用第一步提到的 docker-stack.ym ...

随机推荐

  1. Access SQL实现连续及不连续Rank排名

    一.关于起因 在Excel中我们经常使用Rank函数对数据进行排名操作.而在Access中我们要进行排名是找不到这个Rank函数的,此时我们需要自己书写VBA代码或者建立SQL查询来完成排序操作. 今 ...

  2. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(四)

    一步步打造一个简单的 MVC 电商网站 - BooksStore(四) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore &l ...

  3. AngularJS1.X学习笔记4-内置事件指令及其他

    AngularJS为我们定义了一系列事件指令,方便我们对用户的操作作出响应.甚至他还有一个可选模块提供了触摸事件和手势事件的支持,为移动端开发提供了可能.现在开始学习一下AngularJS的事件指令. ...

  4. ReactJS入门:展示数据

    由于公司开发需要,博主利用闲暇的时间对ReactJS的基础知识进行了一些粗浅的认识和了解.博主对ReactJS的学习主要来自官网(http://reactjs.cn/react/docs/thinki ...

  5. 高吞吐koa日志中间件

    Midlog中间件 node服务端开发中少不了日志打点,而在koa框架下的日志打点在多进程环境中日志信息往往无法对应上下文,而且在高并发下直接进行写buffer操作(内核调用writev)也会造成内存 ...

  6. JavaScript设计模式读书笔记之一:接口

    接口 在JavaScrip中模仿接口 用注释描述接口 用属性检查模仿接口 用鸭式辨型模仿接口 依赖于接口的设计模式 工厂模式 组合模式 装饰者模式 命令模式 接口 在JavaScrip中模仿接口 用注 ...

  7. 进入html+css世界的正确姿势

    今天,我带大家一起走进html+css的世界. HTML其实是HyperText Markup Language的缩写, 超文本标记语言.他是用于告诉浏览器这是一个网页, 也就是说告诉浏览器我是一个H ...

  8. Java面试题04-final关键字详解

    Java面试题04-final关键字详解 本篇博客将会讨论java中final关键字的含义,以及final用在什么地方,感觉看书总会有一些模糊,而且解释的不是很清楚,在此做个总结,以备准备面试的时候查 ...

  9. less补充函数

    1.ceil():向上取整2.floor():向下取整3.percentage():将浮点数转换成百分比3.round():四舍五入4.sqrt():平方根5.abs():绝对值6.pow():乘方运 ...

  10. Apriori算法(C#)

    AprioriMethod.cs using System; using System.Collections.Generic; using System.Linq; using System.Web ...