• Archive by category "Uncategorized"

Blog Archives

mac使用virtualbox和cloudera manager搭建大数据集群

装备集群

利用virtualbox,创建4个guest虚拟机,一个作为管理机,其他3个作为worker。虚拟机网络设置为NAT + HostOnly。NAT保证虚拟机能访问WAN,但是虚拟机之间无法通信。Host Only保证Mac和4个虚拟机之间处在一个网络192.168.56.0/24,可以相互通信。

  • mac 192.168.56.1 安装virtualbox
  • master 192.168.56.10 虚拟机,安装cm server
  • app1 192.168.56.11 虚拟机,安装cm agent
  • app2 192.168.56.12 虚拟机,安装cm agent
  • app3 192.168.56.13 虚拟机,安装cm agent

安装cloudera manager

在master上运行

# wget http://archive.cloudera.com/cm5/installer/5.15.0/cloudera-manager-installer.bin
# chmod u+x cloudera-manager-installer.bin
# ./cloudera-manager-installer.bin

有些包很大,可能会卡很久,或者下载很慢。可以手动下载到/var/cache/yum/x86_64/6/cloudera-manager/packages,然后重新运行./cloudera-manager-installer.bin

最好先手动下载
– oracle-j2sdk1.7-1.7.0+update67-1.x86_64.rpm
– cloudera-manager-agent-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
– cloudera-manager-server-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
– cloudera-manager-daemons-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
然后cp/scp到四个虚拟机的/var/cache/yum/x86_64/6/cloudera-manager/packages下。

下载
– CDH-5.15.0-1.cdh5.15.0.p0.21-el6.parcel
– CDH-5.15.0-1.cdh5.15.0.p0.21-el6.parcel.sha1

扔到master的/opt/cloudera/parcel-repo

配置集群

访问http://192.168.56.10:7180/ ,账号admin,密码admin,选择一些配置选项,加入机器,填写ssh用户名密码(所有机器使用同样账号密码,关闭iptables和selinux)。然后就开始安装jdk、cm agent。

HBaseWD:通过顺序RowKey避免HBase RegionServer热点问题

在看Pinpoint源码时,发现hbaseOperations2.findParallelRowKeyDistributorByHashPrefix,似乎是RowKey做了哈希打散。后来发现是来自https://github.com/sematext/HBaseWD,用于解决HBase写时由于顺序RowKey导致某个Region成为热点。采用了给RowKey加前缀,把写分散到不同Region,同时分区后的新RowKey还是保持了原有的顺序,对并行Scan的性能没有影响。

原文地址:
https://sematext.com/blog/hbasewd-avoid-regionserver-hotspotting-despite-writing-records-with-sequential-keys/
译文来源:
https://blog.csdn.net/jiangmingzhi23/article/details/78575757

在HBase领域,RegionServer热点是一个共性问题。用一句话来描述HBase热点:以顺序RowKey记录数据时,可以通过startRowkey和endRowKey区间最高效地读取数据,但是这种顺序写入却会不可避免地产生RegionServer热点。接下来两部分我们将讨论并告诉你如何避免这个问题。

问题描述

Hbase中的记录是按照字典顺序存储的。因此可以通过确定的RowKey快速找到某个记录,或者通过start RowKey和end RowKey作为区间以快速查询RowKey在这个区间的记录。所以你可能会认为,以顺序RowKey记录数据,然后可以快速通过上面的方法查询某个区间的记录的方式一个不出错的主意。例如我们可能会希望每个记录都与时间戳有关,以便我们可以通过时间区间查询一段时间的记录,这样的RowKey的例子如下:
  • 基于时间戳的格式:Long.MAX_VALUE – new Date().getTime()
  • 递增/递减序列:”001”, ”002”, ”003”,… or ”499”, ”498”, ”497”, …
但是这种幼稚的RowKey会导致RegionServer写热点。

RegionServer Hotspotting

以顺序RowKey记录数据到HBase中时,所有的写操作会命中一个region。但是如果多个RegionServer服务同一个region时,这个问题就不存在了,不过实际并非如此,一个region通常只在一个RegionServer上。每个region都会预定义大小,当一个region达到最大大小时,就会被分割成两个小的region。然后两个较小的region中的一个负责记录所有的新写入的记录,这个region和它说在的RegionServer就成了新的写热点。显然,这种不均匀地写入是需要避免的,因为它将写操作的性能限制到单个RegionServer的写入能力,而没有将集群中所有RegionServer的性能释放出来。这种负载不均匀地写入操作可以从下图一看出:

hbasewd-pic1

可以看出其中一台服务器满负荷运行以写入记录,但是其他服务器却处于空闲状态。HBase的RegionServer热点问题更多的信息可以参考HBase官方文档

解决方案

如何解决这个问题呢?本文讨论的前提是我们不是一次性批量把所有数据写入HBase,而是以数据流的方式不断持续达到。批量数据导入HBase时如何避免热点的问题在HBase文档中有相关的最佳解决方案。但是,如果你和我们一样,数据持续流入并且需要处理和存储,那么解决热点的最简单的方案就是通过随机RowKey将数据流分发到不同的Region中。然而不幸的是,这样会牺牲通过start RowKey和end RowKey快速检索区间数据的能力。这个在HBase mail list和其它地方多次提及的解决方案就是为RowKey增加前缀。例如可以考虑通过下面的方式构造Rowkey:
new_row_key = (++index % BUCKETS_NUMBER) + original_key
这种方式构造的RowKey,以我们可见的数据类型方式展示如下图2示:

hbasewd-pic2

这里:
  • index是我们用于特定记录的RowID的数字部分,例如1,2,3,4,
  • BUCKETS_NUMBER是我们希望新构建的RowKey想要分发到不同的‘桶’的数量。每一个‘桶’内,数据保持着他们原始ID记录的顺序。
  • original_key是写入数据的原始主键。
  • new_row_key是数据写入HBase中的实际RowKey(即distributed key或者prefixed key)。后文中,distributed records用来表示通过distributed key写入的记录。
所以,新的记录将被分发到不同的‘桶’中,被HBase集群的不同RegionServer处理入库。新写入的记录的RowKey不再是顺序序列,但是在每一个‘桶’中,它们依然保持着原始的字典顺序。当然,如果你开始写数据到一个空的HBase表,在这个表被分割成多个region前你可能要等一段时间,等待的时长取决于流入数据大的大小和速度、压缩比以及region的大小。提示:通过HBase region预分割特效,可以避免这个等待。通过上面的方案写数据到不同的region,你的HBase节点负载看起来就好看多了:

Scan操作

数据在写入过程中被分发到不同的‘桶’中,因此我们可以通过基于start RowKey和end RowKey的scan操作从多个‘桶’中提取数据,并且保证数据的原始排序状态。这也意味着BUCKETS_NUMBER个scan操作可能会影响性能。但是幸运的是这些scan操作可以并行,所以性能不至于降低,甚至会有所提高。对比一下,从一个region中读取100K数据,和从10个region中并行的读取10K数据,哪个更快?

hbasewd-pic3

Get/Delete

对单条记录进行Get/Delete操作,操作复杂度为O(1)到O(BUCKETS_NUMBER)。 例如。 当使用“静态”散列作为前缀时,给定原始RowKey,就可以精确地识别prefised rowkey。 如果我们使用随机前缀,我们将不得不在每个可能的‘桶’中执行Get操作。Delete操作也是如此。

MapReduce Input

我们仍旧希望从数据本身出发,因此将distributed record提供给MapReduce作业可能会打乱数据到达mapper的顺序。至少在HBaseWD的实现中,这个问题是存在的。每个map task处理特定的‘桶’中的数据,所以,数据处理的顺序将于它们在‘桶’中的原始顺序一致。然而由于两个原始RowKey相邻的记录可能被分发存储到不同的‘桶’中,它们将会被分配到不同的map task。因此如果mapper认为数据严格的按照其原始顺序流入,我们则很受伤,因为数据只在每个桶保证原始顺序,并非全局保证顺序。

Increased Number of Map Tasks

当我们以上述方案的数据提供给MapReduce作业时,‘桶’的数目可能会增加。在HBaseWD的实现中,与使用相同参数的常规MapReduce作业相比,你需要进行BUCKETS_NUMBER倍分割。这与前面讨论的Get操作的逻辑相似。所以(HBaseWD的实现中,)MapReduce作业需要有BUCKETS_NUMBER倍的map task。如果BUCKETS_NUMBER不大,理论上性能不会降低,当然,MapReduce作业本身的初始化和清理工作需要更多的时间。而且在很多情况下,更多的mapper可以提升性能。很多用户报告指出,基于标准的HBase Tbase输入的MapReduce作业的map task数目过少(每个region对应一个map task),所以(我们的实现)可以不需要额外编码就可以对MapReduce产生积极作用。
如果在你的应用中,除了按顺序Rowkey写入数据到HBase,还需要通过MapReduce持续的处理新数据的增量,那么本文建议的方案及其实现很可能会有所帮助。在这种情况下,数据持续频繁写入,增量处理只会位于少数region中,或者如果写负载不高时,(增量处理)只会在一个region中,亦或者如果最大region大小很大时,批量处理会很频繁。

方案实现: HBaseWD

我们实现了上述方案,并且将之作为一个叫做HBaseWD的项目开源。由于HBaseWD支持HBase本地客户端API,所以它实际上是独立的,而且很容易集成到现有的项目代码中。HBaseWD项目首次在

Configuring Distribution

Simple Even Distribution

使用顺序RowKey分发记录,最多分发到Byte.MAX_VALUE个‘桶’中,(通过在原始Rowkey前添加一个字节的前缀):
byte bucketsCount = (byte) 32; // distributing into 32 buckets
RowKeyDistributor keyDistributor =  new RowKeyDistributorByOneBytePrefix(bucketsCount);
Put put = new Put(keyDistributor.getDistributedKey(originalKey));
... // add values
hTable.put(put);

Hash-Based Distribution

另一个有用的RowKey分发器是RowKeyDistributorByHashPrefix。参考下面的示例。它通过原始RowKey创建distributed key,如果稍后希望通过原始Rowkey更新记录时,可以直接计算出distributed key,而无需调用HBase,也无需知道记录在哪个‘桶’中。或者,你可以在知道原始Rowkey的情况下通过Get操作获取记录,而无需到所有的‘桶’中寻找。
AbstractRowKeyDistributor keyDistributor =
     new RowKeyDistributorByHashPrefix(
            new RowKeyDistributorByHashPrefix.OneByteSimpleHash(15));
你亦可以通过实现下面的接口以使用你自己的hash逻辑:
public static interface Hasher extends Parametrizable {
  byte[] getHashPrefix(byte[] originalKey);
  byte[][] getAllPossiblePrefixes();
}

自定义分发逻辑

HBaseWD的设计灵活,特别是在支持自定义distributed key方法时。 除了上面提到的用于实现用于RowKeyDistributorByHashPrefix的定制哈希逻辑的功能之外,还可以通过扩展AbstractRowKeyDistributor抽象类来定义自己的RowKey分发逻辑,该类的接口非常简单:
public abstract class AbstractRowKeyDistributor implements Parametrizable {
  public abstract byte[] getDistributedKey(byte[] originalKey);
  public abstract byte[] getOriginalKey(byte[] adjustedKey);
  public abstract byte[][] getAllDistributedKeys(byte[] originalKey);
  ... // some utility methods
}

Common Operations

Scan

对数据执行基于范围的scan操作:
Scan scan = new Scan(startKey, stopKey);
ResultScanner rs = DistributedScanner.create(hTable, scan, keyDistributor);
for (Result current : rs) {
  ...
}

Configuring MapReduce Job

通过scan操作在指定的数据块上自习MapReduce作业:
Configuration conf = HBaseConfiguration.create();
Job job = new Job(conf, "testMapreduceJob");
Scan scan = new Scan(startKey, stopKey);
TableMapReduceUtil.initTableMapperJob("table", scan,
RowCounterMapper.class, ImmutableBytesWritable.class, Result.class, job);
// Substituting standard TableInputFormat which was set in
// TableMapReduceUtil.initTableMapperJob(...)
job.setInputFormatClass(WdTableInputFormat.class);
keyDistributor.addInfo(job.getConfiguration());

SSO和CAS

SSO(Single sign-on,单点登录),就是只登录一次(只提供一次凭证,如账号密码),就可以畅通无阻的访问平台的多个应用/服务。不应该把SSO和OAuth等授权协议混淆,OAuth协议要求在登录不同系统是都要进行认证和授权,而SSO只认证一次。OAuth本质是解决授权问题。

要实现SSO有很多方案。CAS(Central Authentication Service,集中式认证服务)就是其中一种。最初的CAS由Yale实现,现在CAS协议的实现有很多。CAS协议已经发展到了3.0版本。

一下对CAS协议的描述来自apereo CAS实现的文档。

CAS protocol

The CAS protocol is a simple and powerful ticket-based protocol developed exclusively for CAS. A complete protocol specification may be found here.

It involves one or many clients and one server. Clients are embedded in CASified applications (called “CAS services”) whereas the CAS server is a standalone component:

  • The CAS server is responsible for authenticating users and granting accesses to applications
  • The CAS clients protect the CAS applications and retrieve the identity of the granted users from the CAS server.

The key concepts are:

  • The TGT (Ticket Granting Ticket), stored in the CASTGC cookie, represents a SSO session for a user
  • The ST (Service Ticket), transmitted as a GET parameter in urls, stands for the access granted by the CAS server to the CASified application for a specific user.

交互流程

cas_flow_diagram

从上图可以看出,web的CAS利用到了session cookie和ticket令牌。不难分析得出CASTGC用于保持浏览器到CAS server的会话,第二次访问不需要再提供凭证。利用GET参数传递ST使得认证结果可以在不同域名之间传递。浏览器和受保护应用之间还有一个session cookie,保持了两者之间的会话,使得第二次直接访问应用也不需要提供凭证,应用也不需要再去CAS认证对令牌进行认证。

零侵入微服务日志追踪(四):ELK做日志分析

上文介绍了利用Pinpoint把trxId以低侵入方式注入到每行日志输出中,方便在日志文件内进行单个请求的日志识别。

但是依然没有解决一个问题是:快速找出某个请求涉及的所有服务的日志内容。

可以利用ELK这类分布式日志系统,自动收集所有服务、所有实例的每个日志文件,统一存储和索引。然后利用ES的查询语句,进行全局的搜索(精确条件过滤、文本模糊搜索)。

由于我们已经把Pinpoint trxId写入日志行中,因此可以对trxId进行索引。这样我们就可以利用ptxId精确的搜索到一个请求的链路经过的所有服务实例产生的日志了,极大的提升了日志定位的速度。

上图展示了一个涉及两个服务(api-gateway、authserver)的请求。

如何获取一个接口调用的trxId将会在下一篇文章介绍。

chrome删除重复书签

google书签在自动重复后会莫名其妙的出现以下重复,同步机制和算法真是不敢恭维,比svn、git之类的diff弱多了。

去掉重复书签可以用Chrome扩展应用如SuperSorter、Bookmark Sentry。
SuperSorter谨慎使用,虽然可以去重,但是不能合并同名文件夹,会打乱文件夹排序。
Bookmark Sentry已经在Chrome Web Store找不到了。

或者可以使用delicious来收藏或者管理,同样有chrome插件。

最后提醒:使用这些插件扫描你的书签可能会泄露隐私。

javascript快速入门

以下内容来自learnxinyminutes.com

Javascript于1995年由网景公司的Brendan Eich发明。 最初发明的目的是作为一个简单的网站脚本语言,来作为 复杂网站应用java的补充。但由于javascript和网站结合度很高 所以javascript逐渐变得比java在前端更为流行了。
JavaScript 不仅仅只可以用于浏览器, 也可用于 Node.js 等后台环境。


// 注释方式和C很像,这是单行注释
/* 这是多行
   注释 */

// 语句可以以分号结束
doStuff();

// ... 但是分号也可以省略,每当遇到一个新行时,分号会自动插入
doStuff()

// 我们在这里会去掉分号,但是否添加最后的分号取决于你个人的习惯
// 及你所在团队的编程风格

///////////////////////////////////
// 1. 数字、字符串与操作符

// Javascript 只有一种数字类型 (即 64位 IEEE 754 双精度浮点).
3 // = 3
1.5 // = 1.5

// 所有基本的算数运算
1 + 1 // = 2
8 - 1 // = 7
10 * 2 // = 20
35 / 5 // = 7

// 包括无法整除的除法
5 / 2 // = 2.5

// 位运算也和其他语言一样。当你对浮点数进行位运算时,
// 浮点数会转换为至多 32 位的无符号整数
1 << 2 // = 4

// 括号可以决定优先级
(1 + 3) * 2 // = 8

// 有三种非数字的数字类型
Infinity //  1/0 的结果
-Infinity // -1/0 的结果
NaN // 0/0 的结果

// 也有布尔值
true
false

// 可以通过单引号或双引号来构造字符串
'abc'
"Hello, world"

// 用!来取非
!true // = false
!false // = true

// 相等 ==
1 == 1 // = true
2 == 1 // = false

// 不等 !=
1 != 1 // = false
2 != 1 // = true

// 更多的比较操作符 
1 < 10 // = true
1 > 10 // = false
2 <= 2 // = true
2 >= 2 // = true

// 字符串用+连接
"Hello " + "world!" // = "Hello world!"

// 字符串也可以用 < 、> 来比较
"a" < "b" // = true

// 比较时会进行类型转换...
"5" == 5 // = true

// ...除非你是用 ===
"5" === 5 // = false

// 你可以用charAt来得到字符串中的字符
"This is a string".charAt(0)

// 还有两个特殊的值:null和undefined
null // 用来表示刻意设置成的空值
undefined // 用来表示还没有设置的值

// null, undefined, NaN, 0 和 "" 都是假的(false),其他的都视作逻辑真
// 注意 0 是逻辑假而  "0"是逻辑真, 尽管 0 == "0".

///////////////////////////////////
// 2. 变量、数组和对象

// 变量需要用 var 这个关键字声明. Javascript是动态类型语言
// 所以你在声明时无需指定类型。 赋值需要用 = 
var someVar = 5

// 如果你在声明时没有加var关键字,你也不会得到错误
someOtherVar = 10

// ...但是此时这个变量就会拥有全局的作用域,而非当前作用域

// 没有被赋值的变量都会返回undefined这个值
var someThirdVar // = undefined

// 对变量进行数学运算有一些简写法
someVar += 5 // 等价于 someVar = someVar + 5; someVar 现在是 10 
someVar *= 10 // 现在 someVar 是 100

// 自增和自减也有简写
someVar++ // someVar 是 101
someVar-- // 回到 100

// 数组是任意类型组成的有序列表
var myArray = ["Hello", 45, true]

// 数组的元素可以用方括号下标来访问
// 数组的索引从0开始
myArray[1] // = 45

// javascript中的对象相当于其他语言中的字典或映射:是键-值的集合
{key1: "Hello", key2: "World"}

// 键是字符串,但是引号也并非是必须的,如果键本身是合法的js标识符
// 而值则可以是任意类型的值
var myObj = {myKey: "myValue", "my other key": 4}

// 对象的访问可以通过下标
myObj["my other key"] // = 4

// ... 或者也可以用 . ,如果属性是合法的标识符
myObj.myKey // = "myValue"

// 对象是可变的,键和值也可以被更改或增加
myObj.myThirdKey = true

// 如果你想要访问一个还没有被定义的属性,那么会返回undefined
myObj.myFourthKey // = undefined

///////////////////////////////////
// 3. 逻辑与控制结构

// if语句和其他语言中一样
var count = 1
if (count == 3){
    // count 是 3 时执行
} else if (count == 4) {
    // count 是 4 时执行
} else {
    // 其他情况下执行 
}

// while循环
while (true) {
    // 无限循环
}

// Do-while 和 While 循环很像 ,但前者会至少执行一次
var input
do {
    input = getInput()
} while (!isValid(input))

// for循环和C、Java中的一样
// 初始化; 继续执行的条件; 遍历后执行.
for (var i = 0; i < 5; i++){
    // 遍历5次
}

// && 是逻辑与, || 是逻辑或
if (house.size == "big" && house.colour == "blue"){
    house.contains = "bear"
}
if (colour == "red" || colour == "blue"){
    // colour是red或者blue时执行
}

// && 和 || 是“短路”语句,在初始化值时会变得有用 
var name = otherName || "default"

///////////////////////////////////
// 4. 函数、作用域、闭包

// JavaScript 函数由function关键字定义
function myFunction(thing){
    return thing.toUpperCase()
}
myFunction("foo") // = "FOO"

// 函数也可以是匿名的:
function(thing){
    return thing.toLowerCase()
}
// (我们无法调用此函数,因为我们不知道这个函数的名字)

// javascript中的函数也是对象,所以函数也能够赋给一个变量,并且被传递
// 比如一个事件处理函数:
function myFunction(){
    // this code will be called in 5 seconds' time
}
setTimeout(myFunction, 5000)

// 你甚至可以直接把一个函数写到另一个函数的参数中

setTimeout(function myFunction(){
    // 5秒之后会执行这里的代码
}, 5000)

// JavaScript 仅有函数作用于,而其他的语句则没有作用域
if (true){
    var i = 5
}
i // = 5 - 并非我们在其他语言中所得到的undefined

// 这就导致了人们经常用一种叫做“即使执行匿名函数”的模式
// 这样可以避免一些临时变量扩散到外边去
function(){
    var temporary = 5
    // 我们可以访问一个全局对象来访问全局作用域
    // 在浏览器中是 'window' 这个对象。 
    // 在Node.js中这个对象的名字可能会不同。
    window.permanent = 10
    // 或者,我们也可以把var去掉就行了
    permanent2 = 15
}()
temporary // 抛出引用异常
permanent // = 10
permanent2 // = 15

// javascript最强大的功能之一就是闭包
// 如果一个函数在另一个函数中定义,那么这个函数就拥有外部函数的所有访问权
function sayHelloInFiveSeconds(name){
    var prompt = "Hello, " + name + "!"
    function inner(){
        alert(prompt)
    }
    setTimeout(inner, 5000)
    // setTimeout 是异步的,所以这个函数会马上终止不会等待。
    // 然而,在5秒结束后,inner函数仍然会弹出prompt信息。
}
sayHelloInFiveSeconds("Adam") // 会在5秒后弹出 "Hello, Adam!" 

///////////////////////////////////
// 5. 对象、构造函数与原型

//  对象包含方法
var myObj = {
    myFunc: function(){
        return "Hello world!"
    }
}
myObj.myFunc() // = "Hello world!"

// 当对象中的函数被调用时,这个函数就可以通过this关键字访问这个对象
myObj = {
    myString: "Hello world!",
    myFunc: function(){
        return this.myString
    }
}
myObj.myFunc() // = "Hello world!"

// 但这个函数访问的其实是其运行时环境,而非定义时环境
// 所以如果函数所在的环境不在当前对象的环境中运行时,就运行不成功了
var myFunc = myObj.myFunc
myFunc() // = undefined

// 相应的,一个函数也可以被指定为一个对象的方法,并且用过this可以访问
// 这个对象的成员,即使在定义时并没有绑定任何值
var myOtherFunc = function(){
    return this.myString.toUpperCase()
}
myObj.myOtherFunc = myOtherFunc
myObj.myOtherFunc() // = "HELLO WORLD!"

// 当你通过new关键字调用一个函数时,就会生成一个对象
// 而对象的成员需要通过this来定义。
// 这样的函数就叫做构造函数

var MyConstructor = function(){
    this.myNumber = 5
}
myNewObj = new MyConstructor() // = {myNumber: 5}
myNewObj.myNumber // = 5

// 每一个js对象都有一个原型,当你要访问一个没有定义过的成员时,
// 解释器就回去找这个对象的原型

// 有一些JS实现会让你通过一个对象的__proto__方法访问这个原型。
// 这虽然对理解这个对象很有用,但是这并不是标准的一部分
// 我们之后会通过标准方式来访问原型。
var myObj = {
    myString: "Hello world!",
}
var myPrototype = {
    meaningOfLife: 42,
    myFunc: function(){
        return this.myString.toLowerCase()
    }
}
myObj.__proto__ = myPrototype
myObj.meaningOfLife // = 42

// This works for functions, too.
myObj.myFunc() // = "hello world!"

// 当然,如果你要访问的成员在原型当中也没有定义的话,解释器就会去找原型的原型。
myPrototype.__proto__ = {
    myBoolean: true
}
myObj.myBoolean // = true

// 这其中并没有对象的拷贝。每个对象的原型实际上是持有原型对象的引用
// 这说明当我们改变对象的原型时,会影响到其他以这个原型为原型的对象
myPrototype.meaningOfLife = 43
myObj.meaningOfLife // = 43

// 我们知道 __proto__ 并非标准规定,实际上也没有办法更改已经指定好的原型。
// 但是,我们有两种方式可以为新的对象指定原型。

// 第一种方式是 Object.create,这个方法是在最近才被添加到Js中的
// 也因此并不是所有的JS实现都有这个放啊
var myObj = Object.create(myPrototype)
myObj.meaningOfLife // = 43

// 第二种方式可以在任意版本中使用,不过需要通过构造函数。
// 构造函数有一个属性prototype。但是这 *不是* 构造函数本身的函数
// 而是通过构造函数和new关键字生成新对象时自动生成的。
myConstructor.prototype = {
    getMyNumber: function(){
        return this.myNumber
    }
}
var myNewObj2 = new myConstructor()
myNewObj2.getMyNumber() // = 5

// 字符串和数字等内置类型也有通过构造函数来创建的包装类型
var myNumber = 12
var myNumberObj = new Number(12)
myNumber == myNumberObj // = true

// 但是它们并非严格等价
typeof myNumber // = 'number'
typeof myNumberObj // = 'object'
myNumber === myNumberObj // = false
if (0){
    // 这段代码不会执行,因为0代表假
}
if (Number(0)){
    // 这段代码会执行,因为Number(0)代表真
}

// 但是,包装类型和内置类型共享一个原型
// 这样你就可以给内置类型也增加一些功能
String.prototype.firstCharacter = function(){
    return this.charAt(0)
}
"abc".firstCharacter() // = "a"

// 这个技巧可以用来用老版本的javascript子集来是实现新版本js的功能
// 这样就可以在老的浏览器中使用新功能了。

// 比如,我们知道Object.create并没有在所有的版本中都实现
// 但是我们仍然可以通过这个技巧来使用
if (Object.create === undefined){ // 如果存在则不覆盖
    Object.create = function(proto){
        // 用正确的原型来创建一个临时构造函数
        var Constructor = function(){}
        Constructor.prototype = proto
        // 之后用它来创建一个新的对象
        return new Constructor()
    }
}

Centos7 gnome3

Desktop-Install-to-Hard-Drive
CentOS 7的gnome桌面版现在使用的是Gnome 3,支持触屏。
gnome-3-Desktop
也可调成像经典Gnome 2样式。
gnome-classic
感觉比Ubuntu Unity要帅吖。

Google Fonts被墙解决方案

Google被彻底搞死后,Google Fonts也加载不出来了,会导致网站迟迟无法显示。

解决方法:

1.使用Google Fonts同步镜像,比如某360提供的fonts.useso.com

;

更改为

;

 

2.把Google Font对应的eot/woff、tff文件下载到本地,更改css引用

参阅:http://www.zhihu.com/question/20587762

 

  1. 不使用Google Fonts