GitHub 上有哪些值得推荐的开源电子书?

语言无关类操作系统

web服务器

版本控制

编辑器

MySQL

NoSQL

项目相关

设计模式

Web

大数据

编程艺术

语言相关类AWK

SED

Java

Android

C/C++

CSS

Go

Groovy

Haskell

iOS

JavaScript

LaTeX

LISP

Lua

Perl

PHP

Prolog

Python

R

Ruby

Scala

Scheme

Shell

Swift

作者:EZLippi
链接:https://www.zhihu.com/question/38836382/answer/88744155
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我为什么喜欢用C#来做并发编程

硅谷才女朱赟(我的家门)昨天发了一篇文章《为什么用 Java —— 关于并发编程》,让大家学习了Java中如何进行并发编程的一些基本知识。作为一个将近15年的.NET程序员,我觉得有必要给大家补充介绍一下C#进行并发编程的知识(当然不会太深入讲解)。这篇文章无意进行技术比较,毕竟技术只是工具(大同小异,各有千秋),主要还是看用工具的人。

并发(英文Concurrency),其实是一个很泛的概念,字面意思就是“同时做多件事”,不过方式有所不同。在.NET的世界里面,并发一般涉及如下几个方面:

  1. 多线程编程(已过时,不介绍)
  2. 异步编程
  3. 并行编程
  4. 响应式编程
  5. 数据流编程

为了支持以上编程,.NET提供了很多基础功能,比如:委托,匿名函数,Lambda表达式,线程池,Task模型,支持并发的集合(线程安全集合和不可变集合) ,调度器,同步功能。在这里,就不对这些内容进行介绍了,大家可以自行搜索学习。另外,对于Actor模型,.NET中也有支持,但我不认为它属于语言/运行时层面的并发,它更像架构层面的并发,我最后会简单介绍。


1,异步编程

异步编程就是使用future模式(又称promise)或者回调机制来实现(Non-blocking on waiting)。

如果使用回调或事件来实现(容易callback hell),不仅编写这样的代码不直观,很快就容易把代码搞得一团糟。不过在.NET 4.5(C# 5)中引入的async/await关键字(在.NET 4.0中通过添加Microsoft.Bcl.Async包也可以使用),让编写异步代码变得容易和优雅。通过使用async/await关键字,可以像写同步代码那样编写异步代码,所有的回调和事件处理都交给编译器和运行时帮你处理了。

使用异步编程有两个好处:不阻塞主线程(比如UI线程),提高服务端应用的吞吐量。所以微软推荐ASP.NET中默认使用异步来处理请求。

要详细了解异步编程,可以参考官方文档:https://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx和《Async in C# 5.0》这本书。另外,在这个官方文档中,微软还特意把异步编程分作了3种不同的模型:基于任务的模式(TAP)就是我上面推荐的这种,基于事件的模式(EAP)和异步编程模型(APM)我上面不推荐的事件和回调。

2,并行编程

并行编程的出现实际上是随着CPU有多核而兴起的,目的是充分利用多核CPU的计算能力。并行编程由于会提高CPU的利用率,更适合客户端的一些应用,对于服务端的应用可能会造成负面影响(因为服务器本身就具有并行处理的特点,比如IIS会并行的处理多个请求)。我自己使用并行编程最多的场景是之前分析环境数据不确定度的时候,使用并行的方式计算蒙特卡洛模拟(计算上千次之后拟合),当然后来我使用泰勒级数展开来计算不确定度,没有这么多的计算量就无需并行了。当然在计算多方案结果比较的情况下,还是继续使用了并发计算。

在.NET中,并行的支持主要靠.NET 4.0引入的任务并行库和并行LINQ。通过这些库可以实现数据并行处理(处理方式相同,输入数据不同,比如我上面提到的应用场景)或者任务并行处理(处理方式不同,且数据隔离)。通过使用并行处理库,你不用关心Task的创建和管理(当然更不用说底层的线程了),只需要关注处理任务本身就行了。

具体的用法还是参考官方文档:https://msdn.microsoft.com/en-us/library/dd460693(v=vs.110).aspx,当然《Parallel Programming with Microsoft .NET》这本书也行。

3,响应式编程

响应式编程最近成为了一个Buzzword,其实微软6年前就开始给.NET提供一个Reactive Extensions了。一开始要理解响应式编程有点困难,但是一旦理解了,你就会对它的强大功能爱不释手。简单来说,响应式编程把事件流看作数据流,不过数据流是从IEnumable中拉取的,而数据流是从IObservable推送给你的。为什么响应式编程可以实现并发呢?这是因为Rx做到线程不可知,每次事件触发,后续的处理会从线程池中任意取出一个线程来处理。且可以对事件设置窗口期和限流。举个例子,你可以用Rx来让搜索文本框进行延迟处理(而不用类似我很早的时候用个定时器来延迟了)。

要详细了解Rx最好的方式就是浏览 IntroToRx.com 这个网站,当然还有官方文档:https://msdn.microsoft.com/en-us/data/gg577609

4,数据流编程

数据流(DataFlow)编程可能大家就更陌生了,不过还是有些常用场景可以使用数据流来解决。数据流其实是在任务并行库(TPL)上衍生出来的一套处理数据的扩展(也结合了异步的特性),TPL也是处理并行编程中任务并行和数据并行的基础库。

望文生义,TPL DataFlow就是对数据进行一连串处理,首先为这样的处理定义一套网格(mesh),网格中可以定义分叉(fork)、连接(join)、循环(loop)。数据流入这样的处理网格就能够并行的被处理。你可以认为网格是一种升级版的管道,实际上很多时候就是被当作管道来使用。使用场景可以是“分析文本文件中词频”,也可以是“处理生产者/消费者问题”。

参考资料当然也是官方文档:https://msdn.microsoft.com/en-us/library/hh228603(v=vs.110).aspx

5,Actor模型

Scala有Akka,其实微软研究院也推出了Orleans来支持了Actor模型的实现,当然也有Akka.NET可用。Orleans设计的目标是为了方便程序员开发需要大规模扩展的云服务, 可用于实现DDD+EventSourcing/CQRS系统。

官方网站是:http://dotnet.github.io/orleans/,善友也有介绍:http://www.cnblogs.com/shanyou/p/4295523.html


那么,我为什么喜欢使用C#来做并发编程呢?显而易见,有上面这些唾手可得的工具,使用C#同样可以轻易开发并发程序。

ElasticSearch性能优化策略

ElasticSearch性能优化主要分为4个方面的优化。

一、服务器部署

1、增加1-2台服务器,用于负载均衡节点

elasticSearch的配置文件中有2个参数:node.masternode.data。这两个参 数搭配使用时,能够帮助提供服务器性能。

1.1> node.master: false    node.data: true

        node服务器只作为一个数据节点,只用于存储索引数据。使该node服务器功能 单一,只用于数据存储和数据查询,降低其资源消耗率。

    1.2> node.master: true    node.data: false

        node服务器只作为一个主节点,但不存储任何索引数据。该node服务器将使用 自身空闲的资源,来协调各种创建索引请求或者查询请求,讲这些请求合理分发到相关 的node服务器上。

    1.3> node.master: false    node.data: false

node服务器即不会被选作主节点,也不会存储任何索引数据。该服务器主要用 于查询负载均衡。在查询的时候,通常会涉及到从多个node服务器上查询数据,并请 求分发到多个指定的node服务器,并对各个node服务器返回的结果进行一个汇总处理, 最终返回给客户端。

2、关闭data节点服务器中的http功能

针对ElasticSearch集群中的所有数据节点,不用开启http服务。将其中的配置 参数这样设置:http.enabled: false,同时也不要安装head, bigdesk, marvel等监控 插件,这样保证data节点服务器只需处理创建/更新/删除/查询索引数据等操作。

http功能可以在非数据节点服务器上开启,上述相关的监控插件也安装到这些服 务器上,用于监控ElasticSearch集群状态等数据信息。

这样做一来出于数据安全考虑,二来出于服务性能考虑。

3、一台服务器上最好只部署一个Node

一台物理服务器上可以启动多个Node服务器节点(通过设置不同的启动port), 但一台服务器上的CPU,内存,硬盘等资源毕竟有限,从服务器性能考虑,不建议一台 服务器上启动多个node节点。

二、服务器配置

1、配置索引线程池的大小

ElastiSearch服务器有多个线程池大小配置。主要有:indexsearchsuggest getbulkpercolatesnapshotsnapshot_datawarmerrefresh

在此主要针对indexsearch进行一个配置调整。index操作包含:创 建/更新/删除索引数据。search操作主要针对用户的各种搜索操作。

具体配置如下:

threadpool:

    index:

        type: fixed

        size: 100

    search:

        type: fixed

        size: 1000

2、创建/查找索引设置相同的分词解析器

索引服务器用到了ik中文分词插件,对于添加到该搜索服务器中的数据都使用该 中文分词(例如orgglobal对象中的orgName就使用了ik中文分词)。当执行搜索请求 时,搜索关键词也需要用到相关的中文分词器,如果不指定设置的话,则会使用服务器 默认的中文分词standard,而使用standard作为中文分词器进行查询时,性能不好。 通过将ik中分词设置为默认的分词器时,则查询效率是standard2-3倍。

该配置具体如下:

index:

     analysis:

         analyzer:

        ik:

          alias: [news_analyzer_ik,ik_analyzer]

          type: org.elasticsearch.index.analysis.IkAnalyzerProvider

index.analysis.analyzer.default.type: ik

    3、确定分片(shard)的数量和副本(replica)的数量

ElasticSearch在创建索引数据时,最好指定相关的shards数量和replicas

    否则会使用服务器中的默认配置参数shards=5replicas=1

因为这两个属性的设置直接影响集群中索引和搜索操作的执行。假设你有足够的   

    机器来持有碎片和副本,那么可以按如下规则设置这两个值:
1)
拥有更多的碎片可以提升索引执行能力,并允许通过机器分发一个大型的索引;
2)
拥有更多的副本能够提升搜索执行能力以及集群能力。
对于一个索引来说,number_of_shards只能设置一次,而number_of_replicas可以使用索引更新设置API在任何时候被增加或者减少。

这两个配置参数在配置文件的配置如下:

index.number_of_shards: 5

index.number_of_shards: 1

    4、查询速度慢的日志配置

在进行实际应用中,会记录下查询速度慢或者添加索引速度慢的操作记录,为后

    续性能优化提供依据。其具体配置如下:

index.search.slowlog.threshold.query.warn: 10s

index.search.slowlog.threshold.query.info: 5s

index.search.slowlog.threshold.query.debug: 2s

index.search.slowlog.threshold.query.trace: 500ms

index.search.slowlog.threshold.fetch.warn: 1s

index.search.slowlog.threshold.fetch.info: 800ms

index.search.slowlog.threshold.fetch.debug: 500ms

index.search.slowlog.threshold.fetch.trace: 200ms

index.indexing.slowlog.threshold.index.warn: 10s

index.indexing.slowlog.threshold.index.info: 5s

index.indexing.slowlog.threshold.index.debug: 2s

index.indexing.slowlog.threshold.index.trace: 500ms

三、数据结构优化

1、尽量减少不需要的字段

ElasticSearch中存储的数据是用于搜索服务,因此其他一些不需要用于搜索的字段最好不存到ES中,这样即节省空间,同时在相同的数据量下,也能提高搜索性能。

2routing值的设置

通常情况下,往ElasticSearch服务器添加索引数据时,是无需指定routing值。ElasticSearch会根据索引Id,将该条数据存储到ElasticSearch集群中的一个shard中。而当指定了routing值为accountId(用户Id),则ElasticSearch会将相同accountId的多个数据都存放到同一个shard中,后续查询的时候,在指定routing值后,ElasticSearch只需要查询一个shard就能得到所有需要的数据,而不用再去查询所有的shard,从而大大提供了搜索性能。

四、运行期优化

1optimize

随着时间的推移,ElasicSearch中每个shard的数据也会越来越多,索引越来越大,而生成的segment(在每个shard中,每个索引文件实际是由多个sgment文件组成)也会越来越多。而segment越多的话,则查询的性能越差,所以通过调用optimize命令,将多个segment合并成更少数量的segment(最少为一个),从而来提高查询性能。

在调用该命令时,可以设置几个参数,这些参数的具体含义如下:

1.1> max_num_segments

段数优化。要全面优化索引,将其设置为1。默认设置只需检查是否需要执行一个合并,如果需要,则执行它。【经过测试,该值越小,查询速度越快】

1.2> only_expunge_deletes

该优化操作是否只清空打有删除标签的索引记录。在Lucence中,在执行删除操作时,不会直接删除segment中的记录,而是对该记录打上delete标签。当多个segment进行合并操作时,就会生成一个新的segment,而该新的segment中不再包含删除的记录。这个参数允许只对哪些包含删除记录的segment进行优化操作。

1.3>flush

在执行完优化操作之后,再执行刷新操作。默认值为true

1.4>wait_for_merge

当该参数设置为true时,表示其他请求操作要等到合并segment操作结束之后,再进行响应。值得注意的是,由于这个优化操作是一个非常耗时,耗资源的事情,用户提交的请求操作是不能容忍等待这么久,所以这个参数最好设置为false.

具体调用命令如下:

http://localhost:9200/indexName/_optimize?only_expunge_deletes=true&wait_for_merge=false

2warmers

ElasticSearch服务器启动之后,业务系统中要使用的索引数据暂时没有导入到内存中,因此当用户进行第一次数据搜索时,会因为数据导入耗时很久,而严重影响用户的使用体验。为了解决该问题,可以使用warmer工具。通过ElastiSearch提供的工具,可以register/delete/get特定名称的warmer。通常情况下,warmer包含的请求需要载入大量的索引数据(例如在数据搜索中需要针对特定字段的排序操作,或者用到一些聚合sum,min,max函数的查询等),这样才能达到预热的效果。

具体调用示例如下(下面的warmer是针对索引名为testwarmer,warmer定义的名字为warmer_1):

curl -XPUT localhost:9200/test/_warmer/warmer_1 -d ‘{
“query” : {
“match_all” : {}
},
“aggs” : {
“aggs_1” : {
“terms” : {
“field” : “field”
}
}
}
}’

ElasticSearch的部署、同步与调优

ElasticSearch是一个强大的搜索服务器,基于Apache Lucene的全文搜索引擎开发,具有高性能、分布式和零配置的优点。在当前的项目中,我们希望ES能承担亿级文档的搜索,而ES也证明了即便面对这样的数据规模,也能实现十分迅速的搜索响应。

概念

  • 节点(Node):节点是一个ES的实例,一般一台主机上部署一个节点-
  • 集群(Cluster):集群由若干节点组成,和任意节点的通信等价于和集群的通信
  • 分片(Shard):一个索引会分成多个分片存储,分片数量在索引建立后不可更改
  • 副本(Replica):副本是分片的一个拷贝,目的在于提高系统的容错性和搜索的效率
  • 索引(Index):类似数据库的库
  • 类型(Type):类似数据库的表
  • 文档(Document):类似数据库的行,包含一个或多个Field
  • 字段(Field):搜索的最小单元,可通过Mapping定义不同的属性(比如可否被搜索)

部署

ElasticSearch 1.5.0版本为例

ES的使用很简单,从官网下载压缩包后,解压后输入如下指令:

./bin/elasticsearch -d --cluster.name [your_cluster_name] --node.name [your_node_name]

一旦在多台主机上启动拥有同一个cluster.name的ES实例,它们会自动组成一个集群。

elasticsearch-head

elasticsearch-head是一个必装的插件,它提供了一个web界面,显示集群和索引的状态,同时具备浏览和搜索文档的功能。只需要通过ES的plugin指令安装就OK了:

./bin/plugin -install mobz/elasticsearch-head

同步

通常线上系统都不会使用ES作为主存储,从主存储创建索引的效率是我们关心的。ES的bulk API能支持批量操作,大大提升了创建索引的效率。以下是使用pyelasticsearch(非官方的一个Python客户端)批量创建索引的范例:

from pyelasticsearch import ElasticSearch
from pyelasticsearch import bulk_chunks

es = ElasticSearch()

def documents():
    for _doc in docs:
        yield es.index_op(doc=_doc, id=doc['id'])

for chunk in bulk_chunks(documents(), docs_per_chunk=500, bytes_per_chunk=10000):
    es.bulk(chunk, index='index-test', doc_type='doc')

单机索引200万条记录的耗时约10分钟。

中文

ES支持中文的前提是安装正确的分词组件,比如elasticsearch-analysis-ik。但貌似该组件的最新版本(1.2.9)不支持plugin指令直接安装,只能通过Maven重新编译了:

git clone https://github.com/medcl/elasticsearch-analysis-ik.git --depth 1
cd elasticsearch-analysis-ik/
# 真心希望你的网络棒棒嗒
mvn package
unzip ./target/releases/elasticsearch-analysis-ik-1.2.9.zip

zip解压得到5个jar包:

  • elasticsearch-analysis-ik-1.2.9.jar
  • httpclient-4.3.5.jar
  • httpcore-4.3.2.jar
  • commons-logging-1.1.3.jar
  • commons-codec-1.6.jar

返回ES目录,新建路径./plugins/analysis-ik并把上述jar包全部移进去。
第二步,把elasticsearch-analysis-ik/config/ik文件夹(IK自带的词典)复制到ES目录的./config路径下。
第三步,在./config/elasticsearch.yml文件的最后加上:

index:
  analysis:
    analyzer:
      ik:
          alias: [news_analyzer_ik,ik_analyzer]
          type: org.elasticsearch.index.analysis.IkAnalyzerProvider

index.analysis.analyzer.default.type : "ik"

至此大功告成。注意配置分词组件必须在创建索引之前,否则是无效的。

调优

ES的调优分两个层面,一是Java层面的调优,包括加大JVM的可用内存及单线程内存。

对Unix系统,可修改./bin/elasticsearch.in.sh文件:

# 一般分配主机1/4-1/2的内存
if [ "x$ES_MIN_MEM" = "x" ]; then
    ES_MIN_MEM=12g
fi
if [ "x$ES_MAX_MEM" = "x" ]; then
    ES_MAX_MEM=12g
fi

JAVA_OPTS="$JAVA_OPTS -Xms${ES_MIN_MEM}"
JAVA_OPTS="$JAVA_OPTS -Xmx${ES_MAX_MEM}"
# 线程大小, ES单线程承载的数据量比较大
JAVA_OPTS="$JAVA_OPTS -Xss128m"

调优的第二个层面是ES本身的调优,修改./config/elasticsearch.yml文件,关键的项目如下所示:

# 分片数量,推荐分片数*副本数=集群数量
# 分片会带来额外的分割和合并的损耗,理论上分片数越少,搜索的效率越高
index.number_of_shards: 20
# 锁定内存,不让JVM写入swapping,避免降低ES的性能
bootstrap.mlockall: true
# 缓存类型设置为Soft Reference,只有当内存不够时才会进行回收
index.cache.field.max_size: 50000
index.cache.field.expire: 10m
index.cache.field.type: soft

来自:建造者说

elasticsearch三个重要的优化

1、内存优化
在bin/elasticsearch.in.sh中进行配置
修改配置项为尽量大的内存:
ES_MIN_MEM=8g
ES_MAX_MEM=8g
两者最好改成一样的,否则容易引发长时间GC(stop-the-world)

elasticsearch默认使用的GC是CMS GC
如果你的内存大小超过6G,CMS是不给力的,容易出现stop-the-world
建议使用G1 GC
注释掉:
JAVA_OPTS=”$JAVA_OPTS -XX:+UseParNewGC”
JAVA_OPTS=”$JAVA_OPTS -XX:+UseConcMarkSweepGC”

JAVA_OPTS=”$JAVA_OPTS -XX:CMSInitiatingOccupancyFraction=75″
JAVA_OPTS=”$JAVA_OPTS -XX:+UseCMSInitiatingOccupancyOnly”
修改为:
JAVA_OPTS=”$JAVA_OPTS -XX:+UseG1GC”
JAVA_OPTS=”$JAVA_OPTS -XX:MaxGCPauseMillis=200″

如果G1 GC优点是减少stop-the-world在几率,但是CPU占有率高。
需要更优化的性能,你可以参考
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html

2、合理配置主节点和数据节点
配置文件:conf/elasticsearch.yaml
node.master: true
node.data: true

1) 当master为false,而data为true时,会对该节点产生严重负荷;
2) 当master为true,而data为false时,该节点作为一个协调者;
3) 当master为false,data也为false时,该节点就变成了一个负载均衡器。

3、设置合理的刷新时间
建立的索引,不会立马查到,这是为什么elasticsearch为near-real-time的原因
需要配置index.refresh_interval参数,默认是1s。
你可以像
http://zhaoyanblog.com/archives/299.html
文件中一样,调用接口配置
也可以直接写到conf/elasticsearch.yaml文件中
index.refresh_interval:1s
这样所有新建的索引都使用这个刷新频率。

ElasticSearch优化的一些方法

1. 多线程程序插入,可以根据服务器情况开启多个线程index
速度可以提高n倍, n>=2

2. 如果有多台机器,可以以每台设置n个shards的方式,根据业务情况,可以考虑取消replias
curl -XPUT ‘http://10.1.*.*:9200/dw-search/’ -d ‘{
“settings” : {
“number_of_shards” : 20,
“number_of_replicas” : 0
}
}’
这里设置20个shards, 复制为0,如果需要replicas,可以完成index后再修改为replicas>=1
原文:http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html

3. 提高ES占用内存
内存适当调大,初始是256M, 最大1G,
调大后,最小和最大一样,避免GC, 并根据机器情况,设置内存大小,
$ bin/elasticsearch -f -Xmx4g -Xms4g -Des.index.storage.type=memory
原文:http://www.elasticsearch.org/guide/reference/setup/installation.html

4. 减少shard刷新间隔
curl -XPUT ‘http://10.1.*.*:9200/dw-search/_settings’ -d ‘{
“index” : {
“refresh_interval” : “-1”
}
}’

完成bulk插入后再修改为初始值
curl -XPUT ‘http://10.1.*.*:9200/dw-search/_settings’ -d ‘{
“index” : {
“refresh_interval” : “1s”
}
}’

5. 设置一个shard的段segment最大数
可以减少段文件数,提高查询速度
curl -XPOST ‘http://10.1.*.*:9200/dw-search/_optimize?max_num_segments=5’
注意:有时候可能需要多次执行
原文:http://www.elasticsearch.org/guide/reference/api/admin-indices-update-settings.html
原文:http://www.elasticsearch.org/guide/reference/index-modules/merge.html

6. 去掉mapping中_all域
Index中默认会有_all的域,这个会给查询带来方便,但是会增加索引时间和索引尺寸
“_all” : {“enabled” : false}
原文:http://www.elasticsearch.org/guide/reference/mapping/all-field.html
curl -XPOST ‘http://10.1.*.*:9200/dw-search/pt_normal/_mapping’ –data-binary @pt_normal_properties.mapping

7. 设置source为压缩模式或者disable
compress=true这个能大大减少index的尺寸
disable将直接没有_source域

8. 增加merge.policy.merge_factor数
设置merge.policy.merge_factor到30,初始是10
增加这个数需要更多的内存,bulk index可以调大这个值.
如果是即时索引,应该调小这个值
原文:http://www.elasticsearch.org/guide/reference/index-modules/merge.html

9. 修改Client获得方式为
Node node = nodeBuilder().client(true).node();
Client client = node.client()
相比transport client更快
测试效果,速度提高不明朗,且报错。去除

大型网站架构系列:消息队列

一、消息队列概述

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。

目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。

二、消息队列应用场景

以下介绍消息队列在实际应用中常用的使用场景。异步处理,应用解耦,流量削锋和消息通讯四个场景。

2.1异步处理

场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种1.串行的方式;2.并行方式。

(1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。

(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。

因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)。

 

小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?

引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:

按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍。

2.2应用解耦

场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图:

传统模式的缺点:

1)  假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;

2)  订单系统与库存系统耦合;

如何解决以上问题呢?引入应用消息队列后的方案,如下图:

  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
  • 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
  • 假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。

2.3流量削锋

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

  1. 可以控制活动的人数;
  2. 可以缓解短时间内高流量压垮应用;

  1. 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面;
  2. 秒杀业务根据消息队列中的请求信息,再做后续处理。

2.4日志处理

日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下:

  • 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列;
  • Kafka消息队列,负责日志数据的接收,存储和转发;
  • 日志处理应用:订阅并消费kafka队列中的日志数据;

以下是新浪kafka日志处理应用案例:

(1)Kafka:接收用户日志的消息队列。

(2)Logstash:做日志解析,统一成JSON输出给Elasticsearch。

(3)Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能。

(4)Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK stack的重要原因。

2.5消息通讯

消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

点对点通讯:

客户端A和客户端B使用同一队列,进行消息通讯。

聊天室通讯:

客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。

以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。

三、消息中间件示例

3.1电商系统

消息队列采用高可用,可持久化的消息中间件。比如Active MQ,Rabbit MQ,Rocket Mq。(1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功可以开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性)

(2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。

(3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。

3.2日志收集系统

分为Zookeeper注册中心,日志收集客户端,Kafka集群和Storm集群(OtherApp)四部分组成。

  • Zookeeper注册中心,提出负载均衡和地址查找服务;
  • 日志收集客户端,用于采集应用系统的日志,并将数据推送到kafka队列;

四、JMS消息服务

讲消息队列就不得不提JMS 。JMS(JAVA Message Service,java消息服务)API是一个消息服务的标准/规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。

在EJB架构中,有消息bean可以无缝的与JM消息服务集成。在J2EE架构模式中,有消息服务者模式,用于实现消息与应用直接的解耦。

4.1消息模型

在JMS标准中,有两种消息模型P2P(Point to Point),Publish/Subscribe(Pub/Sub)。

4.1.1 P2P模式

P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。

 

P2P的特点

  • 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
  • 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列
  • 接收者在成功接收消息之后需向队列应答成功

 

如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式。

4.1.2 Pub/sub模式

包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。

Pub/Sub的特点

  • 每个消息可以有多个消费者
  • 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
  • 为了消费消息,订阅者必须保持运行的状态。

 

为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。

如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型。

4.2消息消费

在JMS中,消息的产生和消费都是异步的。对于消费来说,JMS的消息者可以通过两种方式来消费消息。

(1)同步

订阅者或接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞;

(2)异步

订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage方法。

 

JNDI:Java命名和目录接口,是一种标准的Java命名系统接口。可以在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回资源连接建立所必须的信息。

JNDI在JMS中起到查找和访问发送目标或消息来源的作用。

4.3 JMS编程模型

(1) ConnectionFactory

创建Connection对象的工厂,针对两种不同的jms消息模型,分别有QueueConnectionFactory和TopicConnectionFactory两种。可以通过JNDI来查找ConnectionFactory对象。

(2) Destination

Destination的意思是消息生产者的消息发送目标或者说消息消费者的消息来源。对于消息生产者来说,它的Destination是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,它的Destination也是某个队列或主题(即消息来源)。

所以,Destination实际上就是两种类型的对象:Queue、Topic可以通过JNDI来查找Destination。

(3) Connection

Connection表示在客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection可以产生一个或多个Session。跟ConnectionFactory一样,Connection也有两种类型:QueueConnection和TopicConnection。

(4) Session

Session是操作消息的接口。可以通过session创建生产者、消费者、消息等。Session提供了事务的功能。当需要使用session发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。同样,也分QueueSession和TopicSession。

(5) 消息的生产者

消息生产者由Session创建,并用于将消息发送到Destination。同样,消息生产者分两种类型:QueueSender和TopicPublisher。可以调用消息生产者的方法(send或publish方法)发送消息。

(6) 消息消费者

消息消费者由Session创建,用于接收被发送到Destination的消息。两种类型:QueueReceiver和TopicSubscriber。可分别通过session的createReceiver(Queue)或createSubscriber(Topic)来创建。当然,也可以session的creatDurableSubscriber方法来创建持久化的订阅者。

(7) MessageListener

消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的onMessage方法。EJB中的MDB(Message-Driven Bean)就是一种MessageListener。

 

深入学习JMS对掌握JAVA架构,EJB架构有很好的帮助,消息中间件也是大型分布式系统必须的组件。本次分享主要做全局性介绍,具体的深入需要大家学习,实践,总结,领会。

五、常用消息队列

一般商用的容器,比如WebLogic,JBoss,都支持JMS标准,开发上很方便。但免费的比如Tomcat,Jetty等则需要使用第三方的消息中间件。本部分内容介绍常用的消息中间件(Active MQ,Rabbit MQ,Zero MQ,Kafka)以及他们的特点。

5.1 ActiveMQ

ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。

ActiveMQ特性如下:

⒈ 多种语言和协议编写客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP

⒉ 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)

⒊ 对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性

⒋ 通过了常见J2EE服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上

⒌ 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA

⒍ 支持通过JDBC和journal提供高速的消息持久化

⒎ 从设计上保证了高性能的集群,客户端-服务器,点对点

⒏ 支持Ajax

⒐ 支持与Axis的整合

⒑ 可以很容易得调用内嵌JMS provider,进行测试

5.2 RabbitMQ

RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

结构图如下:

几个重要概念:

Broker:简单来说就是消息队列服务器实体。

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

Queue:消息队列载体,每个消息都会被投入到一个或多个队列。

Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。

Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。

producer:消息生产者,就是投递消息的程序。

consumer:消息消费者,就是接受消息的程序。

channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

消息队列的使用过程,如下:

(1)客户端连接到消息队列服务器,打开一个channel。

(2)客户端声明一个exchange,并设置相关属性。

(3)客户端声明一个queue,并设置相关属性。

(4)客户端使用routing key,在exchange和queue之间建立好绑定关系。

(5)客户端投递消息到exchange。

exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。

5.3 ZeroMQ

号称史上最快的消息队列,它实际类似于Socket的一系列接口,他跟Socket的区别是:普通的socket是端到端的(1:1的关系),而ZMQ却是可以N:M 的关系,人们对BSD套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程。

引用官方的说法: “ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,像框架一样的一个socket library,他使得Socket编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一 层封装。ZMQ让编写高性能网络应用程序极为简单和有趣。”

特点是:

  • 高性能,非持久化;
  • 跨平台:支持Linux、Windows、OS X等。
  • 多语言支持; C、C++、Java、.NET、Python等30多种开发语言。
  • 可单独部署或集成到应用中使用;
  • 可作为Socket通信库使用。

与RabbitMQ相比,ZMQ并不像是一个传统意义上的消息队列服务器,事实上,它也根本不是一个服务器,更像一个底层的网络通讯库,在Socket API之上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的API接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三种基本模型和扩展模型。

 

ZeroMQ高性能设计要点:

1、无锁的队列模型

对于跨线程间的交互(用户端和session)之间的数据交换通道pipe,采用无锁的队列算法CAS;在pipe两端注册有异步事件,在读或者写消息到pipe的时,会自动触发读写事件。

2、批量处理的算法

对于传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统的开销比较大,zeroMQ对于批量的消息,进行了适应性的优化,可以批量的接收和发送消息。

3、多核下的线程绑定,无须CPU切换

区别于传统的多线程并发模式,信号量或者临界区, zeroMQ充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的CPU切换开销。

5.4 Kafka

Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。

Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性:

  • 通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除)
  • 高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息。
  • 支持通过Kafka服务器和消费机集群来分区消息。
  • 支持Hadoop并行数据加载。

 

Kafka相关概念

  • Broker

Kafka集群包含一个或多个服务器,这种服务器被称为broker

  • Topic

每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

  • Partition

Parition是物理上的概念,每个Topic包含一个或多个Partition.

  • Producer

负责发布消息到Kafka broker

  • Consumer

消息消费者,向Kafka broker读取消息的客户端。

  • Consumer Group

每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。

 

一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。