├── .DS_Store ├── Android中的Bitmap.md ├── Android获取全局Context.md ├── ES之当你更新Document你在更新些什么.md ├── ElasticSearch-match-VS-match-phrase.md ├── ElasticSearch与MySQL数据同步以及修改表结构.md ├── ElasticSearch初探.md ├── FastDFS 安装使用说明.md ├── FastDFS和Nginx.md ├── FastDFS概览.md ├── Hadoop集群部署(RedHat).md ├── Java并发(三).md ├── Java并发(二).md ├── Java并发.md ├── Linux上Python版本升级.md ├── MooseFS概览.md ├── MySQL的主从配置.md ├── Python任务调度队列Celery.md ├── Python多进程使用.md ├── SSH免密码登录设置.md ├── Scala之函数.md ├── Scala入门.md ├── Scala学习之数组.md ├── Scala控制结构.md ├── fdfs_nginx_test.py ├── fdfs_test.py ├── images ├── .DS_Store ├── fdfs01.gif ├── fdfs02.gif ├── fdfs03.gif └── leotse.jpg ├── mobile-advertising-attribution.md ├── resources ├── .DS_Store ├── FastDFS 下载效率的优化.pdf ├── Introduction-to-Computational-Advertising │ ├── Lecture 01 Intro.pdf │ ├── Lecture 02 Marketplace Design.pdf │ ├── Lecture 03 Sponsored Search 1 2011.pdf │ ├── Lecture 04 Sponsored Search 2 2011.pdf │ ├── Lecture 05 Display Advertising Part 1 final.pdf │ ├── Lecture 06 Display Advertising Part 2.pdf │ ├── Lecture 07 Targeting 2011.pdf │ ├── Lecture 08 RecSys 2011Final.pdf │ └── Lecture 09 Mobile & Social.pdf ├── The-Beginners-Guide-to-Mobile-Advertising-Analytics.pdf ├── 中国DSP行业发展研究报告(2014).pdf ├── 中国上班人群洞察报告(2015年).pdf ├── 中国网络广告用户行为研究报告简版.pdf ├── 中国网络广告行业年度监测报告简版(2015).pdf ├── 分布式文件系统FastDFS架构剖析.pptx ├── 分布式文件系统FastDFS架构剖析及配置优化.pdf ├── 微信公众号媒体价值研究报告(2015).pdf ├── 艾瑞投资月报(2015年8月).pdf ├── 2015年中国微商市场研究报告.pdf ├── 中国互联网民大健康研究报告—— 健康服务需求篇(2015).pdf ├── 中国在线医疗行业研究报(2015).pdf ├── 中国金融广告主网络营销策略研究 报告简版(2015).pdf └── 艾瑞解读中国医疗健康产业 互联网化六大趋势(2015).pdf ├── 【译】Python中yield关键字用法.md ├── 你真的懂单链表吗.md ├── 分布式常见术语(单点故障、可扩展性、元数据服务器).md ├── 大数据日知录-架构与算法(一).md ├── 改变FastDFS中某个机器的分组.md └── 轻松使用git写博客.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/.DS_Store -------------------------------------------------------------------------------- /Android中的Bitmap.md: -------------------------------------------------------------------------------- 1 | #Android中的Bitmap 2 | _by:leotse_ 3 | 4 | ## Bitmap简介 5 | Bitmap,我们称之为位图文件,它的扩展名一般是.bmp,有时也可以是.dip。位图由点(像素)组成,其可以理解为一个像素矩阵,矩阵中的每一个点表示对应位置上像素的颜色,每个点可以由多种颜色组成,包括2,4,8,16,24,32位色彩。一张1200x628分辨率的32位真彩图片,所占的存储空间为:1200x628x32/(8x1024)=2944KB。由于位图的构造,使得其图像效果很好好,它是非压缩格式的,但是这也导致它需要占用较大的存储空间,这让位图变得不太适合在网络上传输。 6 | 7 | ![Bitmap example](http://a.hiphotos.baidu.com/baike/w%3D268/sign=f0dd0be3533d26972ed30f5b6dfbb24f/d52a2834349b033b0b84caf317ce36d3d539bd8e.jpg) 8 | 9 | 由于Bitmap的应用比较广泛,因此Android中常常使用到Bitmap。但是在使用Bitmap的时候,我们常常需要考虑到其占用内存较大的事实,因此关注Bitmap的OOM异常成为我们使用Bitmap的必修课。我们主要需要注意以下几个方面: 10 | >1.Android系统资源有限; 11 | 2.Bitmap很能吃内存; 12 | 3.应用的UI一般加载多张Bitmap,这样会一下消耗很多内存。 13 | 14 | ## Bitmap的处理 15 | ### Bitmap的加载优化 16 | 考虑我们前面讲到的Bitmap很耗内存,我们在加载Bitmap的时候就需要时时注意内存情况。在选择图片时,我们就需要知道找到一张合适的图片比一张效果好的图片更加合理(除非你的应用对图片质量要求很高,比如壁纸类应用)。这里介绍Android中的Bitmap的处理类:`BitmapFactory`。 17 | 18 | `BitmapFactory`提供了多种decode图片的方法,比如:`decodeByteArray()`,`decodeFile()`,`decodeResource()`等等,这些方法方便我们从不同的来源创建Bitmap,这些方法都可以通过`BitmapFactory.Options`来指定decode选项,设置`inJustDecodeBounds`属性为true可以在decode时避免分配内存,它会返回一个空的Bitmap,但是我们可以获取到Bitmap的outWidth, outHeight与outMimeType。我们这样就可以在构造Bitmap之前读图片的尺寸与类型。为了避免OOM,我们在一开始就可以检查Bitmap图片的尺寸。示例代码如下: 19 | ```java 20 | public void checkBitmap(){ 21 | BitmapFactory.Options mOptions = new BitmapFactory.Options(); 22 | mOptions.inJustDecodeBounds = true; 23 | BitmapFactory.decodeResource(getImgResource(), R.id.mImage, mOptions); 24 | int mImgHeight = mOptions.outHeight; 25 | int mImgWidth = mOptions.outWidth; 26 | String mImgType = mOptions.outMimeType; 27 | } 28 | 29 | private Resources getImgResource(){} 30 | ``` 31 | 我们在获取了Bitmap的尺寸后,就能很灵活地决定是否需要加载完整的Bitmap图片,如果我们在评估以下几项后决定是否需要加载一个缩小版本的图片: 32 | >1.加载完整的Bitmap需要耗费的内存; 33 | 2.加载这张Bitmap是否会增加其他相关的内存占用; 34 | 3.放置这张图片的的控件尺寸是否合适; 35 | 4.屏幕大小以及设备的屏幕密度; 36 | 37 | 在评估后如果觉得没有必要加载完整的图片就可以考虑加载缩小版的Bitmap。我们需要告诉Bitmap的解码器我们打算加载缩小版的Bitmap,这时可以在`BitmapFactory.Options`中设置`inSampleSize`的值,例如, 一个分辨率为2048x1536的图片,如果设置`inSampleSize`为4,那么会产出一个大约512x384大小的Bitmap。加载这张缩小的图片仅仅使用大概0.75MB的内存,如果是加载完整尺寸的图片,那么大概需要花费12MB(前提都是Bitmap的配置是 ARGB_8888)。下面有一段根据目标图片大小来计算Sample图片大小的代码示例: 38 | ```java 39 | public static int calculateInSampleSize( 40 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 41 | // Raw height and width of image 42 | final int height = options.outHeight; 43 | final int width = options.outWidth; 44 | int inSampleSize = 1; 45 | 46 | if (height > reqHeight || width > reqWidth) { 47 | final int halfHeight = height / 2; 48 | final int halfWidth = width / 2; 49 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 50 | // height and width larger than the requested height and width. 51 | while ((halfHeight / inSampleSize) > reqHeight 52 | && (halfWidth / inSampleSize) > reqWidth) { 53 | inSampleSize *= 2; 54 | } 55 | } 56 | 57 | return inSampleSize; 58 | } 59 | ``` 60 | **Note**: 设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。详情参考`inSampleSize`的文档。 61 | 为了使用该方法,首先需要设置`inJustDecodeBounds`为true, 把options的值传递过来,然后设置`inSampleSize`的值并设置 `inJustDecodeBounds`为false,之后重新调用相关的解码方法。 62 | ```java 63 | public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, 64 | int reqWidth, int reqHeight) { 65 | 66 | // First decode with inJustDecodeBounds=true to check dimensions 67 | final BitmapFactory.Options options = new BitmapFactory.Options(); 68 | options.inJustDecodeBounds = true; 69 | BitmapFactory.decodeResource(res, resId, options); 70 | 71 | // Calculate inSampleSize 72 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 73 | 74 | // Decode bitmap with inSampleSize set 75 | options.inJustDecodeBounds = false; 76 | return BitmapFactory.decodeResource(res, resId, options); 77 | } 78 | ``` 79 | 使用上面这个方法可以简单地加载一张任意大小的图片。如下面的代码样例显示了一个接近100x100像素的缩略图: 80 | ```java 81 | mImageView.setImageBitmap( 82 | decodeSampledBitmapFromResource(getImgResources(), R.id.mImage, 100, 100)); 83 | ``` 84 | 类似的,我们也可以通过替换合适的`BitmapFactory`解码方法来实现一个类似的方法从其他的数据源解析Bitmap。 85 | 86 | ### Bitmap的内存优化 87 | 在Android的演变进程中,管理Bitmap内存也发生了改变。在Android 2.2及以前的版本,当GC的时候,应用的线程会被暂停,在Android2.3开始,新增了并发GC机制,**这意味着在一个Bitmap不再被引用之后,它所占用的内存会被立即回收。** 88 | 在Android2.3.3及之前,一个Bitmap的像素级是存放在Native空间里,这些数据与Bitmap本身是隔离的,Bitmap本身被存放在Dalvik堆中,我们无法预测在Native内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。自Android 3.0开始, 像素级数据则是与Bitmap本身一起存放在Dalvik堆中。 89 | 90 | 好在我们现在Android的主流版本在Android4.0以上,因此Bitmap在不被引用后就会被回收。 91 | 92 | >在过去,一种比较流行的内存缓存实现方法是使用软引用(SoftReference)或弱引用(WeakReference)对Bitmap进行缓存,然而我们并不推荐这样的做法。从Android 2.3开始,垃圾回收机制变得更加频繁,这使得释放软(弱)引用的频率也随之增高,导致使用引用的效率降低很多。而且在Android 3.0之前,备份的Bitmap会存放在Native Memory中,它不是以可预知的方式被释放的,这样可能导致程序超出它的内存限制而崩溃。 93 | 94 | 95 | ### Bitmap的线程操作 96 | 我们可以通过`BitmapFactory`的解码方法来获取Bitmap,但是当图片来源于网络或者其他非内存来源时,我们就需要考虑由此带来的线程问题,毕竟我们在UI线程上不适合干这件事,那么我们可以考虑使用`AsyncTask`:加载Bitmap的类继承`AsyncTask`,并重载`doInBackground()`以及`onPostExecute()`方法。开始异步加载Bitmap,只需要创建一个新的任务并执行它即可。 97 | >为ImageView使用WeakReference确保了AsyncTask所引用的资源可以被垃圾回收器回收。由于当任务结束时不能确保ImageView仍然存在,因此我们必须在onPostExecute()里面对引用进行检查。 98 | 99 | ```java 100 | class BitmapWorkerTask extends AsyncTask { 101 | private final WeakReference imageViewReference; 102 | private int data = 0; 103 | 104 | public BitmapWorkerTask(ImageView imageView) { 105 | // Use a WeakReference to ensure the ImageView can be garbage collected 106 | imageViewReference = new WeakReference(imageView); 107 | } 108 | 109 | // Decode image in background. 110 | @Override 111 | protected Bitmap doInBackground(Integer... params) { 112 | data = params[0]; 113 | return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); 114 | } 115 | 116 | // Once complete, see if ImageView is still around and set bitmap. 117 | @Override 118 | protected void onPostExecute(Bitmap bitmap) { 119 | if (imageViewReference != null && bitmap != null) { 120 | final ImageView imageView = imageViewReference.get(); 121 | if (imageView != null) { 122 | imageView.setImageBitmap(bitmap); 123 | } 124 | } 125 | } 126 | } 127 | ``` 128 | 我们可以按照这样异步加载Bitmap: 129 | ```java 130 | public void loadBitmap(int resId, ImageView imageView) { 131 | BitmapWorkerTask task = new BitmapWorkerTask(imageView); 132 | task.execute(resId); 133 | } 134 | ``` 135 | 136 | 至此,我们就Android中的Bitmap进行比较全面的介绍,当然可以参考官网了解更多有关[Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html)的信息。 137 | 138 | -------------------------------------------------------------------------------- /Android获取全局Context.md: -------------------------------------------------------------------------------- 1 | # Android获取全局Context 2 | 3 | by: leotse 4 | 5 | 在Android的开发中,我们经常需要在不同的场景中使用Context对象。比如,弹出一个Toast,启动一个Service或者一个Activity等等。 6 | 7 | 每个开发者都有自己的方法去获取Context,比较常见的是将Context作为一个参数传递到需要使用的方法,这种方法虽然可以实现这个目的,但是也存在着弊端,Context作为参数传递固然ok,但是需要我们在需要使用到Context的方法里都加上一个参数,这种做法并不优雅。 8 | 9 | 这时候我们有另一种办法去实现我们的目的,那就是在Application中定义一个静态方法,用以获取Context: 10 | 11 | ```java 12 | public class MyApplication extends Application { 13 | 14 | private static Context cxt; 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | cxt = getApplicationContext(); 21 | } 22 | 23 | public static Context getContext(){ 24 | return cxt; 25 | } 26 | 27 | } 28 | ``` 29 | 30 | 31 | 这样我们就可以在project的任意地方通过调用 32 | ```java 33 | MyApplication.getContext(); 34 | ``` 35 | 来获取Context对象。 36 | 37 | -------------------------------------------------------------------------------- /ES之当你更新Document你在更新些什么.md: -------------------------------------------------------------------------------- 1 | # ES之当你更新Document,你在更新些什么? 2 | _by:leotse_ 3 | 4 | ## 怎么更新ElasticSearch中的Document? 5 | 在ES中更新数据的场景有很多,比如我们要修改一个用户的年龄、爱好,又或者我们需要实时同步MySQL中的数据到ES中。我们都有修改已经存在的Document的需求。ES本身提供了两种方法让我们修改一个Document的数据,我们假设我们想要修改的内容如下: 6 | ``` 7 | GET /test/customer/1/_source 8 | 9 | { 10 | "name": "yolovon", 11 | "age": 24 12 | } 13 | ``` 14 | 我们想将yolovon女神的年龄修改到真实年龄18岁。我们可以进行以下两种方法: 15 | 1.使用PUT,就像我们插入数据时那样: 16 | ``` 17 | PUT /test/customer/1 18 | { 19 | "name": "yolovon", 20 | "age": 18 21 | } 22 | 23 | { 24 | "_index": "test", 25 | "_type": "customer", 26 | "_id": "1", 27 | "_version": 2, 28 | "created": false 29 | } 30 | ``` 31 | 这里需要注意的是,我们必须将所有的字段全部输入一次,如果你只在PUT的body里输入我们要更新的字段(比如在这里我们只传入age的值),那么新的Document的就会变成我们后面PUT进去的样子(即只有age一个字段)。 32 | 另外,我们可以看到这里的返回信息有用以标示该document唯一性的`_index`,`_type`,`_id`,以及`_version`表明该document的更新版本,`created`表明这个不是新建的而是已经存在的document。 33 | 34 | 2.使用POST,update模式更新Document: 35 | ``` 36 | POST /test/customer/1/_update 37 | { 38 | "doc": { 39 | "age": 20 40 | } 41 | } 42 | ``` 43 | 你会看到这样的结果: 44 | ``` 45 | { 46 | "_index": "test", 47 | "_type": "customer", 48 | "_id": "1", 49 | "_version": 3 50 | } 51 | ``` 52 | 这时候,我们的document也变为了: 53 | ``` 54 | { 55 | "name": "yolovon", 56 | "age": 20 57 | } 58 | ``` 59 | 这里的`age`已经变成了20。而且在这里,我们只针对需要修改的`age`字段进行操作,而其他字段并不需要关心。 60 | 61 | 62 | ## 更新操作都干了些什么? 63 | 那么,在更新的时候到底发生了什么?上面的两种更新策略又有什么区别? 64 | 65 | 我们知道,在Elasticsearch中,document是不可变的。 66 | >Documents in Elasticsearch are immutable; we cannot change them. Instead, if we need to update an existing document, we reindex or replace it. 67 | 68 | 这样一来,我们在更新Document时到底是怎么实现的? 69 | 在ES中,不管我们用以上哪种方式进行更新document,它都不是真正地对原来的document进行操作。而是先将原来的document标记为删除状态,然后重新新增一个document(也就是我们看到的新的document),实际上,原来的document并没有立即消失,只是你已经不能访问它了,ES稍后会在后台真正地删除原来的document。 70 | 71 | 不管我们用那种方式进行更新,ES都会按照以下的步骤进行更新: 72 | >1.查询出旧的document; 73 | 2.修改document中的字段; 74 | 3.删除旧的document; 75 | 4.重新索引一个新的document。 76 | 77 | 但是两者还是有区别的,当使用`PUT`一个完整的document时,它需要请求两次,一次`get`请求和一次`index`请求,而使用`POST`进行`_update`操作的时候只需要一次`_udpate`请求即可。 78 | 79 | 因此我们在使用的时候,尽量避免使用`PUT`进行document的更新,特别是当我们需要批量地修改ES数据时。 -------------------------------------------------------------------------------- /ElasticSearch-match-VS-match-phrase.md: -------------------------------------------------------------------------------- 1 | #ElasticSearch - match VS match_phrase 2 | _by:leotse_ 3 | 4 | ## match VS match_phrase 5 | 我们以一个使用的示例开始,我们有student这个type中存储了一些学生的基本信息,我们分别使用match和match_phrase进行查询,我们得到如下结果: 6 | 首先,使用match。 7 | ``` 8 | GET /test/student/_search 9 | { 10 | "query": { 11 | "match": { 12 | "description": "He is" 13 | } 14 | } 15 | } 16 | ``` 17 | 执行这条查询,得到的结果如下: 18 | ``` 19 | { 20 | "took": 3, 21 | "timed_out": false, 22 | "_shards": { 23 | "total": 5, 24 | "successful": 5, 25 | "failed": 0 26 | }, 27 | "hits": { 28 | "total": 4, 29 | "max_score": 0.2169777, 30 | "hits": [ 31 | { 32 | "_index": "test", 33 | "_type": "student", 34 | "_id": "2", 35 | "_score": 0.2169777, 36 | "_source": { 37 | "name": "februus", 38 | "sex": "male", 39 | "age": 24, 40 | "description": "He is passionate.", 41 | "interests": "reading, programing" 42 | } 43 | }, 44 | { 45 | "_index": "test", 46 | "_type": "student", 47 | "_id": "1", 48 | "_score": 0.16273327, 49 | "_source": { 50 | "name": "leotse", 51 | "sex": "male", 52 | "age": 25, 53 | "description": "He is a big data engineer.", 54 | "interests": "reading, swiming, hiking" 55 | } 56 | }, 57 | { 58 | "_index": "test", 59 | "_type": "student", 60 | "_id": "4", 61 | "_score": 0.01989093, 62 | "_source": { 63 | "name": "pascal", 64 | "sex": "male", 65 | "age": 25, 66 | "description": "He works very hard because he wanna go to Canada.", 67 | "interests": "programing, reading" 68 | } 69 | }, 70 | { 71 | "_index": "test", 72 | "_type": "student", 73 | "_id": "3", 74 | "_score": 0.016878016, 75 | "_source": { 76 | "name": "yolovon", 77 | "sex": "female", 78 | "age": 24, 79 | "description": "She is so charming and beautiful.", 80 | "interests": "reading, shopping" 81 | } 82 | } 83 | ] 84 | } 85 | } 86 | ``` 87 | 而当你执行match_phrase时: 88 | ``` 89 | GET /test/student/_search 90 | { 91 | "query": { 92 | "match_phrase": { 93 | "description": "He is" 94 | } 95 | } 96 | } 97 | ``` 98 | 结果如下: 99 | ``` 100 | { 101 | "took": 3, 102 | "timed_out": false, 103 | "_shards": { 104 | "total": 5, 105 | "successful": 5, 106 | "failed": 0 107 | }, 108 | "hits": { 109 | "total": 2, 110 | "max_score": 0.30685282, 111 | "hits": [ 112 | { 113 | "_index": "test", 114 | "_type": "student", 115 | "_id": "2", 116 | "_score": 0.30685282, 117 | "_source": { 118 | "name": "februus", 119 | "sex": "male", 120 | "age": 24, 121 | "description": "He is passionate.", 122 | "interests": "reading, programing" 123 | } 124 | }, 125 | { 126 | "_index": "test", 127 | "_type": "student", 128 | "_id": "1", 129 | "_score": 0.23013961, 130 | "_source": { 131 | "name": "leotse", 132 | "sex": "male", 133 | "age": 25, 134 | "description": "He is a big data engineer.", 135 | "interests": "reading, swiming, hiking" 136 | } 137 | } 138 | ] 139 | } 140 | } 141 | ``` 142 | 143 | 占的篇幅有点长,但是如果能基于此看清这两者之间的区别,那也是值得的。 144 | 145 | 我们分析一下这两者结果的差别: 146 | >1.非常直观的一点,对于同一个数据集,两者检索出来的结果集数量不一样; 147 | 2.对于match的结果,我们可以可以看到,结果的Document中description这个field可以包含“He is”,“He”或者“is”; 148 | 3.match_phrased的结果中的description字段,必须包含“He is”这一个词组; 149 | 4.所有的检索结果都有一个_score字段,看起来是当前这个document在当前搜索条件下的评分,而检索结果也是按照这个得分从高到低进行排序。 150 | 151 | 我们要想弄清楚match和match_phrase的区别,要先回到他们的用途:match是全文搜索,也就是说这里的搜索条件是针对这个字段的全文,只要发现和搜索条件相关的Document,都会出现在最终的结果集中,事实上,ES会根据结果相关性评分来对结果集进行排序,这个相关性评分也就是我们看到的_score字段;总体上看,description中出现了“He is”的Document的相关性评分高于只出现“He”或“is”的Document。(至于怎么给每一个Document评分,我们会在以后介绍)。 152 | >相关性(relevance)的概念在Elasticsearch中非常重要,而这个概念在传统关系型数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。 153 | 154 | 那么,如果我们不想将我们的查询条件拆分,应该怎么办呢?这时候我们就可以使用match_phrase: 155 | match_phrase是短语搜索,亦即它会将给定的短语(phrase)当成一个完整的查询条件。当使用match_phrase进行搜索的时候,你的结果集中,所有的Document都必须包含你指定的查询词组,在这里是“He is”。这看起来有点像关系型数据库的like查询操作。 156 | 157 | ## 相关性评分的相关知识 158 | 相信到这里,我们都能比较清楚的理解这两者的区别。但是我们还有一个问题没有弄清楚,那就是_score到底是怎么得出的?为什么同样包含了“He is”这个phrase,_id为2的Document得分为0.30685282,而_id为1的Document的得分为0.23013961? 159 | 160 | 查询语句会为每个Document计算一个相关性评分_score,评分的计算方式取决于不同的查询类型。ES的相似度算法为TF/IDF(检索词频率/反向文档频率)。我们在这里顺带介绍一下TF/IDF的几个相关概念: 161 | 162 | 1.**字段长度准则**:这个准则很简单,字段内容的长度越长,相关性越低。我们在上面的两个例子中都能看到,同样包含了“He is”这个关键字,但是"He is passionate."的相关性评分高于"He is a big data engineer.",这就是因为字段长度准则影响了它们的相关性评分; 163 | 164 | 2.**检索词频率准则**:检索关键字出现频率越高,相关性也越高。这个例子中没有比较明显的体现出来,你可以自己试验一下; 165 | 166 | 3.**反向Document频率准则**:每个检索关键字在Index中出现的频率越高,相关性越低。 167 | 168 | 一般的,我们理解了以上三个准则,就能了解ES的相关性评分的基本守则。以下是一些相关性评分的Tips: 169 | >单个查询可以使用TF/IDF评分标准或其他方式。 170 | 如果多条查询子句被合并为一条复合查询语句,那么每个查询子句计算得出的评分会被合并到总的相关性评分中。 171 | 172 | 因为“相关性评分”这个概念和这篇博文的“相关性评分”并不高,因此在此就不展开讨论,只是点到为止,如果想要了解更多有关ES相关性评分的内容,可以自行Google,也可以继续关注我的博客,以后会专门探讨这一块内容。 173 | 174 | -------------------------------------------------------------------------------- /ElasticSearch与MySQL数据同步以及修改表结构.md: -------------------------------------------------------------------------------- 1 | # ElasticSearch与MySQL数据同步以及修改表结构 2 | _by:leotse_ 3 | 4 | ## ES与MySQL的数据同步 5 | 如果你需要进行ES与MySQL的数据同步,亦即将MySQL中的数据导入到ES中,并保持同步,一般来看,有以下几种方法: 6 | 1.自己动手写一个同步的模块。实时tail处理MySQL的binlog,将数据库的新增、修改或删除这些操作同步在ES上执行。这种方案可行,但是实现起来代价大; 7 | 8 | 2.[go-mysql-elasticsearch](https://github.com/siddontang/go-mysql-elasticsearch)。这是Git上的一个开源项目,差不多是第一种方案的一个实现版本。这个插件支持实时同步MySQL与ES的新增、修改以及删除操作。但是缺点是使用起来不够灵活,它的作者给出了它的以下缺点: 9 | >binlog row image must be full for MySQL, you may lost some field data if you update PK data in MySQL with minimal or noblob binlog row image. MariaDB only supports full row image. 10 | >Can not alter table format at runtime. 11 | >mysqldump must exist in the same node with go-mysql-elasticsearch, if not, go-mysql-elasticsearch will try to sync binlog only. 12 | 13 | 这三点比较重要的缺点影响我使用这种方案,特别是不能运行时修改表结构。对于很多在线业务来说是比较难以接受的。 14 | 15 | 3.[elasticsearch-river-jdbc](https://github.com/jprante/elasticsearch-jdbc)。这种方案是1、2的结合体。可以较为灵活地进行数据同步,比如可以在同步的时候指定需要同步的字段以及筛选条件。这也是比较流行的解决方案,但是它也有一个比较致命的缺点,那就是删除操作不能同步(物理删除)! 16 | 17 | 如果你的系统对删除操作频繁,而且都是物理删除,并且能接受不能运行时修改表结构等条件,那么使用go-mysql-elasticsearch将是非常不错的选择;如果你的表结构改变得相对频繁,而且不用对表进行物理删除(比如用逻辑删除取而代之),那么你可以选择elasticsearch-river-jdbc。如果你对这两者都不满意,而且觉得自己的编码能力还不错,那么完全可以自己定制一个满足自己需求的插件。 18 | 19 | ## elasticsearch-river-jdbc使用 20 | 我们这里选择使用elasticsearch-river-jdbc作为我们同步ES与MySQL的插件。我们这里简单介绍一下elasticsearch-river-jdbc的安装与使用: 21 | 1.确保ES的集群的每个Node都能访问MySQL数据库; 22 | 23 | 2.安装River: 24 | ``` 25 | >./bin/plugin --install river-jdbc --url http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.5.0.5/elasticsearch-river-jdbc-1.5.0.5-plugin.zip 26 | ``` 27 | 3.下载mysql-connector-java-5.1.30-bin.jar并将其保存在{$ES_HOME}/plugins/jdbc/目录下: 28 | ``` 29 | wget http://cdn.mysql.com//Downloads/Connector-J/mysql-connector-java-5.1.37.tar.gz 30 | ``` 31 | 如果是ES集群,你需要在每一个Node上执行这一步。 32 | 33 | 4.创建一个JDBC river 34 | ``` 35 | >curl -XPUT 'localhost:9200/_river/my_jdbc_river/_meta' -d '{ 36 | "type" : "jdbc”, 37 | "jdbc" : { 38 | "driver" : "com.mysql.jdbc.Driver", 39 | "url" : "jdbc:mysql://localhost:3306/test", 40 | "user" : “root", 41 | "password" : “123456", 42 | "sql" : "select * from test.student;”, 43 | "interval" : "30", 44 | "index" : "test", 45 | "type" : "student" 46 | } 47 | }’ 48 | ``` 49 | 50 | 5.你可以查看ES是否已经同步了这些数据: 51 | ``` 52 | > curl -XGET 'localhost:9200/test/student/_search?pretty&q=*' 53 | ``` 54 | 55 | ## 修改表结构后的数据同步 56 | 如果你需要修改正在与ES进行数据同步的表的结构,你有以下三种方案(亲测可行): 57 | **方案一**:创建JDBC river的时候使用sql语句:`select * from table_name;` 58 | 乍一看,这种方法很坑。但是确实适合那些喜欢简单粗暴的coder。但是这种方案的场景比较苛刻: 59 | >MySQL中表在创建时id命名为_id,这是因为_id是ES中每一条document的唯一标识;如果不这样干,你会发现每一次同步ES的type中都会增加MySQL表中条目数个Document; 60 | >MySQL表所有的字段对ES都是有效的、必要的; 61 | 62 | **方案二**.方案一的改进版,使用sql语句:`select *, id as _id from table_name;` 63 | 这个方案在你需要在ES中存储MySQL中表所有字段的时候变得比较有效。 64 | 65 | **方案三**:使用elasticsearch的alias。具体的操作比较简单,可以参照[官网的介绍](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html)。 66 | 67 | 前两种方案有点取巧,第三种方案才是正道。 68 | 69 | 70 | 如果你愿意,完全建议自己定制一套解决方案。由于业务需要,本人最近也在开始写这样的插件。我会在这个博客里保持更新。 -------------------------------------------------------------------------------- /ElasticSearch初探.md: -------------------------------------------------------------------------------- 1 | # ElasticSearch初探 2 | _by:leotse_ 3 | 4 | ## ES介绍 5 | 我们首先来看[ES](https://www.elastic.co)(全称是Elastic Search)到底是什么,下面是Wikipedia给出的定义: 6 | >Elasticsearch is a search server based on Lucene. It provides a distributed, multitenant-capable full-text search engine with a RESTful web interface and schema-free JSON documents. Elasticsearch is developed in Java and is released as open source under the terms of the Apache License. Elasticsearch is the second most popular enterprise search engine after Apache Solr. 7 | 8 | >ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是第二流行的企业搜索引擎。 9 | 10 | 在这段定义之外,我们看看ES还有哪些吸引人的地方: 11 | 1.ES的搜索几乎是实时的,当你向一个ES服务器或者一个ES集群发出搜索请求后,你可以在非常短的延迟后获取到你想要的数据(不超过1s); 12 | 2.ES的集群是去中心化的,也就是说,不像Hadoop这种分布式框架,ES集群不存在单点故障。ES集群中包含多个节点,节点可以通过选举选出它们的中心节点,因此,当集群中的一个节点出现故障时,集群马上会选取出下一个新的中心节点来管理集群; 13 | 3.ES的横向扩展性能非常好,集群维护者不用担心集群出现资源不够的情况,因为你只需要增加一台服务器,然后在其上安装ES然后简单进行配置,这台新的节点就能成为集群中的一员; 14 | 4.ES提供了分片机制(Shard),一个索引(Index)可以划分为多个Shard,这些Shard分布在集群中不同的节点上,能有效提升集群的处理效率; 15 | 5.ES为其中的数据提供了replica,确保数据的冗余存储,从而使得整个ES集群可用性大大增强,亦即当一个节点出现故障时,存储在其上的数据仍然可以在其他的节点上访问到; 16 | 6.ES简单易用,除了为开发者和使用者提供了RESTful接口外,ES本身非常易于学习和使用,这一点在应用中你可以感受到。它的社区也十分活跃,相关的文档资料也较健全。 17 | 18 | 这些点足以让我们拥有信心在需要的时候投入ES的怀抱,当然我们还需要考虑ES的一些不足,在本人的了解范围里以及结合本人的使用经验,ES集群可能存在以下问题: 19 | 1.脑裂问题:ES集群有可能在节点间网络通信故障时成为“裂脑人”。这是ES集群的去中心化带来的不足。我们假设这样一个场景,我们的ES集群有10个节点,它们分别在两个机房A和B,有一天机房的网络出现问题,A机房无法与B机房进行通信,这时候,ES的选举机制会被触发,A机房会选取出一个中心节点,B机房也会选取出一个中心节点,整个集群一分为二,虽然对外提供访问时仍然问题不大,但是这两个集群会出现数据不同步的问题; 20 | 2.权限管理机制不健全:迄今为止,ES集群没有比较健全的权限管理机制,如果你需要对ES的访问和使用权限进行管理,或者你需要为ES集群提供一个前端web服务,那么你需要在后台自行实现一个权限管理机制。 21 | 22 | ## ES相关概念 23 | 我们这里只介绍ES的一些相关的术语,在此之前,我们结合关系型数据库来简单介绍一下ES的结构: 24 | >Relational DB -> Databases -> Tables -> Rows -> Columns 25 | Elasticsearch -> Indices -> Types -> Documents -> Fields 26 | 27 | 理解ES的结构本身不难,但是由于ES的这些术语在我们的圈子里已经有一些其他的受众较广的含义,所以这样一种与RDB的对比可以帮助我们理解ES的结构,ES本身可以想象成一个MySQL,ES中的Index就是数据库DB,而Type相当于数据库中的表,以此类推。 28 | 29 | **索引Index**:在ES中,Document中的所有字段都会建立索引(这里的索引是我们平常所理解的意思,如数据库的索引),这种设定使得ES中的每个文档的每个字段都是可以直接用来搜索的。事实上,我们的数据被存储和索引在分片中,索引只是一个把一个或多个分片分组在一起的逻辑空间。 30 | 31 | **分片Shard**:我们可以将ES中的分片Shard理解为HDFS中的文件块Block,一个完整的Index可以划分为多个Shard,然后分别存储在不同的节点上; 32 | 33 | **类型Type**:我们能够自己定义ES中的Type,Type是Index的一个逻辑分区,每个类型都有自己的映射或者结构定义,就像传统数据库表中的列一样; 34 | 35 | **文档Document**:如果你的Tool Kit中有OOP,那么你可以简单将Document理解为对象Object,它是ES中可被索引的基础信息单元;Type是序列化为JSON格式的数据。 36 | 37 | **映射Mapping**:ES中的映射相当于数据库中表的定义,映射定义了字段类型,每个字段的数据类型,以及字段被ES处理的方式。映射还用于设置关联到类型上的元数据。 38 | 39 | 40 | 这里只简单介绍了ES相关的基础信息,万里长征这才是第一步,但是希望这些信息可以让我们对ES有一个比较清晰的理解,至于如何使用ES进行存储数据、查询数据以及分析数据,我们需要在实践上不断学习和参悟。 41 | 42 | 最后推荐一本书,希望能帮助到各位学习和理解ES:[Elasticsearch 权威指南](http://es.xiaoleilu.com/index.html)。 43 | -------------------------------------------------------------------------------- /FastDFS 安装使用说明.md: -------------------------------------------------------------------------------- 1 | # FastDFS安装使用说明 2 | 整理:LeoTse 3 | 4 | ## 一、概述 5 | FastDFS是C语言实现的、开源的、轻量级的应用级分布式文件系统,开发者为淘宝开发平台部资深架构师余庆。它提供了负载均衡、冗余备份机制,是一个可扩展、高可用、高性能的分布式文件系统。 6 | 更多内容见论坛:[http://bbs.chinaunix.net/forum-240-1.html](http://bbs.chinaunix.net/forum-240-1.html) 7 | 8 | 本文档内容包括:FastDFS安装、Python API安装及调用、通过Nginx访问文件。FastDFS安装又包括Tracker、Storage以及Nginx的安装。 9 | 10 | ## 二、准备工作 11 | 1.建议先了解一下FastDFS; 12 | 13 | 2.所用到的环境或软件有如下一些: 14 | 1)Ubuntu 12.04 15 | 2)libfastcommon-master.zip 16 | 3)fastdfs-5.05.tar.gz 17 | 4)fastdfs-nginx-module_v1.16.tar.gz 18 | 5)nginx-1.9.0.tar.gz 19 | 6)pcre-8.33.tar.gz 20 | 7)zlib-1.2.8.tar.gz 21 | 8)fdfs_client-py-master.zip 22 | 虽然看起来用到的软件比较多,但是安装配置过程比较简单。(注:相关软件都可以在本压缩文件中找到。) 23 | 24 | 3.我们假设我们用到的机器有: 25 | **Tracker**:192.168.9.229 26 | **Storage**:192.168.9.230, 192.168.9.231 27 | 28 | ## 三、安装步骤 29 | ### 1.安装libfastcommon(Tracker和Storage都需要): 30 | 相较于旧版的FastDFS,新版的FastDFS已经不需要依赖libevent,我们需要先安装它的依赖包libfastcommon。 31 | a)解压libfastcommon-master.zip: 32 | unzip libfastcommon-master.zip 33 | 34 | b)进入libfastcommon-master目录,执行: 35 | ./make.sh 36 | ./make.sh install 37 | 38 | 我们可以看到libfastcommon.so安装到了/usr/lib64/libfastcommon. so。但是FastDFS主程序设置的目录lib的目录是/usr/local/lib,所以需要创建软连接: 39 | ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so 40 | ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so 41 | ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so 42 | ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so 43 | 44 | ### 2.安装FastDFS(Tracker和Storage都需要): 45 | a)解压fastdfs-5.05.tar.gz; 46 | tar -xzvf fastdfs-5.05.tar.gz 47 | b)进入fastdfs-5.05目录,依次执行: 48 | cd fastdfs-5.05 49 | ./make.sh 50 | ./make.sh install 51 | 到此我们的安装暂时结束,相应的配置等会会介绍到。我们下一步来安装Nginx依赖。 52 | 53 | ### 3.安装Nginx依赖模块(只需要在Storage上安装,如果需要Tracker直接提供文件访问服务,Tracker也需安装): 54 | FastDFS通过HTTP服务器来去提供HTTP服务。我们采用Nginx作为HTTP服务器,因为Nginx能支持高并发的访问并提供负载均衡等高性能的服务。 55 | 我们约定,所有的这些包都在/home/leotse/fastdfs/下。 56 | a)fastdfs-nginx-module安装: 57 | 我们知道FastDFS通过Tracker将用户上传的文件保存在Storage服务器上,但是同一个Group上的机器进行文件复制会有一定的延时。假设我们有A、B两台Storage服务器,一开始c文件保存在A,在c文件复制到B服务器之前用户到B服务器访问c文件,势必会报错,fastdfs-nginx-module的作用就是重定向到源服务器(即示例中的A)读文件,避免了复制延迟的问题。 58 | tar -xzvf fastdfs-nginx-module_v1.16.tar.gz 59 | 我们需要根据fastdfs以及fastcommon的文件位置判断是不是需要修改config文件。 60 | CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/" 61 | 改成: 62 | CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/" 63 | 64 | b)zlib库安装: 65 | tar -xzvf zlib-1.2.8.tar.gz 66 | cd zlib-1.2.8 67 | 设置安装路径并安装: 68 | ./configure --prefix=/usr/local/zlib 69 | make 70 | make install 71 | 72 | c)pcre库安装: 73 | tar -xzvf pcre-8.33.tar.gz 74 | cd pcre-8.33 75 | ./configure --prefix=/usr/local/pcre --libdir=/usr/local/lib/pcre --includedir=/usr/local/include/pcre 76 | make 77 | make install 78 | 79 | ### 4.安装Nginx: 80 | a)解压Nginx安装包: 81 | tar -xzvf nginx-1.9.0.tar.gz 82 | 83 | b)进入Nginx目录并设置安装路径以及指定依赖库: 84 | cd nginx-1.9.0 85 | ./configure --prefix=/usr/local/nginx \ 86 | --with-zlib=/home/leotse/fastdfs/zlib-1.2.8\ 87 | --with-pcre=/home/leotse/fastdfs/pcre-8.33\ 88 | --sbin-path=/usr/local/nginx\ 89 | --add-module=/home/leotse/fastdfs/fastdfs-nginx-module/src 90 | 91 | c)make安装: 92 | make 93 | make install 94 | 95 | ### 5.Tracker配置与启动: 96 | 在配置之前,非常郑重地推荐这个:[FastDFS 配置文件详解(修订版1) ](http://bbs.chinaunix.net/thread-1941456-1-1.html),里面非常详尽地介绍了FastDFS的配置文件具体参数。 97 | 98 | a)修改tracker.conf文件,该文件的位置在fastdfs-5.05/conf下。在此之前,我们需要创建一个目录存放Tracker的data和log,我们在这里假定这个路径为:/home/leotse/fastdfs/tracker/,我们需要确保该路径存在。我们只需要修改: 99 | base_path=/home/leotse/fastdfs/tracker/ 100 | 101 | b)复制tracker.conf文件到/etc/fdfs/目录下: 102 | cp tracker.conf /etc/fdfs/ 103 | 104 | c)运行Tracker: 105 | fdfs_tracker /etc/fdfs/tracker.conf 106 | 停止Tracker:/usr/local/bin/stop.sh fdfs_tracker /etc/fdfs/tracker.conf 107 | 重启Tracker:/usr/local/bin/restart.sh fdfs_tracker /etc/fdfs/tracker.conf 108 | 109 | ### 6.Storage配置与运行: 110 | 在这里,同样我们需要创建一个目录保存Storage的data和log,我们假定Storage上该目录为/home/leotse/fastdfs/storage/。 111 | a)修改Nginx端口(可选): 112 | Nginx默认的端口号为80,为了防止引起冲突,我们建议修改Nginx的端口: 113 | vi /usr/local/nginx/conf/nginx.conf 114 | 修改 115 | server { 116 | listen 80; 117 | server_name localhost; 118 | 在这个示例里,我们将80改为9096(可以更改)。 119 | 120 | b)在Nginx中支持FastDFS模块: 121 | vi /usr/local/nginx/conf/nginx.conf 122 | 我们在server模块中增加: 123 | location /group1/M00{ 124 | root /home/leotse/fastdfs/storage/data; 125 | ngx_fastdfs_module; 126 | } 127 | 128 | c)给Storage的存储目录做一个软连接: 129 | ln -s /home/leotse/ fastdfs /storage/data /home/leotse/ fastdfs /storage /data /M00 130 | 131 | d)修改Storage配置。我们将注意力从Nginx转到FastDFS上来, 132 | 修改storage.conf文件,该文件的位置在fastdfs-5. 05/conf下,我们需要创建目录/home/leotse/fastdfs/storage/用以保存Storage的data和logs。我们需要做以下修改: 133 | 修改group_name,根据实际配置确定: 134 | group_name=group1 135 | 接下来修改存储data和logs的目录: 136 | base_path=/home/leotse/fastdfs/storage/ 137 | 然后就是放置文件的目录,需要和nginx中的server中的设置保持一样。建议与上面的目录保持一致: 138 | store_path0=/home/leotse/fastdfs/storage/ 139 | 接着就是修改Tracker的host,如果有多个Tracker可以分行写: 140 | tracker_server=192.169.9.229:22122 141 | 然后就是配置web server的端口,需要和Nginx的端口保持一致: 142 | http.server_port=9096 143 | 修改storage.conf文件后,将其copy到/etc/fdfs/目录下: 144 | cp storage.conf /etc/fdfs/ 145 | 146 | e)修改fastdfs-nginx-module配置: 147 | 将mod_fastdfs.conf复制到/etc/fdfs/目录下: 148 | cp mod_fastdfs.conf /etc/fdfs/ 149 | 修改mod_fastdfs.conf文件: 150 | vi /etc/fdfs/mod_fastdfs.conf 151 | 修改base_path以及store_path0,使其和storage.conf中保持一致: 152 | base_path=/home/leotse/fastdfs/storage/ 153 | store_path0=/ home/leotse/fastdfs/storage/ 154 | 修改tracker server以及group_name: 155 | tracker_server=192.168.9.229:22122 156 | group_name=group1 157 | 修改group count,注意,如果只有单个group,设置为0,如果有多个group,根据实际情况配置: 158 | group_count = 0 159 | 在url中包含group name,一定要设置为true: 160 | url_have_group_name = true 161 | 修改完毕。 162 | 163 | conf目录下的http.conf和mime.types两个文件也需要copy到/etc/fdfs目录下。 164 | 165 | 运行nginx: 166 | /usr/local/nginx/sbin/nginx 167 | 运行Storage: 168 | fdfs_storaged /etc/fdfs/storage.conf 169 | 停止Storage: 170 | /usr/local/bin/stop.sh fdfs_storaged /etc/fdfs/storage.conf 171 | 重启Storage: 172 | /usr/local/bin/restart.sh fdfs_storaged /etc/fdfs/storage.conf 173 | 174 | ### 7.运行: 175 | 我们已经成功配置了Tracker和Storage以及Nginx,接下来就是上传和下载文件。 176 | 我们这里要用到client.conf文件来获取FastDFS的相关信息,该文件在fastdfs中的conf目录下: 177 | 修改如下地方: 178 | base_path=/home/leotse/fastdfs/tracker 179 | tracker_server=192.168.9.229:22122 180 | http.tracker_server_port=9096 181 | 182 | 我们可以直接使用直接调用或者调用API(我们这里介绍的是Python API)。 183 | 首先是直接调用,我们假设/home/leotse/test/下有文件test_fdfs.txt 184 | fdfs_test /etc/fdfs/client.conf upload /home/leotse/test/test_fdfs.txt 185 | 我们可以看到这样的返回: 186 | example file url: http://192.168.9.230:9096/group1/M00/00/08/wKgJ5lVPFzCAJVXwAAApRhJa9hc052_big.txt 187 | 我们可以通过这个url获取这个文件。 188 | 189 | 同样,我们来测试FastDFS的下载文件,以上面的文件为例: 190 | fdfs_download_file /etc/fdfs/client.conf group1/M00/00/08/wKgJ5lVPFzCAJVXwAAApRhJa9hc052_big.txt download_test.txt 191 | 我们可以看到我们将这个文件下载下来并且文件名为download_test.txt。 192 | 193 | 接下来介绍Python API调用。我们首先要获取最新的python api包,我们这里用到的是fdfs_client-py-master. zip。我们将其在机器上解压,然后调用: 194 | python setup.py install 195 | 安装完毕后即可调用。 196 | 我们主要用到的方法有: 197 | 上传文件:upload_by_filename 198 | 下载文件:download_to_file 199 | 删除文件:delete_file 200 | 我们在这里也上传了两个脚本(与该blog在同一级目录),fdfs_test.py演示了Python API的使用方法,fdfs_nginx_test.py演示了如何以nginx的方式下载文件。 201 | 202 | ## 四、FastDFS性能测试 203 | 我们测试了FastDFS的性能,主要是Python API的上传文件以及下载文件, Nginx下的下载文件性能,下面为测试结果: 204 | 205 | a)FastDFS Python API Test Results: 206 | ==============================result============================ 207 | file size: 8.00MB 208 | upload files 20 times 209 | failed upload 0 times 210 | rate of failed upload: 0.0 % 211 | upload average duration: 0.317473113537 s 212 | download average duration: 0.165269041061 s 213 | 214 | ==============================result============================ 215 | file size: 39.00MB 216 | upload files 20 times 217 | failed upload 0 times 218 | rate of failed upload: 0.0 % 219 | upload average duration: 1.21865663528 s 220 | download average duration: 0.707462477684 s 221 | 222 | ==============================result============================ 223 | file size: 128.00MB 224 | upload files 20 times 225 | failed upload 0 times 226 | rate of failed upload: 0.0 % 227 | upload average duration: 3.0637313962 s 228 | download average duration: 1.59568111897 s 229 | 230 | =============================result============================= 231 | file size: 336.00MB 232 | upload files 20 times 233 | failed upload 0 times 234 | rate of failed upload: 0.0 % 235 | upload average duration: 5.62887555361 s 236 | download average duration: 4.49918934107 s 237 | 238 | b)FastDFS Nginx Download Test Results: 239 | ==============================result============================ 240 | file size: 8.00MB 241 | download file 50 times 242 | failed 0 times 243 | rate of failed download: 0.0 % 244 | average download duration: 0.0893847703934 s 245 | 246 | ==============================result============================ 247 | file size: 39.00MB 248 | download file 50 times 249 | failed 0 times 250 | rate of failed download: 0.0 % 251 | average download duration: 0.36199669838 s 252 | 253 | ==============================result============================ 254 | file size: 128.00MB 255 | download file 50 times 256 | failed 0 times 257 | rate of failed download: 0.0 % 258 | average download duration: 1.27810112 s 259 | 260 | ==============================result============================ 261 | file size: 336.00MB 262 | download file 50 times 263 | failed 0 times 264 | rate of failed download: 0.0 % 265 | average download duration: 3.0081551671 s 266 | -------------------------------------------------------------------------------- /FastDFS和Nginx.md: -------------------------------------------------------------------------------- 1 | # FastDFS和Nginx 2 | by:Leo Tse 3 | 4 | ## Intro 5 | FastDFS,国产分布式文件系统,轻便而快捷,简单又灵活,兼顾了高可用、可扩展及高性能,它在国内不少公司得到部署使用。(参见[FastDFS概览](https://github.com/leotse90/blogs/blob/master/FastDFS%E6%A6%82%E8%A7%88.md))。但是,FastDFS自身并不提供http服务(注:以前的老版本是提供HTTP服务的,但是由于性能不佳,原作者不建议使用),因此我们需要依靠其他的WebService来提供Storage的HTTP访问服务。一般地,我们选择Nginx或者Apache作为我们的WebService提供者。 6 | 我们在这里主要介绍FastDFS与Nginx配合提供HTTP访问。 7 | 8 | ## FastDFS+Nginx 9 | 首先,你得有一个FastDFS集群,然后,你得部署好Nginx(FastDFS+Nginx集群部署请看[FastDFS安装使用说明](https://github.com/leotse90/blogs/blob/master/FastDFS%20%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md))。 10 | 11 | 一般情况下,Nginx只对外提供当前Storage的文件访问HTTP服务。因此,我们必须在每台Storage服务器上都部署Nginx以及ngx-http-fastdfs-module。我们可以在mod_fastdfs.conf配置文件中配置FastDFS nginx模块的一些主要参数。比如response_mode字段,这个我们用以指定Nginx的响应模式。 12 | 13 | **Nginx在这里充当什么角色?** 14 | 一般情况下,在FastDFS文件系统中,Nginx的角色是单一的HTTP访问服务,这也是Nginx最基本的功能。有些用户可能会觉得Nginx在这里有点大材小用,其实,本人也是这样认为的,并且期待FastDFS作者能在FastDFS中集成可靠的HTTP服务。 15 | 当然也有人说,FastDFS作为分布式文件系统,它的职责就是进行文件存储,使得文件的存储可靠而高效,它完全没有义务提供额外的HTTP服务。这就完全是仁者见仁了。 16 | 17 | **上文提到的Nginx响应模式,能否大概介绍下?** 18 | 我们首先简单说一下FastDFS的文件同步方式,它是主从式同步,也就是它有一个源Storage,Storage服务器下载完文件后就告诉客户端已经ok了,然后由源Storage负责将文件同步到其他的Storage。 19 | 在HTTP访问FastDFS上的文件时,如果由于同步延迟问题造成最新的文件A并没有同步到所有的Storage上,这时,如果用户访问还没来得及同步A文件的Storage,就会Not Found,Nginx模块这时就可以帮我们解决同步延时问题。 20 | 我们在mod_fastdfs.cong中可以看到response_mode响应模式有两种:redirect和proxy。如果当前的Storage找不到文件A,根据我们配置,会出现以下两种情况之一: 21 | 1)proxy:代理模式。工作原理如同反向代理的做法,而仅仅使用源storage地址作为代理proxy的host,其余部分保持不变; 22 | 2)redirect:重定向。该模式下要求源Storage配备公开访问的webserver、同样的端口、同样的path配置。因为是同意配置,这点我们一般能满足。此时服务端返回302响应码,url如下: 23 | `http:// {源storage地址} : {当前port} {当前url} {参数"redirect=1"}(标记已重定向过)` 24 | 25 | **当某台Storage上的Nginx模块挂了,Tracker能否感知并中断其提供HTTP访问?** 26 | 很遗憾,不能!! 27 | 是不是很抓狂!毕竟FastDFS和Nginx作为两个独立的产品。不过,不要沮丧,我们可以做统一的访问代理来解决,Storage 的nginx上再加一层nginx做反向代理。退一步,我们可以自己增加对Nginx服务的监控。 28 | 29 | 30 | ### 参考: 31 | [FastDFS使用经验分享](http://tech.uc.cn/?p=2579) 32 | [fastdfs-nginx扩展模块源码分析](http://www.cnblogs.com/littleatp/p/4361318.html) -------------------------------------------------------------------------------- /FastDFS概览.md: -------------------------------------------------------------------------------- 1 | # FastDFS概览 2 | _整理:LeoTse_ 3 | 4 | 5 | ## Definition 6 | FastDFS是C语言实现的、开源的、轻量级的**应用级分布式文件系统**,开发者为淘宝开发平台部资深架构师余庆。它提供了负载均衡、冗余备份机制,是一个可扩展、高可用、高性能的分布式文件系统。 7 | 8 | ## Architecture 9 | FastDFS一共由三部分组成: 10 | **TrackerServer**:负责负载均衡和调度。是整个FastDFS的中心,它将StorageServer的分组信息以及状态信息保存在内存中; 11 | **StorageServer**:存储文件和文件meta信息。直接使用操作系统的文件系统管理DFS上的文件; 12 | **Client**:使用者与请求发起方。通过专有接口,使用TCP/IP协议与跟踪器服务器或存储节点进行数据交互; 13 | 14 | ![FastDFS架构](https://github.com/leotse90/blogs/blob/master/images/fdfs01.gif) 15 | 16 | **上传文件**: 17 | ![FastDFS上传文件](https://github.com/leotse90/blogs/blob/master/images/fdfs02.gif) 18 | 19 | **下载文件**: 20 | ![FastDFS下载文件](https://github.com/leotse90/blogs/blob/master/images/fdfs03.gif) 21 | 22 | ## Applies 23 | 适合大中型网站使用,用于视频、图片、音频等中小资源文件的存储。(建议范围:4KB` 67 | `` 68 | ` fs.defaultFS` 69 | ` hdfs://Master:9000` 70 | `` 71 | `` 72 | ` hadoop.tmp.dir` 73 | ` file:/home/xiefeng/dependecies/hadoop-2.6.0/tmp` 74 | ` Abase for other temporary directories.` 75 | `` 76 | `` 77 | 78 | 4.修改hdfs-site.xml配置文件为: 79 | `` 80 | `` 81 | ` dfs.namenode.secondary.http-address` 82 | ` Master:50090` 83 | `` 84 | `` 85 | ` dfs.namenode.name.dir` 86 | ` file:/home/xiefeng/dependecies/hadoop-2.6.0/tmp/dfs/name` 87 | `` 88 | `` 89 | ` dfs.datanode.data.dir` 90 | ` file:/home/xiefeng/dependecies/hadoop-2.6.0/tmp/dfs/data` 91 | `` 92 | `` 93 | ` dfs.replication` 94 | ` 1` 95 | `` 96 | `` 97 | 98 | 5.复制mapred-site.xml.template得到mapred-site.xml文件: 99 | `cp mapred-site.xml.template mapred-site.xml` 100 | 修改mapred-site.xml配置文件为: 101 | `` 102 | `` 103 | ` mapreduce.framework.name` 104 | ` yarn` 105 | `` 106 | `` 107 | 108 | 6.修改yarn-site.xml配置文件为: 109 | `` 110 | `` 111 | `` 112 | ` yarn.resourcemanager.hostname` 113 | ` Master` 114 | `` 115 | `` 116 | ` yarn.nodemanager.aux-services` 117 | ` mapreduce_shuffle` 118 | `` 119 | `` 120 | 121 | 7.一般的,我们还需要修改一下hadoop-env.sh,将其JAVA_HOME修改为当前机器的JAVA_HOME: 122 | `# The java implementation to use.` 123 | `# export JAVA_HOME=${JAVA_HOME}` 124 | `export JAVA_HOME=/work/p/jdk/default` 125 | 126 | ## Slave部署 127 | 1.将 Master 上的 Hadoop 文件先打包然后复制到各个节点上: 128 | `sudo tar -zcf hadoop-2.6.0.tar.gz hadoop-2.6.0/` 129 | `scp hadoop-2.6.0.tar.gz Slave1:/home/xiefeng/dependecies` 130 | 131 | 2.解压到Slave1的安装目录: 132 | `sudo tar -zxf hadoop-2.6.0.tar.gz` 133 | `sudo chown -R xiefeng:xiefeng /home/xiefeng/dependecies/hadoop-2.6.0` 134 | 135 | ## 集群启动 136 | 我们回到Master,进入hadoop安装目录: 137 | `cd /home/xiefeng/dependecies/hadoop-2.6.0` 138 | 第一次执行,初始化: 139 | `bin/hdfs namenode -format` 140 | 141 | 启动dfs: 142 | `sbin/start-dfs.sh` 143 | 启动yarn: 144 | `sbin/start-yarn.sh` 145 | 146 | 分别在Master和Slave1输入jps,看看是否有以下输出,有则表明安装ok: 147 | `[xiefeng@Master hadoop-2.6.0]$ jps` 148 | `8498 NameNode` 149 | `8837 ResourceManager` 150 | `8680 SecondaryNameNode` 151 | `9817 Jps` 152 | 153 | `[xiefeng@Slave1 logs]$ jps` 154 | `20208 NodeManager` 155 | `20344 Jps` 156 | `20107 DataNode` 157 | 158 | 在浏览器输入:http://master:8088就可以查看集群All Applications信息;输入http://master:50070可以查看namenode信息。 159 | 160 | 161 | 停止dfs: 162 | `sbin/stop-dfs.sh` 163 | 停止yarn: 164 | `sbin/stop-yarn.sh` 165 | 166 | 167 | 另外,还可以使用以下脚本进行集群的启动和停止: 168 | `sbin/start-all.sh` 169 | `sbin/stop-all.sh` 170 | 171 | 至此,Hadoop集群搭建完毕!!! -------------------------------------------------------------------------------- /Java并发(三).md: -------------------------------------------------------------------------------- 1 | #Java并发(三) 2 | _by:leotse_ 3 | 4 | ## 返回值 5 | 我们知道,可以通过创建Runnable来创建任务,但是我们却不能通过这种方式返回值。Java因此提供了另一个接口来达到返回一个值的目的,这个接口就是Callable。Callable是一种具有类型参数的泛型,它的类型参数表示的是从call()方法中返回的值的类型,我们通过ExecutorService.submit()来调用。 6 | 7 | ```java 8 | package com.leotse.thread; 9 | 10 | import java.util.ArrayList; 11 | import java.util.concurrent.Callable; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.Future; 16 | 17 | public class CallableTask implements Callable{ 18 | 19 | private int id; 20 | public CallableTask(int id) { 21 | this.id = id; 22 | } 23 | 24 | @Override 25 | public String call() throws Exception { 26 | return "This is task " + id; 27 | } 28 | 29 | public static void main(String[] args) { 30 | ExecutorService eService = Executors.newCachedThreadPool(); 31 | ArrayList> results = new ArrayList>(); 32 | for (int i=0; i<5; i ++){ 33 | results.add(eService.submit(new CallableTask(i))); 34 | } 35 | 36 | for (Future r : results){ 37 | try { 38 | System.out.println(r.get()); 39 | } catch (InterruptedException | ExecutionException e) { 40 | e.printStackTrace(); 41 | } finally { 42 | eService.shutdown(); 43 | } 44 | } 45 | } 46 | 47 | } 48 | ``` 49 | 这个例子向我们展示了Callable的用法。Callable中的call()相当于Runnable中的run()方法。 50 | Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。可以通过get方法获取执行结果,get()方法会阻塞直到任务返回结果。 51 | 52 | 我们在这里对比一下Callable与Runnable的一些主要区别: 53 | >1.Callable接口提供了call()方法,Runnable接口提供了run()方法; 54 | 2.call()有返回值,run()方法没有返回值; 55 | 3.call()可以抛出受检查的异常,run()则不能抛出受检查的异常。 56 | 57 | ## 线程休眠 58 | 有时候,我们在程序中,需要线程等待一段时间,我们可以通过sleep()方法通知Thread暂停指定的时间。线程的休眠相对比较简单,还是来看一个例子: 59 | 60 | ```java 61 | public class SleepTask implements Runnable{ 62 | 63 | private int id; 64 | 65 | public SleepTask(int id) { 66 | this.id = id; 67 | } 68 | 69 | @Override 70 | public void run() { 71 | System.out.println("Task " + id + ", currentTimeMillis is " + System.currentTimeMillis()); 72 | } 73 | 74 | public static void main(String[] args) { 75 | for (int i=0; i<5; i++){ 76 | Thread t = new Thread(new SleepTask(i)); 77 | t.start(); 78 | try { 79 | Thread.sleep(2 * 1000); 80 | } catch (InterruptedException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | 86 | } 87 | ``` 88 | 得到的结果如下: 89 | ``` 90 | Task 0, currentTimeMillis is 1452526404372 91 | Task 1, currentTimeMillis is 1452526406373 92 | Task 2, currentTimeMillis is 1452526408373 93 | Task 3, currentTimeMillis is 1452526410374 94 | Task 4, currentTimeMillis is 1452526412375 95 | ``` 96 | 这样的时间比较难以辨认,你可以使用Date类打印标准化时间,以便更加直观看到sleep()的效果。 97 | 98 | ## 线程的优先级 99 | 我们在并发程序中启动了多个线程,但是这些线程并不一定在重要程度上一样,我们用线程的**优先级**来定义线程的重要程序。线程的优先级意味着不同级别的线程占用CPU的概率不同,优先级低的线程不会不执行。一般来说,程序中的优先级都以默认的优先级来运行,线程的优先级可以通过getPriority()方法来获取。但是一般不建议控制线程的优先级,因为调度器的行为并不可控。 100 | 101 | ## 线程让步 102 | 我们在上一篇[博客](http://leotse90.com/2016/01/07/Java-concurrency2/)中介绍Thread的时候提到了yield()方法,该方法的作用是告诉当前线程:嘿,你已经用了CPU一段时间了,是时候让出CPU了。但是这只是一种建议,而不是一种保证。一般来说,对于任何重要的控制或在调整应用时,都不能指望yield()方法能达成我们的目的。 103 | 104 | ## 后台线程 105 | 如果在应用中,我们需要进行网络请求,或者我们需要执行一些不太重要的事情,这些任务就可以放在后台线程中。后台线程(Daemon)并不是程序中不可或缺的部分,所以如果程序中所有的非后台线程都已经结束,那么程序就已经执行结束,同时会kill掉所有的后台线程。换言之,只要有任何非后台线程还在运行状态,程序就不会结束。 106 | 107 | 如果要指定一个线程为Daemon线程,只需调用setDaemon()方法即可,如: 108 | ```java 109 | thread.setDaemon(true); 110 | ``` 111 | 112 | **当程序中最后一个非后台线程终止运行,后台线程就会突然终止,因此一旦main()退出,JVM就会立即关闭所有的后台进程。** 113 | 114 | -------------------------------------------------------------------------------- /Java并发(二).md: -------------------------------------------------------------------------------- 1 | #Java并发(二) 2 | _by:leotse_ 3 | 4 | ## Java的线程机制 5 | 我们在上一篇[博客](http://leotse90.com/2016/01/06/Java并发(一)/)中,提到最理想的实现并发编程的方式就是使用进程,但是Java中我们使用线程进行多任务处理,原因在上一篇博客中也已经提及。 6 | 7 | 在Java中,每一个线程都会对应一个任务,这些任务构成了一个并发的程序。 在底层看来,每一个线程都觉得自己独占了CPU,虽然事实上它们只是在某一个时间切片上占有了CPU。CPU会轮流给每一个线程分配时间,这些细节一般不需要我们去了解。 8 | 9 | ### Runnable接口 10 | 我们知道,Java中的并发程序是由任务构成,那么Java中怎么声明一个任务呢?Java为我们提供了一个Runnable接口,凡是实现了Runnable接口的类皆可以称之为一个任务。下面的实例展示了Java中如何声明任务: 11 | ```java 12 | public class MyRunnable implements Runnable { 13 | @Override 14 | public void run() { 15 | System.out.println("I am a task"); 16 | } 17 | } 18 | ``` 19 | 我们看到这里有一个run()方法,我们将打算在这个任务中执行的业务逻辑都放在run()方法中。我们另行编写一个测试类用来执行这个Task。 20 | ```java 21 | public class MyRunnableDemo { 22 | public static void main(String[] args) { 23 | MyRunnable task = new MyRunnable(); 24 | task.run(); 25 | } 26 | } 27 | ``` 28 | 运行的结果如下: 29 | ``` 30 | I am a task 31 | ```` 32 | 这样看来,觉得Runnable和普通的Java类没有什么区别。这里只是在main()线程中执行了这个任务。如果我们希望这个任务和一个线程对应起来,应该怎么做呢?我们需要显式地将任务绑定到一个线程上。这就需要我们用到Thread类。 33 | 34 | ### Thread类 35 | 在Java中,一个Thread类的对象就是一个Java程序的执行线程。JVM允许一个程序有多个线程。每一个线程都有其优先级,高优先级的线程会先于低优先级的线程执行,想必这个很容易理解。每一个线程都可以被声明为一个后台线程。如果一个运行的线程中创建了一个新的Thread对象,那么这个新的Thread对象的初始优先级和创建它的线程一样,如果创建它的线程是一个后台线程,那么这个新的Thread对象也是一个后台线程。 36 | 37 | 当JVM启动时,通常会有一个非后台线程运行,这就是我们熟知的main线程,JVM会一直执行程序中的线程直到发生了下列情况: 38 | 1)Runtime类的exit()方法被调用,并且安全管理器允许这个退出操作执行; 39 | 2)所有的线程都是非后台进程,而且都已经停止运行;或者获取了run()方法调用的返回值;又或者run()抛出了一个异常。 40 | 41 | 我们在上面提到需要显示将一个任务和一个线程绑定在一起。常用的方法就是在创建一个Thread对象的时候将任务作为构造器的参数。我们将前面的MyRunnable类改造一下: 42 | ```java 43 | public class MyRunnable implements Runnable { 44 | private int threadCount = 0; 45 | 46 | public MyRunnable(int count) { 47 | this.threadCount = count; 48 | } 49 | 50 | @Override 51 | public void run() { 52 | System.out.println("Thread " + threadCount); 53 | } 54 | 55 | } 56 | ``` 57 | 然后,我们编写另一个类MyThreadDemo来创建任务并执行: 58 | ```java 59 | public class MyThreadDemo { 60 | public static void main(String[] args) { 61 | for (int i = 0; i < 5; i ++){ 62 | MyRunnable task = new MyRunnable(i); 63 | Thread t = new Thread(task); 64 | t.start(); 65 | } 66 | 67 | } 68 | } 69 | ``` 70 | 或者我们可以这样: 71 | ```java 72 | public class MyThreadDemo { 73 | public static void main(String[] args) { 74 | for (int i = 0; i < 5; i ++){ 75 | new Thread(new MyRunnable(i)).start(); 76 | } 77 | 78 | } 79 | } 80 | ``` 81 | 在这里,我们创建了5个任务,并把它们分别和一个Thread对象进行绑定,然后执行这些线程,得到结果如下: 82 | ``` 83 | Thread 0 84 | Thread 4 85 | Thread 3 86 | Thread 1 87 | Thread 2 88 | ``` 89 | 由于线程调度机制是非确定性的,所以每次执行的结果都可能会不同。 90 | 91 | 在Thread类中,我们需要关注以下一些主要的方法: 92 | **yield()**:该方法会建议调度器当前进程将要让出自己对处理器的使用,但是调度器可以选择是否需要接受这个建议。一般不建议使用这个方法,但是如果在debug或测试的时候还是可以使用的。 93 | 94 | **sleep()**:这个方法可以短暂中断当前执行线程。我们可以指定当前执行线程的休眠时间; 95 | 96 | **start()**:开始执行一个线程,当调用这个方法时,JVM会执行这个线程的run()方法。调用这个方法的结果就是两个线程并发执行:从这个start()方法调用返回的线程,执行run()方法的线程。需要注意的是,我们不能多次启动同一个线程,而且一个线程一旦执行结束就不能重启;阅读Thread的源码可知,start()本身是一个同步方法,且里面调用了本地方法start0()方法启动一个线程: 97 | ```java 98 | public synchronized void start() { 99 | /** 100 | * This method is not invoked for the main method thread or "system" 101 | * group threads created/set up by the VM. Any new functionality added 102 | * to this method in the future may have to also be added to the VM. 103 | * 104 | * A zero status value corresponds to state "NEW". 105 | */ 106 | if (threadStatus != 0) 107 | throw new IllegalThreadStateException(); 108 | 109 | /* Notify the group that this thread is about to be started 110 | * so that it can be added to the group's list of threads 111 | * and the group's unstarted count can be decremented. */ 112 | group.add(this); 113 | 114 | boolean started = false; 115 | try { 116 | start0(); 117 | started = true; 118 | } finally { 119 | try { 120 | if (!started) { 121 | group.threadStartFailed(this); 122 | } 123 | } catch (Throwable ignore) { 124 | /* do nothing. If start0 threw a Throwable then 125 | it will be passed up the call stack */ 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | **exit()**:系统调用这个方法以期让一个线程在其真正退出运行前进行资源的清理工作; 132 | 133 | **interrupt()**:中断当前线程。但是在实际编程中,我们在使用它时需要特别注意,你可以看看这篇[博客](http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html) 134 | 135 | 136 | 这些都是Java的线程机制中比较基础的,还有其他的诸如Executor以及优先级等其他概念,我们将在下一次介绍。 -------------------------------------------------------------------------------- /Java并发.md: -------------------------------------------------------------------------------- 1 | #Java并发(一) 2 | _by:leotse_ 3 | 4 | ## 并发,并发,并发 5 | 有顺序编程,就有并发编程。事实上,几乎我们所有的程序都可以通过顺序编程来完成,只是你必须忍受一些非常情况。 6 | 并发说起来很奇怪,它具有可论证的确定性,但是实际上却具有不可确定性。它的不可确定性是因为你没有办法预知在实际情况下会发生事情导致工作失败,而且你也没有办法通过编写代码进行完备的测试。 7 | 虽然实际应用中,并发很难做到完全掌握,但是这不能成为我们不作为的理由。 8 | >如果视而不见,你就会遭其反噬。 9 | 10 | 作为一名骄傲的程序员,并发编程是基础,也是必备知识。 11 | 12 | ## 为什么需要并发 13 | 这是一个老生常谈的问题,但是我们不能规避这个问题,要想深入理解一件事物,就必须弄清楚它的来龙去脉。 14 | 15 | 我们设定我们现在在做饭,我们需要做的事情list如下: 16 | 1)淘洗大米; 17 | 2)插电做饭; 18 | 3)洗好紫菜; 19 | 4)打鸡蛋; 20 | 5)洗净豌豆并剥开; 21 | 6)牛肉切丁; 22 | ... 23 | 24 | 是的,我们想来一顿米饭+紫菜蛋汤+豌豆牛肉粒的穷人版商务套餐。我们可以有很多种方法完成这顿午餐。其中有两种常见可行的方案: 25 | 1.完全按照顺序来,即1) -> 2) -> 3) -> 4) -> 5) -> 6),也就是每次都需要等待上一个步骤完成再进行下一步; 26 | 2.我们将时间打碎,在完成1) -> 2)之后,无需等待2)完成,即不用等到生米煮成熟饭,就开始着手洗紫菜、打鸡蛋,然后在煲汤的同时,开始准备豌豆和牛肉。 27 | 28 | 很明显,除了死心眼,大家都会选择第2种方案或者其他类似的方案。第1种方案就是我们所说的顺序编程,而第二种方案就是并发编程。 29 | 30 | >通常来说,并发是提高运行在单核上的程序的性能。 31 | 32 | 为什么是提供单核上程序的性能呢?如果只有一个核即处理器,我们来回切换工作,反而会增加上下文切换的损耗,怎么会是提高性能呢?如果和上述的例子结合起来就不难发现,如果有些事情需要等待,但是这个等待的时间间隙我们可以做一些并不依赖于正在处理的事情的其他业务,比如做菜并不依赖煮饭(除非你用同一口锅),洗豌豆并不依赖于煲汤。这样我们的时间就被充分利用起来了。用行话来说,就是**阻塞**。如果程序中的某个任务因为该程序控制范围外的某些条件而导致不能继续执行,那么我们就说这个任务或者线程阻塞了。如果没有引入并发,那么线程将一直阻塞,直至这些条件发生改变。 33 | 34 | >某种程度上讲,没有阻塞,就没有并发。(单核) 35 | 36 | 最简单直接的方式就是使用进程。 37 | 多任务操作系统会周期性将CPU从一个进程切换到另一个进程,虽然会有切换带来的耗时,但是对用户来说基本可以当作同时在运行多个进程。而且操作系统会帮我们将这些进程隔离开来,每个进程都有自己的一亩三分地,使得它们变得相互独立。这对并发编程来说,似乎非常理想,不会出现争夺公共资源的情况,每个任务都在自己的地址空间中做着自己的事情,进程们也根本不需要相互通信。但是进程通常都有数量限制以及开销上的限制,因此在实际编程中并不常见直接将进程作为并发的实现方式。 38 | 39 | >函数型语言被设计为可以将并发任务彼此隔离,其中每个函数的调用都不会产生任何副作用,因此可以当作独立的任务来驱动。 40 | 41 | 在Java中,我们常常用到的是在顺序编程的基础上使用比进程更小粒度的线程来实现并发。编写Java并发程序的一个最基本的困难是如何协调不同线程驱动的任务之间对一些公共资源(如IO、内存)的使用。 42 | 43 | 使用并发的另一个目的是改进代码设计。我们前面提到,我们可以使用顺序编程完成几乎所有程序,但是这会使得我们的程序变得复杂,使用并发可以使得我们的程序结构更加清晰简化。 44 | 45 | 我们假设我们的程序由很多的部件的组成,每一个部件都可以独立运作,那么理想情况就是每一个部件都对应一个线程,但是实际上我们的多线程系统一般都会限制线程的数量,而且这个数字不会太大,因此我们往往我们不能达到理想情况下的每一个部件都对应一个线程。在Java中,线程机制是抢占式的,也就是说,系统可能周期性中断线程,然后切换到另一个线程,从而让每个线程都有运行的时间片。我们可以通过协作多线程来解决这个问题。在协作式系统中,每个任务都是可控的,即可以通过编程人员的控制自动放弃控制,相对于抢占式线程机制,协作式线程机制一方面上下文切换的开销小,另一方面同时执行的线程数量理论上没有限制。 46 | 47 | 当然,并发是有代价的,那就是我们需要额外花费上下文切换的代价,但是这个代价不算高昂,特别是和它所带来的收益进行比较。 48 | 49 | -------------------------------------------------------------------------------- /Linux上Python版本升级.md: -------------------------------------------------------------------------------- 1 | # Linux上Python版本升级 2 | __整理:leotse__ 3 | 4 | ### 目标 5 | 将linux上的python版本升级到较高的版本。 6 | 7 | ### 环境 8 | 我们假定服务器操作系统为RedHat(不同OS大同小异),python版本由2.6升级到2.7。在升级前,你需要确定是否会影响当前的工程和项目的运行。 9 | 10 | ### 升级 11 | 1.下载python2.7安装包: 12 | `wget http://python.org/ftp/python/2.7.6/Python-2.7.6.tgz` 13 | 14 | 2.安装python2.7: 15 | `tar -zxvf Python-2.7.6.tgz` 16 | `cd Python-2.7.6` 17 | `./configure --prefix=/usr/local/python27` 18 | `make` 19 | `sudo make install` 20 | 21 | 3.覆盖原来的python: 22 | `sudo mv /usr/bin/python /usr/bin/python_old` 23 | `sudo ln -s /usr/local/python27/bin/python /usr/bin/` 24 | 25 | 4.验证Python安装,输入`python`,查看版本。 26 | 27 | ### 其他 28 | 升级python版本的一个后遗症就是yum无法正常工作,只需要做一点点修改即可: 29 | `sudo vi /usr/bin/yum` 30 | 将第一行 31 | `#!/usr/bin/python` 32 | 改成原来的版本,在这里我们改成: 33 | `#!/usr/bin/python2.6` 34 | 35 | 再次输入yum即可发现已经ok了! -------------------------------------------------------------------------------- /MooseFS概览.md: -------------------------------------------------------------------------------- 1 | # MooseFS概览 2 | 3 | ## Intro 4 | [MooseFS](http://www.moosefs.org/),是一种容错的网络分布式文件系统。它提供了FUSE接口的客户端,挂载后和读写本地磁盘上的文件无异,是替代NFS的理想选择。 5 | 用户访问MooseFs系统中不同机器上的数据没有差异,对用户来说,只有一个源。 6 | 7 | ## Architecture 8 | MooseFS主要由四部分组成: 9 | ### Master Server(元数据服务器) 10 | 管理整个MooseFS系统的单点,保存了MooseFS中每个文件的元数据,包括文件的大小、属性、文件位置等等;我们的元数据信息同时保存在Master的内存和磁盘里。 11 | ### Metalogger Server(元数据日志服务器) 12 | 存储元数据服务器的变更日志,用以恢复Master Server,它也会周期性下载MasterServer的元数据文件,服务器数量不定。我们也可以在Master Server宕掉后使用Metalogger Server作为我们的MasterServer; 13 | ### Chunk Server(数据存储服务器) 14 | 用于存储系统中的数据。如果我们指定数据需要备份(我们一般都会这么做),那么ChunkServer之间就会通过算法完成数据的备份工作; 15 | ### Client(客户端) 16 | 所有通过mfsmount进程和Master进行交互的机器,都可以叫做MooseFS的客户端。对客户端来说,所有的ChunkServer就像一台普通的NFS服务器一样在为它提供强大的数据存取服务; 17 | 18 | 笔者从MooseFS的官网上kiang了两张图,分别是MooseFS读数据和写数据的,大家闭上眼睛,用心感受下: 19 | 20 | ![MooseFS Read Process](http://www.moosefs.org/tl_files/mfs_folder/read862.png) 21 | ![MooseFS Write Process](http://www.moosefs.org/tl_files/mfs_folder/write862.png) 22 | 23 | 我们从图上可以很清晰地看到整个读写过程。 24 | 25 | ## Uses 26 | 1.大规模高并发的数据存储与访问(大文件、小文件皆可); 27 | 2.大规模的数据处理; 28 | 29 | ## Features 30 | ### Advantages 31 | 1)简单易用。安装、部署以及配置都相对简单容易; 32 | 2)高可靠性:MooseFS采用数据备份的方式确保我们的数据安全可靠; 33 | 3)可伸缩性:我们可以在不停服务的情况下,新增或者删除MooseFS中的服务器; 34 | 4)支持POSIX访问,支持FUSE; 35 | 5)可移植性:适用于任何实现FUSE的系统,包括但不限于Linux、FreeBSD、OpenSolaris以及MacOS X; 36 | 6)MooseFS提供了快照功能,可以对整个文件甚至在正在写入的文件创建文件的快照; 37 | 7)提供类似JVM的GC机制; 38 | 8)提供web GUI监控接口; 39 | 9)随机读写的效率较高,海量小文件读写效率高; 40 | 10)发展比较成熟,文档全面; 41 | 42 | ### Disadvantages 43 | 1)MooseFS客户端程序使用FUSE编写加载MooseFS磁盘的命令;因此我们需要确保我们系统内置或者安装了FUSE; 44 | 2)存在单点故障。MooseFS只有一个Metadata Server,因此MDS的性能就会成为MooseFS性能的瓶颈; 45 | 3)故障恢复需手动恢复; 46 | 4)MooseFS的Master是单线程的程序,并不能发挥多核CPU的优势,由于大部分的处理逻辑都是内存操作,因此并不会存在太大的问题,但一旦涉及到磁盘I/O就有可能导致阻塞,严重的话整个集群会瘫痪掉,因此不建议把Master放在虚拟机中。[MooseFS之虚拟机惹的祸](http://tech.uc.cn/?tag=moosefs) 47 | 48 | ## Conclusion 49 | 当前,MooseFS在国际国内都拥有了大量的用户,正是由于这些用户基础,也推动了MooseFS继续发展。 50 | 虽然MooseFS仍然存在单点故障这样的瓶颈,但是它的简单易用以及高可靠性,仍然让它成为我们DFS方案的不错的选择。 51 | 52 | ## Others 53 | MooseFS官网解答了我们一些常见的问题:[FAQ](http://www.moosefs.org/moosefs-faq.html) 54 | 55 | **NFS**:Network File System。一种用于分散式系统的协议。通过网络让不同的机器、不同的操作系统分享彼此个别的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在Unix系统间实现磁盘文件共享的一种方法。 -------------------------------------------------------------------------------- /MySQL的主从配置.md: -------------------------------------------------------------------------------- 1 | # MySQL的主从配置 2 | 整理:LeoTse 3 | 4 | ## 准备工作 5 | 我们假设有两台机器,操作系统为linux,其中一台作为Master,另一台作为Slave: 6 | Master:10.0.0.1 7 | Slave:10.0.0.2 8 | 这两台机器都已经安装了MySQL数据库。 9 | 10 | ## 主从配置 11 | ### Master配置 12 | 1.创建备份账户 13 | 首先,我们在Master上专门为Slave访问Master进行数据备份建立一个账号,用户名为backup,密码为“backup_mysql”。我们在Master上执行以下SQL语句: 14 | `GRANT REPLICATION SLAVE, RELOAD, SUPER ON *.* TO backup@'10.0.0.2' IDENTIFIED BY 'backup_mysql';` 15 | 如果我们有多个Slave,就执行上面的SQL多次,只需将backup@10.0.0.2中的IP改成其他Slave的IP。 16 | 这个账号可以说是Slave访问Master的通行证。 17 | 18 | 2.Master配置 19 | 20 | 我们开始配置Master,修改/etc/mysql/my.cnf文件,找到[mysqld]段,增加以下字段: 21 | `log-bin = mysql-bin` 22 | `server-id = 1` 23 | `binlog-do-db = test_db` 24 | `expire-logs-days= 7` 25 | 下面来解释一下这些字段: 26 | `log-bin = mysql-bin`表示启用二进制日志,用以记录Master中数据的更新日志; 27 | `server-id = 1`唯一标识Master的ID,一般建议使用IP地址的最后一段; 28 | `binlog-do-db = test_db`选择需要备份的数据库,**如果需要备份全部,可以不增加这行**; 29 | `expire-logs-days= 7`指定只保存7天的二进制日志,防止占用磁盘空间; 30 | 31 | 配置好后,我们需要重启MySQL服务。 32 | `/etc/init.d/mysql restart` 33 | 34 | 我们需要看看是否已经配置成功了,我们可以执行以下SQL语句: 35 | SHOW MASTER STATUS\G; 36 | 如果你看到类似下面的信息,则说明Master基本ok,如果数据库中已经有数据,这个信息将会用到: 37 | ` File: mysql-bin.000002` 38 | ` Position: 107` 39 | ` Binlog_Do_DB: test_db` 40 | `Binlog_Ignore_DB: ` 41 | 42 | 3.主数据库已经有数据的Master配置 43 | 44 | 如果Master的数据库已经有数据了,你可以遵照下面的步骤: 45 | 1)进入MySQL终端,查看当前正在使用的库有哪些: 46 | `SHOW databases;` 47 | 48 | 2)停止当前MySQL的所有写操作,并查看当前数据库的状态: 49 | `FLUSH TABLES WITH READ LOCK;` 50 | 51 | 52 | 3)打开另一个终端(也可以直接exit离开当前MySQL,但是为了避免重复进入MySQL输密码,建议打开一个新的终端),将要备份的数据库导出成sql文件,并将其传到Slave机器上: 53 | `mysqldump -uroot -p****** test_db > test_db.sql` 54 | `scp test_db.sql root@10.0.0.2:/root/` 55 | 56 | 4)切换到MySQL终端,解锁: 57 | `UNLOCK TABLES;` 58 | 59 | 至此,Master配置结束。 60 | 61 | ### Slave配置 62 | Slave的配置也很简单。同样修改/etc/mysql/my.cnf文件,找到[mysqld]段,增加以下字段: 63 | `log-bin = mysql-bin` 64 | `server-id = 2` 65 | `binlog-do-db = test_db` 66 | `relay-log = mysql-relay-bin` 67 | `log-slave-updates = 1` 68 | 我们解释一下2个在Master中没有出现的字段: 69 | `relay-log = mysql-relay-bin`配置中继日志(主要是在MySQL服务器的主从架构中的Slave上用到的,当Slave想要和Master进行数据的同步时,从服务器将Master的二进制日志文件拷贝到自己的主机上放在中继日志中,然后调用SQL线程按照拷中继日志文件中的二进制日志文件执行以便就可达到数据的同步。) 70 | `log-slave-updates = 1`表示slave将复制事件写进自己的二进制日志; 71 | 72 | 重启MySQL服务: 73 | `/etc/init.d/mysql restart` 74 | 75 | 如果主数据库已经有数据,需要将新建database并将数据导入: 76 | `CREATE DATABASE test_db;` 77 | 切换到终端(linux终端): 78 | `mysql -uroot -p****** test_db < test_db.sql` 79 | 80 | 接下来就是让Slave连接Master,在Slave上执行以下SQL语句: 81 | `CHANGE MASTER TO MASTER_HOST='10.0.0.1',MASTER_USER='backup', MASTER_PASSWORD='backup_mysql', MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=0;` 82 | (如果主数据库有数据,需要将MASTER_LOG_FILE和MASTER_LOG_POS按照SHOW MASTER STATUS\G;的结果修改,如MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=107) 83 | `START SLAVE\G;` 84 | 85 | 86 | 最后,我们通过执行`SHOW SLAVE STATUS\G;`来查看是否配置成功,如果出现: 87 | `*************************** 1. row ***************************` 88 | ` Slave_IO_State: ` 89 | ` Master_Host: 10.0.0.1` 90 | ` Master_User: backup` 91 | ` Master_Port: 3306` 92 | ` Connect_Retry: 60` 93 | ` Master_Log_File: mysql-bin.000001` 94 | ` Read_Master_Log_Pos: 4` 95 | ` Relay_Log_File: mysqld-relay-bin.000001` 96 | ` Relay_Log_Pos: 4` 97 | ` Relay_Master_Log_File: mysql-bin.000001` 98 | ` Slave_IO_Running: No` 99 | ` Slave_SQL_Running: No` 100 | ` Replicate_Do_DB: ` 101 | ` Replicate_Ignore_DB: ` 102 | ` Replicate_Do_Table: ` 103 | ` Replicate_Ignore_Table: ` 104 | ` Replicate_Wild_Do_Table: ` 105 | ` Replicate_Wild_Ignore_Table: ` 106 | ` Last_Errno: 0` 107 | ` Last_Error: ` 108 | ` Skip_Counter: 0` 109 | ` Exec_Master_Log_Pos: 4` 110 | ` Relay_Log_Space: 107` 111 | ` Until_Condition: None` 112 | ` Until_Log_File: ` 113 | ` Until_Log_Pos: 0` 114 | ` Master_SSL_Allowed: No` 115 | ` Master_SSL_CA_File: ` 116 | ` Master_SSL_CA_Path: ` 117 | ` Master_SSL_Cert: ` 118 | ` Master_SSL_Cipher: ` 119 | ` Master_SSL_Key: ` 120 | ` Seconds_Behind_Master: NULL` 121 | `Master_SSL_Verify_Server_Cert: No` 122 | ` Last_IO_Errno: 0` 123 | ` Last_IO_Error: ` 124 | ` Last_SQL_Errno: 0` 125 | ` Last_SQL_Error: ` 126 | ` Replicate_Ignore_Server_Ids: ` 127 | ` Master_Server_Id: 0` 128 | 129 | 那么恭喜你,配置成功。 130 | 131 | 至此,整个MySQL主从集群已经搭建好了。 -------------------------------------------------------------------------------- /Python任务调度队列Celery.md: -------------------------------------------------------------------------------- 1 | # Python任务调度队列Celery 2 | _BY:leotse_ 3 | 4 | ## Introduction 5 | 在Python的使用过程中,我们常常会遇到执行一些多进程任务,或者一系列长时间的后台任务。比如,多进程下载视频并上传到某一个文件系统中。这时候,我们可以使用任务调度队列帮我们进行任务的分发与管理。 6 | 7 | Celery就是这样一个任务队列,易于使用,入门简单。Celery常常需要第三方作为发送和接收消息的中间层,一般我们用到的有RabbitMQ、Redis、MongoDB等等,次等的选择也可以是数据库。 8 | 9 | 一般推荐使用RabbitMQ,但是我们这里用到Redis,因为Redis安装的时候依赖少,而且性能稳定,但是Redis也有缺点,那就是断电的时候会丢失数据。我们在这里,就以Redis作为Celery的第三方中间层。 10 | 11 | ## Installation 12 | 我们这里使用Celery+redis套餐进行任务的调度。 13 | 14 | Celery的安装非常简单,在linux系统下直接执行: 15 | `sudo pip install Celery` 16 | `sudo pip install celery-with-redis` 17 | 如果上述安装失败,可以尝试: 18 | `sudo easy_install Celery` 19 | 20 | 我们来验证一下Celery是否安装成功,进入python shell,输入: 21 | `from celery import Celery` 22 | 如果没有报错,则说明安装成功。 23 | 24 | 接着我们安装redis: 25 | `sudo apt-get install redis-server` 26 | 安装完成后,redis会自动启动,我们也来验证一下redis是否安装成功: 27 | `ps -aux|grep redis` 28 | 如果看到以下输出,则说明安装ok: 29 | `redis 942 0.2 0.0 73852 1832 ? Ssl Apr13 302:26 /usr/bin/redis-server /etc/redis/redis.conf` 30 | 31 | 它们的安装都比较简单。接下来我们看如何使用Celery进行任务调度。 32 | 33 | ## Usage 34 | 我们应该都知道生产者-消费者模型,在使用Celery的时候,我们也需要一个生产者和一个消费者,生产者负责往队列里写入待处理的数据,消费者负责将数据从队列中取出并进行处理。我们在这里将redis作为存储这种“数据”的地方。 35 | 36 | 我们来看这样一个示例,我们假设要下载一批视频v1,v2,v3....,这批视频列表存在另一个文件系统中,我们假设通过get_video_list方法来获取这批视频列表,另一方面,我们可以通过download_video_worker(video)来下载视频。 37 | 38 | 那么,生产者的伪代码如下: 39 |
  
40 | import download_video_worker
41 | video_list = get_video_list()
42 | for video in video_list: 
43 |     download_video_worker.apply_async([video])
44 | 
45 | 46 | 消费者的伪代码如下: 47 |
 
48 | download_app=Celery("download_videos", broker="redis://localhost:6379/0")
49 | @download_app.task`  
50 | def download_video_worker(video):
51 |     download_video_to_local(video)
52 | 
53 | 54 | 接着我们运行Celery: 55 | `celery -A download_video_worker worker --loglevel=info` 56 | 57 | 这样,当我们每次往队列中放入video信息时,celery就会执行download_video_woker中的逻辑处理video的下载过程。 58 | 59 | 60 | ### 推荐阅读 61 | [Homepage - Celery: Distributed Task Queue](http://www.celeryproject.org/) 62 | [CELERY - BEST PRACTICES](https://denibertovic.com/posts/celery-best-practices/) -------------------------------------------------------------------------------- /Python多进程使用.md: -------------------------------------------------------------------------------- 1 | # Python多进程使用 2 | 3 | ## Intro 4 | 我们知道,由于[GIL](https://wiki.python.org/moin/GlobalInterpreterLock)的关系,Python中多线程并不被看好。因此,Python我们常常使用模块subprocess模块和多进程multiprocessing模块来实现并发。而subprocess因为是调用外部程序而且只是通过管道进行文本交流,因此我们建议在Python并发编程中,尽量使用multiprocessing。 5 | 6 | multiprocessing模块和threading模块很像,该模块同时提供了本地和远程并发,你也不用担心GIL产生的副作用。并且multiprocessing可以在Unix和Windows下使用(区别于为shell而生的subprocess)。 7 | 8 | ## multiprocessing使用 9 | 在multiprocessing模块中,我们使用multiprocessing.Process()来创建一个新的进程对象。 10 | 11 | 一般情况下,我们需要在创建Process对象时指定进程执行的函数,以及该函数的参数: 12 | `process = multiprocessing.Process(target=worker, args=(param1, param2)` 13 | 该对象的主要方法有: 14 | **start()**:启动进程;每个进程最多只能调用一次; 15 | **run()**:进程的执行逻辑在run()里。如果Process对象没有指定target,就会默认执行Process的run()方法; 16 | **join([timeout])**:阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程; 17 | **is_alive()**:返回该进程是否存活; 18 | **terminate()**:终结一个进程。当调用这个函数的时候,运行逻辑中的exit和finally代码段将不会执行。而且这个进程的子进程不会被终结而是成为孤儿进程; 19 | 20 | 下面我们给出一段多进程使用的示例代码: 21 | `import multiprocessing` 22 | `def controller():` 23 | ` processes = []` 24 | ` for i in range(5):` 25 | ` process = multiprocessing.Process(target=worker, args=[i])` 26 | ` processes.append(process)` 27 | ` for process in processes:` 28 | ` process.start()` 29 | ` for process in processes:` 30 | ` process.join()` 31 | `def worker(param):` 32 | ` print param` 33 | `if __name__ == '__main__':` 34 | ` controller()` 35 | 36 | 我们可以得到如下的输出: 37 | `1` 38 | `0` 39 | `2` 40 | `4` 41 | `3` 42 | 43 | ## 子进程通信 44 | multiprocessing支持两种类型的进程通信手段,分别是Queue和Pipe。 45 | ### Queue 46 | Queue是一种多线程优先队列。它允许多个进程读和写,我们通过`mutiprocessing.Queue(maxsize)`创建一个Queue,maxsize表示队列中可以存放对象的最大数量。它的一些主要方法有: 47 | **get()**:删除并返回队列中的一个元素; 48 | **put()**: 添加元素到队列; 49 | **qsize()** : 返回队列中元素的个数; 50 | **empty()**: 队列为空返回True否则返回False; 51 | **full()**: 队列已满返回True,负责返回False; 52 | 53 | 在下面的示例里,我们用Queue实现获取多进程执行时的输出: 54 | 55 | `import multiprocessing` 56 | `def controller():` 57 | ` processes = []` 58 | ` result_queue = multiprocessing.Queue()` 59 | ` for i in range(5):` 60 | ` process = multiprocessing.Process(target=worker, args=[i, result_queue])` 61 | ` processes.append(process)` 62 | ` for process in processes:` 63 | ` process.start()` 64 | ` for process in processes:` 65 | ` process.join()` 66 | ` while not result_queue.empty():` 67 | ` print result_queue.get()` 68 | `def worker(param, result_queue):` 69 | ` result_queue.put(param + 100)` 70 | `if __name__ == '__main__':` 71 | ` controller()` 72 | 73 | 执行这段代码,输出为: 74 | `101` 75 | `102` 76 | `103` 77 | `100` 78 | `104` 79 | 80 | ### Pipe 81 | Pipe可以是单向,也可以是双向。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。它主要有send()和recv()两种方法,顾名思义,分别是发送消息和接受消息。 82 | 我们同样来看一段示例代码: 83 | `import multiprocessing` 84 | `def controller():` 85 | ` processes = []` 86 | ` parent_conn, child_conn = multiprocessing.Pipe()` 87 | ` for i in range(5):` 88 | ` process = multiprocessing.Process(target=worker, args=[i, child_conn])` 89 | ` processes.append(process)` 90 | ` for process in processes:` 91 | ` process.start()` 92 | ` print parent_conn.recv()` 93 | ` for process in processes:` 94 | ` process.join()` 95 | `def worker(param, child_conn):` 96 | ` child_conn.send(param + 100)` 97 | `if __name__ == '__main__':` 98 | ` controller()` 99 | 执行这段代码,输出为: 100 | `100` 101 | `101` 102 | `102` 103 | `103` 104 | `104` 105 | 106 | 107 | ## Q&A 108 | 为什么要先依次调用start再调用join,而不是start完了就调用join呢? 109 | 答:假设我们有两个进程p1,p2,如果我们在p1执行后先join()然后再p2.start(),我们就会发现是先执行完p1,再执行主线程,最后才开始p2。这是因为join是用来阻塞当前线程的,p1.start()之后,p1就提示主线程,需要等待p1结束才向下执行,那主线程就乖乖的等着啦,自然没有执行p2.start()。 110 | -------------------------------------------------------------------------------- /SSH免密码登录设置.md: -------------------------------------------------------------------------------- 1 | # SSH免密码登录设置 2 | __整理:LeoTse__ 3 | 4 | ## SSH免密码登录设置 5 | 准备工作: 6 | 两台机器S1,S2。IP分别为IP1,IP2,用户名为user。 7 | 我们想从S1免密码登录S2机器。以下为SSH设置的步骤: 8 | 9 | Step1:生产key。在S1上输入: 10 | `ssh-keygen -t rsa` 11 | 然后一直enter,直到结束。 12 | 13 | Step2: 我们进入到~/.ssh目录,然后将生成的key通过scp复制到S2的.ssh目录(我们首先要确保目录存在): 14 | `cd ~/.ssh` 15 | `scp ~/.ssh/id_rsa.pub user@IP2:/home/user/.ssh/IP2` 16 | 截至目前,S1上的配置已经完成。 17 | 18 | Step3:接下来我们登录S2机器。进入.ssh目录,确保authorized_keys文件存在: 19 | `cd ~/.ssh` 20 | `cat IP2 >> authorized_keys` 21 | SSH免登录已经设置完毕,如果是普通用户,继续往下看。 22 | 23 | ## 普通用户 24 | 对于普通用户authorized_keys的权限必须限定为600,否则普通用户无法实现无密钥访问,而ROOT用户按照默认即可实现无密码访问: 25 | `sudo chmod 600 authorized_keys` 26 | 27 | ## 免密码登录验证 28 | 我们在S1上直接ssh连接S2机器,看能否免密码登录: 29 | `ssh IP2` 30 | 如果可以直接登录上去,则说明已经设置成功。 31 | 32 | 另,如果要设置S1和S2互相免密码登录,以上步骤在S1和S2都执行一次就好了! -------------------------------------------------------------------------------- /Scala之函数.md: -------------------------------------------------------------------------------- 1 | # Scala之函数 2 | _by:leotse_ 3 | 4 | 今天我们来介绍Scala中的函数的一些基本概念以及用法。 5 | 6 | ## 函数是一等公民 7 | 在Scala中,函数是一等公民。 8 | 首先来解释一下什么是_一等公民_,它指的是在程序中可无限使用的对象。 9 | 那么”函数是Scala的一等公民“这句话怎么理解呢?一言蔽之,就是函数能作为实参,还能作为返回值,它能作为一个普通变量进行使用。详细一点来说,Scala中作为一等公民的主要表现有: 10 | -可以传递和赋值 11 | -嵌套函数 12 | -匿名函数 13 | -高阶函数 14 | -闭包 15 | 16 | ## 函数 17 | 我们来正式介绍Scala中的函数,先看函数的主要构成,和其他编程语言一样,函数主要由:**函数名**、**参数**以及**函数体**三部分组成。下面我们会从这三个方面介绍Scala中的函数。下面是一个函数的示例: 18 |

19 | def foo(x: Int) = println(x)
20 | 
21 | 22 | ### 函数名 23 | 关于函数名,没有太多需要介绍的,和其他编程语言一样,支持数字、字母以及下划线,$符号,同样的,数字不能放在函数名第一位。一般建议,函数命名采用驼峰风格(小驼峰)。 24 | 25 | ### 参数 26 | 接下来是参数,在Scala中,所有的参数必须指定类型。在某些情况下,函数的参数可以有默认值,我们称之为默认参数: 27 |

28 | scala> def func(x: Int, y: Int = 3) = x + y
29 | func: (x: Int, y: Int)Int
30 | 
31 | scala> func(2)
32 | res14: Int = 5
33 | 
34 | scala> func(2, 4)
35 | res15: Int = 6
36 | 
37 | scala> func(1, 3, 4)
38 | :16: error: too many arguments for method func: (x: Int, y: Int)Int
39 |        func(1, 3, 4)
40 | 
41 | scala> func(y=2, x=6)
42 | res17: Int = 8
43 | 
44 | 我们需要注意,如果我们传入的参数数量和函数的参数数量不一致(不能多于函数的参数数量),函数会从左到右依次将值传给参数。但是当你在传参时指定了参数名(带名参数),那么参数的顺序就不再重要。 45 | 46 | 如果我们的参数长度未知,这时我们可以使用**变长参数**。变长参数实际上是一个类型为Seq的参数(Seq是一个有先后顺序的值的序列,比如数组或者列表)。但是你不能直接将值的序列当成参数传给函数(特指参数为变长参数的函数)。我们看下面的代码便能明白变长参数的使用: 47 |

48 | scala> def toStr(args: String *) = args.mkString(",")
49 | toStr: (args: String*)String
50 | 
51 | scala> toStr("leotse", "yolovon")
52 | res20: String = leotse,yolovon
53 | 
54 | scala> toStr("One", "Two", "Three")
55 | res21: String = One,Two,Three
56 | 
57 | 58 | ### 函数体 59 | 在上面的示例中,我们已经多次见过Scala中的函数,他们的函数体如果只有一个表达式或者语句,那么直接跟在函数名后即可(有等号=),如果函数体包含多条语句,那么我们可以使用我们以前介绍过的块表达式。 60 | 61 | ### 返回值 62 | 我们知道,块表达式的值为{...}中的最后一个表达式的值,那么在Scala的函数中,如果没有指定函数的返回值,块表达式的返回值就是块表达式的最后一个语句的值。**一般情况下,我们都不需要显式指定Scala函数的返回值。**当然我们也可以直接指定函数的返回值类型,如下: 63 |

64 | def func(v1: Int, v2: String): Double = {...}
65 | 
66 | 这里再一次说明了函数是第一等公民,因为我们直接将函数func指定了一个类型Double。 67 | 68 | ## 过程 69 | 在前面介绍函数的时候,我们看到Scala函数和其他编程语言函数定义不同的地方,那就是函数名后面加了一个等号=。那么这个=是不是必须的呢?答案是不。如果函数体在块表达式中而且返回值为Unit类型,那么就可以不要这里的=,我们称这样的函数为**过程**。不过,有人建议我们一般情况下都加上=号,并且指定函数的返回值类型,哪怕是Unit类型: 70 |

71 | scala> def func1 (a: Int, b: Int) {
72 |      |     println(a + b)
73 |      | }
74 | func1: (a: Int, b: Int)Unit
75 | 
76 | scala> func1(2, 5)
77 | 7
78 | 
79 | scala> def func2 (a: Int, b: Int): Unit = {
80 |      |     println(a + b)
81 |      | }
82 | func2: (a: Int, b: Int)Unit
83 | 
84 | scala> func2(4, 5)
85 | 9
86 | 
87 | 88 | Scala的函数基础知识暂且告一段落,但是只有多动手试试才能对Scala的函数有更深层次的理解。 89 | -------------------------------------------------------------------------------- /Scala入门.md: -------------------------------------------------------------------------------- 1 | # Scala入门 2 | _by:leotse_ 3 | 4 | ### 为什么是Scala 5 | 有人问Java之父James Gosling“除了Java语言以外,您现在还使用JVM平台上的哪种编程语言?”,他毫不犹豫的回答“Scala”。 6 | 7 | `During a meeting in the Community Corner (java.net booth) with James Gosling, a participant asked an interesting question:"Which Programming Language would you use now on top of JVM, except Java?". The answer was surprisingly fast and very clear: - Scala.` 8 | 9 | 可见,Scala是一门很受待见的编程语言,另一位大牛Horstmann指出,Scala试图将以下三组对立的思想融合在一门编程语言中: 10 | `-函数式编程 VS 面向对象编程` 11 | `-富有表达力的语法 VS 静态类型` 12 | `-高级的语言特性 VS 与Java高度集成` 13 | 这样看来,Scala就是想集各家所长,打造一种平衡的和谐,这看起来像是编程语言世界的乌邦托。 14 | Scala和很多在Java基础上发展的语言一样,需要基于JVM,这一点使得Scala拥有强大的Java所拥有的特性,比如跨平台。但是成也JVM,败也JVM,JVM启动较慢的问题,需要编译等等这些也成为了Scala的瓶颈。只要还立足于JVM,Scala就一直会受到JVM的限制。 15 | 16 | 但是这些都无碍于Scala本身成为一门成功的语言,现在很多公司都已经逐渐投靠Scala阵营,比如twitter,特别是当使用Scala编写的Spark兴起之后,Scala更是成为很多公司大数据编程语言的首选。 17 | 18 | ### Scala & REPL 19 | 首先,我们需要认识一下REPL,全称为Read-Eval-Print-Loop,亦即读取-求值-打印-循环。如果是第一次接触这个概念,会觉得有点奇怪,我们可以将REPL称为**交互式解释器**。一般的,我们通过REPL可以快速学习和验证一门语言的特性(前提是这门语言支持REPL)。 20 | 但是并不是所有的语言都支持REPL,常见的编程语言支持REPL的有Ruby、Python、Lua,使用很广的Java、C++、C#、PHP以及JS等并不支持原生的REPL。当然,我们既然在这里讨论REPL,Scala是肯定支持REPL的。我们在学习Scala的时候可以直接在REPL coding。实际上,我们敲进去的代码首先被编译,然后JVM会执行编译后的字节码,然后返回执行的结果。 21 | 22 | 我们看一个示例: 23 | `scala> "Hello, Scala"` 24 | `res0: String = Hello, Scala` 25 | 从这个示例中,我们可以看到输入的是文本“Hello, Scala”,下面一行是输出,这里的res0是REPL为这个文本起的名字,后面的String表明这个文本是字符串类型。我们可以直接使用res0这个变量名去调用这个字符串文本。 26 | 27 | ### 变量声明:val和var 28 | 在上一小节里面,我们已经见识到了Scala的变量声明,但是,我们一般情况下都希望将变量命名的权力牢牢控制在自己手上,因此我们可以使用以下方式声明变量。 29 | -var:variable的缩写。用法如下: 30 | `var variable_name: [variable_type] = variable_value` 31 | 变量的类型是可选的,因为如果没有指定变量的类型,Scala的编译器会根据变量的值推断出它的类型。我们看下面的示例: 32 | 33 | `scala> var x: Int = 1 // 指定x的类型为Int` 34 | `x: Int = 1` 35 | `scala> x = 2 // 改变变量x的值` 36 | `x: Int = 2` 37 | `scala> var y = 3 // 没有指定y的类型` 38 | `y: Int = 3` 39 | 40 | -val:这里说变量其实不太准确,因为val声明的是不可变的常量。使用方法和var类似,只是不能修改它的值。在实际开发中,除非我们可以预知需要改变一个值的内容,否则我们一般用val声明。示例如下: 41 | 42 | `scala> val z = 3` 43 | `z: Int = 3` 44 | `scala> z = 2 // 不可以修改一个常量的值` 45 | `:11: error: reassignment to val` 46 | 47 | 一般的,我们不需要声明类型,除非必须。而且我们注意到Scala和Java等语言不一样,变量的类型声明在变量名后面。 48 | 49 | 另外,我们在REPL中的示例中可以看到,解释器为我们没有命名的变量定义res0这个名字,凡是解释器定义的变量,如res0、res1等等,都是常量,不可修改: 50 | `scala> res0` 51 | `res1: String = Hello, Scala` 52 | `scala> res0 = 1990` 53 | `:11: error: reassignment to val` 54 | 55 | 在Scala中,有8种类型,它们分别为Boolean、Byte、Char、Short、Int、Long、Float以及Double。在这里,我们和Java那样称它们为八大基本类型,因为Scala中并不会刻意去区分基本类型和引用类型,因为他们都是类。 56 | 57 | 58 | ### 参考资料 59 | 《快学Scala》Cay S. Horstmann 电子工业出版社 60 | 《深入理解Scala》 Joshua D. Suereth 人民邮电出版社 61 | [Scala官方API](http://www.scala-lang.org/api/current/#package) 62 | [Scala官方Tutorials](http://docs.scala-lang.org/tutorials/?_ga=1.213857492.1110750532.1444722905) 63 | [Scala中文社区](http://www.scalachina.com) 64 | 网上前辈们学习交流的技术博客与论坛 -------------------------------------------------------------------------------- /Scala学习之数组.md: -------------------------------------------------------------------------------- 1 | # Scala学习之数组 2 | 3 | _by:leotse_ 4 | 5 | ## 概述 6 | 我们从这里开始接触Scala中的数据结构,我们首先来看最基础却非常有用的数据结构——数组。 7 | 这里会涉及两种数组:定长数组以及可变数组,顾名思义,定长数组就是数组在声明的时候就固定了大小,而可变数组可以根据我们使用的实际情况进行调整数组的长度。在Scala中,定长数组为Array,而可变数组为ArrayBuffer。 8 | 9 | ## 定长数组 10 | 我们先来看定长数组。声明一个定长数组有以下两种方式: 11 | **1.声明数组的类型与长度** 12 | ```scala 13 | scala> val arr1 = new Array[Int](4) 14 | arr1: Array[Int] = Array(0, 0, 0, 0) 15 | ``` 16 | 可以看到我们用`new`关键字来声明一个Array对象,用来存储4个Int类型的数值,而这4个Int类型的数值初始值都为0。想必这点大家都很容易理解,类似的,String类型的初始值为null,Double类型的初始值为0.0。 17 | 18 | **2.声明数组时直接提供初始值** 19 | ```scala 20 | scala> val arr2 = Array(2, 3, 5, 6) 21 | arr2: Array[Int] = Array(2, 3, 5, 6) 22 | 23 | scala> val arr3 = Array(2, "hello") 24 | arr3: Array[Any] = Array(2, hello) 25 | ``` 26 | 我们在这种声明方式中,无需使用`new`关键字,Scala会根据初始值的类型来推断出数组的类型,当数组存在多种类型时,此时数组的类型为这多种类型的最近公共父类。 27 | 28 | 另外,我们在这里还可以注意到,我们在声明数组的时候使用了关键字`val`,即数组为常量,但是实际上,这里的数组元素都是可以改变值的。这时候,我们可以将`val`定义的数组理解为:我们用`val`来指定arr1这个容器只能用来存储4个Int类型的数字,多了不行,其他类型的也不行,但是这4个Int类型的数字究竟是什么,声明方表示并不关心。 29 | 30 | 我们用`(location)`来访问数组中的元素。并且可以直接赋值给数组中的元素: 31 | ```scala 32 | scala> arr2(0) 33 | res12: Int = 2 34 | 35 | scala> arr2(0) = 0 36 | 37 | scala> arr2 38 | res14: Array[Int] = Array(0, 3, 5, 6) 39 | ``` 40 | 41 | ## 可变数组 42 | 有时候,我们在一开始声明的时候只知道我们需要数组这种容器,但是具体要放多少数据我们暂且不知道,这时候我们需要用到可变数组(又称为数组缓冲)ArrayBuffer。ArrayBuffer的声明和Array并没有太大的差异: 43 | ```scala 44 | scala> import scala.collection.mutable.ArrayBuffer 45 | import scala.collection.mutable.ArrayBuffer 46 | 47 | scala> val arrbuf1 = new ArrayBuffer[Int] 48 | arrbuf1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() 49 | 50 | scala> val arrbuf2 = ArrayBuffer(1, 2, 3) 51 | arrbuf2: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3) 52 | 53 | scala> val arrbuf3 = ArrayBuffer(1, "hello") 54 | arrbuf3: scala.collection.mutable.ArrayBuffer[Any] = ArrayBuffer(1, hello) 55 | ``` 56 | 和数组Array一样,可变数组也使用`(location)`来访问数组的元素;并且,ArrayBuffer使用`+=`来添加元素,使用`++=`来实现对其他集合的扩展。 57 | ```scala 58 | scala> arrbuf2(0) 59 | res18: Int = 1 60 | 61 | scala> arrbuf2(0) = 0 62 | 63 | scala> arrbuf2 64 | res20: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(0, 2, 3) 65 | 66 | scala> arrbuf2 += 5 67 | res21: arrbuf2.type = ArrayBuffer(0, 2, 3, 5) 68 | 69 | scala> arrbuf2 ++= Array(8, 9, 10) 70 | res22: arrbuf2.type = ArrayBuffer(0, 2, 3, 5, 8, 9, 10) 71 | ``` 72 | 其他的一些有关Array以及ArrayBuffer的操作,可以去ScalaDoc上查阅,你可以点击[Array](http://www.scala-lang.org/api/current/#scala.Array)或者[ArrayBuffer](http://www.scala-lang.org/api/current/#scala.collection.mutable.ArrayBuffer)。 73 | 74 | ## 总结 75 | 我们到此了解了数组的基本声明和使用方式,关于数组的具体使用需要根据实际需求进行选择。数组的有关知识也不是这几十行文字和代码所能表达得清楚的,以后的博文中会继续介绍Scala中其他的数据结构以及数组的其他的特性。 76 | -------------------------------------------------------------------------------- /Scala控制结构.md: -------------------------------------------------------------------------------- 1 | # Scala控制结构 2 | _by:leotse_ 3 | 4 | ### 块表达式 5 | 首先,我们需要了解一下块表达式。我们这样定义块表达式:凡是用{}包含的语句都同属一个块表达式,在Scala中,块表达式的值等于块表达式最后一条语句的值(在Scala中,每一个表达式都有一个值)。我们看下面的示例: 6 |

 7 | scala> var v = {var a = 2; var b = 4; a + b;}
 8 | v: Int = 6
 9 | scala> val v1 = {var s1 = "hello"; var i = 20;}
10 | v1: Unit = ()
11 | 
12 | 13 | 在这里,我们可以总结以下两点:1.块表达式的值由{...}中的最后一条语句的值决定,那么当我们在定义一个函数的时候,一般不需要定义返回值,因为最后一条语句的值就是这个块表达式的值;2.侧面反映了Scala中赋值语句的值为Unit类型(注:Scala中的Unit类型相当于Java中的void,其只有一个值())。 14 | 15 | ### 条件表达式 16 | 条件表达式的作用就不赘述,我们直接来介绍Scala中的条件表达式。它的一般形式如下: 17 | `if (expression) block1 else block2` 18 | 值得注意的是,Scala虽然和Java一样,判断条件expression需要为Boolean类型,但是和其他一些语言不一样的是,在Scala中,只有真正的Boolean类型:true或者false能作为判断的依据,而正整数、非空字符串等不能表示true,同理,整数0、空字符串等亦不能表示false。看下面的代码: 19 |

20 | scala> if (1) 1 else -1
21 | :11: error: type mismatch;
22 |  found   : Int(1)
23 |  required: Boolean
24 |        if (1) 1 else -1
25 |            ^
26 | 
27 | scala> if ("") 1 else -1
28 | :11: error: type mismatch;
29 |  found   : String("")
30 |  required: Boolean
31 |        if ("") 1 else -1
32 |            ^
33 | 
34 | 35 | 在Scala中,我们可以将条件表达式的值直接赋给一个变量: 36 |

37 | scala> val r = if ("hello" > "world") "hello" else "world"
38 | r: String = world
39 | 
40 | 41 | 这种用法熟悉Python的coder可能会比较容易接受,Scala这种设计可能会使得代码在阅读起来并没有那么友善,但是确实可以使得我们的代码更简洁。 42 | 43 | 另外,Scala并不支持switch语句,因此如果我们有模式匹配上的需求,我们需要用到Scala提供的更强大的模式匹配机制,感兴趣的朋友可以先行了解一下。 44 | 45 | ### 循环表达式 46 | 和Java一样,Scala提供了两种循环表达式:while以及for。 47 | 通常,函数式语言会避开while循环,因为while实现的大多数操作都可以使用递归来完成。Scala中while循环和Java类似: 48 |

49 | scala> var x = 3
50 | x: Int = 3
51 | 
52 | scala> while (x > 0) {
53 |      |     println(x)
54 |      |     x -= 1
55 |      | }
56 | 3
57 | 2
58 | 1
59 | 
60 | 61 | Scala中的for循环却和Java中的不太一样,在形式上有较大的改变,有一种观点认为:“Scala 的for实际上是一条管道,它在将元素传递给循环主体之前处理元素组成的集合,每次一个。此管道其中的一部分负责将更多的元素添加到管道中(生成器),一部分负责编辑管道中的元素(过滤器),还有一些负责处理中间的操作(比如记录)。”: 62 |

63 | scala> for (i <- 1 to 4) print(i + " ")
64 | 1 2 3 4 
65 | 
66 | 这段代码等同于: 67 |

68 | scala> for (i <- 1 to 4) {
69 |      |     print(i + " ")
70 |      | }
71 | 1 2 3 4 
72 | 
73 | 74 | 这里我们看到`for (i <- 1 to 4)`这种形式的表达式,这条表达式的意思是我们从1到4进行一个遍历,然后将这些值依次赋予变量i。更普遍的for定义是`for (v <- expression)`,也就是将expression每次生成的值赋给变量v。我们看到**在for循环的变量v之前并没有var或者val,此时,v的类型是集合的元素类型。** 75 | 76 | 这种循环便利非常方便,比如我们要遍历一个字符串: 77 |

78 | scala> for (c <- "hello, scala") print(c + "-")
79 | h-e-l-l-o-,- -s-c-a-l-a-
80 | 
81 | 82 | 我们还可以这样遍历其他诸如数组、集合之类。 83 | 84 | for循环表达式还有更加先进的使用方法,我们称之为_for推导式_。还是先看一段代码: 85 |

86 | scala> for (i <- 1 to 2; j <- 1 until 3) println((i, j))
87 | (1,1)
88 | (1,2)
89 | (2,1)
90 | (2,2)
91 | 
92 | scala> for (i <- 1 to 2; j <- 1 until 3 if i != j) println((i, j))
93 | (1,2)
94 | (2,1)
95 | 
96 | 我们需要解释一下这段代码,首先额外解释一下to和until的区别,x to y表示从x到y的所有整数,包含y;而x until y表示从x到y的所有整数不包含y(y > x)。我们在这段代码看到,for循环的循环控制语句中有多条表达式,并且以分号分割,每个表达式我们称之为**生成器**,生成器的作用是在每一次循环的时候生成变量的值,控制变量j的赋值语句后跟着`if i != j`,这是一个**守卫**,守卫用以过滤符合条件的控制变量。 97 | 98 | 99 | 到这里为止,我们已经对Scala的控制结构有一个大致的了解。在实际编码中,我们会看到更灵活、更强大的控制结构使用方法,但是万变不离其宗,它们都是基于这些基本的控制结构形式。 -------------------------------------------------------------------------------- /fdfs_nginx_test.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import os 4 | import sys 5 | import time 6 | 7 | # constants 8 | FDFS_HOST = u"192.168.9.230" 9 | FDFS_IP = 9096 10 | 11 | def download_file_by_nginx(file_id, download_file_path): 12 | src_url = u"http://{host}:{port}/{file_id}".format(host=FDFS_HOST, port=FDFS_IP, file_id=file_id) 13 | file_name = file_id.split("/")[-1] 14 | 15 | start_time = time.time() 16 | ret = os.system(u"wget {src_url}".format(src_url=src_url)) 17 | end_time = time.time() 18 | 19 | duration = end_time - start_time 20 | 21 | return ret == 0, duration 22 | 23 | if __name__ == "__main__": 24 | file_id = sys.argv[1] 25 | download_file_path = u"/home/leotse/test/" 26 | 27 | failed_count = 0 28 | duration_list = [] 29 | for i in range(50): 30 | ret, duration = download_file_by_nginx(file_id, download_file_path) 31 | duration_list.append(duration) 32 | if not ret: 33 | failed_count += 1 34 | 35 | print "===========================result================================" 36 | print "download file", str(len(duration_list)), "times" 37 | print "failed", str(failed_count), "times" 38 | print "rate of failed download:", str(1.0 * failed_count / len(duration_list) * 100), "%" 39 | print "average download duration:", str(sum(duration_list) / len(duration_list)) 40 | print "=================================================================" 41 | -------------------------------------------------------------------------------- /fdfs_test.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import time 4 | 5 | from fdfs_client.client import * 6 | from md5mgr import mkmd5fromfile 7 | 8 | # constants 9 | CLIENT_CONF = u"/etc/fdfs/client.conf" 10 | 11 | 12 | def upload_file_to_fdfs(file_path): 13 | client = Fdfs_client(CLIENT_CONF) 14 | file_md5 = mkmd5fromfile(file_path) 15 | 16 | start_time = time.time() 17 | rt_upload = client.upload_by_filename(file_path) 18 | end_time = time.time() 19 | duration = end_time - start_time 20 | 21 | file_id = rt_upload["Remote file_id"].replace("\\", "/") 22 | file_size = rt_upload["Uploaded size"] 23 | 24 | return file_md5, duration, file_id, file_size 25 | 26 | def download_file_from_fdfs(file_id, download_file_path): 27 | client = Fdfs_client(CLIENT_CONF) 28 | 29 | start_time = time.time() 30 | rt_download = client.download_to_file(download_file_path, file_id) 31 | end_time = time.time() 32 | duration = end_time - start_time 33 | 34 | file_md5 = mkmd5fromfile(download_file_path) 35 | 36 | return file_md5, duration 37 | 38 | def delete_file_from_fdfs(file_id): 39 | client = Fdfs_client(CLIENT_CONF) 40 | 41 | start_time = time.time() 42 | rt_delete = client.delete_file(file_id) 43 | end_time = time.time() 44 | duration = end_time - start_time 45 | 46 | return rt_delete, duration 47 | 48 | if __name__ == "__main__": 49 | file_path = u"/home/xiefeng/test/test.mp4" 50 | download_file_path = u"/home/xiefeng/test/download_test.mp4" 51 | 52 | failed_cnt = 0 53 | upload_duration_list = [] 54 | download_duration_list = [] 55 | 56 | for i in range(1000): 57 | upoload_file_md5, upload_duration, file_id, file_size = upload_file_to_fdfs(file_path) 58 | download_file_md5, download_duration = download_file_from_fdfs(file_id, download_file_path) 59 | rt_delete, delete_duration = delete_file_from_fdfs(file_id) 60 | upload_duration_list.append(upload_duration) 61 | download_duration_list.append(download_duration) 62 | 63 | if upoload_file_md5 != download_file_md5: 64 | failed_cnt += 1 65 | 66 | aver_upload_duration = sum(upload_duration_list) / len(upload_duration_list) 67 | aver_download_duration = sum(download_duration_list) / len(download_duration_list) 68 | test_count = len(upload_duration_list) 69 | 70 | print "==============================result==================================" 71 | print "file size:", file_size 72 | print "upload files", str(test_count), "times" 73 | print "failed upload", str(failed_cnt), "times" 74 | print "rate of failed upload:", str(1.0 * failed_cnt / test_count * 100), "%" 75 | print "upload average duration:", str(aver_upload_duration), "s" 76 | print "download average duration:", str(aver_download_duration), "s" 77 | print "======================================================================" 78 | -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/images/.DS_Store -------------------------------------------------------------------------------- /images/fdfs01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/images/fdfs01.gif -------------------------------------------------------------------------------- /images/fdfs02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/images/fdfs02.gif -------------------------------------------------------------------------------- /images/fdfs03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/images/fdfs03.gif -------------------------------------------------------------------------------- /images/leotse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/images/leotse.jpg -------------------------------------------------------------------------------- /mobile-advertising-attribution.md: -------------------------------------------------------------------------------- 1 | # 移动广告主要转化归因方式 2 | 3 | _by:leotse_ 4 | 5 | ## 概述 6 | 在移动互联网广告圈子里,无论你是Publisher,或者Ad Network,还是Ad Tracker,你都有必要了解移动广告的转化归因方式。 7 | 8 | 了解移动归因方式,可以帮助你窥探移动广告从投放到转化中很重要的一环,那就是如何甄别广告转化的归属。我们知道主要的移动互联网广告种类有CPM、CPC、CPA,这些不同的广告类型在转化归因上大同小异,所以在介绍具体的归因方式的时候再行说明。 9 | 10 | 首先,一般有下面四种归因方式: 11 | >1.Google Install Referrer(Google 安装引荐网址) 12 | 2.Identifier Matching(标识符匹配) 13 | 3.Fingerprint Matching(指纹匹配) 14 | 4.Open URL with Click ID(带clickid的打开链接) 15 | 16 | 17 | Open URL with ClickID主要用于点击归因,并不适合于安装归因,因此不在我们的讨论范围内。接下来,我们详细讨论这几种归因方式的原理以及适用范围。 18 | 19 | ## Google Install Referrer 20 | Google Install Referrer,我们在[移动广告之GooglePlay推广流程](http://leotse90.com/2016/03/21/Mobile-Ad-GooglePlay/)有过介绍,这里简单说明一下,用户点击Google Play的推广URL安装的应用,GP会在该应用启动的时候发送一个Install Referrer广播告知其推广的来源,被推广的应用会上报该referrer到广告主后台,从而确定该转化的来源。 21 | 22 | Google Install Referrer可以用来唯一标识广告商或者广告合作伙伴,这就是它可以用来确定转化的原因,有兴趣的话,你可以使用[Google Play URL Builder](https://developers.google.com/analytics/devguides/collection/android/v3/campaigns#google-play-url-builder)生成一个带referrer的推广URL,你可以了解一下其中每一个字段的用途(前提是你能翻墙)。下面是一个referrer的示例: 23 | `https://play.google.com/store/apps/details?id=com.test.appname&referrer=af_tranid=com.test.appname_324a78c1-c345-5cba-7a76-bc88-aacb2318a101&pid=clicksmob_int&c=US-Android-01&click_id=cA_12487526aa_278973188a866872ac981007bc8_zl&af_siteid=1111&advertising_id=yourgaid&app-id=com.test.appname` 24 | 25 | Google Install Referrer主要适用于Google Play应用商店推广的APP,它不适用于Android上除GP以外的应用商店(这点不难理解),而且它也仅仅在APP安装的时候才有意义。 26 | 27 | ## Identifier Matching 28 | Android平台上常见的广告标识符有GAID(Google广告ID,可以唯一标识一台Android设备,可重置);GAID是一种由Google Play服务提供的唯一、用户特定可重置广告ID,它会以类似于38400000-8cf0-11bd-b23e-10b96e40000d的通用唯一标识符 (UUID) 格式公开用于访问字符串形式用户广告ID的API。如: 29 | `https://12345.api-01.com/serve?action=click&publisher_id=100&site_id=3000&google_aid=38400000-8cf0-11bd-b23e-10b96e40000d` 30 | 尽管我们最依赖广告标识符进行归因,但在某些情况下无法使用广告标识符。例如,不支持广告标识符以及用户从非 Google Android 应用商店下载的旧有版本。如果广告标识符不可用,我们还可以依赖以下设备标识符进行归因:Android ID(适用于Android设备的ANDROID ID是一个64位数字(十六进制字符串形式),在设备首次启动时随机生成,通常在设备生命周期内保持不变)、设备 ID(适用于Android设备的设备 ID 是一种采用小写格式值的唯一设备ID)、MAC地址(联网设备的MAC地址是一个网络地址,用于对设备的无线网络适配器进行唯一标识,采用以冒号分隔的大写形式,例如,“AA:BB:CC:DD:EE:FF”)。 31 | 32 | iOS平台上的标识符主要就是IFA(可唯一标识一台iOS设备,但是也可以通过刷机重置)。Apple从iOS6开始引入了广告商标识符 (IFA),它为应用提供用于为广告服务的标识符的访问权,并提供用以指示用户是否启用了Limit Ad Tracking(限制广告追踪)功能的标志。IFA 值是每台设备唯一的字母数字字符串,使用带连字符的大写形式。如:“AAAAAAAAA-BBBB-CCCC-1111-222222220000”。尽管我们最依赖广告标识符进行归因,但在某些情况下无法使用广告标识符。例如,不支持广告标识符的旧有版本。如果广告标识符不可用,我们依赖开放UDID进行归因,“开放 UDID”是iOS系统 UIDevice 类遭弃用的唯一标识符属性(又名 UDID)的简易替代者。它是一个长 40 个字符(20 个字节)的十六进制值。 33 | 34 | 标识符匹配的归因流程如下: 35 | 1.用户点击应用上的广告。一方面,用户设备会跳转到应用市场,另一方面,设备会向Tracker发送带有包含用户设备标识符的Ad Click URL; 36 | 2.用户点击安装广告上的应用后,应用会上报该用户设备标识符给Tracker; 37 | 3.Tracker对比点击时上传的用户设备标识符以及用户安装后上传的设备标识符,如果一致,就可以确定这个转化的来源。 38 | 39 | Identifier Matching不仅适用于转化的归因,同样适用于点击事件、浏览事件等,它是适用范围最广的转化归因方式。 40 | 41 | ## Fingerprint Matching 42 | 要理解Fingerprint Matching转化归因,首先你得理解什么是Fingerprint,我们可以把Fingerprint理解为可以唯一标识一个用户的基本信息(尽管很多时候并不能真正唯一映射到一个确定的用户),它可以是用户的IP地址,也可以是用户设备的可用的HTTP头,这些基本信息可以用于归因分析中创建用户的点击事件的指纹。 43 | 44 | **当一个用户安装了一个移动应用,植入在该应用的归因分析SDK就会收集该设备的指纹信息,然后上报到归因分析平台,平台会生成一个设备指纹并有序地在匹配的指纹中查找,归因分析平台会将转化算在匹配的所有指纹中最后一次带来点击上。** 45 | 46 | 默认情况下,匹配设备指纹的归因时间窗口是24小时,因此为了找到匹配,归因分析只会考虑24小时内发生的点击事件。当然也有一些广告网络和广告伙伴、媒体声称他们能将归因窗口扩展至48小时甚至72小时,但是实际上一旦超过24小时指纹匹配的统计的精度就会下降。 47 | 48 | 当一个用户点击一个广告跳转URL,归因分析SDK会设置其HTTP cookie,用于区分唯一点击数与Gross点击(理解为毛点击数),该cookie会在24小时后失效。 49 | 如果设备上没有设置cookie,归因分析SDK就会认定这个设备是一个从未记录在案的新设备,并记录这次点击为唯一点击和Gross点击; 50 | 如果设备上已经设置了cookie,归因分析SDK就会认为这是一个已经存在的用户(24小时内),于是只记录这次点击为一次Gross点击,而并不会记录其为唯一点击。 51 | 52 | 归因分析中会根据Gross点击和唯一点击去判断一个用户是新增还是已经存在。如果归因分析平台认定该用户是过去24小时已经存在的用户,那么平台就不会重新创建该用户的指纹,相反的,平台将会更新指纹的更新时间为最后一次点击的时间戳。因此,用户点击多次并不会创建额外的设备指纹,从而可以降低错误匹配数。 53 | 54 | > 关于Gross点击与唯一点击,举一个简单的例子: 55 | 用户A点击:广告1、广告2、广告1 56 | 用户B点击:广告1、广告3 57 | 那么: 58 | 广告1:2次唯一点击以及3次Gross点击; 59 | 广告2:1次唯一点击以及1次Gross点击; 60 | 广告3:1次唯一点击以及1次Gross点击。 61 | 62 | 指纹匹配在移动应用后台中异步运行,此方法可强制打开浏览器,不会妨碍用户体验。由于使用另外的几种归因可提供1:1的准确性,而指纹匹配取决于统计概率(大约86%的统计概率),因此另外两种归因方法始终胜过指纹匹配。 63 | 64 | -------------------------------------------------------------------------------- /resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/.DS_Store -------------------------------------------------------------------------------- /resources/FastDFS 下载效率的优化.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/FastDFS 下载效率的优化.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 01 Intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 01 Intro.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 02 Marketplace Design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 02 Marketplace Design.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 03 Sponsored Search 1 2011.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 03 Sponsored Search 1 2011.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 04 Sponsored Search 2 2011.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 04 Sponsored Search 2 2011.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 05 Display Advertising Part 1 final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 05 Display Advertising Part 1 final.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 06 Display Advertising Part 2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 06 Display Advertising Part 2.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 07 Targeting 2011.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 07 Targeting 2011.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 08 RecSys 2011Final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 08 RecSys 2011Final.pdf -------------------------------------------------------------------------------- /resources/Introduction-to-Computational-Advertising/Lecture 09 Mobile & Social.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/Introduction-to-Computational-Advertising/Lecture 09 Mobile & Social.pdf -------------------------------------------------------------------------------- /resources/The-Beginners-Guide-to-Mobile-Advertising-Analytics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/The-Beginners-Guide-to-Mobile-Advertising-Analytics.pdf -------------------------------------------------------------------------------- /resources/中国DSP行业发展研究报告(2014).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国DSP行业发展研究报告(2014).pdf -------------------------------------------------------------------------------- /resources/中国上班人群洞察报告(2015年).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国上班人群洞察报告(2015年).pdf -------------------------------------------------------------------------------- /resources/中国网络广告用户行为研究报告简版.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国网络广告用户行为研究报告简版.pdf -------------------------------------------------------------------------------- /resources/中国网络广告行业年度监测报告简版(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国网络广告行业年度监测报告简版(2015).pdf -------------------------------------------------------------------------------- /resources/分布式文件系统FastDFS架构剖析.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/分布式文件系统FastDFS架构剖析.pptx -------------------------------------------------------------------------------- /resources/分布式文件系统FastDFS架构剖析及配置优化.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/分布式文件系统FastDFS架构剖析及配置优化.pdf -------------------------------------------------------------------------------- /resources/微信公众号媒体价值研究报告(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/微信公众号媒体价值研究报告(2015).pdf -------------------------------------------------------------------------------- /resources/艾瑞投资月报(2015年8月).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/艾瑞投资月报(2015年8月).pdf -------------------------------------------------------------------------------- /resources/2015年中国微商市场研究报告.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/2015年中国微商市场研究报告.pdf -------------------------------------------------------------------------------- /resources/中国互联网民大健康研究报告—— 健康服务需求篇(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国互联网民大健康研究报告—— 健康服务需求篇(2015).pdf -------------------------------------------------------------------------------- /resources/中国在线医疗行业研究报(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国在线医疗行业研究报(2015).pdf -------------------------------------------------------------------------------- /resources/中国金融广告主网络营销策略研究 报告简版(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/中国金融广告主网络营销策略研究 报告简版(2015).pdf -------------------------------------------------------------------------------- /resources/艾瑞解读中国医疗健康产业 互联网化六大趋势(2015).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leotse90/blogs/afd22552f10d64de6986a8fd2c8f616d9a5fd1a6/resources/艾瑞解读中国医疗健康产业 互联网化六大趋势(2015).pdf -------------------------------------------------------------------------------- /【译】Python中yield关键字用法.md: -------------------------------------------------------------------------------- 1 | # 【译】Python中yield关键字用法 2 | 译:LeoTse 3 | 4 | 本文译自stackoverflow [What does the yield keyword do in Python?](http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python) 5 | 6 | ## 引子 7 | 这一切始于一位童鞋在Stack Overflow上问的问题: 8 | 9 | Python中的yield关键字是用来干嘛的?它都干了些什么? 10 | 11 | 例如,我试图理解下面这段代码: 12 | 13 | ` 14 | def node._get_child_candidates(self, distance, min_dist, max_dist):` 15 | ` if self._leftchild and distance - max_dist < self._median:` 16 | ` yield self._leftchild` 17 | ` if self._rightchild and distance + max_dist >= self._median:` 18 | ` yield self._rightchild ` 19 | 20 | 下面是调用代码: 21 | 22 | ``` python 23 | result, candidates = list(), [self]` 24 | `while candidates:` 25 | ` node = candidates.pop()` 26 | ` distance = node._get_dist(obj)` 27 | ` if distance <= max_dist and distance >= min_dist:` 28 | ` result.extend(node._values)` 29 | ` candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))` 30 | `return result`   31 | ``` 32 | 33 | 当_get_child_candidates函数被调用时发生了什么?返回了一个list?返回了单个元素?还是它再次被调用了?后面的调用什么时候停止呢? 34 | 35 | 下面是这个问题的精华回复: 36 | 37 | ## yield关键字用法 38 | 39 | 要想理解yield关键字,首先你得理解什么是**生成器**。而在这之前我们先得了解迭代。 40 | 41 | ### 迭代 42 | 43 | 当你创建了一个list,你就可以逐个元素去读取这个list,这就叫做迭代: 44 | 45 | `>>> mylist = [1, 2, 3]` 46 | `>>> for i in mylist:` 47 | `... print(i)` 48 | `1` 49 | `2` 50 | `3` 51 | 52 | mylist就是可迭代的。当你使用list表达式,你就创建了一个list,亦即创建了一个迭代器: 53 | 54 | `>>> mylist = [x*x for x in range(3)]` 55 | `>>> for i in mylist:` 56 | `... print(i)` 57 | `0` 58 | `1` 59 | `4` 60 | 61 | Python中所有你可以用到"for...in..."表达式的地方都是可迭代的:list,string,files等等。迭代器的优点是你可以读你所需,但是你需要在内存中存储(这些迭代器中的)所有的值,而当我们拥有大量数据时我们并不希望这样做。 62 | 63 | ### 生成器 64 | 65 | 生成器亦即迭代器,但是生成器只能迭代一次。因为它并不会将所有的数据存在内存中,而是实时生成我们所需的数据: 66 | 67 | `>>> mygenerator = (x*x for x in range(3))` 68 | `>>> for i in mygenerator:` 69 | `... print(i)` 70 | `0` 71 | `1` 72 | `4` 73 | 74 | 这个和你用元组()取代列表[]道理一样。但是,你不能指望`for i in mygenerator`运行第二次,因为生成器只能被使用一次:它(首先)计算得到0,然后会遗忘0并计算得出1,最终得到4(并遗忘1),以此类推。 75 | 76 | ### yield关键字 77 | 78 | Yield关键字和return的用法一样,只是(用到yield的)函数将会返回一个生成器。 79 | 80 | `>>> def createGenerator():` 81 | `... mylist = range(3)` 82 | `... for i in mylist:` 83 | `... yield i*i` 84 | `...` 85 | `>>> mygenerator = createGenerator() # create a generator` 86 | `>>> print(mygenerator) # mygenerator is an object!` 87 | `` 88 | `>>> for i in mygenerator:` 89 | `... print(i)` 90 | `0` 91 | `1` 92 | `4` 93 | 94 | 这个例子不是太好。但是当你发现你的函数要返回大量只需读一次的数据时你会体会到它(yield)的好处。 95 | 96 | 要想掌握yield关键字,你必须知道:当你调用这个函数时,函数体里的代码并没有执行,它只是**返回一个生成器对象**,这看起来有点难以理解。 97 | 98 | 接着,每次你用到这个生成器的时候你的代码都会运行一次。 99 | 100 | 现在最难的部分来了: 101 | 102 | 当你第一次调用函数返回的生成器时,它将会运行函数中的代码直到执行到yield,然后它将返回这个循环产生的第一个值。接下来,每一次调用都将执行函数中的这个循环一次,并返回下一个值,直到没有值可以返回为止。 103 | 104 | 生成器会在函数执行但是没有遇到yield的情况下置空,这可能是因为循环结束了,或者是不再满足"if/else"条件。 105 | 106 | ### 问题中的代码解释 107 | 108 | **生成器**: 109 | 110 | `# 在这里你创建了node对象的一个返回生成器的函数` 111 | `def node._get_child_candidates(self, distance, min_dist, max_dist):` 112 | 113 | `# 下面这段代码将会在每次你使用这个生成器时被调用:` 114 | 115 | ` # 如果node对象仍然有一个左child` 116 | ` # 而且distance满足条件,则返回下一个左child` 117 | ` if self._leftchild and distance - max_dist < self._median:` 118 | ` yield self._leftchild` 119 | 120 | ` # 如果node对象仍然有一个右child` 121 | ` # 而且distance满足条件,则返回下一个右child` 122 | ` if self._rightchild and distance + max_dist >= self._median:` 123 | ` yield self._rightchild` 124 | 125 | `# 如果这个函数运行到这里了,意味着这个生成器可以看成空的了。` 126 | `# 亦即:再也没有符合条件的左右child了` 127 | 128 | **调用方**: 129 | 130 | `# 创建一个空的list和一个包含当前对象引用的list` 131 | `result, candidates = list(), [self]` 132 | 133 | `# 循环处理candidates (最初只有一个元素)` 134 | `while candidates:` 135 | 136 | ` # 获取最后一个candidate将其移除` 137 | ` node = candidates.pop()` 138 | 139 | ` # 获取obj和candidate之间的距离` 140 | ` distance = node._get_dist(obj)` 141 | 142 | ` # 如果距离合适,保存结果在result中` 143 | ` if distance <= max_dist and distance >= min_dist:` 144 | ` result.extend(node._values)` 145 | 146 | ` # 将candidate的子节点保存在candidates中` 147 | ` # 该循环会一直循环直到遍历了所有的子节点。` 148 | ` candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))` 149 | 150 | `return result` 151 | 152 | 这段代码包含了以下几个很有意思的地方: 153 | 154 | 1)这个循环在遍历一个list,而这个list会在遍历的过程中增大。虽然存在变成无限循环的风险,但是这仍不失为遍历嵌套数据的好方法。在这里,candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))方法获取了这个生成器的所有元素,但是`while`在产生新的生成器,它们就会继续生产新的元素,直到换了一个节点。 155 | 156 | 2)extend()方法是list的一个用于将迭代器的元素追加在list中。 157 | 一般地,我们传一个list给它: 158 | 159 | `>>> a = [1, 2]` 160 | `>>> b = [3, 4]` 161 | `>>> a.extend(b)` 162 | `>>> print(a)` 163 | `[1, 2, 3, 4]` 164 | 165 | 但是在你给出的代码里,它获取了一个生成器,它有如下几个好处: 166 | a.你不需要两次读取这些元素; 167 | b.如果你有很多子节点,你不需要把它们都保存在内存中; 168 | 这个方法很管用,因为Python不关心你传入的参数是不是一个list。Python只关心参数是否是可迭代的如字符串、list、元组以及生成器。这叫做[鸭子类型](http://zh.wikipedia.org/zh-cn/%E9%B8%AD%E5%AD%90%E7%B1%BB%E5%9E%8B),这也是Python为何如此赞的一个原因。但是这些都不在我们的讨论范围内。 169 | 170 | 171 | 你可以到此结束,也可以接着看下面生成器的一些高级用法: 172 | 173 | ### 控制生成器 174 | 175 | `>>> class Bank(): # let's create a bank, building ATMs` 176 | `... crisis = False` 177 | `... def create_atm(self):` 178 | `... while not self.crisis:` 179 | `... yield "$100"` 180 | `>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want` 181 | `>>> corner_street_atm = hsbc.create_atm()` 182 | `>>> print(corner_street_atm.next())` 183 | `$100` 184 | `>>> print(corner_street_atm.next())` 185 | `$100` 186 | `>>> print([corner_street_atm.next() for cash in range(5)])` 187 | `['$100', '$100', '$100', '$100', '$100']` 188 | `>>> hsbc.crisis = True # crisis is coming, no more money!` 189 | `>>> print(corner_street_atm.next())` 190 | `` 191 | `>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs` 192 | `>>> print(wall_street_atm.next())` 193 | `` 194 | `>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty` 195 | `>>> print(corner_street_atm.next())` 196 | `` 197 | `>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business` 198 | `>>> for cash in brand_new_atm:` 199 | `... print cash` 200 | `$100` 201 | `$100` 202 | `$100` 203 | `$100` 204 | `$100` 205 | `$100` 206 | `$100` 207 | `$100` 208 | `$100` 209 | `...` 210 | 211 | 如果想要控制对资源的访问,这将非常受用。 212 | 213 | ### itertools,你上佳的朋友 214 | 215 | itertools模块包含了操纵迭代的一些特殊的函数。你是不是曾经想过复制一个生产器?串联两个生成器?抑或在线性时间内将嵌套list中的元素分组?或者不依赖创建新list的情况下map/zip? 216 | 217 | 那么,你只要导入itertools模块就行了。 218 | 219 | 想要看个例子?让我们看看四匹马到达终点的可能顺序: 220 | 221 | `>>> horses = [1, 2, 3, 4]` 222 | `>>> races = itertools.permutations(horses)` 223 | `>>> print(races)` 224 | `` 225 | `>>> print(list(itertools.permutations(horses)))` 226 | `[(1, 2, 3, 4),` 227 | ` (1, 2, 4, 3),` 228 | ` (1, 3, 2, 4),` 229 | ` (1, 3, 4, 2),` 230 | ` (1, 4, 2, 3),` 231 | ` (1, 4, 3, 2),` 232 | ` (2, 1, 3, 4),` 233 | ` (2, 1, 4, 3),` 234 | ` (2, 3, 1, 4),` 235 | ` (2, 3, 4, 1),` 236 | ` (2, 4, 1, 3),` 237 | ` (2, 4, 3, 1),` 238 | ` (3, 1, 2, 4),` 239 | ` (3, 1, 4, 2),` 240 | ` (3, 2, 1, 4),` 241 | ` (3, 2, 4, 1),` 242 | ` (3, 4, 1, 2),` 243 | ` (3, 4, 2, 1),` 244 | ` (4, 1, 2, 3),` 245 | ` (4, 1, 3, 2),` 246 | ` (4, 2, 1, 3),` 247 | ` (4, 2, 3, 1),` 248 | ` (4, 3, 1, 2),` 249 | ` (4, 3, 2, 1)]` 250 | 251 | 252 | ### 理解迭代的内部机制 253 | 254 | 迭代是实现迭代(实现了\_\_iter\_\_()函数)和迭代器(实现了\_\_next\_\_()函数)的过程。你可以从可迭代对象上获取一个迭代器,而迭代器是你可以迭代的对象。 255 | 256 | 想要了解更多,你可以看看这篇[文章](http://effbot.org/zone/python-for-statement.htm)。 257 | -------------------------------------------------------------------------------- /你真的懂单链表吗.md: -------------------------------------------------------------------------------- 1 | # 你真的懂单链表吗 2 | 3 | ## 引子 4 | **首先,上一道开胃菜:怎么判断两个单链表是否相交?** 5 | 6 | 我们假设两个单链表分别是A(有m个结点)和B(有n个结点),当然,最容易想到的肯定是两层循环,遍历A中的元素,然后分别与B中的元素进行比较,但是这样做的时间复杂度达到了O(m*n),那么有没有更简单的办法呢?肯定有! 7 | 8 | 我们来看看单链表的性质:每个结点只通过一个指针指向后继结点。那么是不是意味着两个单链表如若相交,它们就只能是Y形相交,而不可能是X形相交,亦即从第一个相同的结点开始,后面的结点全部一样。如果能想到这个,后面的就简单明了了:只要A链表和B链表的最后一个结点值相等,则证明A链表和B链表相交。该算法的时间复杂度也下降到O(m+n)。 9 | 10 | 我们进一步来思考:**怎么找到第一个相交的元素呢?** 11 | 12 | 这里就当然不能像刚才那样,但是出发点还是一样,我们同样可以保证只要两次遍历。我们假设m > n,那么如果我们将两个链表的末尾对齐,是不是从最后一个往前看(当然单链表不能往前遍历,我们先这样看)的时候,A链表会比B链表多m-n个元素,而A链表中的前m-n个元素可以忽略,直接从第m-n个元素开始和B链表一起向前遍历,比较A链表上第m-n+i个元素和B链表第i个元素(i