一致性Hash算法背景

一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用。

但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性hash时采用如下步骤:

  1. 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
  2. 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
  3. 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。

 

从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在园(continuum)上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响,如下图所示:

一致性Hash性质

考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来,如何保证当系统的节点数目发生变化时仍然能够对外提供良好的服务,这是值得考虑的,尤其实在设计分布式缓存系统时,如果某台服务器失效,对于整个系统来说如果不采用合适的算法来保证一致性,那么缓存于系统中的所有数据都可能会失效(即由于系统节点数目变少,客户端在请求某一对象时需要重新计算其hash值(通常与系统中的节点数目有关),由于hash值已经改变,所以很可能找不到保存该对象的服务器节点),因此一致性hash就显得至关重要,良好的分布式cahce系统中的一致性hash算法应该满足以下几个方面:

  • 平衡性(Balance)

平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。

  • 单调性(Monotonicity)

单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区。简单的哈希算法往往不能满足单调性的要求,如最简单的线性哈希:x = (ax + b) mod (P),在上式中,P表示全部缓冲的大小。不难看出,当缓冲大小发生变化时(从P1到P2),原来所有的哈希结果均会发生变化,从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时,所有的映射关系需要在系统内全部更新。而在P2P系统内,缓冲的变化等价于Peer加入或退出系统,这一情况在P2P系统中会频繁发生,因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。

  • 分散性(Spread)

在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。

  • 负载(Load)

负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

  • 平滑性(Smoothness)

平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。

原理

基本概念

一致性哈希算法(Consistent Hashing)最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下:

 

 

整个空间按顺时针方向组织。0和232-1在零点中方向重合。

下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下:

 

 

接下来使用如下算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。

例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:

 

 

根据一致性哈希算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。

下面考虑另外一种情况,如果在系统中增加一台服务器Node X,如下图所示:

 

 

此时对象Object A、B、D不受影响,只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。

综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

另外,一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,

 

 

此时必然造成大量数据集中到Node A上,而只有极少量会定位到Node B上。为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:

 

 

同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。

相关实现

https://en.wikipedia.org/wiki/Consistent_hashing

原文

Redis用于数据类型 及 应用场景

字符串(String)

应用场景

String是最常用的一种数据类型,普通的key/ value 存储都可以归为此类.即可以完全实现目前 Memcached 的功能,并且效率更高。还可以享受Redis的定时持久化,操作日志及 Replication等功能。除了提供与 Memcached 一样的get、set、incr、decr 等操作外,Redis还提供了下面一些操作:

  • 获取字符串长度
  • 往字符串append内容
  • 设置和获取字符串的某一段内容
  • 设置及获取字符串的某一位(bit)
  • 批量设置一系列字符串的内容

应用举例
统计网站访问数量、当前在线人数、微博数、粉丝数等,incr命令(++操作)

实现方式

String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

常用接口

接口 介绍
SET key value 设置指定 key 的值
GET key 获取指定 key 的值。
GETRANGE key start end 返回 key 中字符串值的子字符
GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值。
SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
SETNX key value 只有在 key 不存在时设置 key 的值。
SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
STRLEN key 返回 key 所储存的字符串值的长度。
MSET key value [key value …] 同时设置一个或多个 key-value 对。
MSETNX key value [key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
INCR key 将 key 中储存的数字值增一。
INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。
INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。
DECR key 将 key 中储存的数字值减一。
DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。
APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。

列表(List)

应用场景

Lists 就是链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。

使用List结构,可以轻松地实现:
1、最新消息排行榜。
2、消息队列,以完成多程序之间的消息交换。可以利用List的PUSH将任务存在List中(生产者),然后工作线程再用POP将任务取出(消费者)。
3、Redis还提供了操作List中某一段的api,可以直接查询,删除List中某一段的元素。

应用举例
1.微博关注列表、粉丝列表。
2.获取最新n条微博(缓存主页或第一个评论页,不用去DB查询)

实现方式

Redis list的实现为一个双向链表,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

常用接口

接口 介绍
BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LINDEX key index 通过索引获取列表中的元素
LINSERT key BEFORE AFTER pivot value
LLEN key 获取列表长度
LPOP key 移出并获取列表的第一个元素
LPUSH key value1 [value2] 将一个或多个值插入到列表头部
LPUSHX key value 将一个或多个值插入到已存在的列表头部
LRANGE key start stop 获取列表指定范围内的元素
LREM key count value 移除列表元素
LSET key index value 通过索引设置列表元素的值
LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
RPOP key 移除并获取列表最后一个元素
RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPUSH key value1 [value2] 在列表中添加一个或多个值
RPUSHX key value 为已存在的列表添加值

哈希(Hash)

应用场景

hash特别适合存储对象。

  • 1、相对于将对象的每个字段存成单个string 类型。一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。
  • 2、相对于采用String类型的存储对象,需要将对象进行序列化。增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回。而Redis的Hash提供了直接存取这个Map成员的接口。

应用举例
例如存储、读取、修改用户属性(name,age,pwd等)

实现方式

redis的Hash数据类型的value内部是一个HashMap,如果该Map的成员比较少,则会采用一维数组的方式来紧凑存储该Map,省去了大量指针的内存开销,这个参数在redis.conf配置文件中下面2项。

Hash-max-zipmap-entries 64
Hash-max-zipmap-value 512

Hash-max-zipmap-entries含义:是当value这个Map内部不超过64个成员时会采用线性紧凑格式存储(默认是64),即value内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的HashMap。
Hash-max-zipmap-value含义:是当value这个Map内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。

以上两个条件任意一个条件超过设置值都会转成真正的HashMap,也就不会再节省内存了,这个值设置多少需要权衡,HashMap的优势就是查找和操作效率高。

常用接口

接口 介绍
HDEL key field2 [field2] 删除一个或多个哈希表字段
HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
HGET key field 获取存储在哈希表中指定字段的值。
HGETALL key 获取在哈希表中指定 key 的所有字段和值。
HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。
HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
HKEYS key 获取所有哈希表中的字段
HLEN key 获取哈希表中字段的数量
HMGET key field1 [field2] 获取所有给定字段的值
HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。
HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
HVALS key 获取哈希表中所有值
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。

注意:Redis提供了接口(HGETALL )可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而导致其它客户端的请求完全不响应。

集合(Set)

应用场景

Redis Set对外提供的功能与List类似是一个列表的功能,特殊之处在于Set是自动去重的,Set中的元素是没有顺序的。Set提供了判断某个成员是否在一个Set集合内的接口,这个也是List没有的。

Set集合的概念就是一堆不重复值的组合。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

应用举例
1.利用交集求共同好友。
2.利用唯一性,可以统计访问网站的所有独立IP。
3.好友推荐的时候根据tag求交集,大于某个threshold(临界值的)就可以推荐。

实现方式

set 的内部实现是一个value永远为null的HashMap,实际就是通过计算hash的方式来快速去重的,这也是set能提供判断一个成员是否在集合内的原因。

常用接口

接口 介绍
SADD key member1 [member2] 向集合添加一个或多个成员
SCARD key 获取集合的成员数
SDIFF key1 [key2] 返回给定所有集合的差集
SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中
SINTER key1 [key2] 返回给定所有集合的交集
SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中
SISMEMBER key member 判断 member 元素是否是集合 key 的成员
SMEMBERS key 返回集合中的所有成员
SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合
SPOP key 移除并返回集合中的一个随机元素
SRANDMEMBER key [count] 返回集合中一个或多个随机数
SREM key member1 [member2] 移除集合中一个或多个成员
SUNION key1 [key2] 返回所有给定集合的并集
SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中
SSCAN key cursor [MATCH pattern] [COUNT count] 迭代集合中的元素

有序集合(Sorted Set)

使用场景

Redis Sorted Set的使用场景与set类似,区别是Set是无序的,而Sorted Set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择Sorted Set数据结构,比如微博的timeline(时间线)可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

应用举例
1.timeline(时间线);
2.可以用于一个大型在线游戏的积分排行榜,每当玩家的分数发生变化时,可以执行zadd更新玩家分数(score),此后在通过zrange获取几分top ten的用户信息;
3.用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

实现方式

Redis Sorted Set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

常用接口

接口 说明
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD key 获取有序集合的成员数
ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
ZINTERSTORE destination numkeys key [key …] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量
ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合成指定区间内的成员
ZRANGEBYLEX key min max [LIMIT offset count] 通过字典区间返回有序集合的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员
ZRANK key member 返回有序集合中指定成员的索引
ZREM key member [member …] 移除有序集合中的一个或多个成员
ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员
ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员
ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到底
ZREVRANGEBYSCORE key max min [WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
ZSCORE key member 返回有序集中,成员的分数值
ZUNIONSTORE destination numkeys key [key …] 计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZSCAN key cursor [MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值)

Nginx 反爬虫

#禁止Scrapy等工具的抓取
if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {
     return 403;
}
#禁止指定UA及UA为空的访问
if ($http_user_agent ~ "FeedDemon|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt|HttpClient|MJ12bot|heritrix|EasouSpider|Ezooms|^$" ) {
     return 403;             
}
#禁止非GET|HEAD|POST方式的抓取
if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 403;
}

垃圾UA列表

FeedDemon             内容采集
BOT/0.1 (BOT for JCE) sql注入
CrawlDaddy            sql注入
Java                  内容采集
Jullo                 内容采集
Feedly                内容采集
UniversalFeedParser   内容采集
ApacheBench           cc攻击器
Swiftbot              无用爬虫
YandexBot             无用爬虫
AhrefsBot             无用爬虫
YisouSpider           无用爬虫(已被UC神马搜索收购,此蜘蛛可以放开!)
MJ12bot               无用爬虫
ZmEu phpmyadmin       漏洞扫描
WinHttp               采集cc攻击
EasouSpider           无用爬虫
HttpClient            tcp攻击
Microsoft URL Control 扫描
YYSpider              无用爬虫
jaunty                wordpress爆破扫描器
oBot                  无用爬虫
Python-urllib         内容采集
Indy Library          扫描
FlightDeckReports Bot 无用爬虫
Linguee Bot           无用爬虫

What does “LC_ALL=C” do?

$ LC_ALL=es_ES man
¿Qué página de manual desea?
$ LC_ALL=C man
What manual page do you want?
$ LC_ALL=en_US sort <<< $'a\nb\nA\nB'
a
A
b
B
$ LC_ALL=C sort <<< $'a\nb\nA\nB'
A
B
a
b

Use PHP composer to clone git repo

DO YOU HAVE A REPOSITORY?

Git, Mercurial, SVN is supported by Composer.

DO YOU HAVE WRITE ACCESS TO THE REPOSITORY?

Yes?

DOES THE REPOSITORY HAVE A composer.json FILE

If you have a repository you can write to: Add a composer.json file, or fix the existing one, and DON’T use the solution below.

Go to @igorw ‘s answer

ONLY USE THIS IF YOU DON’T HAVE A REPOSITORY
OR IF THE REPOSITORY DOES NOT HAVE A composer.json AND YOU CANNOT ADD IT

This will override everything that Composer may be able to read from the original repository’s composer.json, including the dependencies of the package and the autoloading.

Using the package type will transfer the burden of correctly defining everything onto you. The easier way is to have a composer.json file in the repository, and just use it.

This solution really only is for the rare cases where you have an abandoned ZIP download that you cannot alter, or a repository you can only read, but it isn’t maintained anymore.

"repositories": [
    {
        "type":"package",
        "package": {
          "name": "l3pp4rd/doctrine-extensions",
          "version":"master",
          "source": {
              "url": "https://github.com/l3pp4rd/DoctrineExtensions.git",
              "type": "git",
              "reference":"master"
            }
        }
    }
],
"require": {
    "l3pp4rd/doctrine-extensions": "master"
}

忧愁上身 / 贾樟柯

有一年春节,我从北京回老家汾阳过年,电话里和一帮高中同学定下初四聚会。初四早晨,县城里有零星的鞭炮声。我一大早就醒来,开始洗澡换衣服,心乱,像去赴初恋约会。
又是一年不见,那些曾经勾肩搭背、横行乡里的春风少年,被时间平添了些陌生。到底是有牵挂,一干人围坐桌边,彼此客气,目光却死盯着对方。一个同学捧着菜单和服务员交涉,其余人假装礼貌选择沉默。包间里静极了,大家听他点菜,个个斯文得像上班主任的课。他们一口一口吸烟,我一眼一眼相望。可惜满目都是同窗好友老了的证据,想调侃几句,却一时找 不到合适的乡音。
还是酒厂搞推销的同学生猛,吐个烟圈后一下找到了高中时代看完黄色录像后的兴奋感,盯着我拷问道:贾导演,老实交代,今年你潜规则了几个? 青春虽走,荷尔蒙犹在。这话题让一屋子刚进中年早期的同学顿时焕发了青春。对我的“审讯”让所有人激动,我接受这莫须有的“冤案”,只为找回当年的亲近。就像高一时,他们捕获了我投向她的目光中的爱慕,在宿舍熄灯后杜撰着我和她的爱情,而我不选择辩白,夜夜在甜蜜的谣言中睡去。 今天,甜蜜已经不在。我被他们的“罚酒”搞得迅速醉倒,在酒精的炙烤中睡去。下午醒来,不知身处何处,耳边又传来同学们打牌赌博的声音,就闭眼听他们吵吵闹闹,像回到最初。记得高考前也有同样的一刻,我们这些注定考不上的差生破罐子破摔,高考在即却依旧麻将在手。有一天我躺在宿舍床上听着旁边的麻将声,想想自己的未来,心里突然一阵潮湿。十八岁前的日子清晰可见,之后的大片岁月却还是一整张白纸。我被深不见底的未来吓倒,在搓麻声中用被子拼命捂住自己,黑暗中我悄悄哭了。 那天没有人知道,他们旁边的少年正忧愁上身。
二十几年过去,县城里的老同学已经习惯了上酒店开房打牌。朦胧中我又听到了熟悉的麻将声,听他们讲县里的煤矿、凶杀、婚外恋,竟觉得自己日常乐趣太少,一时心里空虚。年少时总以为未来都会是闪亮的日子,虚荣过后才发现所有的记忆都会褪色。这时,又偷偷想了想自己的未来,未来于我却好像已经见底,一切一目了然。我为这一眼见底的未来伤感,心纠结成一片。
原来,人到中年竟然还会忧愁上身。
想一个人走走便起身出门,到院子里骑了同学的摩托车漫无目的地开了起来。不知不觉中我已经穿越县城,旁边车过卷起尘土,躲避过后才发现自己习惯地选择了那条村路。村路深处,暮霭中的山村有我曾经朝夕相处的同学。
他和我一样,第一年高考落榜了。我避走太原学画画,他没有复读又回了农村。以后的日子,见面越来越少。友情的火焰被乌黑坚硬的现实压住,大家没有告别便已各奔前程。“曾经年少爱追梦,一心只想往前飞”的这些年,一路上失散了多少兄弟,连我自己都搞不清楚。此时此地,我却站在了他的村口。以前县城里老停水,一停水我就拉一辆水车去他们村里拉水。每次都会在他家里小坐一会儿,那时候村里普遍还住土炕,或许他在县城读书受了影响,把一间窑洞的土炕拆了,自己生炉子搭床睡。床铺收拾得整整齐 齐,他的房间与周围环境相比,特殊得像一块租界。
我骑摩托车进了村,村里有些变化,街道结构却一切如旧。到了他家刻有“耕读之家”的大门口,一眼就看到院子里他的父母。我被他们“搀扶”着进了房间,热情过后才知道我的同学走亲戚去了。多年不见,他的房间竟然没有任何改变,甚至床单被罩,甚至一桌一椅。 “他还没有成家?”我问。
“没有。”他的父母齐声回答。
沉默中我环顾左右,突然在他的枕边发现了一本书,那是80年代出版的一册杂志《今古传奇》,对,就是前边几篇侦探小说,最后《书剑恩仇录》的那本。这册《今古传奇》上高中的时候从一个同学手里传递到另一个同学手里。这本书在教室里传来递去,最后到了他的手里,直到现在还躺在他的枕边。这二十几年,日日夜夜,他是不是翻看着同样一册小说?一
种“苦”的味道涌上心头,一个又一个漫长的山村之夜,他是不是就凭这一册《今古传奇》挺了过来?我一个人骑摩托返城,山边星星衬托着乡村的黑夜,这里除了黑还是黑。我突然想,在这一片漆黑的夜里,他会不会也和我一样经常忧愁上身? 春节过后回了北京,又好像和那片土地断了联系。不久之后,一次又一次的“富士康”事件让我瞠目结舌。我和他们来自同一种贫穷,我和他们投入的是同一种不公。我们有相同的来路,我相信我了解他们的心魔,那一刹那他们慌不择路,那一刹那他们忧愁上身。
可是,我们又能做些什么呢?我想起我忧愁上身的时候,我用被子捂着脸哭的时候,其实特别需要有人和我聊聊。那就拍部片子吧,找那些已经走出一片天的过来人,谈谈他们生命中最黑暗的时刻,聊聊他们走出艰难岁月的智慧和勇气。后来,我找到六位年轻的导演一起合作,我们拍摄了十二位人士,拍出了十二部短片,影片的名字叫《语路》。
感谢影片中来自商业、艺术、公益领域的十二位人物慷慨的讲述,感谢他们以金玉良言相送。还要感谢苏格兰威士忌尊尼获加金玉相送,他们发起 和投资了这次“语路计划”,让我们的“问道”之旅得以实现。
我常常会会想起过去,想起我们各奔前程的青春往事。可是,同处这个世界,我们真的能彼此不顾,各奔前程吗?
继续阅读“忧愁上身 / 贾樟柯”

js appy call

The difference is that apply lets you invoke the function with arguments as an array; call requires the parameters be listed explicitly. A useful mnemonic is “A for array and C for comma.”

See MDN’s documentation on apply and call.

Pseudo syntax:

theFunction.apply(valueForThis, arrayOfArgs)

theFunction.call(valueForThis, arg1, arg2, …)

There is also, as of ES6, the possibility to spread the array for use with the call function, you can see the compatibilities here.

Sample code:

function theFunction(name, profession) {
    console.log("My name is " + name + " and I am a " + profession + ".");
}
theFunction("John", "fireman");
theFunction.apply(undefined, ["Susan", "school teacher"]);
theFunction.call(undefined, "Claude", "mathematician");
theFunction.call(undefined, ...["Matthew", "physicist"]); // used with the spread operator

// Output: 

// My name is John and I am a fireman.
// My name is Susan and I am a school teacher.
// My name is Claude and I am a mathematician.
// My name is Matthew and I am a physicist.