注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

环信FAQ

环信FAQ

集成常见问题及答案
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

解析:带你从源码的角度彻底理解,Android事件分发机制(上)

其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListVie...
继续阅读 »
其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的。

在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了。目前虽然网上相关的文章也不少,但我觉得没有哪篇写得特别详细的(也许我还没有找到),多数文章只是讲了讲理论,然后配合demo运行了一下结果。而我准备带着大家从源码的角度进行分析,相信大家可以更加深刻地理解Android事件分发机制。


阅读源码讲究由浅入深,循序渐进,因此我们也从简单的开始,本篇先带大家探究View的事件分发,下篇再去探究难度更高的ViewGroup的事件分发。


那我们现在就开始吧!比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:
 
button.setOnClickListener(new OnClickListener() {  
02. @Override
03. public void onClick(View v) {
04. Log.d("TAG", "onClick execute");
05. }
06.});
这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:
button.setOnTouchListener(new OnTouchListener() {  
02. @Override
03. public boolean onTouch(View v, MotionEvent event) {
04. Log.d("TAG", "onTouch execute, action " + event.getAction());
05. return false;
06. }
07.});
onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:


1.jpg


可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。

细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:


2.png


我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。

如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。

首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:


3.png


然后我们来看一下View中dispatchTouchEvent方法的源码:
public boolean dispatchTouchEvent(MotionEvent event) {  
02. if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
03. mOnTouchListener.onTouch(this, event)) {
04. return true;
05. }
06. return onTouchEvent(event);
07.}
这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。

先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:
public void setOnTouchListener(OnTouchListener l) {  
02. mOnTouchListener = l;
03.}
Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。

根据以上源码的分析,从原理上解释了我们前面例子的运行结果。而上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:
public boolean onTouchEvent(MotionEvent event) {  
02. final int viewFlags = mViewFlags;
03. if ((viewFlags & ENABLED_MASK) == DISABLED) {
04. // A disabled view that is clickable still consumes the touch
05. // events, it just doesn't respond to them.
06. return (((viewFlags & CLICKABLE) == CLICKABLE ||
07. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
08. }
09. if (mTouchDelegate != null) {
10. if (mTouchDelegate.onTouchEvent(event)) {
11. return true;
12. }
13. }
14. if (((viewFlags & CLICKABLE) == CLICKABLE ||
15. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
16. switch (event.getAction()) {
17. case MotionEvent.ACTION_UP:
18. boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
19. if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
20. // take focus if we don't have it already and we should in
21. // touch mode.
22. boolean focusTaken = false;
23. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
24. focusTaken = requestFocus();
25. }
26. if (!mHasPerformedLongPress) {
27. // This is a tap, so remove the longpress check
28. removeLongPressCallback();
29. // Only perform take click actions if we were in the pressed state
30. if (!focusTaken) {
31. // Use a Runnable and post this rather than calling
32. // performClick directly. This lets other visual state
33. // of the view update before click actions start.
34. if (mPerformClick == null) {
35. mPerformClick = new PerformClick();
36. }
37. if (!post(mPerformClick)) {
38. performClick();
39. }
40. }
41. }
42. if (mUnsetPressedState == null) {
43. mUnsetPressedState = new UnsetPressedState();
44. }
45. if (prepressed) {
46. mPrivateFlags |= PRESSED;
47. refreshDrawableState();
48. postDelayed(mUnsetPressedState,
49. ViewConfiguration.getPressedStateDuration());
50. } else if (!post(mUnsetPressedState)) {
51. // If the post failed, unpress right now
52. mUnsetPressedState.run();
53. }
54. removeTapCallback();
55. }
56. break;
57. case MotionEvent.ACTION_DOWN:
58. if (mPendingCheckForTap == null) {
59. mPendingCheckForTap = new CheckForTap();
60. }
61. mPrivateFlags |= PREPRESSED;
62. mHasPerformedLongPress = false;
63. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
64. break;
65. case MotionEvent.ACTION_CANCEL:
66. mPrivateFlags &= ~PRESSED;
67. refreshDrawableState();
68. removeTapCallback();
69. break;
70. case MotionEvent.ACTION_MOVE:
71. final int x = (int) event.getX();
72. final int y = (int) event.getY();
73. // Be lenient about moving outside of buttons
74. int slop = mTouchSlop;
75. if ((x < 0 - slop) || (x >= getWidth() + slop) ||
76. (y < 0 - slop) || (y >= getHeight() + slop)) {
77. // Outside button
78. removeTapCallback();
79. if ((mPrivateFlags & PRESSED) != 0) {
80. // Remove any future long press/tap checks
81. removeLongPressCallback();
82. // Need to switch from pressed to not pressed
83. mPrivateFlags &= ~PRESSED;
84. refreshDrawableState();
85. }
86. }
87. break;
88. }
89. return true;
90. }
91. return false;
92.}
相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧:
public boolean performClick() {  
02. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
03. if (mOnClickListener != null) {
04. playSoundEffect(SoundEffectConstants.CLICK);
05. mOnClickListener.onClick(this);
06. return true;
07. }
08. return false;
09.}
可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:
public void setOnClickListener(OnClickListener l) {  
02. if (!isClickable()) {
03. setClickable(true);
04. }
05. mOnClickListener = l;
06.}
一切都是那么清楚了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。

参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。

是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:
imageView.setOnTouchListener(new OnTouchListener() {  
02. @Override
03. public boolean onTouch(View v, MotionEvent event) {
04. Log.d("TAG", "onTouch execute, action " + event.getAction());
05. return false;
06. }
07.});
运行一下程序,点击ImageView,你会发现结果如下:


6.jpg


ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。

好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解。

1. onTouch和onTouchEvent有什么区别,又该如何使用?

从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

2. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?

如果你阅读了Android滑动框架完全解析,教你如何一分钟实现滑动菜单特效 这篇文章,你应该会知道滑动菜单的功能是通过给ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。

3. 为什么图片轮播器里的图片使用Button而不用ImageView?

提这个问题的朋友是看过了Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。

今天的讲解就到这里了,相信大家现在对Android事件分发机制又有了进一步的认识,在后面的文章中我会再带大家一起探究Android中ViewGroup的事件分发机制,感兴趣的朋友可以继续关注~
 
作者:郭霖 收起阅读 »

手把手教你如何从从零开始构建JavaScript模块化加载器

对任何程序,都存在一个规模的问题,起初我们使用函数来组织不同的模块,但是随着应用规模的不断变大,简单的重构函数并不能顺利的解决问题。尤其对JavaScript程序而言,模块化有助于解决我们在前端开发中面临的越来越复杂的需求。   为什么需要模块化   对开发者...
继续阅读 »
对任何程序,都存在一个规模的问题,起初我们使用函数来组织不同的模块,但是随着应用规模的不断变大,简单的重构函数并不能顺利的解决问题。尤其对JavaScript程序而言,模块化有助于解决我们在前端开发中面临的越来越复杂的需求。
 
为什么需要模块化
 
对开发者而言,有很多理由去将程序拆分为小的代码块。这种模块拆分的过程有助于开发者更清晰的阅读和编写代码,并且能够让编程的过程更多的集中在模块的功能实现上,和算法一样,分而治之的思想有助于提高编程生产率。
在下文中,我们将集中讨论JavaScript的模块化开发,并实现一个简单的module loader。
 实现模块化

使用函数作为命名空间

在JavaScript中,函数是唯一的可以用来创建新的作用域的途径。考虑到一个最简单的需求,我们通过数字来获得星期值,例如通过数字0得到星期日,通过数字1得到星期一。我们可以编写如下的程序:
var names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

function dayName(number) {
return names[number];
}

console.log(dayName(1));
上面的程序,我们创建了一个函数dayName()来获取星期值。但问题是,names变量被暴露在全局作用域中。更多的时候,我们希望能够构造私有变量,而暴露公共函数作为接口。

对JavaScript中的函数而言,我们可以通过创建立即调用的函数表达式来达到这个效果,我们可以通过如下的方式重构上面的代码,使得私有作用域成为可能:
var dayName = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

return {
name: function(number) {
return names[number];
},

number: function(name) {
return names.indexOf(name);
}
};
}();

console.log(dayName.name(3));
console.log(dayName.number("Sunday"));
上面的程序中,我们通过将变量包括在一个函数中,这个函数会立即执行,并返回一个包含两个属性的对象,返回的对象会被赋值给dayName变量。在后面,我们可以通过dayName变量来访问暴露的两个函数接口name和number。

对代码进一步改进,我们可以利用一个exports对象来达到暴露公共接口的目的,这种方法可以通过如下方法实现,代码如下:
var weekDay = {};

(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

exports.name = function(number) {
return names[number];
};

exports.number = function(name) {
return names.indexOf(name);
};

})(weekDay); // outside of a function, this refers to the global scope object

console.log(weekDay.name(weekDay.number("Saturday")));
上面的这种模块构造方式在以浏览器为核心的前端编码中非常常见,通过暴露一个全局变量的方式来将代码包裹在私有的函数作用域中。但这种方法依然会存在问题,在复杂应用中,你依然无法避免同名变量。

从全局作用域中分离,实现require方法

更进一步的,为了实现模块化,我们可以通过构造一个系统,使得一个函数可以require另一个函数的方式来实现模块化编程。所以我们的目标是,实现一个require方法,通过传入模块名来取得该模块的调用。这种实现方式要比前面的方法更为优雅的体现模块化的理念。对require方法而言,我们需要完成两件事。

我们需要实现一个readFile方法,它能通过给定字符串返回文件的内容。
我们需要能够将返回的字符串作为代码进行执行。

我们假设已经存在了readFile这个方法,我们更加关注的是如何能够将字符串作为可执行的程序代码。通常我们有好几种方法来实现这个需求,最常见的方法是eval操作符,但我们常常在刚学习JavaScript的时候被告知,使用eval是一个非常不明智的决策,因为使用它会导致潜在的安全问题,因此我们放弃这个方法。

一个更好的方法是使用Function构造器,它需要两个参数:使用逗号分隔的参数列表字符串,和函数体字符串。例如:
var plusOne = new Function("n", "return n+1");
console.log(plusOne(5)); // 6
下面我们可以来实现require方法了:
// module.js
function require(name) {

// 调用一个模块,首先检查这个模块是否已被调用
if(name in require.cache) {
return require.cache[name];
}

var code = new Function("exports, module", readFile(name));
var exports = {},
module = {exports: exports};
code(exports, module);

require.cache[name] = module.exports;
return module.exports;
}

// 缓存对象,为了应对重复调用的问题
require.cache = Object.create(null);

// todo:
function readFile(fileName) { ... }
在页面中使用require函数:
 




demo





通过这种方式实现的模块化系统通常被称为是CommonJS模块风格的,Node.js正式使用了这种风格的模块化系统。这里只是提供了一个最简单的实现方法,在真实应用中会有更加精致的实现方法。

慢载入模块和AMD

对浏览器编程而言,通常不会使用CommonJS风格的模块化系统,因为对于Web而言,加载一个资源远没有在服务端来的快,这收到网络性能的影响,尤其一个模块如果过大的话,可能会中断方法的执行。Browserify是解决这个问题的一个比较出名的模块化方案。

这个过程大概是这样的:首先检查模块中是否存在require其他模块的语句,如果有,就解析所有相关的模块,然后组装为一个大模块。网站本身为简单的加载这个组装后的大模块。

模块化的另一个解决方案是使用AMD,即异步模块定义,AMD允许通过异步的方式加载模块内容,这种就不会阻塞代码的执行。

我们想要实现的功能大概是这个样子的:
// index.html 中的部分代码
define(["weekDay.js", "today.js"], function (weekDay, today) {
console.log(weekDay.name(today.dayNumber()));
document.write(weekDay.name(today.dayNumber()));
});
问题的核心是实现define方法,它的第一个参数是定义该模块说需要的依赖列表,参数而是该模块的具体工作函数。一旦所依赖的模块都被加载后,define便会执行参数2所定义的工作函数。weekDay模块的内容大概是下面的内容:
// weekDay.js
define([], function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

return {
name: function(number) { return names[number]},
number: function(name) { return names.indexOf(name)}
}
});
下面我们来关注如何实现define()方法。为了实现这个方法,我们需要定义一个backgroundReadFile()方法来异步的获取文件内容。此外我们需要能够监视模块的加载状态,当模块加载完后能够告诉函数去执行具体的工作函数(回调)。
// 通过Ajax来异步加载模块
function backgroundReadFile(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function () {
if (req.status < 400)
callback(req.responseText);
});
req.send(null);
}
通过实现一个getModule函数,通过给定的模块名进行模块的调度运行工作。同样,我们需要通过缓存的方式避免同一个模块被重复的载入。实现代码如下:
// module.js 的部分内容
var defineCache = Object.create(null);
var currentMod = null;

function getModule(name) {
if (name in defineCache) {
return defineCache[name];
}

var module = {
exports: null,
loaded: false,
onLoad: []
};

defineCache[name] = module;
backgroundReadFile(name, function(code) {
currentMod = module;
new Function("", code)();
});
return module;
}
有了getModule()了函数之后,define方法可以借助该方法来为当前模块的依赖列表获取或创建模块对象。define方法的简单实现如下:
// module.js 的部分内容
function define(depNames, moduleFunction) {
var myMod = currentMod;
var deps = depNames.map(getModule);

deps.forEach(function(mod) {
if(!mod.loaded) {
mod.onLoad.push(whenDepsLoaded);
}
});

// 用于检查是否所有的依赖模块都被成功加载了
function whenDepsLoaded() {
if(!deps.every(function(m) { return m.loaded; })) {
return;
}

var args = deps.map(function(m) { return m.exports; });

var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.forEach(function(f) { f(); });
}
}

whenDepsLoaded();
}
关于AMD的更加常见的实现是RequireJS,它提供了AMD风格的更加流行的实现方式。

小结

模块通过将代码分离为不同的文件和命名空间,为大型程序提供了清晰的结构。通过构建良好的接口可以使得开发者更加建议的阅读、使用、扩展代码。尤其对JavaScript语言而言,由于天生的缺陷,使得模块化更加有助于程序的组织。在JavaScript的世界,有两种流行的模块化实现方式,一种称为CommonJS,通常是服务端的模块化实现方案,另一种称为AMD,通常针对浏览器环境。其他关于模块化的知识,你可以参考这篇文章。

References

Eloquent JavaScript, chapter 10, Modules
Browserify运行原理分析
Why AMD?
JavaScript模块化知识点小结 收起阅读 »

微信开放平台之公众号第三方平台开发及全网发布验证

微信公众号第三方平台的开放,让公众号运营者在面向垂直行业需求时,可以通过一键登录授权给第三方开发者,来完成相关的处理能力,方便快捷,那如何才能开发出一个公众号第三方平台供一键授权呢?本文以JAVA作为后台服务的实现语言,实现了微信第三方开放平台开发所需要的主要...
继续阅读 »
微信公众号第三方平台的开放,让公众号运营者在面向垂直行业需求时,可以通过一键登录授权给第三方开发者,来完成相关的处理能力,方便快捷,那如何才能开发出一个公众号第三方平台供一键授权呢?本文以JAVA作为后台服务的实现语言,实现了微信第三方开放平台开发所需要的主要业务流程,并针对全网发布的检测做了相应的代码处理,以通过微信全网检测,可以接入任意的微信公众号。
根据微信第三方平台的审核需求,你需要在微信开放平台上注册第三方平台信息时,提供如下几个主要的服务:


41.png




42.png



1、授权事件接收服务,对应填写的审核资料中授权事件接收URL,微信会将相关的授权事件信息推送到该REST服务上,推送的主要消息包括验证票据ComponentVerifyTicket和取消授权的公众号AuthorizerAppid,该服务需要对微信推送过来的该类消息立即做出回应并返回success内容,该服务事件的JAVA实现方式如下:
  /**
* 授权事件接收
*
* @param request
* @param response
* @throws IOException
* @throws AesException
* @throws DocumentException
*/
@RequestMapping(value = "/open/event/authorize", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void acceptAuthorizeEvent(HttpServletRequest request, HttpServletResponse response) throws IOException, AesException, DocumentException {
WeixinOpenService.getInstance().processAuthorizeEvent(request);
WeixinOpenService.getInstance().output(response, "success"); // 输出响应的内容。
}


更具体的实现代码如下:
  /**
* 处理授权事件的推送
*
* @param request
* @throws IOException
* @throws AesException
* @throws DocumentException
*/
public void processAuthorizeEvent(HttpServletRequest request) throws IOException, DocumentException, AesException {
String token = WeixinOpenService.TOKEN;
String nonce = request.getParameter("nonce");
String timestamp = request.getParameter("timestamp");
String signature = request.getParameter("signature");
String msgSignature = request.getParameter("msg_signature");

if (!StringUtils.isNotBlank(msgSignature))
return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
boolean isValid = WechatCallbackServiceController.checkSignature(token, signature, timestamp, nonce);
if (isValid) {
StringBuilder sb = new StringBuilder();
BufferedReader in = request.getReader();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
String xml = sb.toString();
String encodingAesKey = WeixinOpenService.ENCODINGAESKEY;// 第三方平台组件加密密钥
String appId = getAuthorizerAppidFromXml(xml, "authorizationEvent");// 此时加密的xml数据中ToUserName是非加密的,解析xml获取即可
WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
xml = pc.decryptMsg(msgSignature, timestamp, nonce, xml, "AppId");
processAuthorizationEvent(xml);
}
}



2、公众号消息与事件接收服务,对应填写的审核资料中公众号消息与事件接收URL,微信会将粉丝发送给公众号的消息和事件推送到该REST服务上,微信公众平台要求该消息和事件接收服务在5秒内做出回应,如果5秒内微信公众平台得不到响应消息,粉丝将将收到提示公众号暂时服务提供服务的错误信息。对于需要对粉丝发送的消息走人工渠道做出响应的公众号来说,此时就需要首先接收下消息,将消息交给后来逻辑转人工处理,然后立即以空格消息响应微信公众平台,微信收到空格消息后就会知道该粉丝发送的消息已经被妥善处理,并对该响应不做任何处理,同时不会发起消息重新推送的重试。该服务的JAVA实现实现方式如下:
 /**
* 处理微信推送过来的授权公众号的消息及事件
*
*/
public void processMessageAndEvent(HttpServletRequest request,String xml) throws IOException, AesException, DocumentException {
String nonce = request.getParameter("nonce");
String timestamp = request.getParameter("timestamp");
String msgSignature = request.getParameter("msg_signature");

String encodingAesKey = WeixinOpenService.ENCODINGAESKEY;
String token = WeixinOpenService.TOKEN;
WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, WeixinOpenService.COMPONENT_APPID);
xml = pc.decryptMsg(msgSignature, timestamp, nonce, xml, "ToUserName");
WechatCallbackServiceController.processMessage(xml);
}


以上是开发微信第三方开发平台的主要服务代码,想要通过微信全网接入检测并成功发布,还有如下的工作的需要做:
  • 开发一个体验页,可以直接让审核人员体验,因为需要的是直接体验,所以访问该页面就不要有认证和权限控制之类的逻辑了,这个页面要求符合微信第三方平台基本的设计要求,本人简单实现了如下的页面格式是可以成功通过审核的,如下:

43.png

 
  • 针对微信全网检测的固定账号做出特定的响应,主要包括一个文本消息响应,一个事件消息响应和一个客服接口调用验证,微信全网检测要求测试的固定账号接收到以上消息后,分别做出如下的响应:接收到TESTCOMPONENT_MSG_TYPE_TEXT这样的文本消息立即回复给粉丝文本内容TESTCOMPONENT_MSG_TYPE_TEXT_callback;接收到事件消息,立即以文本内容的消息格式回复粉丝内容event + “from_callback”,其中event需要根据实际内容替换为具体事件类型;接收到QUERY_AUTH_CODE:query_auth_code  这样的文本消息,需要立即响应空字符串给微信,之后调用客服接口回复粉丝文本消息,内容为:$query_auth_code\$_from_api,其中query_auth_code需要替换为微信实际推送过来的数据。主要的JAVA后台实现代码如下:
 
 /**     * 公众号消息与事件接收     *      * @param request     * @param response     * @throws DocumentException     * @throws AesException     * @throws IOException     */    @RequestMapping(value = "/open/{appid}/callback", method = RequestMethod.POST)    @ResponseStatus(HttpStatus.NO_CONTENT)    public void acceptMessageAndEvent(HttpServletRequest request, HttpServletResponse response) throws IOException, AesException, DocumentException {        String msgSignature = request.getParameter("msg_signature");        if (!StringUtils.isNotBlank(msgSignature))            return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息         StringBuilder sb = new StringBuilder();        BufferedReader in = request.getReader();        String line;        while ((line = in.readLine()) != null) {            sb.append(line);        }        in.close();         String xml = sb.toString();        Document doc = DocumentHelper.parseText(xml);        Element rootElt = doc.getRootElement();        String toUserName = rootElt.elementText("ToUserName");         if (StringUtils.equalsIgnoreCase(toUserName, "gh_3c884a361561")) {// 微信全网测试账号            WeixinWholeNetworkTestService.getInstance().checkWeixinAllNetworkCheck(request,response,xml);        }else{            WeixinOpenService.getInstance().processMessageAndEvent(request,xml);            WeixinOpenService.getInstance().output(response, "");        }    }
 
  • 其中gh_3c884a361561这个账号是微信全网接入检测的固定账号,针对全网检测需要对该账号做特出响应,一旦全网接入检测通过,这部分的代码是可以去掉的,只有全网检测的时候才需要这部分代码。

 public void checkWeixinAllNetworkCheck(HttpServletRequest request, HttpServletResponse response,String xml) throws DocumentException, IOException, AesException{
String nonce = request.getParameter("nonce");
String timestamp = request.getParameter("timestamp");
String msgSignature = request.getParameter("msg_signature");

String encodingAesKey = WeixinOpenService.ENCODINGAESKEY;
String token = WeixinOpenService.TOKEN;
WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, WeixinOpenService.COMPONENT_APPID);
xml = pc.decryptMsg(msgSignature, timestamp, nonce, xml, "ToUserName");

Document doc = DocumentHelper.parseText(xml);
Element rootElt = doc.getRootElement();
String msgType = rootElt.elementText("MsgType");
String toUserName = rootElt.elementText("ToUserName");
String fromUserName = rootElt.elementText("FromUserName");

switch (msgType) {
case "event":
String event = rootElt.elementText("Event");
replyEventMessage(request,response,event,toUserName,fromUserName);
break;
case "text":
String content = rootElt.elementText("Content");
processTextMessage(request,response,content,toUserName,fromUserName);
break;
default:
break;
}
}


根据消息或事件类型区分后,剩余的逻辑只需要处理成对应的回复内容即可,如下:
public void replyEventMessage(HttpServletRequest request, HttpServletResponse response, String event, String toUserName, String fromUserName) throws DocumentException, IOException {
String content = event + "from_callback";
replyTextMessage(request,response,content,toUserName,fromUserName);
}

public void processTextMessage(HttpServletRequest request, HttpServletResponse response,String content,String toUserName, String fromUserName) throws IOException, DocumentException{
if("TESTCOMPONENT_MSG_TYPE_TEXT".equals(content)){
String returnContent = content+"_callback";
replyTextMessage(request,response,returnContent,toUserName,fromUserName);
}else if(StringUtils.startsWithIgnoreCase(content, "QUERY_AUTH_CODE")){
WeixinOpenService.getInstance().output(response, "");
//接下来客服API再回复一次消息
replyApiTextMessage(request,response,content.split(":")[1],fromUserName);
}
}

public void replyApiTextMessage(HttpServletRequest request, HttpServletResponse response, String auth_code, String fromUserName) throws DocumentException, IOException {
String authorization_code = auth_code;
// 得到微信授权成功的消息后,应该立刻进行处理!!相关信息只会在首次授权的时候推送过来
WeixinOpenData weixinOpenData = WeixinOpenService.getInstance().getWeixinOpenData(WeixinOpenService.COMPONENT_APPID);
long accessTokenExpires = weixinOpenData.getAccessTokenExpires();
String componentAccessToken = weixinOpenData.getComponentAccessToken();
String componentVerifyTicket = weixinOpenData.getComponentVerifyTicket();
JSONObject authorizationInfoJson;
if (!this.isExpired(accessTokenExpires)) {
authorizationInfoJson = WeixinOpenService.getInstance().apiQueryAuth(componentAccessToken, WeixinOpenService.COMPONENT_APPID, authorization_code);
} else {
JSONObject accessTokenJson = WeixinOpenService.getInstance().getComponentAccessToken(WeixinOpenService.COMPONENT_APPID, WeixinOpenService.COMPONENT_APPSECRET, componentVerifyTicket);
componentAccessToken = accessTokenJson.getString("component_access_token");
authorizationInfoJson = WeixinOpenService.getInstance().apiQueryAuth(componentAccessToken, WeixinOpenService.COMPONENT_APPID, authorization_code);
}
if (log.isDebugEnabled()) {
log.debug("weixinopen callback authorizationInfo is " + authorizationInfoJson);
}

JSONObject infoJson = authorizationInfoJson.getJSONObject("authorization_info");
String authorizer_access_token = infoJson.getString("authorizer_access_token");

String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + authorizer_access_token;
DefaultHttpClient client = new DefaultHttpClient();
enableSSLDefaultHttpClient(client);
HttpPost httpPost = new HttpPost(url);

JSONObject message = processWechatTextMessage(client, httpPost, fromUserName, auth_code + "_from_api");
if(log.isDebugEnabled()){
log.debug("api reply messto to weixin whole network test respose = "+message);
}
}
public void replyTextMessage(HttpServletRequest request, HttpServletResponse response, String content, String toUserName, String fromUserName) throws DocumentException, IOException {
Long createTime = Calendar.getInstance().getTimeInMillis() / 1000;
StringBuffer sb = new StringBuffer();
sb.append("");
sb.append("");
sb.append("");
sb.append("" + createTime + "");
sb.append("");
sb.append("");
sb.append("");
String replyMsg = sb.toString();

String returnvaleue = "";
try {
WXBizMsgCrypt pc = new WXBizMsgCrypt(WeixinOpenService.TOKEN, WeixinOpenService.ENCODINGAESKEY, WeixinOpenService.COMPONENT_APPID);
returnvaleue = pc.encryptMsg(replyMsg, createTime.toString(), "easemob");
} catch (AesException e) {
log.error("auto reply to weixin whole network test occur exception = "+ e);
e.printStackTrace();
}
if(log.isDebugEnabled()){
log.debug("return weixin whole network test Text message is = "+returnvaleue);
}
WeixinOpenService.getInstance().output(response, returnvaleue);
}



以上是微信第三方开放平台开发主要的业务流程,在实际开发中,还有两点需要特别注意:
1、微信要求第三方开放平台必须以密文方式接收消息;
2、在实际部署时,需要更换JAVA安全包相关的内容,否则将出现秘钥长度不够的异常,需要替换的文件包括JAVA_HOME/jre/lib/security/local_policy.jar和  JAVA_HOME/jre/lib/security/US_export_policy.jar这两个文件。
作者:徐正礼 收起阅读 »

详解:Android开发中常用的 DPI / DP / SP

Android的碎片化已经被喷了好多年,随着国内手机厂商的崛起,碎片化也越来越严重,根据OpenSignal的最新调查,2014年市面上有18796种不同的Android设备,作为开发者,一个无法回避的难题就是需要适配各种各样奇奇怪怪的机型。 设备机型不...
继续阅读 »
Android的碎片化已经被喷了好多年,随着国内手机厂商的崛起,碎片化也越来越严重,根据OpenSignal的最新调查,2014年市面上有18796种不同的Android设备,作为开发者,一个无法回避的难题就是需要适配各种各样奇奇怪怪的机型。

设备机型不同必然也会导致屏幕大小和分辨率(Resolution)的不同,但是无论分辨率有多大,屏幕有多大,我们手指触控范围的大小不会发生变化,所以最优的适配方式应该是指定大小的控件在所有的设备上的显示都一样。

Android的官方文档对此也有明确的说明
 
When adding support for multiple screens, applications do not work directly with resolution; applications should be concerned only with screen size and density, as specified by the generalized size and density groups.
 
所以,适配应该与分辨率无关,只与屏幕大小和屏幕密度相关,首先来看一下什么是屏幕密度 - DPI。
 
DPI
 
DPI的全称是 Dots Per Inch,Inch是一个物理单位(无论在任何设备上,其大小都是固定的),所以DPI就指在一个Inch的物理长度内有多少个Dot,160DPI的屏幕就表示一个Inch包含160个Dot,320DPI的屏幕表示一个Inch有320个Dot,所以说Dot的大小是不固定的。

Android设备用DPI来表示屏幕密度(Density),屏幕密度大就表示一个Inch包含的Dot比较多。那PPI是什么呢?
我们会经常看到iPad、iPhone是用PPI来表示屏幕密度,小米Pad也是用PPI来表示。


51.png


PPI in mi pad
 
其实对Android而言,DPI等价于PPI(Pixels-Per-Inch),DPI最早是用于印刷行业,跟PPI还是有本质不同的,Android应该是误用了DPI这个概念。具体可以参考PPI vs. DPI: what’s the difference?。

其实我们只要知道在Android设备中,DPI 等价于 PPI 就可以了。


52.png


PPI 定义
通常我们说一个设备是多少寸时,指的是屏幕对角线(Diagonal)是多少inch,所以用对角线的像素值(px)除以对角线长度(inch),就可以计算出PPI。


53.png


PPI 计算公式
为了简化适配工作,Android根据屏幕大小(Inch)和屏幕密度(DPI)对设备做了如下划分:


54.png


PPI 对应屏幕尺寸

DP

既然有那么多不同分辨率、不同大小的屏幕,使用PX必然会导致适配困难,为了进一步简化适配工作,Android为我们提供了一个虚拟的像素单位 - DP 或者 DIP (Density-Independent pixel),当然也可以理解为 Device-Independent Pixel。为什么说是虚拟呢,因为它的大小不是一个物理(Phisical)值,而是由操作系统根据屏幕大小和密度动态渲染出来的。

PX跟DP之间的换算关系很简单 
px = dp * (dpi / 160)
举例来说,小米Pad的屏幕密度为326dpi,如果需要显示的图片大小为20dp,那么就需要提供一个 20 (326 / 160) = 40px的图片才能达到最佳显示效果,如果还要适配一个163dpi的屏幕,那么还需要再提供一个20 (163 / 160) = 20px的图片。

那么一个20dp的图片,在不同设备上的显示效果如何呢?我们以iPad为例来说明。


55.jpg


iPad 屏幕参数
 
iPad2 和 iPad Retina的物理尺寸都是 9.7 inch,不同的是分辨率和PPI,一个是1024x768 / 132ppi,另一个是2048x1536 / 264ppi,
分别计算一下20dp对应多少inch
ipad2 = 20 * (132 / 160) * (7.9 / (math.sqrt(1024 * 1024 + 768 * 768))) 
ipad_retina = 20 * (264 / 160) * (7.9 / (math.sqrt(2048 * 2048 + 1536 * 1536)))
计算结果都是0.1018359375,这就是dp的功能,它能保证在所有的设备上显示的大小都一样。

如果只提供了一个大小为20px的图片,为了保证图片在所有设备上的物理大小都一样,高DPI的设备上系统会拉伸图片,低DPI的设备上图片会被缩小,这样既会影响UE也会影响APP的执行效率。所以我们需要为不同屏幕密度的设备提供不同的图片,他们之间的对应关系如下。



56.png



Android 设备屏幕分级
我们在用Sketch作图的时候,如果1x图片对应的是屏幕是MDPI (160dpi),那么1.5x,2x就分别对应HDPI,XHDPI。


57.png



screens_densitiesSP
 
SP 

SP 全称是 Scale-independent Pixels,用于字体大小,其概念与DP是一致的,也是为了保持设备无关。因为Android用户可以根据喜好来调整字体大小,所以要使用sp来表示字体大小。

58.png



changing_android_font_size
 
参考文献

http://developer.android.com/guide/practices/screens_support.html#DeclaringTabletLayouts
http://developer.android.com/training/multiscreen/screendensities.html
http://stackoverflow.com/questions/2025282/difference-between-px-dp-dip-and-sp-in-android
http://99designs.com/designer-blog/2013/02/26/ppi-vs-dpi-whats-the-difference/
作者:liangfeizc 收起阅读 »

推荐:八个最优秀的 Android Studio 插件

Android Studio是目前Google官方设计的用于原生Android应用程序开发的IDE。基于JetBrains的IntelliJ IDEA,这是Google I/O 2013第一个宣布的作为Eclipse的继承者,深受广大Android社区的欢迎。...
继续阅读 »
Android Studio是目前Google官方设计的用于原生Android应用程序开发的IDE。基于JetBrains的IntelliJ IDEA,这是Google I/O 2013第一个宣布的作为Eclipse的继承者,深受广大Android社区的欢迎。在经过漫长的测试阶段后,最终版本于去年12月发布。

Android Studio是一个功能全面的开发环境,装备了为各种设备——从智能手表到汽车——开发Android应用程序所需要的所有功能。不但总是有改进的余地,Android Studio还提供了对第三方插件的支持,下面本文将列出一些最有用的插件。

1. H.A.X.M(硬件加速执行管理器)

如果你想使用Android模拟器更快地执行应用程序,那么H.A.X.M是你的最佳选择。H.A.X.M提供Android SDK模拟器在英特尔系统中的硬件加速。我认为H.A.X.M是最有用的插件,因为它能让Android开发人员尽快地在模拟器上运行最新的Android版本。

安装H.A.X.M

打开Android SDK管理器,选择“Intel x86 Emulator Accelerator (HAXM installer)”,接受许可并安装软件包。



31.png




这个进程只是下载软件包,还没有安装。为了完成安装到图片所示的SDK路径C:\Users\Administrator\AppData\Local\Android\sdk\ (安装在Windows机器上)并找到下载的文件夹。我的是:C:\Users\Administrator\AppData\Local\Android\sdk\extras\intel. 打开安装文件Hardware_Accelerated_Execution_Manager,单击可执行的intelhaxm-android,继续安装。完成此安装后,你就可以使用该模拟器了。


312.png




2. genymotion

Genymotion是测试Android应用程序,使你能够运行Android定制版本的旗舰工具。它是为了VirtualBox内部的执行而创建的,并配备了一整套与虚拟Android环境交互所需的传感器和功能。使用Genymotion能让你在多种虚拟开发设备上测试Android应用程序,并且它的模拟器比默认模拟器要快很多。



313.png




如果你想要确保你开发的应用程序能够在所有支持的设备上流畅地运行,但在特定设备上排除错误有困难时,那就应该好好利用这款伟大的插件。

想要安装Genymotion,可以参见以前发布过的教程。

3. Android  Drawable Importer

为了适应所有Android屏幕的大小和密度,每个Android项目都会包含drawable文件夹。任何具备Android开发经验的开发人员都知道,为了支持所有的屏幕尺寸,你必须给每个屏幕类型导入不同的画板。Android  Drawable Importer插件能让这项工作变得更容易。它可以减少导入缩放图像到Android项目所需的工作量。Android  Drawable Importer添加了一个在不同分辨率导入画板或缩放指定图像到定义分辨率的选项。这个插件加速了开发人员的画板工作。



314.png






315.png




安装Android  Drawable Importer


Drawable-Importer.gif



4. Android ButterKnife Zelezny

Android ButterKnife是一个“Android视图注入库”。它提供了一个更好的代码视图,使之更具可读性。 ButterKnife能让你专注于逻辑,而不是胶合代码用于查找视图或增加侦听器。用ButterKnife编程,你必须对任意对象进行注入,注入形式是这样的:@InjectView(R.id.title) TextView title;Android ButterKnife Zelezny是一款Android Studio插件,用于在活动、片段和适配器中,从所选的XML布局文件生成ButterKnife注入。该插件提供了生成XML对象注入的最快方式。如果只是一两个注入,那么这样写是没有问题的,但如果你有很多要写,那就需要参考所有的注入,将它们编写到源文件中。

下面是一个代码在使用Android ButterKnife之前的样子的例子:




316.png



以及使用之后:



317.png




安装ButterKnife Zelezny:


ButterKnife-Zelezny.gif



5. Android  Holo Colors Generator

开发Android应用程序需要伟大的设计和布局。Android  Holo Colors Generator则是定制符合喜好的Android应用程序的最简单方法。Android  Holo Colors Generator是一个允许你为你的应用程序随心所欲地创建Android布局组件的插件。此插件会生成所有必要的可在项目中使用的相关的XML画板和样式资源。

安装 Holo Colors Generator:


41.gif



6. Robotium Recorder

Robotium Recorder是一个自动化测试框架,用于测试在模拟器和Android设备上原生的和混合的移动应用程序。Robotium Recorder可以让你记录测试案例和用户操作。你也可以查看不同Android活动时的系统功能和用户测试场景。

Robotium Recorder能让你看到当你的应用程序运行在设备上时,它是否能按预期工作,或者是否能对用户动作做出正确的回应。如果你想要开发稳定的Android应用程序,那么此插件对于进行彻底的测试很有帮助。

下面是一个例子,是我的应用程序使用Robotium Recorder时的样子:



412.jpg




想要安装Robotium Recorder,请登录它的官方页面,并根据你的操作系统的版本在安装区域选择Robotium Recorder。

7.jimu Mirror

Android Studio配备了一个可视化的布局编辑器。但是一个静态的布局预览有时候对于开发人员而言可能还不够,因为静态预览不能预览动画、颜色和触摸区域,所以jimu Mirror来了,这是一个可以让你在真实的设备上迅速测试布局的插件。jimu Mirror允许在设备上预览随同编码更新的Android布局。

安装jimu Mirror:


413.gif



8.Strings-xml-tools

Strings-xml-tools是一个虽小但很有用的插件,可以用来管理Android项目中的字符串资源。它提供了排序Android本地文件和添加缺少的字符串的基本操作。虽然这个插件是有限制的,但如果应用程序有大量的字符串资源,那这个插件就非常有用了。

安装Android Strings.xml tools:


414.gif



您有更优秀的Android Studio插件吗,欢迎在留言中告诉我们~ 收起阅读 »

技术分享:开源代码应用之Eclipse篇

开写这篇的时候,恰逢Eclipse Mars(4.5)正式发布,终于由日蚀变登火星了,也离我开始基于Eclipse开发产品已经过去10年,这10年间,经历了Eclipse由私有核心框架到拥抱OSGi, 由单一Java IDE成长为巨无霸式的技术平台,由纯桌面到...
继续阅读 »
开写这篇的时候,恰逢Eclipse Mars(4.5)正式发布,终于由日蚀变登火星了,也离我开始基于Eclipse开发产品已经过去10年,这10年间,经历了Eclipse由私有核心框架到拥抱OSGi, 由单一Java IDE成长为巨无霸式的技术平台,由纯桌面到Web,嵌入式全面开花,个人也经历了从普通开发者成长为committer,又离开社区的过程,唯一不变的是:Eclipse依然是我开发Java唯一的选择。

对于这样一个由全世界最smart的一群人贡献和维护的开源项目(群),我相信任何热爱这个行业的工程师都能从中获得收益,这次就谈谈我基于Eclipse写的一个小工具。

不知道大家有没有类似的体会,每到产品发布期截止的时候,team就会开始忙乱的整理Java源代码中的license声明问题,严格统一的开发风格对所有的team来讲,基本都是一种奢望,从头开始不可能,那怎么办,不修复吧,不能发布,修复吧,这样的烂活没人愿意干,大概说来,修复Java源代码里面的license声明分为以下两个主流方式:
1. 既然是Java源代码,那就Java上啊,不就读出文件来,插入或替换吗?,定位吗,嗯,文件头的easy,成员变量型的,得想想...
2. 杀鸡焉用牛刀?,组合下Unix里面的小命令,分分钟搞定。

两种方式下的结果我都见过,实话说,的确不怎么样。

这件事情简单吗?说实话不难,但 Oracle依然把Java源代码里的license声明整成下面这个模样,就为了把以前Sun的license声明改成自己的。


1.png


这对很多有代码格式强迫症的工程师来讲,比杀了他们还难受啊。

其实我并没有接到这样的烂活,我只是思考了下,如果要处理好,该怎么办?嗯,这事要搞好,要是能操纵Java源代码的每一个部分不就行了?

哇靠,有人马上会跳起来说,这得懂编译器哪,对,就是编译器,不过也没有那么复杂,也就用了一丁丁点AST知识,不知道AST?哦,哪也没问题,有Eclipse替你做。

于是我开始动手实现这么一个能快速修复Java源代码中license声明的小工具,基本思路是基于Eclipse JDT里的AST实现,在Java语法这个粒度来修改,并做成一个Eclipse Plug-in,这下大家安装后,简单到点个button,就能完成工作。

具体实现步骤如下:

1. 生成一个Eclipse Plug-in项目,选个模版,最简单的那种,能点toolbar上面的button,弹出个"hello, world"对话框就可以。不知道怎么开发一个Eclipse Plug-in啊,没关系,看完这篇blog,你就会了。(别忘了好评!)

2. 在Action的回调方法里面,代码如下。
public void run(IAction action) {  
license = getLicenseContent(LICENSE_FILE_NAME);
license_inline = getLicenseContent(LICENSE_INLINE_FILE_NAME);
if (license_inline.endsWith("\n")) {
license_inline = license_inline.substring(0, license_inline.length() - 1);
}
sum = 0;

IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IProject[] projects = root.getProjects();
for (IProject project : projects) {
try {
if (project.isOpen()) {
processProject(project);
}
} catch (Exception e) {
MessageDialog.openInformation(window.getShell(), "Fix License", "Exception happened, please check the console log.");
e.printStackTrace();
return;
}
}
MessageDialog.openInformation(window.getShell(), "Fix License", "All java source files have been processed. Total = " + sum);
}
首先获得license的内容,分为主license和行内license,具体内容这里就不显示了,然后获取Eclipse里面所有的项目,遍历每个项目并处理,这里只处理打开的项目,如果你有不想处理的项目,关闭就行。

3. 处理项目
private void processProject(IProject project) throws Exception {  
if (project.isNatureEnabled("org.eclipse.jdt.core.javanature")) {
IJavaProject javaProject = JavaCore.create(project);
IPackageFragment[] packages = javaProject.getPackageFragments();
for (IPackageFragment mypackage : packages) {
if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
sum = sum + 1;
processJavaSource(unit);
}
}
}
}
}
当然只修复Java项目,没有Java nature的,一律抛弃。
获得Java项目后,获取所有的package,这里的package和通常意义上Java的package不同,具体意义看API,就当课后作业。
再进一步,就可以获取Java源文件,并取得编译单元,有了这个,以后的路就有方向了。

4. 处理Java源文件
private void processJavaSource(ICompilationUnit unit) throws Exception {  
ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
IPath path = unit.getPath();
try {
bufferManager.connect(path, null);
ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(path);
IDocument doc = textFileBuffer.getDocument();
if ((license !=null) && (license.length() > 0)) {
processHeadLicense(doc);
}
if ((license_inline != null) && (license_inline.length() > 0)) {
processInlineLicense(doc);
}
textFileBuffer.commit(null, false);
} finally {
bufferManager.disconnect(path, null);
}
}
这里用到了一些Eclipse Jface text包里面的东西,和Java里面常见的文件读写API有些不一样,但基本思想是一致的。等取到了IDocument对象,就可以开始正式的license处理。

5. 处理Java文件头license声明
private void processHeadLicense(IDocument doc) throws Exception {  
CompilationUnit cu = getAST(doc);
Comment comment = null;
if (cu.getCommentList().size() == 0) {
doc.replace(0, 0, license);
} else {
comment = (Comment)cu.getCommentList().get(0);
String firstComment = doc.get().substring(comment.getStartPosition(), comment.getStartPosition() + comment.getLength());
if (validateHeadLicense(firstComment)) {
doc.replace(comment.getStartPosition(), comment.getLength(), license);
} else {
doc.replace(0, 0, license);
}
}
}

private CompilationUnit getAST(IDocument doc) {
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setSource(doc.get().toCharArray());
parser.setResolveBindings(true);
CompilationUnit cu = (CompilationUnit) parser.createAST(null);

return cu;
}
基于AST就可以得到Java源代码里面的所有comments,接下来就可以根据各种情况插入或替换文件头的license声明。

6. 成员变量型license声明
这种license声明类似下面这个例子。
public class Demo {  
public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";

public Demo() {

}

public void hello() {

}

}
它的处理方式如下。
private void processInlineLicense(IDocument doc) throws Exception {  
CompilationUnit cu = getAST(doc);
cu.recordModifications();
AST ast = cu.getAST();

if (cu.types().get(0) instanceof TypeDeclaration) {
TypeDeclaration td = (TypeDeclaration)cu.types().get(0);
FieldDeclaration[] fd = td.getFields();
if (fd.length == 0) {
td.bodyDeclarations().add(0, createLiceseInLineField(ast));
} else {
FieldDeclaration firstFd = fd[0];
VariableDeclarationFragment vdf = (VariableDeclarationFragment)firstFd.fragments().get(0);
if (vdf.getName().getIdentifier().equals("COPYRIGHT")) {
td.bodyDeclarations().remove(0);
td.bodyDeclarations().add(0, createLiceseInLineField(ast));
} else {
td.bodyDeclarations().add(0, createLiceseInLineField(ast));
}
}
}

//record changes
TextEdit edits = cu.rewrite(doc, null);
edits.apply(doc);
}

private FieldDeclaration createLiceseInLineField(AST ast) {
VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName("COPYRIGHT"));
StringLiteral sl = ast.newStringLiteral();
sl.setLiteralValue(license_inline);
vdf.setInitializer(sl);
FieldDeclaration fd = ast.newFieldDeclaration(vdf);
fd.modifiers().addAll(ast.newModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL));
fd.setType(ast.newSimpleType(ast.newSimpleName("String")));

return fd;
}
成员变量类型的license声明处理起来稍显麻烦,主要原因是牵扯到Java成员变量的创建和解析,但其实也不是很难理解,而且从中可以学到AST是如何精细处理Java类的各个组成部分的。

7. 测试
启动一个新的调试Eclipse Plug-in的Eclipse Runtime,导入任意几个Java项目,从菜单或工具栏上面选择“Fix License” action,完成之后检查任意的Java源文件,看看license是否已经修复。

来看看一个简单的测试结果吧。
这个是修复前的
package com.demo;  

public class Demo {
public Demo() {

}

public void hello() {

}

}
这个是修复后的
/* IBM Confidential 
* OCO Source Materials
*
* (C)Copyright IBM Corporation 2013, 2014, 2015.
*
* The source code for this program is not published or otherwise
* divested of its trade secrets, irrespective of what has been
* deposited with the U.S. Copyright Office.
*/
package com.demo;

public class Demo {
public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";

public Demo() {

}

public void hello() {

}

}
8. 打包分发
这个工具Plug-in可以按Eclipse的标准插件打包并安装,或者生成一个Update Site以供用户在线安装。

好了,啰嗦了这么多,到了该结束的时刻,最后一句,这个小工具所有的源代码已经在GitHub上开源,喜欢可以去下载并测试,源代码里面附有一份详细安装的文档。

工具地址:https://github.com/alexgreenbar/open_tools.git
作者:Alex 收起阅读 »

环信CTO马晓宇:开源重燃移动IM大连接梦想

    2014年,微信凭借着QQ得天独厚的优势牢牢把控着国内的市场,陌陌通过资本力量洗牌一骑绝尘奔赴美国纳斯达克上市,Facebook 拆资190亿美元收购WhatsApp,买下了一个未来。当人们都认为移动IM局面已经尘埃落定时,众多通过移动IM走向社交类的...
继续阅读 »
    2014年,微信凭借着QQ得天独厚的优势牢牢把控着国内的市场,陌陌通过资本力量洗牌一骑绝尘奔赴美国纳斯达克上市,Facebook 拆资190亿美元收购WhatsApp,买下了一个未来。当人们都认为移动IM局面已经尘埃落定时,众多通过移动IM走向社交类的APP也相继拿到风投,加入了这场无法预知的战争。在2015年的今天,移动互联网将进入活跃度时代,随着人们对手机依赖感的提升,用户需求不断在变化,4G的迅速发展,移动IM又掀起一场入口争夺战。WhatsApp股东的知名风投机构红杉资本高管合伙人Aaref Hilaly曾表示,移动聊天正在重新定义社交网络,诸如Facebook等充斥陌生人关系的社交模式,已经显得没有价值。移动IM将迎来一个巨大的创新机会,无论是社交、电商、教育、甚至嵌入式领域,都可以创造一个大链接时代。

     "我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。"

    与记者谈起移动IM的发展以及环信未来的方向,作为环信CTO的马晓宇从移动通信领域的实践经验和感悟来表达了自己的看法。同时作为一个开源理想主义者,他和整个团队致力把环信逐步开源,也把开源运用在整个工作环境里面,让企业跟随着开源世界的脚步,坚定在技术路线上前进,让一切都变得美好起来。


wKiom1UYNwDCJmxQAACwdgCMSuw087.jpg



     马晓宇,环信CTO,从事十七八年程序开发,从最早的IC设计,到后来电信短信网关程序以及网关软件的开发,先后入职Symbian和Nokia公司,从事中间件以及内核软件开发,在移动技术领域有深厚积累,可以说马晓宇伴随着中国移动互联网的成长一路走来。

    在环信项目的早期,马晓宇和其他项目的成员看到移动互联网这个大趋势,以及市场的需求,想基于BaaS平台上提供IM以及推送这样的服务,在Key-Value存储、用户体系、权限管理等方面做一套完整的BaaS平台,不过这个项目持续了半年就被终止。面对着这样的打击马晓宇和整个团队也并没有因此而停止整个项目的开发,从非理性的热烈追捧到泡沫破裂,这让他们更加认识到了整个市场用户的需求和开放性及整个生态圈的重要性。2013年底,马晓宇和他们的团队再次出发,他们把之前的项目全部回收,重新定位并聚焦一件事情,那就是即时通讯业务。

    在移动互联网依旧澎湃的今天,移动IM在整个市场的冲击下愈演愈烈。不过马晓宇认为移动IM领域仍然蕴藏着巨大的潜力,差异化的市场仍有很大的一部分用户需求。所以他们开始以平台化、入口化为重点,在满足用户的即时通讯需求外,还继续不断的纵向扩展。

    可以说2013年是马晓宇和整个团队最艰难的一年。微信、易信、飞信、来往、陌陌等等似乎已经占据了整个移动IM市场,但是移动端即时通讯应用的形式还远没有定型,用户也在不断适应和迭代,一切的确定都是充满不确定性。马晓宇和整个团队也是看到了这个市场,凭借着整个团队有着开源和移动技术背景,一步一步的把环信这个项目带入移动互联网的台面上,在成功的背后也隐藏着很多艰难的决定和方向的抉择。下面就同记者一起来了解马晓宇和环信。

    记者:您是基于什么考虑和整个团队一起研发环信这类移动IM产品?

    马晓宇:我们环信这个项目从2013年初开始,当时我们看到移动互联网这个大趋势和需求,最早想做一个BaaS平台(后端即服务:Backend as a Service),提供一部分的IM功能,当时也搭建了一个推送功能,加入Key-Value存储,用户体系,权限管理等功能,最终想做一套比较全的BaaS平台,大概有半年多的时间,发现这个项目有一个很大的难度。

     主要问题是BaaS对中小开发者很好,不需要有自己的后台,很容易跑起来。但是如果你的业务发展,很难满足个性化的需求定制。实际上在2013年底我们就讨论这个事情,BaaS这个平台基本解决不了用户的个性化需求,所以后来我们就回收了这个项目,然后聚焦做一件事情---也就是现在IM即时通讯。从2013年底我们就重新定位,开始专注即时通讯业务,产品真正上线是在2014年的年中,6月份正式上线。


wKioL1UYOGPjYE2ZAADuoQziNd0748.jpg



环信后台架构图

    记者:在2012年大家开始提及到BaaS平台,2013年在移动互联网上就有越来越多的人在谈整合BaaS服务,您认为在BaaS服务上比较适合做些什么?

    马晓宇:它对中小开发团队特别有效。如果你做移动APP开发,首先要有你的用户系统,还有Key-Value数据存储。然后了解推送社交,甚至朋友圈模块,发现很容易地搭建一套这种APP。

    但是用户有什么需求就不好解决,Key-Value即使能存储不同数据,从用户权限管需求这一块就不好办。因为有些应用,比如说只能是领导的秘书可以有一个特定的权限。所以真正到企业级的移动应用,我觉得就比较难做进一步的扩展。我们做了一年这个BaaS平台。

    记者:后面有没有继续?

    马晓宇:没有,不过我们做BaaS平台的这个团队都有科研背景,我们选了Usergrid Apache这顶级的开源项目,同时我们的主要创始人也是Usergrid Committer,通过技术的演变,基于Usergrid开发环信的用户系统。

    记者:据了解你们团队很多高管都有着一些重要的开源项目背景,像Jboss和Hibernate这类顶级的开源项目中也有着丰富的经验,而环信这样的平台大部分的用户都是开发者,而您以及整个团队从某个角度来讲也算是自己产品的用户,也是直接最懂自己用户的。那么您认为最能够让开发者接受的产品最重要的是什么?

    马晓宇:对,首先我们用环信,同时我们也用其他的开发者工具,从统计到一些分析、监控、推送,我们自身觉得比较重要的还是服务。包括我们以前做开源社区,有时候也在一些兄弟公司QQ群里看看,基本上环信是唯一一个每天晚上两三点开发者能找到主要技术人员给你回复,我们在服务商还是获得了很好的评价,这也是我们现在能赢得开发者的认可。

    记者:从开源的角度看,环信把即时通信云的SDK代码开源了,这对于开发者来说是非常有利部署自己的应用,从这方面我们能够看出环信对开源的态度。请谈谈你们是如何看待开源?以及接下来的开源计划是怎样的?

    马晓宇:首先我们是逐步开源的,包括我们的后台。因为我们最终不是靠这个CODE本身,毕竟即时通讯现在都快做成免费市场了,所以我们最终还是要靠服务来赢得市场。

    还有一个,我们其实做开源的时间也比较长,利用这个机会做环信也是想做一些尝试,也一直在推动。我们是希望做环信能做到三个成功。

    第一个是商业成功,首先是产品有价值,我们做的是toD面向开发者和面向企业SAAS服务,最后我们是能有收入,能有一个合理的利润,能够商业成功。

    第二个是技术成功。我们本身的技术栈用到的不光是我们自己研发的技术,同时我们也用了大量的优秀开源软件,从中间件到数据库大多数都是来自国外的。我们在解决移动终端和服务器之间的这种消费程度,或者是跨平台部署。所以我们是希望我们的一个目标,技术成功的目标是说,我们找一个important的问题,然后做一个方案,作为我们商业产品的一部分,同时我们把它进行开源,吸引用户来使用,这也是整个团队需要长期要做的事情。

    第三个是团队成功。我们团队有一部分都是从事过开源项目背景,比较向往开源企业的文化和情怀。这个团队成功最好理解,就是说比如我们有同事家在海南,他就可以在海南工作,拿北京的工资,然后隔几个月来一起开会。

    我们也开始逐渐尝试,在过完年后有一个同事在泰国工作了一星期,然后回来跟我们分享。这样的结果我们认为还是比较靠谱的,在自己比较向往或者熟悉的地方工作效率完全不一样,思路特别清晰。所以我们是希望在商业公司也实现一些开源的企业文化。

    记者:目前市场上即时通信类的产品很多,我个人认为还都比较成熟,但是当技术发展到一定程度的时候,用户往往不会再关注到底技术哪家强,更多的是取决于产品体验以及服务态度。在这方面,您认为环信从哪些方面更能够体现出来?

    马晓宇:现在是三方面,一方面还是服务进一步深化,我觉得接入环信平台,提供IM只是第一步,我们现在专门有一个CSM(Cluster System Management 专门用于集群系统管理的中间件),帮助我们的客户成功。通过第一步的发消息到头像、位置、分级、互动、推送等等提高它的活跃度,让用户真正玩起来。所以我们专门有这么一个团队开始组建,帮助这些比较大的APP真正去分析用户指标,通过我们的数据怎么去帮助他们更多的互动,这是我们叫客户成功。

    第二步其实大家也在做了,就是做数据挖掘,挖掘用户的行为实际上对APP是非常有利的。比如,我们有一个用户是猎聘网,可以通过挖掘用户和露猎头在上面的一些行为关键词进行分析,根据这些数据去指导他的业务。

    第三步就是我们现在逐渐感到有一定用户量后,即时通讯需要保持一个长连接,所以这是有一定费用的。我认为成本优势也是一个优势,企业真正做到百万用户后,每月运维成本可能是几万。但是如果环信能经过优化,提供一半或者四分之一的价格,对企业来讲是挺大一笔费用。

    所以总结一下,第一个是我们的客户成功,第二个是数据挖掘,第三个是成本优势。目前客户成功和数据挖掘我们都启动,成本优势我们还在继续优化过程中,下一步主要是在技术上,我们要把成本给优化下来。

    记者:环信是基于PaaS平台去做的,而像这样的产品也很多,但是真正的提高核心竞争力我认为还是要加强自己的生态圈建设,在不断的收集用户的需求以及痛点并及时解决,完善和壮大自己的产品,在这方面你们是如何建设的呢?

    马晓宇:现在我们是分三层,第一层是我们所有的用户,就是环信的直接用户。

    第二层是我们有一个QQ群的形式,一个QQ群有2000人。我们有五个群,现在建了第六个群。然后在这个基础上,我们有一些基于环信的核心开发者,他们会给我们做demo,或者真正地提交了一个完全开源的APP,就是基于环信的。

    第三层是我们和一些合作伙伴做的一些探索,专门做个IM Geek开源社区,作为我们整个生态圈的核心部分。

    记者:从微信、飞信、易信这些即时通信软件到即时通信云平台,从只需要服务自己一家产品到要服务于成千上万家不同的APP产品,她们的需求也是不一样的,从系统设计和运维的角度,你们如何保证他们的稳定性以及安全性?
   
    马晓宇:不太一样,因为我们是做多租户APP平台,每个APP有不同的需求,所以我们现在是有一个基于APP一些参数可以动态的,这是一部分。

    还有一部分是因为多租户,所以我们对数据安全比较看重。我们参与运维项目主要是用户体系比较好,提供了最基本的用户隔离和安全,完全做好用户访问其他APP信息的隔离。

    另外从运维层面上,平台上运维用户数据比较关键,系统内部规定只有两人有权限,真正登陆以后是能够看到用户。

    还有一部分是大家做云端要解决的一个共享问题,也就是公有云。怎么能保证所有用户体验在操作上不影响其他的用户。最基本的接口有限流,服务器端提供调用接口,可以给你的客户发消息,可以创建群组,可以做用户管理,但这个是有限流的,不能无限制地发,超过限流就会反馈错误。

    最后是队列设计。避免有些APP大量给用户发消息,影响其他用户,所以在系统的这些队列设计上,我们也考虑到让APP尽量隔离。现在我们有高速、低速两条通道,如果发现有大量的信息时就会走到低速队列,我们尽量保持用户体验。

    记者:从近两年来看,我们都能够感觉到移动互联网都是呈现爆发式增长,还有从今年春节我们也看到了大家都比较倾向通过IM通信这种方式拜年,交流,IM通信功能也会被广泛应用在其他的APP当中,面对这种趋势,我们在这方面如何去保证高稳定性及高并发这种情况?还有应急措施有哪些?

    马晓宇:我们会有相应的措施,像您说春节期间的突发状况,我们主要技术人员和运维在春节期间都没怎么休息,就是为了保证整个系统正常运转。首先我们的架构比较好,在后面的数据库、服务器,如果有系统瓶颈的话,我们能够及时加机器。现在我们加机器用的是云,不需要到机房去搭建,基本我们现在做到在分钟级就能够配好整个架构集群。

    真正线上我们有标准的监控,通过购买第三方服务监控全国各地到我们服务器的访问情况。对比系统的指标,比如登陆整个集群时间,登陆每台服务器的时间,监控发消息的整个过程时间。然后监控数据库系统的主要的队列,当发现有队列堆积,我们会及时进一步处理。

    同时我们也监控DB,主要是DB负载,整个系统是自动化的,有问题会报警,防患于未然。然后如果是真出了紧急情况,在系统里有一些降级开关,如果有紧急情况,保证最小可用。所以现在我们的系统设计,在关键模块是有降级开关的,那么降级开关实际上是要运维,我们每个月有一次演习,比如说我们的缓存宕机,整个储存都宕机,如何做到进一步重新加载数据。还有一些没有出现过的情况,假如数据库宕机,如何切换成直接写到log文件,再把log重新导到数据库恢复。

    记者:我们都知道2月27日工信部正式向中国电信和中国联通发放了FDD制式4G牌照,这也意味着移动音频和视频的时代到来了,我认为提高这方面的技术支持给开发者更好的体验将是突破竞争门槛的重要突破点,你们将如何面对这个浪潮?

    马晓宇:我们今年上半年主要的开发方向就是将音视频产品化,现在iOS和安卓都支持实时语音,而且可以互通,但是实时视频只有Android支持,iOS系统在今年4月初我们也会出支持实时视频的release版本, 有开发者基于我们视频SDK,做一些视频交流,这也是4G带宽的优势,把之前的不可能变成为可能。

    另外除了我们基础功能之外,我们还深入研究,在一对一语音接通率进行优化。大家在Wifi下,中间要跨防火墙,所以有接通率,我们现在在改进这一块。还有在4G网络下,当网络不稳定或弱网络的情况下,我们也做了优化。通过优化对音视频掉包补偿算法,在标准算法的基础上再做进一步优化。

    现在看到了很多机会,很多APP开始集成视频功能。包括我们一个合作伙伴,基于我们多人语音开始做智能的导航设备。通过定位,基于4G加入频道通话。现在实验是在3G网,但在使用上设备绑定是4G卡,基于云端。

    记者:还是要看未来终端的发展,兼容性好的话可以往上发力。从即时通信产品的多样化来看,环信还是显得比较单一,是做“专”还是做“全”,环信在未来将有哪样的布局?

    马晓宇:首先我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。现在很多巨头都在定标准,我们目前只提供SDK,让设备之间互联。

    同时,我们从去年年底基于即时通讯提供SaaS服务,它能提供移动应用实时客服,这和以前的呼叫中心不太一样,我们围绕的客户是一些移动应用,进入APP后点击帮助就可以通过IM技术和后台的客服沟通,给你提供帮助。 收起阅读 »

关于发帖礼仪

1. 问题尽量描述清楚,一般需要包括平台、编程语言、版本、错误信息 2. 问题是否得到了解答,需要回帖说明,如果有时间可以总结解决过程并贴出来 3. 如果你认同别人的回复,记得点赞;  4. 如果别人的回帖对你有帮助,记得点“感谢”
1. 问题尽量描述清楚,一般需要包括平台、编程语言、版本、错误信息
2. 问题是否得到了解答,需要回帖说明,如果有时间可以总结解决过程并贴出来
3. 如果你认同别人的回复,记得点赞; 
4. 如果别人的回帖对你有帮助,记得点“感谢”

APICloud牵手环信 为你的App量身打造精巧丰富社交功能

移动互联网时代,用户对于移动应用的各种功能的要求正变得越来越高,而即时通讯就是其中之一。说到通信,可能首先想到的是微信、陌陌、易信这样的工具,但实际上,IM通信功能被更广泛地用于其他的移动应用中,例如会议系统、协作工具,或是其他的社交平台中的附属功能。App开...
继续阅读 »
移动互联网时代,用户对于移动应用的各种功能的要求正变得越来越高,而即时通讯就是其中之一。说到通信,可能首先想到的是微信、陌陌、易信这样的工具,但实际上,IM通信功能被更广泛地用于其他的移动应用中,例如会议系统、协作工具,或是其他的社交平台中的附属功能。App开发过程中如果让开发者自己去做,不仅耗时耗力,要保证数百万甚至上亿人即时通讯的顺畅、安全,可不是一件易事。

561830035.jpg


环信是国内即时通讯云服务领域第一品牌,提供基于移动互联网的即时通讯能力,如单聊、群聊、发语音、发图片、发位置、实时音频、实时视频等,通过云端开放的Rest API 和客户端 SDK包的方式提供给开发者和企业。让App内置聊天功能和以前网页中嵌入分享功能一样简单。同时环信还基于领先的即时通讯云技术,提供专业的移动客服平台。

环信一直致力于为开发者提供更稳定更好用的即时通讯云服务,让APP内置聊天功能和以前网页中嵌入分享功能一样简单。环信能够帮助开发者在一天时间内为APP极简、极速集成单聊、群聊等社交功能。截至2015年上半年,环信共服务了23062家APP客户,SDK覆盖用户数高达2.51亿。已覆盖包括移动电商、P2P金融、移动医疗、O2O、移动教育、移动广告、移动游戏、移动新闻、移动旅游、移动健康管理、智能硬件等20大领域的Top10客户。

近日,环信即时通讯云服务提供商与国内领先的“云端一体”移动应用云服务提供商APICloud达成合作。APICloud平台帮助开发者快速实现移动应用的开发、测试、发布、管理和运营的全生命周期管理。APICloud为开发者从“云”和“端”两个方向提供API,简化移动应用开发技术,让移动应用的开发周期从一个月缩短到7天。通过APICloud平台,只需添加移动环信SDK即可快速实现iOS和安卓双平台的即时通讯功能,快速实现App开发。

双方此次强强联合,大大降低了互联网创业的门槛,尤其是让传统行业的中小型商户也有了触网的机会。同时,创业者和开发者可以把精力放到自己的核心业务上,提升用户体验和粘性,来开拓更广阔的商业价值。 收起阅读 »

imgeek可以用QQ, google登陆了

大家帮测试一下,看有没有问题
大家帮测试一下,看有没有问题

关于UIView 你知道多少?

    曾经有人这么说过,在iphone里你看到的,摸到的,都是UIView,所以UIView在iphone开发里具有非常重要的作用。那么UIView我们到底知道多少呢。请看看下面的问题, 如果这些你都知道,那么本文章的内容就请绕道,如果你还不太清楚,我想看了...
继续阅读 »
    曾经有人这么说过,在iphone里你看到的,摸到的,都是UIView,所以UIView在iphone开发里具有非常重要的作用。那么UIView我们到底知道多少呢。请看看下面的问题, 如果这些你都知道,那么本文章的内容就请绕道,如果你还不太清楚,我想看了下面的内容,你就明白了。
 

  • bounds和frame分别表示什么?

  • ContentMode里UIViewContentModeScaleToFill代表什么?

  • contentStretch 里的指定UIView里缩放区域是如何计算的?

  • UIVIew里的哪些属性变化可以用动画来呈现?

  • UIKit的坐标系和Core Graphics的坐标系的差别是什么? 




     视图和窗口展示了应用的用户界面,同时负责界面的交互。UIKit和其他系统框架提供了很多视图,你可以就地使用而几乎不需要修改。当你需要展示的内容与标准视图允许的有很大的差别时,你也可以定义自己的视图。

    不管你是使用系统的视图还是创建自己的视图,你需要理解UIView和UIWindow类所提供的基本结构。这些类提供了复杂的方法来管理视图的布局和展示。理解这些方法的工作非常重要,使你在应用发生改变时可以确认视图有合适的行为。

 视图架构 fundamentals 

    大部分你想要可视化操作都是由视图对象-即UIView类的实例-来进行的。一个视图对象定义了一个屏幕上的一个矩形区域,同时处理该区域的绘制和触屏事件。一个视图也可以作为其他视图的父视图,同时决定着这些子视图的位置和大小。UIView类做了大量的工作去管理这些内部视图的关系,但是需要的时候你也可以定制默认的行为。

    视图与Core Animation层联合起来处理着视图内容的解释和动画过渡。每个UIKit框架里的视图都被一个层对象支持(通常是一个CALayer类的实例),它管理管理着后台的视图存储和处理视图相关的动画。然而,当你需要对视图的解释和动画行为有更多的控制权时,你可以使用层。

    为了理解视图和层之间的关系,我们可以借助于一些例子。图1-1显示了ViewTransitions样例程序的视图层次及其对底层Core Animation层的关系。应用中的视图包括了一个window(同时也是一个视图),一个通用的表现得像一个容器视图的UIView对象,一个图像视图,一个控制显示用的工具条,和一个工具条按钮(它本身不是一个视图但是在内部管理着一个视图)。(注意这个应用包含了一个额外的图像视图,它是用来实现动画的)。为了简化,同时因为这个视图通常是被隐藏的,所以没把它包含在下面的图中。每个视图都有一个相应的层对象,它可以通过视图礶r属性被访问。(因为工具条按钮不是一个视图,你不能直接访问它的层对象。)在它们的层对象之后是Core Animation的解释对象,最后是用来管理屏幕上的位的硬件缓存。 
Figure 1-1 View architecture

1.jpg

    使用Core Animation的层对象有很重要的性能意义。一个视图对象的绘制代码需要尽量的少被调用,当它被调用时,其绘制结果会被Core Animation缓存起来并在往后可以被尽可能的重用。重用已经解释过的内容消除了通常需要更新视图的开销昂贵的绘制周期。内容的重用在动画中特别重要,我们可以使用已有的内容,这样比创建新的内容开销更小。

    视图层次和子视图管理

    除了提供自己的内容之外,一个视图也可以表现得像一个容器。当一个视图包含其他视图时,就在两个视图之间创建了一个父子关系。在这个关系中孩子视图被当作子视图,父视图被当作超视图。创建这样一个关系对应用的可视化和行为都有重要的意义。

    在视觉上,子视图隐藏了父视图的内容。如果子视图是完全不透明的,那么子视图所占据的区域就完全的隐藏了父视图的相应区域。如果子视图是部分透明的,那么两个视图在显示在屏幕上之前就混合在一起了。每个父视图都用一个有序的数组存储着它的子视图,存储的顺序会影响到每个子视图的显示效果。如果两个兄弟子视图重叠在一起,后来被加入的那个(或者说是排在子视图数组后面的那个)出现在另一个上面。

   父子视图关系也影响着一些视图行为。改变父视图的尺寸会连带着改变子视图的尺寸和位置。在这种情况下,你可以通过合适的配置视图来重定义子视图的尺寸。其他会影响到子视图的改变包括隐藏父视图,改变父视图的alpha值,或者转换父视图。

    视图层次的安排也会决定着应用如何去响应事件。在一个具体的视图内部发生的触摸事件通常会被直接发送到该视图去处理。然而,如果该视图没有处理,它会将该事件传递给它的父视图,在响应者链中以此类推。具体视图可能也会传递事件给一个干预响应者对象,像视图控制器。如果没有对象处理这个事件,它最终会到达应用对象,此时通常就被丢弃了。

    获取更多关于如何创建视图层次,查看 creating and managing a view hierarchy 

    视图绘制周期

    UIView类使用一个点播绘制模型来展示内容。当一个视图第一次出现在屏幕前,系统会要求它绘制自己的内容。在该流程中,系统会创建一个快照,这个快照是出现在屏幕中的视图内容的可见部分。如果你从来没有改变视图的内容,这个视图的绘制代码可能永远不会再被调用。这个快照图像在大部分涉及到视图的操作中被重用。
如果你确实改变了视图内容,也不会直接的重新绘制视图内容。相反,使用setNeedsDisplay或者   setNeedsDisplayInRect:方法废止该视图,同时让系统在稍候重画内容。系统等待当前运行循环结束,然后开始绘制操作。这个延迟给了你一个机会来废止多个视图,从你的层次中增加或者删除视图,隐藏,重设大小和重定位视图。所有你做的改变会稍候在同一时间反应。

    注意:改变一个视图的几何结构不会自动引起系统重画内容。视图的contentMode属性决定了改变几何结构应该如果解释。大部分内容模式在视图的边界内拉伸或者重定位了已有快照,它不会重新创建一个新的快照。获取更多关于内容模式如果影响视图的绘制周期,查看 content modes 

    当绘制视图内容的时候到了时,真正的绘制流程会根据视图及其配置改变。系统视图通常会实现私有的绘制方法来解释它们的视图,(那些相同的系统视图经常开发接口,好让你可以用来配置视图的真正表现。)对于定制的UIView子类,你通常可以覆盖drawRect:方法并使用该方法来绘制你的视图内容。也有其他方法来提供视图内容,像直接在底部的层设置内容,但是覆盖drawRect:时最通用的技术

    内容模式

    视图的内容模式控制着视图如何回收内容来响应视图几何结构的变化,也控制着是否需要回收内容。当一个视图第一次显示时,它通常会解释内容,其结果会被底层的层级树捕获为一张位图。在那之后,改变视图的几何结构不会导致重新创建位图。相反,视图中contentMode属性的值决定着这张位图是否该被拉伸,以适应新的边界或者只是简单的被放到角落或者视图的边界。

视图的内容模式在你进行如下操作时被应用:

  • 改变视图frame或者bounds矩形的宽度或者高度时。

  • 赋值给视图的transform属性,新的转换包括一个放缩因子。

  • 大部分视图的contentMode值是UIViewContentModeScaleToFiill,它使视图的内容被放缩到适合新框架的值。Figure 1-2展示了使用其他可用的内容模式的结果。正如你在图中所看到的那样,不是所有的内容模式都可以填充视图的范围,可以的模式可能会扭曲内容。

  • 内容模式很好的支持了视图的内容回收,但是当你想视图在放缩和重设尺寸的操作中重绘你也可以用UIViewContentModeRedraw内容模式。设置这个值绘强制系统调用视图的drawRect:方法来响应几何结构的变化。通常来讲,你应该尽可能的避免使用这个模式,同时你不应该在标准的系统视图中使用这个模式。

  • 获取更多骨干与可用的内容模式,查看UIView Class Reference


Figure 1-2

2.jpg

    拉伸视图

    你可以指定视图的某部分为可拉伸的,以便当视图的尺寸改变时只有可拉伸的部分被影响到。可拉伸的部分通常给按钮或者其他的部分为重复模式的视图。由你指定的可拉伸区域允许沿着两条或者其中一条轴拉伸。当然,当一个视图沿着两条轴拉伸的时候,视图的边界必须也定义了一个重复的模式来避免任何的扭曲。Figure1-3展示了这种扭曲在视图里是怎么表现自己的。每个视图里的原始像素的颜色都自我复制,以便可以填充更大视图的相应区域。
Figure 1-3 拉伸一个按钮的背景
你可以用contentStretch属性来定义一个视图的可拉伸区域。这个属性的值一个边的值被标准化为0.0到1.0之间的矩形。当拉伸这个视图时,系统将视图的当前边界值和放缩因子乘以标准值,以便决定哪些像素需要被拉伸。使用标准值可以减轻每次改变视图的边界值都更新contentStretch属性的需要。
视图的内容模式也在决定如何视图的可拉伸区域的使用中扮演着重要的角色。只有当内容模式可能绘引起视图内容放缩的时候可拉伸区域才会被使用。这意味这你的可拉伸视图只被UIViewContentModeScaleToFill, UIViewContentModeScaleAspectFit和UIViewContentModeScaleAspectFill内容模式。如果你指定了一个将内容弹到边界或者角落的内容模式(这样就没有真正的放缩内容),这个视图会忽视可拉伸区域。
注意:当需要创建一个可拉伸UIImage对象作为视图的背景时,使用contentStretch属性是推荐的。可拉伸视图完全被Core Animation层处理,这样性能通常更好。

    嵌入式动画支持

    使用层对象来支持视图的其中一个利益是你可以轻松的用动画处理视图相关的改变。动画是与用户进行信息交流的一个有用的方法,而且应该总是在进行应用设计的过程中考虑使用动画。UIView类的很多属性是动画化的-也就是,可以半自动的从一个值动画的变化到另一个值。为了实现这样一个动画,你需要做的只是:

  • 告诉UIKit你想要实现一个动画


改变这个属性的值

  • 在一个UIView对象中有以下的动画化属性:


frame - 你可以使用这个来动画的改变视图的尺寸和位置
bounds - 使用这个可以动画的改变视图的尺寸
center - 使用这个可以动画的改变视图的位置
transform - 使用这个可以翻转或者放缩视图
alpha - 使用这个可以改变视图的透明度
backgroundColor - 使用这个可以改变视图的背景颜色
contentStretch - 使用这个可以改变视图内容如何拉伸

    动画的一个很重要的地方是用于从一组视图到另一组视图的过渡。通常来说,会用一个视图控制器来管理关系到用户界面的主要变更的动画。例如,涉及到从高层到底层信息的导航的界面,通常会使用一个导航控制器来管理视图的过渡,这些视图显示了数据的每一个连续层面。然而,你也可以使用动画来创建两组视图的过渡,而不是视图控制器。当你想用一个系统提供的视图控制器无法支持的导航方案时你可能会这样做。
    除了用UIKit类可以创建动画外,你也可以用Core Animation层来创建动画。在更低层你有更多的在时间或者动画属性上的控制权。
    获取更多关于如何创建一个基于视图的动画,查看 Animations
    获取更多关于使用Core Animation创建动画的信息,查看Core Animation Programming Guide和Core Animation Cookbook.

    视图几何结构和坐标系统

    UIKit的默认坐标系统把原点设置在左上角,两条轴往下和右扩展。做标志被表示为浮点数,这样允许内容的精确布局和定位而不管底层的屏幕。Figure1-4展示了相对于屏幕的坐标系统。除了屏幕坐标系统窗口和视图也定义了它们自己的本地坐标系统,这样允许你指定相对于视图或者窗口原点的坐标而不是屏幕。
Figure 1-4 UIKit中的坐标系统

7.jpg

 
    因为每个视图和窗口都定义了它自己的本地坐标系统,你需要留意在任何时间内是哪个坐标系统在起作用。每次绘制或者改变一个视图都是基于一个坐标系统的。在某些绘制中会基于视图本身的坐标系统。在某些几何结构变更中是基于父视图的坐标系统的。UIWindow和UIView类都包含了帮助你从一个坐标系统转换到另一个的方法。
重要:一些iOS技术定义了默认的坐标系统,它们的原点和方向与UIKit的不同。;例如,Core Graphics和OpenGL ES的坐标系统是原点在可视区域的左下角,而y轴往上递增。当绘制或者创建内容时,你的代码应该考虑到一些不同并且适应坐标值。
    frame, bounds和center属性之间的关系

  • 视图对象使用frame, bounds和center属性来跟踪它的尺寸和位置:

  • frame属性包含了frame矩形,指定了在父视图坐标系统中该视图的尺寸和位置。

  • center属性包含了在父视图坐标系统中的已知中心点。

  • bounds属性包含了边界矩形,指定了在视图本地坐标系统中视图的尺寸。


 
    主要使用center和frame属性来控制当前视图的几何结构。例如,当在运行时构建你的视图层次或者改变视图的尺寸或者位置时你可以使用这些属性。如果你只是要改变视图的位置,那么推荐使用center属性。center属性的值永远是可用的,即使添加了放缩或者转换因子到视图的转换矩阵当中。但是对于frame属性却不是,当视图的转换矩形不等于原始矩阵时它被当作时无效的。

    在绘制的过程中主要使用bounds属性。这个边界矩阵在视图的本地坐标系统被解释。这个矩形的默认原点是(0, 0),它的尺寸也适应frame矩形的尺寸。任何绘制在这个矩形当中的东西都是该视图的可视内容的一部分。如果你改变了bounds矩形的原点,任何你绘制在新矩形的东西都会变成该视图可视内容的一部分。
Figure1-5展示了一个图像视图的frame和bounds矩形之间的关系。图中,图像视图的右上角被定位在父视图坐标系统的(40, 40),它的矩形尺寸为240x380。对于bounds矩形,原点是(0, 0),矩形尺寸也是240x380。
Figure 1-5 视图frame和bounds之间的关系

6.jpg

    即使你可以独立的改变frame,bounds和center属性,其中一个改变还是会影响到另外两个属性:

    当你设置了frame属性,bounds属性的尺寸值也改变来适应frame矩形的新尺寸。center属性也会改变为新frame矩形的中心值。

  • 当你设置了center属性,frame的原点也会相应的改变。

  • 当你设置了bounds属性,frame属性会改变以适应bounds矩形的新尺寸。


 
    视图的框架默认不会被它的父视图框架裁剪。这样的化,任何放置在父视图外的子视图都会被完整的解释。你可以改变这种行为,改变父视图的clipsToBounds属性就可以。不管子视图是否在视觉上被裁剪,触屏事件总是发生在目标视图父视图的bounds矩形。换句话说,如果触摸位于父视图外的那部分视图,那么该事件不会被发送到该视图。

    坐标系统转换矩阵

    坐标系统转换矩阵给改变视图(或者是它的视图)提供了一个轻松和简易的方法。一个仿射转换是一个数学矩阵,它指定了在坐标系统中的点是怎么被映射到另一个坐标系统中的点。你可以对整个视图应用仿射转换,以基于其父视图来改变视图的尺寸,位置或者朝向。你也可以在你的绘制代码中应用仿射转换,以对已解释内容的独立部分实现相同的操控。如何应用仿射转换是基于这样的上下文的:

  • 为了修改整个视图,可以修改视图transform属性的仿射转换值。

  • 为了在视图中的drawRect:方法中修改内容的指定部分,可以修改与当前图形上下文相关的仿射转换。


 
    当你想实现动画时,通常可以修改视图的transform属性值。例如,你可以使用这个属性来制作一个视图围绕中心点翻转的动画。你不应该在其父视图的坐标空间中用这个属性来永久的改变你的视图,像修改它的位置和尺寸。对于这种类型的改变,你可以修改视图的frame矩形。

    注意:当修改视图的transform属性值时,所有的转换都是基于视图的中心点来实现的。

    在视图的drawRect:方法中,你可以使用仿射转换来定位或者翻转你想要绘制的项目。相对于在视图某些部位中修正对象的位置,我们更倾向于相对于一个固定点去创建对象,通常是(0, 0),同时在绘制之前使用转换来定位对象。这样的话,如果在视图中对象的位置改变了,你要做的只是修改转换矩阵,这样比为对象重新创建新的位置性能更好开销更低。你可以通过使用CGContextGetCTM方法来获取关于图形上下文的仿射转换,同时可以用Core Graphics的相关方法在绘制中来设置或者修改这个转换矩阵。

    当前转换矩阵(CTM)是一个在任何时候都被使用的仿射矩阵。当操控整个视图的几何结构时,CTM就是视图transform属性的值。在drawRect:方法中,CTM是关于图形上下文的仿射矩阵。

    每个子视图的坐标系统都是构建在其祖先的坐标系统之上的。所以当你修改一个视图的transform属性,这个改变会影响到视图及其所有的子视图。然而,这些改变只会影响到屏幕上视图的最终解释。因为每个视图都负责绘制自己的内容和对自己的子视图进行布局,所以在绘制和布局的过程中它可以忽略父视图的转换。

    Figure1- 6描述了在解释的时候,两个不同的转换因子是如何在视觉上组合起来的。在视图的drawRect:方法中,对一个形状应用一个45度的转换因子会使该形状翻转指定的角度。另外加上一个45度的转换因子会导致整个形状翻转90度。这个形状对于绘制它的视图来讲仍然只是翻转了45度,但是视图自己的转换让它看起来像使翻转了90度。
Figure 1-6 翻转一个视图和它的内容

5.jpg

    重要:如果一个视图的transform属性不是其定义时转换矩阵,那么视图的frame属性是未定义的而且必须被忽略。当对视图应用转换时,你必须使用视图的bounds和center属性来获取视图的位置和尺寸。子视图的frame矩形仍然是有效的,因为它们与视图的bounds相关。

    获取更多关于在运行时修改视图的transform属性,查看 “Translating, Scaling, and Rotating Views.”获取更多如何在绘制过程中使用转换来定位内容,查看 Drawing and Printing Guide for iOS.
点与像素在iOS中,所有的坐标值和距离都被指定为使用浮点数,其单元值称为点。点的数量随着设备的不同而不同,而且彼此不相关。要明白关于点的最主要一点是它们提供了一个绘制用的固定框架。
Table 1-1 列出了不同iOS设备的分辨率(点度量)。前为宽后为长。只要你依照这些屏幕的尺寸来设计用户界面,你的视图就回被相应的设备正确显示。

Table 1-1 

Device                                            Screen dimensions (in points)

iPhone and iPod touch                      320 x 480

iPad                                               768 x 1024

    每一种使用基于点度量系统的设备都定义了一个用户坐标空间。这是几乎在你所有的代码都会用到的标准坐标空间。例如,当你要操控视图的几何结构或者调用Core Graphics方法来绘制内容时会用到点和用户坐标空间。即使有时用户坐标空间里的坐标时直接映射到设备屏幕的像素,你还是永远不应该假设这是永远不变的。相反,你应该记住:一个点并不一定对应着屏幕上的一个像素在设备层面,所有由你指定的视图上的坐标在某些点上必须被转化成像素。然而,从用户坐标空间上的点到设备坐标空间上的像素通常由系统来处理。UIKit和Core Graphics都主要使用基于向量的绘制模型,所有的坐标值都被指定为使用点。这样,如果你用Core Graphics画了一条曲线,你会用一些值来指定这条曲线,而不管底层屏幕使用怎样的解决方法。

    当你需要处理图像或者其他基于像素的技术,像OpenGL ES时,iOS会帮你管理这些像素。对于存储为应用程序的束中的资源的静态图像文件,iOS定义了一些约定,可以指定不同像素密度的图像,也可以在加载图像时最大限度的适应当前屏幕的解决方案。视图也提供了关于当前放缩因子的信息,以便你可以适当的调整任何基于像素的绘制代码来适应有更高级解决方案的屏幕。在不同屏幕的解决方案中处理基于像素内容的技术可以在"Supporting High-Resolution Screens"和"Drawing and Printing Guide for iOS"找到描述。 

    视图的运行时交互模型 

    当用户和界面进行交互时,或者由代码程序性的改变一些东西时,一系列复杂的事件就会发生在UIKit的内部来处理这些交互。在这个系列中的某些点,UIKit唤出你的视图类,同时给它们一个机会去响应程序的行为。理解这些唤出点对于理解视图在哪里融入系统很重要。Figure 1-7 展示了这些事件的基本序列,从用户触屏开始到图形系统更新屏幕内容来响应结束。同样的事件序列也会发生在任何程序性启动的动作。
Figure 1-7 UIKit 与视图对象进行交互

4.jpg


以下的步骤分解了图1-7中的事件序列,既解释了在每一步发生了什么,也解释了应用如何响应
 

  • 用户触屏


 

  • 硬件报告触摸事件给UIKit框架


 

  • UIKit框架将触摸事件打包成UIEvent对象,同时分发给适合的视图。(对于UIKit框架如何提交事件给视图的详细解释,查看 Event Handing Guide for iOS)


 

  • 视图中的事件处理代码可能进行以下的动作来响应:


改变视图或者其子视图的属性(frame, bounds, alpha, 等等)
调用setNeedsLayout方法以标记该视图(或者它的子视图)为需要进行布局更新
调用setNeedsDisplay或者setNeedsDisplayInRect:方法以标记该视图(或者它的子视图)需要进行重画
通知一个控制器关于一些数据的更新
当然,哪些事情要做,哪些方法要被调用是由视图来决定的。
 

  • 5 如果一个视图的几何结构改变了,UIKit会根据以下几条规则来更新它的子视图:


a 如果自动重设尺寸的规则在发生作用,UIKit会根据这些规则来调整视图。获取更多关于自动重设尺寸规则如何工作,查看"Handling Layout Changes Automatically Using Autoresizing Rules."
b 如果视图实现了layoutSubviews方法,UIKit会调用它。你可以在你的定制视图中覆盖这个方法同时用它来调整任何子视图的位置和大小。例如,一个提供了巨大滚动区域的视图会需要使用几个子视图作为“瓦块”而不是创建一个不太可能放进内存的巨大视图。在这个方法的实现中,视图会隐藏任何屏幕外的子视图,或者重定位它们然后用来绘制新的可视内容。作为这个流程的一部分,视图的布局代码也可以废止任何需要被重画的视图。
 

  • 如果任何视图的任何部分被标记为需要重画,UIKit会要求视图重画自身。


对于显式的定义了drawRect:方法的定制视图,UIKit会调用这个方法。这方法的实现应该尽快重画视图的指定区域,并且不应该再做其他事。不要在这个点上做额外的布局,也不要改变应用的数据模型。提供这个方法仅仅是为了更新视图的可视内容。
标准的系统视图通常不会实现drawRect:方法,但是也会在这个时候管理它们的绘制。
 

  • 任何已经更新的视图会与应用余下的可视内容组合在一起,同时被发送到图形硬件去显示。


 

  • 图形硬件将已解释内容转化到屏幕上。




注意:上面的更新模型主要应用于使用标准系统视图和绘制技术的应用。使用OpenGL ES来绘制的应用通常会配置一个单一的全屏视图和直接绘制相关的OpenGL图像上下文。你的视图还是应该处理触屏事件,但是它是全屏的,毋需给子视图布局或者实现drawRect:方法。获取更多关于使用OpenGL ES的信息,查看 OpenGL ES Programming Guide for iOS.

给定之前的一系列步骤,将自己的定制视图整合进去的方法包括:

事件处理方法:

touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
layoutSubviews方法
drawRect:方法
    这些是视图的最常用的覆盖方法,但是你可能不需要覆盖全部。如果你使用手势识别来处理事件,你不需要覆盖事件处理方法。相似的,如果你的视图没有包含子视图或者它的尺寸不会改变,那就没有理由去覆盖layoutSubviews方法。最后,只有当视图内容会在运行时改变,同时你要用UIKit或者Core Graphics等本地技术来绘制时才需要用到drawRect。

    要记住这些是主要的整合点,但是不仅仅只有这些。UIView类中有些方法是专门设计来给子类覆盖的。你应该到UIView Class Reference中查看这些方法的描述,以便在定制时清楚哪些方法适合给你覆盖。

    有效使用视图的提示 

    当你需要绘制一些标准系统视图不能提供的内容时,定制视图是很有用的。但是你要负责保证视图的性能要足够的高。UIKit会尽可能的优化视图相关的行为,也会帮助你提高性能。然而,考虑一些提示可以帮助到UIKit。 
重要:在调整绘制代码之前,你应该一直收集与你视图当前性能有关的数据。估量当前性能让你可以确定是否真的有问题,同时如果真的有问题,它也提供一个基线,让你在未来的优化中可以比较。
视图不会总是有一个相应的视图控制器 

    在应用中,视图和视图控制器之间的一对一关系是很少见的。视图控制器的工作是管理一个视图层次,而视图层次经常是包含了多个视图,它们都有自包含特性。对于iPhone应用,每个视图层次通常都填满了整个屏幕,尽管对于iPad应用来说不是。 

    当你设计用户界面的时候,考虑到视图控制器的所扮演的角色是很重要的。视图控制器提供了很多重要的行为,像协调视图的展示,协调视图的剔除,释放内存以响应低内存警告,还有翻转视图以响应界面的方向变更。逃避这些行为会导致应用发生错误。

获取更多关于视图控制器的信息,查看 View Controller Programming Guide for iOS 

    最小化定制的绘画

    尽管定制的绘画有时是需要的,但是你也应该尽量避免它。真正需要定制绘画的时候是已有的视图类无法提供足够的表现和能力时。任何时候你的内容都应该可以被组装到其他视图,最好结果时组合那些视图对象到定制的视图层次 

    利用内容模式 

    内容模式可以最小化重画视图要花费的时间。默认的,视图使用UIViewContentModeScaleToFill 内容模式,这个模式会放缩视图的已有内容来填充视图的frame矩形。需要时你可以改变这个模式来调整你的内容,但是应该避免使用UIViewContentModeRedraw内容模式。不管哪个内容模式发生作用,你都可以调用setNeedsDisplay或者setNeedsDisplayInRect:方法来强制视图重画它的内容。

    可能的话将视图声明为不透明

    UIKit使用opaque属性来决定它是否可以优化组合操作。将一个定制视图的这个属性设置为YES会告诉UIKit不需要解释任何在该视图后的内容。这样可以为你的绘制代码提高性能并且是推荐的。当然,如果你将这个属性设置为YES,你的视图一定要用不透明的内容完全填充它的bounds矩形。

    滚动时调整视图的绘制行为

    滚动会导致数个视图在短时间内更新。如果视图的绘制代码没有被适当的调整,滚动的性能会非常的缓慢。相对于总是保证视图内容的平庸,我们更倾向于考虑滚动操作开始时改变视图行为。例如,你可以暂时减少已解释的内容,或者在滚动的时候改变内容模式。当滚动停止时,你可以将视图返回到前一状态,同时需要时更新内容。
 
    不要嵌入子视图来定制控制 

    尽管在技术上增加子视图到标准系统控制对象-继承自UIControl的类-是可行的,你还是永远不应该用这种方法来定制它们。控制对象支持定制,它们有显式并且良好归档的接口。例如,UIButton类包含了设置标题和背景图片的方法。使用已定义好的定制点意味着你的代码总是会正确的工作。不用这些方法,而嵌入一个定制的图像视图或者标签到按钮中去会导致应用出现未预期的结果。 收起阅读 »

从showalert方法中看iOS中的MVC

我们知道MVC是个模式,一个开发模式,一个构架模式,一个放之四海而皆为准的模式。 那什么是MVC?  MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分: 模型(Model)、视图(View)和控...
继续阅读 »
我们知道MVC是个模式,一个开发模式,一个构架模式,一个放之四海而皆为准的模式。
那什么是MVC? 
MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:
模型(Model)、视图(View)和控制器(Controller)。
  • (控制器 Controller)- 负责转发请求,对请求进行处理。
  • (视图 View) - 界面设计人员进行图形界面设计。
  • (模型 Model) -程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

那么iOS中的showAlert函数又是什么样子的呢?
我们来看一个最简单的例子: 

@IBAction func showAlert(){

        let alert = UIAlertController(title: "Hello,World", message: "This is my first app!", preferredStyle: .Alert)

        let action = UIAlertAction(title: "Awesome", style: .Default, handler: nil)

        alert.addAction(action)

        presentViewController(alert, animated: true, completion: nil)
    }
那我们根据MVC来一一确认这些都代表什么意思?
let alert 是一个UIAlertController,很明显,首先它是一个Controller,但是我更感觉它像MV,
如 title,message很明显是View的内容,preferredStyle 表示是一个什么方式,应该是属于Model,功能的设计。
let Action 是一个 UIAlertAction,其中包含了 View 和 Control 的内容。title,style应属于
View 内容。handler是属于Controller。
alert.addAction(action) 将Action添加到Controller中,这是一个混合了View和Model和Controll
的组合。
presentViewController表示在当前视图中将对应的controller表示出来,这不折不扣的就是controller
里面的参数就是前面对应已经添加了action的alert。 收起阅读 »

小窍门:关于环信的问题,如果想收到更及时的回复......

小窍门:关于环信的问题,提交问题后, 如果是关于android的问题,邀请se_android如果是关于ios的问题,邀请se_ios如果是其它问题,记得邀请SE  相关人员将会收到邮件提醒,以便及时回复
小窍门:关于环信的问题,提交问题后,
  • 如果是关于android的问题,邀请se_android
  • 如果是关于ios的问题,邀请se_ios
  • 如果是其它问题,记得邀请SE

 相关人员将会收到邮件提醒,以便及时回复

用swift集成iOS聊天页面

今天有人问swift集成问题,临时写了一个。基于ios sdk2.1.8,没有集成实时语音电话页面,但是应该也能用,抛砖引玉。 http://pan.baidu.com/s/1dDlOYzv
今天有人问swift集成问题,临时写了一个。基于ios sdk2.1.8,没有集成实时语音电话页面,但是应该也能用,抛砖引玉。
http://pan.baidu.com/s/1dDlOYzv

开发技巧:iOS 9适配系列教程--后台定位

Demo:GitHub地址:https://github.com/ChenYilong/iOS9AdaptationTips 【iOS9在定位的问题上,有一个坏消息一个好消息】坏消息:如果不适配iOS9,就不能偷偷在后台定位(不带蓝条,见图)!好消息:将允许出...
继续阅读 »

1.jpg


Demo:GitHub地址:https://github.com/ChenYilong/iOS9AdaptationTips
【iOS9在定位的问题上,有一个坏消息一个好消息】坏消息:如果不适配iOS9,就不能偷偷在后台定位(不带蓝条,见图)!好消息:将允许出现这种场景:同一App中的多个location manager:一些只能在前台定位,另一些可在后台定位,并可随时开启或者关闭特定location manager的后台定位。
如果没有请求后台定位的权限,也是可以在后台定位的,不过会带蓝条:
【iOS9在定位的问题上,有一个坏消息一个好消息】坏消息:如果不适配iOS9,就不能偷偷在后台定位(不带蓝条,见图)!好消息:将允许出现这种场景:同一App中的多个location manager:一些只能在前台定位,另一些可在后台定位,并可随时开启或者关闭特定location manager的后台定位。
如果没有请求后台定位的权限,也是可以在后台定位的,不过会带蓝条:

2.jpg


 // 1. 实例化定位管理器
_locationManager = [[CLLocationManager alloc] init];
// 2. 设置代理
_locationManager.delegate = self;
// 3. 定位精度
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
// 4.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
//注意:建议只请求?和?中的一个,如果两个权限都需要,只请求?即可,
//??这样的顺序,将导致bug:第一次启动程序后,系统将只请求?的权限,?的权限系统不会请求,只会在下一次启动应用时请求?
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
    //[_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
    [_locationManager requestAlwaysAuthorization];//?在后台也可定位
}
// 5.iOS9新特性:将允许出现这种场景:同一app中多个location manager:一些只能在前台定位,另一些可在后台定位(并可随时禁止其后台定位)。
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9) {
    _locationManager.allowsBackgroundLocationUpdates = YES;
}
// 6. 更新用户位置
[_locationManager startUpdatingLocation];
但是如果照着这种方式尝试,而没有配置Info.plist,100%你的程序会崩溃掉,并报错:

*** Assertion failure in -[CLLocationManager setAllowsBackgroundLocationUpdates:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/CoreLocationFramework_Sim/CoreLocation-1808.1.5/Framework/CoreLocation/CLLocationManager.m:593
要将 Info.plist 配置如下:

1434597259325009.png


对应的 Info.plist 的XML源码是:

4.png


内容来源:ChenYilong 收起阅读 »

开发技巧:iOS界面布局之一——使用autoresizing进行动态布局

autoresizing是iOS开发中传统的布局模式。通过它可以设计控件与其父视图的变换关系。autoresizing是iOS中传统的界面自动布局方式,通过它,当父视图frame变换时,子视图会自动的做出相应的调整。 一、通过代码进行布局 任何一个view...
继续阅读 »
autoresizing是iOS开发中传统的布局模式。通过它可以设计控件与其父视图的变换关系。autoresizing是iOS中传统的界面自动布局方式,通过它,当父视图frame变换时,子视图会自动的做出相应的调整。
一、通过代码进行布局
任何一个view都有autoresizingMask这个属性,通过这个属性可以设置当前view与其父视图的相对关系。我们先来看UIViewAutoresizing这个枚举:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,//默认
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,//与父视图右边间距固定,左边可变
    UIViewAutoresizingFlexibleWidth        = 1 << 1,//视图宽度可变
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,//与父视图左边间距固定,右边可变
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,//与父视图下边间距固定,上边可变
    UIViewAutoresizingFlexibleHeight       = 1 << 4,//视图高度可变
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5//与父视图上边间距固定,下边可变
};

下面我们通过效果来看这些属性的作用:

  • 先创建两个view,为了区分,设置不同的背景色:


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(20, 40, 200, 200)];
    view1.backgroundColor=[UIColor redColor];
    UIView * view2 = [[UIView alloc]initWithFrame:CGRectMake(10, 10, 100, 100)];
    view2.backgroundColor=[UIColor greenColor];
    [view1 addSubview:view2];
    [self.view addSubview:view1];
}

  • 设置view2的自动布局属性如下:


这时的效果如下:

124749_YHKL_2340880.png


改变view1的frame如下:
UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(20, 40, 300, 300)];
效果如下:

2.png

这时view2的下边距离相对父视图是可变的。
设置如下:
view2.autoresizingMask=UIViewAutoresizingFlexibleHeight;
效果如下:

3.png


可以看出,这时子视图的高度是随父视图变化而自动改变的。
如下设置:
view2.autoresizingMask=UIViewAutoresizingFlexibleLeftMargin;
效果如下:

4.png


这时子视图的左边是随父视图变化而可变的。
同理,UIViewAutoresizingFlexibleRightMargin将使子视图右边与父视图的距离可变。
UIViewAutoresizingFlexibleTopMargin将使子视图上边与父视图距离可变。UIViewAutoresizingFlexibleWidth将使子视图的宽度可变。
注意:这些自动布局的属性是可以叠加的,比如保持视图与父视图边距不变,如下设置:
view2.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
效果如下:

5.png


二、nib文件中可视化设置自动布局
在storyboard中我们可以更加轻松的进行autoresizing自动布局。在view设置栏中有autoresizing这个设置,点中相应的箭头,就是刚才我们探讨的设置选项。并且我们把鼠标放在这个上面的时候,右侧会自动为我们预览效果。

6.png


如果你觉得autoresizing很强大,那么你就太容易满足了,autoresizing可以满足大部分简单的自动布局需求,可是它有一个致命的缺陷,它只能设置子视图相对于父视图的变化,却不能精确这个变化的度是多少,因此对于复杂的精准的布局需求,它就力不从心了。但是有一个好消息告诉你,iOS6之后的autolayout自动布局方案,正是解决复杂布局的好帮手。
  收起阅读 »

移动互联网实时视频通讯之视频采集

一 、前言 一套完整的实时网络视频通讯系统包括视频采集、视频编码、视频传输、视频解码和播放。对于视频采集,大多数视频编码器对输入原始视频的格式要求是YUV420。YUV420格式是YUV格式的一种,YUV分为三个分量,Y代表亮度,也就是灰度值,U和V表示的是...
继续阅读 »
一 、前言

一套完整的实时网络视频通讯系统包括视频采集、视频编码、视频传输、视频解码和播放。对于视频采集,大多数视频编码器对输入原始视频的格式要求是YUV420。YUV420格式是YUV格式的一种,YUV分为三个分量,Y代表亮度,也就是灰度值,U和V表示的是色度,用于描述图像的色彩和饱和度。YUV420格式的数据的各分量的布局是YYYYYYYYUUVV,视频采集时,如果输入的YUV数据各分量不是这种布局,需要先转化为这种布局,然后再送到编码器进行编码。对于android手机,从摄像头捕获的YUV数据格式是NV21,其YUV布局是YYYYYYYYVUVU;对于iphone手机,摄像头采集的YUV数据格式是NV12,其YUV布局是YYYYYYYYUVUV。因此对于android手机和iphone手机,视频采集获得的数据都需 要进行转换为YUV420的数据布局才能进一步进行编码和传输等工作。
 
二、android视频采集
 
对于android系统,通过Camera.PreviewCallback的onPreviewFrame回调函数,实时截取每一帧视频流数据。在Camera对象上,有3种不同的方式使用这个回调:
  • setPreviewCallback(Camera.PreviewCallback):在屏幕上显示一个新的预览帧时调用onPreviewFrame方法。
  •  
  • setOneShotPreviewCallback(Camera.PreviewCallback):当下一幅预览图像可用时调用onPreviewFrame。
  •  
  • setPreviewCallbackWithBuffer(Camera.PreviewCallback):在Android 2.2中引入了该方法,其与setPreviewCallback的工作方式相同,但需要提供一个字节数组作为缓冲区,用于保存预览图像数据。这是为了能够更好地管理处理预览图像时使用的内存,避免内存的频繁分配和销毁。

 
示例代码
 
public class VideoActivity extends Activity implements SurfaceHolder.Callback,Camera.PreviewCallback {
 
static int screenWidth = 0;
static int screenHeight = 0;
private SurfaceView mSurfaceview = null;
private SurfaceHolder mSurfaceHolder = null;
private Camera mCamera = null;
private byte yuv_frame[];
private Parameters mParameters;
static int mwidth = 320;
static int mheight = 240;
 
// Setup
protected void onCreate(Bundle savedInstanceState) {
 
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_capture);
mSurfaceview = (SurfaceView) findViewById(R.id.surfaceview);
mSurfaceHolder = mSurfaceview.getHolder();
mSurfaceHolder.addCallback(this);
screenWidth = getWindowManager().getDefaultDisplay().getWidth();
screenHeight = getWindowManager().getDefaultDisplay().getHeight();
LayoutParams layoutparam = new LayoutParams(screenWidth, screenHeight);
SurfaceHolder holder = mSurface.getHolder();
holder.setFixedSize(screenWidth,screenHeight);
}
 
void startcapture()
{
try {
if (mCamera == null) {
mCamera = Camera.open();
}
mCamera.stopPreview();
mParameters = mCamera.getParameters();
mParameters.setPreviewSize(mwidth, mheight);//set video resolution ratio
mParameters.setPreviewFrameRate(15); //set frame rate
mCamera.setParameters(mParameters);
int mformat = mParameters.getPreviewFormat();
int bitsperpixel = ImageFormat.getBitsPerPixel(mformat);
yuv_frame = new byte[mwidth * mheight * bitsperpixel / 8];//buffer to store NV21preview  data
mCamera.addCallbackBuffer(yuv_frame);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setPreviewCallbackWithBuffer(this);//set callback for camera
mCamera.startPreview();//trigger callback onPreviewFrame
}catch (IOException e) {
e.printStackTrace();
}
}
 
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//add code to process the data captured,data layout is YYYYYYYYVUVU,should be converted to                               // YYYYYYYYUUVV before encoded
camera.addCallbackBuffer(yuv_frame);
}
protected void onPause() {
super.onPause();
}
public void onResume() {
super.onResume();
}
protected void onDestroy() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview(); //stop capture video data
mCamera.release();
mCamera = null;
}
super.onDestroy();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width_size,int height_size) {
startcapture();//start capture NV21 video data
}
}
 
二、IOS视频采集
 
对于IOS系统,为了完成实时视频采集,首先初始化一个AVCaputureSession对象,AVCaptureSession对象用于将AV输入设备的数据流转换到输出。然后,初始化一个AVCaptureDeviceInput对象,调用addInput方法将AVCaptureDeviceInput对象添加到AVCaptureSession对象。接着初始化一个AVCaptureVideoDataOuput对象,调用addOutput方法将该对象添加到AVCaputureSession对象。AVCaptureVideoDataOutput初始化后可以通过captureOutput:didOutputSampleBuffer:fromConnection:这个委托方法获取视频帧,这个委托方法必须采用AVCaptureVideoDataOutputSampleBufferDelegate协议。
 
示例代码
 
int frame_rate = 15;
int mWidth = 640;
int mHeight 480;
AVCaptureDeviceInput *videoInput = nil;
AVCaptureVideoDataOutput *avCaptureVideoDataOutput =nil;
AVCaptureSession* mCaptureSession = nil;
AVCaptureDevice *mCaptureDevice = nil;
 
- (void)startCapture
{
if(mCaptureDevice || mCaptureSession)
{
NSLog(@"Already capturing");
return;
}
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in cameras){
if (device.position == AVCaptureDevicePositionFront){
AVCaptureDevice = device;
}
}
if(AVCaptureDevice == nil)
{
AVCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
if(mCaptureDevice == nil)
{
NSLog(@"Failed to get valide capture device");
return;
}
NSError *error = nil;
videoInput = [AVCaptureDeviceInput deviceInputWithDevice:mCaptureDevice error:&error];
if (!videoInput)
{
NSLog(@"Failed to get video input");
mCaptureDevice = nil;
return;
}
mCaptureSession = [[AVCaptureSession alloc] init];
mCaptureSession.sessionPreset = AVCaptureSessionPreset640x480;//set video resolution ratio
[mCaptureSession addInput:videoInput];
avCaptureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],                                   kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithInt: mWidth], (id)kCVPixelBufferWidthKey,
[NSNumber numberWithInt: mHeight], (id)kCVPixelBufferHeightKey,
nil];
avCaptureVideoDataOutput.videoSettings = settings;
[settings release];
avCaptureVideoDataOutput.minFrameDuration = CMTimeMake(1, frame_rate);//set video frame rate
avCaptureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES;
dispatch_queue_t queue_ = dispatch_queue_create("www.easemob.com", NULL);
[avCaptureVideoDataOutput setSampleBufferDelegate:self queue:queue_];
[mCaptureSession addOutput:avCaptureVideoDataOutput];
[avCaptureVideoDataOutput release];
dispatch_release(queue_);
}
- (void)stopCapture
{
 
if(mCaptureSession){
[mCaptureSession stopRunning];
[mCaptureSession removeInput:videoInput];
[mCaptureSession removeOutput:avCaptureVideoDataOutput];
[avCaptureVideoDataOutput release];
[videoInput release];
[mCaptureSession release], mCaptureSession = nil;
[mCaptureDevice release], mCaptureDevice = nil;
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
/* unlock the buffer*/
if(CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess)
{
UInt8 *bufferbasePtr = (UInt8 *)CVPixelBufferGetBaseAddress(imageBuffer);
UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1);
size_t buffeSize = CVPixelBufferGetDataSize(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,0);
size_t bytesrow1  = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,1);
size_t bytesrow2 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,2);
UInt8 *yuv420_data = (UInt8 *)malloc(width * height *3/ 2);//buffer to store YUV with layout YYYYYYYYUUVV
 
/* convert NV21 data to YUV420*/
 
UInt8 *pY = bufferPtr ;
UInt8 *pUV = bufferPtr1;
UInt8 *pU = yuv420_data + width*height;
UInt8 *pV = pU + width*height/4;
for(int i =0;i {
memcpy(yuv420_data+i*width,pY+i*bytesrow0,width);
}
for(int j = 0;j {
for(int i =0;i {
*(pU++) = pUV[i<<1];
*(pV++) = pUV[(i<<1) + 1];
}
pUV+=bytesrow1;
}
//add code to push yuv420_data to video encoder here
 
free(yuv420_data);
/* unlock the buffer*/
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}
 
作者: 彭祖元 收起阅读 »

环信RTBDA系统架构

RTBDA(Real Time Big Data Analysis)架构是目前大数据分析领域的流行架构理念,最早是由David Smith在他非常流行的博客([Reference 2])里头提出来的。他的提议里包括了四层架构 - 数据,分析,集成,决策。尽管他...
继续阅读 »
RTBDA(Real Time Big Data Analysis)架构是目前大数据分析领域的流行架构理念,最早是由David Smith在他非常流行的博客([Reference 2])里头提出来的。他的提议里包括了四层架构 - 数据,分析,集成,决策。尽管他当初的提议主要是为了进行预测性分析计算,但后来这个架构逐渐演变为大数据分析领域的主流架构。如下图:

1.png


图1 :David Smith 的 RTBDA架构图

今天我们以这个主流架构模式来看看和理解环信分析系统的体系架构。

环信大数据分析系统架构由底向上基本上也可以分四层 :数据,分析,集成,决策。

环信大数据系统的最底层使用的是cassandra数据库,所有需要分析的数据存储在这个数据库里。环信也使用mysql数据库保存与分析系统管理相关的数据。这两个数据库里的所有这些个数据是整个分析系统的基础。数据层之上的第三层是分析层。环信大数据系统分析层使用spark做分析平台。各种社交指标分析算法的实现采用spark环境支持的scala,python 或者java 语言编写。Spark本身包含一个机器学习的库。环信的预测建模以这个库为基础。SPARK平台构成环信指标分析模型计算的基础。

2.png


图2 :环信系统RTBDA架构图

分析层之上是集成层,集成层就像一层粘糊剂,把系统的各个组成部分有机的粘接起来。在这个层次上,环信主要需要开发两个子系统 – 社交建模规则执行引擎和和任务调度引擎。社交建模规则执行引擎根据建模规则调用分析层的分析计算进程,计算各类社交指标。任务调度引擎按照执行逻辑的优先次序和依赖关系调用规则执行逻辑完成计算任务,计算结果通过web服务反馈到web前端,所以集成层以分析层为基础,把分析计算和决策层无缝连接起来,形成一个大数据分析计算的处理系统。

环信大数据系统分析层之上的决策层包括有手机和web前端,主要提供各个社交指标的计算结果及其所产生各类分析图表。基本的图形组件有饼图,线图,柱状图等等,全部采用javascripts开发。环信大数据系统决策层直接面对用户及其相关的决策领导,从环信的角度出发,这一层是吸引用户购买和使用环信社交大数据分析系统工具的重要一环。

环信有2万多家app客户,为了适应这么多不同客户的需求, 环信构造的这四层分析系统基础架构尽量采取灵活的可配置功能。每层都有其相关的配置对应,使用起来非常灵活,应当能够高效率的充分满足不同领域的客户需求。

[Reference]
 
1. 环信 Social Activity Indicator Analysis Engine - TD.docx
2. http://blog.revolutionanalytics.com/
3. http://www.oreilly.com/data/free/big-data-analytics-emerging-architecture.csp
 
作者:黄智,负责环信大数据部门运作和大数据分析系统的搭建 收起阅读 »

imgeek更新

2015.6.23 1.将”活动“菜单置于顶层 2.修改”活动“报名页 3.”发现“右下页加上赞助商LOGO栏  
2015.6.23
1.将”活动“菜单置于顶层
2.修改”活动“报名页
3.”发现“右下页加上赞助商LOGO栏
 

Docker五大优势:持续集成、版本控制、可移植性、隔离性和安全性

对于Docker,应该不需要进行详细的介绍了。它是最火热的开源项目之一,通过在容器中增加一个抽象层(a layer of abstraction),就可以将应用程序部署到容器中。在看似稳定而成熟的场景下,使用Docker的好处越来越多。在这篇文章中,我不谈论D...
继续阅读 »
对于Docker,应该不需要进行详细的介绍了。它是最火热的开源项目之一,通过在容器中增加一个抽象层(a layer of abstraction),就可以将应用程序部署到容器中。在看似稳定而成熟的场景下,使用Docker的好处越来越多。在这篇文章中,我不谈论Docker是什么或者Docker是怎么工作的,取而代之,我会提出使用这个不断成长的平台的五大好处。
 
持续部署与测试

Docker在开发与运维的世界中具有极大的吸引力,因为它能保持跨环境的一致性。在开发与发布的生命周期中,不同的环境具有细微的不同,这些差异可能是由于不同安装包的版本和依赖关系引起的。然而,Docker可以通过确保从开发到产品发布整个过程环境的一致性来解决这个问题*Docker容器通过相关配置,保持容器内部所有的配置和依赖关系始终不变。最终,你可以在开发到产品发布的整个过程中使用相同的容器来确保没有任何差异或者人工干预。

使用Docker,你还可以确保开发者不需要配置完全相同的产品环境,他们可以在他们自己的系统上通过VirtualBox建立虚拟机来运行Docker容器。Docker的魅力在于它同样可以让你在亚马逊EC2实例上运行相同的容器。如果你需要在一个产品发布周期中完成一次升级,你可以很容易地将需要变更的东西放到Docker容器中,测试它们,并且使你已经存在的容器执行相同的变更。这种灵活性就是使用Docker的一个主要好处。和标准部署与集成过程一样,Docker可以让你构建、测试和发布镜像,这个镜像可以跨多个服务器进行部署。哪怕安装一个新的安全补丁,整个过程也是一样的。你可以安装补丁,然后测试它,并且将这个补丁发布到产品中。

多云平台

Docker最大的好处之一就是可移植性。在过去的几年里,所有主流的云计算提供商,包括亚马逊AWS和谷歌的GCP,都将Docker融入到他们的平台并增加了各自的支持。Docker容器能运行在亚马逊的EC2实例、谷歌的GCP实例、Rackspace服务器或者VirtualBox这些提供主机操作系统的平台上。举例来说,如果运行在亚马逊EC2实例上的Docker容器能够很容易地移植到其他几个平台上,比如说VirtualBox,并且达到类似的一致性和功能性,那这将允许你从基础设施层中抽象出来。除了AWS和GCP,Docker在其他不同的IaaS提供商也运行的非常好,例如微软的Azure、OpenStack和可以被具有不同配置的管理者所使用的Chef、Puppet、Ansible等。

环境标准化和版本控制
 
通过上面的讨论,Docker容器可以在不同的开发与产品发布生命周期中确保一致性,进而标准化你的环境。除此之外,Docker容器还可以像git仓库一样,可以让你提交变更到Docker镜像中并通过不同的版本来管理它们。设想如果你因为完成了一个组件的升级而导致你整个环境都损坏了,Docker可以让你轻松地回滚到这个镜像的前一个版本。这整个过程可以在几分钟内完成,如果和虚拟机的备份或者镜像创建流程对比,那Docker算相当快的,它可以让你快速地进行复制和实现冗余。此外,启动Docker就和运行一个进程一样快。

隔离性

Docker可以确保你的应用程序与资源是分隔开的。几个月前,Gartner发表了一篇报告,这份报告说明了运行Docker 容器进行资源隔离的效果和虚拟机(VM)管理程序一样的好,但是管理与控制方面还需要进行完善。

我们考虑这样一个场景,你在你的虚拟机中运行了很多应用程序,这些应用程序包括团队协作软件(例如Confluence)、问题追踪软件(例如JIRA)、集中身份管理系统(例如Crowd)等等。由于这些软件运行在不同的端口上,所以你必须使用Apache或者Nginx来做反向代理。到目前为止,一切都很正常,但是随着你的环境向前推进,你需要在你现有的环境中配置一个内容管理系统(例如Alfresco)。这时候有个问题发生了,这个软件需要一个不同版本的Apache Tomcat,为了满足这个需求,你只能将你现有的软件迁移到另一个版本的Tomcat上,或者找到适合你现有Tomcat的内容管理系统(Alfresco)版本。

对于上述场景,使用Docker就不用做这些事情了。Docker能够确保每个容器都拥有自己的资源,并且和其他容器是隔离的。你可以用不同的容器来运行使用不同堆栈的应用程序。除此之外,如果你想在服务器上直接删除一些应用程序是比较困难的,因为这样可能引发依赖关系冲突。而Docker可以帮你确保应用程序被完全清除,因为不同的应用程序运行在不同的容器上,如果你不在需要一款应用程序,那你可以简单地通过删除容器来删除这个应用程序,并且在你的宿主机操作系统上不会留下任何的临时文件或者配置文件。

除了上述好处,Docker还能确保每个应用程序只使用分配给它的资源(包括CPU、内存和磁盘空间)。一个特殊的软件将不会使用你全部的可用资源,要不然这将导致性能降低,甚至让其他应用程序完全停止工作。

安全性

如上所述,Gartner也承认Docker正在快速地发展。从安全角度来看,Docker确保运行在容器中的应用程序和其他容器中的应用程序是完全分隔与隔离的,在通信流量和管理上赋予你完全的控制权。Docker容器不能窥视运行在其他容器中的进程。从体系结构角度来看,每个容器只使用着自己的资源(从进程到网络堆栈)。

作为紧固安全的一种手段,Docker将宿主机操作系统上的敏感挂载点(例如/proc和/sys)作为只读挂载点,并且使用一种写时复制系统来确保容器不能读取其他容器的数据。Docker也限制了宿主机操作系统上的一些系统调用,并且和SELinux与AppArmor一起运行的很好。此外,在Docker Hub上可以使用的Docker镜像都通过数字签名来确保其可靠性。由于Docker容器是隔离的,并且资源是受限制的,所以即使你其中一个应用程序被黑,也不会影响运行在其它Docker容器上的应用程序。

结语

将云计算一起考虑,上面提到的这些好处能够清楚地证明Docker是一个有效的开源平台。使用Docker的好处越来越多,今天我只想强调这前五大好处。如果你使用了Docker,欢迎分享你的使用案例或者任何你觉得使用Docker带来的好处。
  收起阅读 »

深度思考:互联网产业的困境与进化

在互联网、风险投资和资本市场互相结合、互相支持的运作机制下,依托“互联网产业资本市场估值特权”而制造的造富效应是十分惊人的。在这种造富效应的烘托下,互联网产业必然产生一系列的政治、社会、文化影响。这些影响是非常深刻的,它会反馈给科技、产业,影响基本的生活态度,...
继续阅读 »
在互联网、风险投资和资本市场互相结合、互相支持的运作机制下,依托“互联网产业资本市场估值特权”而制造的造富效应是十分惊人的。在这种造富效应的烘托下,互联网产业必然产生一系列的政治、社会、文化影响。这些影响是非常深刻的,它会反馈给科技、产业,影响基本的生活态度,进而影响人类未来的命运。

互联网公司在获得人类社会高科技公司的代表资格之后,开始塑造文化尤其是高智商、高技术人群的文化。如果将1920-1970年代的科技文化与如今互联网为首的科技文化作对比,就能发现两者气质很不一样。在1920-1970年代,从早期的航空到后来的航天,代表了一种强有力的生产型文化,那时赞美这种社会进步的艺术如装饰类风格(ART DECO)等,富有男性阳刚美,大有“改天换地舍我其谁”的气概,这在美、苏、德、日、韩等不同社会制度的国家都是类似的。

但到1980年代末之后,这种“边疆开拓”的风格就悄然消退了。这种蜕化鲜明地反映在某些行业的研究重点上,比如航天,1990年代以前主要是对地球以外的探测,对地球主要是搞卫星通讯;1990年代以后,由于“发明”了气候变化理论,西方航天主流一个劲地研究地球本身,地球以外研究领域配置资源占比下降。这是人类前沿――科技行业的重大气质转折。

当然,这时候中国出现了,因为没有太多受到二十世纪七八十年代西方社会思潮的影响,中国延续了西方二十世纪五六十年代黄金时代的思维,一举成为世界工业中心,西方大工程领域的大批精英纷纷投靠中国,直到如今大量发展中国家被中国模式所吸引,群起响应“一带一路,互联互通”。而西方进入互联网时代尤其是近几年社交-移动互联网时代后,很多公司一方面说是高科技公司,另一方面又特像时尚传媒公司,“小清新”味道很浓,与此前“边疆开拓”的科技气质已经非常不同。

那么,人类科技系统是怎么走到今天这一步的?它未来会怎么发展?有没有可能再把过去的优秀成分拾起来形成新的文化?这关系到全球科技产业下一步提高的可能性,进而影响资本市场价值创造的性质――是有着坚实的实体基础还是浮夸的泡沫。需要说明的是,互联网走到今天这步有着复杂的缘由,和1920-1970年代西方科技繁盛期的理念并不是截然两条路的,它的起源其实包含了后者最优秀的文化元素,但因为其他因素的渗透出现了“变异”,变成了今天这个模样。

工业时代后期科技机制的难题与“极客资本主义”的诞生

工业时代开启后,科技类人才获得普遍尊敬始于19世纪后半叶的美国和德国,这两个国家形成了系统性培养工程师的传统。一大批技术人才转变为企业家,取得社会的尊敬。此时的科技企业家,集科技、探索、企业、资本于一身,主要体现的是开拓边疆的气质,颇具“征服自然”的男子汉气概。但是,随着技术进步的要求,工业生产日趋复杂,对应管理系统也快速进化成复杂的科层制,组织设计及管理本身成为一门学问。

这样的组织机构有其优势也有其劣势。优势在于机构庞大,经营稳定,有充分的剩余来养活其内部的研究所。在这些研究部门里,一些耐得住寂寞,对创办企业没有欲望的纯技术天才能够创造出让人惊叹的基础性产品,为后世再次技术起飞创造基础。比如,一度垄断美国电信行业的美国电话电报公司(AT&T)旗下的贝尔实验室,先后发明了射电天文学、半导体、激光、信息论、UNIX操作系统、C语言和C++语言,实现了商用微波通信、商用通信卫星……几乎整个现代信息产业的基础就在那里诞生。其弊端是企业有动力维持垄断高利润,经营改进的动力放缓(AT&T经营的呆板是惊人的),后台慢慢地也就按部就班――大部分科研人员也就是平庸的,要由少数书呆子天才来产生闪光点,而这些闪光点有时会因为前台经营的慵懒而被应用缓慢。总而言之,“经理人资本主义”锐气减弱,但蕴藏了大量潜在的人类科技财富。

在这样的社会环境里,人才怎么办?体系内偶尔能出全才,其中有的人很可能得到快速提升,但事实证明这样的全才往往痛恨这个体系,最后成为改革者(比如通用电气的韦尔奇)。另一种技术性人才则选择了反叛:他们要么从这样的体系里离职创办新公司,要么干脆就不加入这样的巨人而另起炉灶。这样的文化最早诞生于1960年代――西方已经相当富足同时又比较平均的年代,年轻人学习了知识,又没有经历过战争,倾向于认为自己无所不能,这就是极客文化(Geek)的来源。

这个群体中有很多天才,他们不甘心于当螺丝钉,而有兴趣了解各种事情,并付诸于个人实践――包括制造新奇玩意,进而创造公司。极客文化的大本营之一就在加州――一个美丽、富足、温暖的地方。到1970年代,以半导体、电子产业为核心的信息产业已经经历了资本市场追捧的热潮,加州硅谷及其风险投资机制正在启动。而技术进步使得计算机从大型机开始小型、微型化发展,计算机的文化形象从二十世纪五六十年代“军工联合体科层体系”的“监视工具”(好比《1984》老大哥的探头)变成个人自由的万能工具。于是一些投资人开始促成大量小团体的科技发明转化为企业(比如马库拉促成乔布斯及其伙伴成立苹果)。这些新诞生的企业,相对于老一辈信息企业如IBM,早期就是小不点,在西方文化中有“大卫对抗巨人哥利亚”的道德感。

很自然的,这样的新创科技企业又吸收了1960年代学生运动(加州伯克利正是美国左派学生运动的心脏)、嬉皮士“重归田园”运动的叛逆色彩,崇尚个人自由,并把这种意识形态注入企业经营的口号里。虽然这些企业成熟后,其内部经营仍然充满政治斗争与领导专断(比如苹果),但在产品设计、宣传口号上则高喊消费者自由,并以此得到已经中产化的社会的支持。极客得到风险投资进而资本市场的追捧,从而产生“极客资本主义”。这种文化底子为1990年代互联网发展时的基调做了铺垫。

美国资本市场大慢牛对“轻产业”的追捧及变迁

1980年代“大慢牛”盘活了美国资本市场,自然需要有新的内容充实其牛市根基。资本的欲求天然地追逐具有爆炸性增长潜力或想象的领域。1980年代的产业寻找选择了电信(通信)产业,并期望于整合广电、传媒产业。原因是这个领域比较“轻”,而且人的通信需求看起来是无穷无尽的,增长空间大。

以资本青睐“爆炸性增长”的标准来选择产业,选择信息产业并最后选择纯信息的互联网产业是必然的。与传统工业相比,信息内容被认为更可能可实现爆炸性增长。这个领域不涉及物质(至少到2013年智能硬件兴起前是如此),各种开发都在相当表层的应用层面(网站做到巨型之后才要考虑架构优化问题),而且始终不需要什么生产设备(巨型网站最多也就需要堆海量服务器),所以至少在创业阶段可以“很轻”。当然,和传统通信产业相比,强调信息获取便捷性和免费性,但内容又只是纯信息本身的互联网产业更难从实体经营中获取收入,因此,如笔者分析互联网公司资本市场的奥妙所显示的,美国资本市场参与者修订了资本市场规则,建立事实上的“互联网公司豁免权”,对互联网公司不再计算信息增长带来的收入增长,而追求信息量或某个业务指标量(如注册用户数)本身的增长,做出信息爆炸的样子。

同时,又对长期成本支出(如技术研发尤其是基础技术研发)有着本能的厌恶,只对能带来指标量爆炸性增长的支出感兴趣。这使得互联网公司天生带上了传媒行业的基因。自1995年网景上市以来,互联网一直是风投的重点,近五六年甚至成为美国风投财富创造的绝大部分源泉,而高科技行业也正是从那时开始带上了传媒产业色彩,与以往的科技产业乃至1990年代以前信息产业形成强烈对比。

互联网产业追求眼球的“传媒产业”天性及其后果

互联网产业因为是纯信息产业,只要找对门路,所以可以脱离上下游的物质羁绊而爆炸式发展。这种形象与“极客”文化气质正好相符合,于是成为新世纪最好的“企业英雄”。1990年代以来,整个科技产业传媒越来越倾向于渲染明星企业、独行侠或明星团队,而不再提及大产业系统的复杂性。同理,投资它们的天使、风险投资人以及先创业再转化为风投的个人,也成为类似的英雄。这就是美国近二十年“明星企业”道路的特点。这样一条道路媒体性极强。这是由信息产业技术和本身业务内容两方面决定的。

从信息产业技术方面来看,随着基础技术的阶段性成熟,就会出现“模块化”的现象,也就是新从业者不需要从底部干起,底层技术可以以“模块”的形态直接使用,这使得应用层面的生产者(或服务提供者)“傻瓜化”。举一个相近领域的产业――手机制造业来说明,2005年以前,手机是高端产品,只有诺基亚、摩托罗拉公司能生产,但是亚洲一些电子、芯片企业将手机核心部件模块化,它们的动力就是让更多的老板能够生产手机,不再为少数手机的发明企业垄断,扩大其下游市场。

于是手机生产的门槛大大降低,这就有了深圳的山寨手机进而山寨智能手机的繁盛。但是当门槛普遍降低的时候,手机这个领域的竞争焦点就前移到市场营销方面,当门槛进一步降低到有一定资源的个人也可以做自己的品牌手机的时候,竞争就更下移到口碑传播方面,于是会编段子的脱口秀演员而不是工程师型企业家就更容易赢得媒体聚光灯的青睐。这个产业的媒体性就大大增强。

互联网也是这样,由于西方开源社区的贡献,开发流程模块化,网站、手机应用开发难度大大降低,行业竞争焦点转移到抢占用户、UI界面的简洁靓丽上,美术人才而非技术人才成了关键。中国2013年以后“互联网思维”满天飞,一些年轻人语不惊人死不休,也是这个机理所致。

从互联网本身业务内容上看,它天生倾向于媒体化。由于生产领域的系统性要求比较强,一个企业单点突破基本不可能,所以美国互联网近十年的主要突破点都在消费、社交等非生产领域。互联网诞生之初,因为本身内容免费,最后选择的商业模式只能是广告,这就是媒体行业的商业模式,所以谷歌本身就类似于媒体的广告部。2005年以后,形形色色的社交互联网干脆自身就是个媒体。实际上这已经是个媒体行当而不是一个科技行当了。

而互联网的中心在加州,加州又恰恰是西方左派思潮的大本营(反对大工业大公司、“环保”、气候变化、女权运动、同性恋权利、动物权利、“不作恶”……),各种互联网企业或者为了迎合第一批用户的需求,或者自身创立者就是左派媒体、社区工作者(如Twitter创始人),无不把自己的外表风格、经营风格整得非常的“文艺范”、“小清新”。而这些思潮由于普遍不能带来经济利益甚至是“负经济利益”,或者经不起科学的推敲,所以特别重视媒体传播,需要用“压倒声势”的宣传来站住脚。于是两者一拍即合,新兴社交-移动互联网公司用加州文艺风格,推送西方左派内容,西方左派运动借它们推销自己,发展社会运动,并成了“高科技”这个词语的定义者。这正是西方左派政治代表人物希拉里那么喜欢Facebook、Twitter的原因。

更进一步的是,由于互联网创业技术门槛大大降低,大量本来没什么经济地位的左翼社会人士开始互联网创业,并通过资本市场对互联网企业的热捧,也能实现以前不可想象的数十亿美元身家――比如估值百亿美元以上的Airbnb,创始人就是个加州风格整天闲游的背包客。至此,信息产业的文化风格走向了其诞生时的反面――IBM的创立者老沃森和半导体发明者兼企业家肖克利都是非常典型的保守主义者,他们的初衷是为生产、军事服务。

但是,具有强烈传媒性的移动互联网的大发展其实对科技进步有着强需求――因为它催生了对大量高质量的图像、声音、视频的需求,带来了海量数据的传输和处理需求,这需要强大通信技术的支撑。可是经过前几年通信产业的优胜劣汰,西方通信设备企业已经没有几家能支撑研发这样的技术。华为领导人任正非这几年常说“要抢占大数据的战略制高点,占住这个制高点,别人将来想攻下来就难了……小孩拿着手机啪啪啪拍照不删减就往数据中心传,流量超几何级数的增长……华为要做太平洋那么粗(信息)管道的铁皮,全世界能做的没几家”,就是针对这个趋势说的。这个大数据才是真正的大数据技术,硬骨头。

事实上,正是这几年在中国悄然完成的光通信宽带革命支撑了新出现在公众眼前的互联网电视、智能手机产业。如果在西方设备企业已经无法完成这一社会需求的时候,中国的集成电路、光通信、无线通信技术能继续像任正非所说的那样取得主导地位,那么中国就能在未来人类生活――不管它走哪种内容路线――把握住其科技基础。从这个意义上说,中国必须扶植互联网之外的信息技术“中坚”企业。

以上述这种“媒体化”的趋向,互联网领域的成功企业表现出几个重要特点:
  • 其一,著名互联网公司只在人口大国中产生。
因为互联网行业技术门槛不高,关键是谁先发现点子并靠烧钱迅速铺开。爆炸性的铺开需要规模效应,尤其是近十年来兴盛的社交性移动互联网,互联网移动终端普及率达到一定水平的人口大国具有先天优势。因此当前成功的互联网公司几乎只存在于美国和中国(日韩俄等国的网站影响力均难以超出其国门,理论上也就一亿用户上下,这撑不起成功互联网企业):美国是人口大国且可以影响大量其他国家的人口,中国本身就是人口大国且手机、电脑已经普及,两国企业都天然享有十几亿人的市场,极容易成功。人口大国印度目前信息设备普及率还不高但正在迅速提高。因此可以预见,从现在起到2020年,必然会诞生面向印度市场的巨型互联网公司,就看是印度本地公司还是中国公司(或中国风投扶持的印度公司)到印度去占领市场了。另外,这种人口大国占优的网络经济规律这还会产生另一种效果,即未必是最领先、优秀的技术能在竞争中取胜。比如即时通信,日韩的LINE就比微信要好,但是日韩人口太少,而且LINE功能是服务于日韩那种极度先进的网络环境的,这反而限制了LINE在大人口市场的应用,网络属性天生有利于那些“中不溜”的应用技术,所以微信轻松超越了LINE。
  • 其二,成功的互联网公司通常是2C(to consumer,面向个人消费者)而不是2B(to business,面向公司型客户)。
因为2B业务受制于客户的理性和谨慎,其业务会受所属行业系统的一定约束。即便是信息产业历史上赶上最好时机的2B公司,例如1980年代赶上企业后台快速普及微型计算机机遇的微软,2003-2007年赶上中国出口爆炸式增长和江浙中小企业出口相关服务信息剧增机遇的阿里巴巴,也难以实现后来社交属性的Facebook这样的病毒式扩散。2C业务由于个人更容易非理性、情绪化,反而容易渗透。那么,什么样的2C业务最容易实现爆炸增长?从近十年经验看,娱乐化、社交化、主体内容由用户产生,对用户的碎片化休闲时间有充分黏性的网站容易实现。所以,近十年的明星互联网公司多倾向服务于生活、娱乐、社交。
  • 其三,互联网明星公司获得高估值后,其获得的资金将强化上游那些在技术密集型行业中已经获得成功的公司(他们在资本市场上已经不再被青睐)的地位。
因为互联网明星公司聚焦于用户体验,希望保证硬件不出问题,所以倾向于使用已成功公司提供的元件。其结果是,互联网公司或“互联网思维”的公司享受巨大的估值,可以以这个估值不断融资(上市前多次风投投资,上市后多次增发),而所融得的资金以收入形态流进了上游公司的口袋,成了资本市场变相支持已领先的上游公司。比如,小米公司在芯片上就必然使用高通的芯片,至多在红米等廉价品上使用联发科的芯片,乐视公司在液晶面板上必定使用夏普、三星的面板,这就造成强者恒强。近两三年,德州仪器、博通、意法-爱立信等西方居于第三位及以后的企业退出手机芯片领域就是证明。因此,西方半导体等领域不再有风投投入并不意味着其衰弱,而是这个产业已经由若干成熟公司把持。但这一规律对于追赶者如中国高科技公司是不利的,在很多领域,这将维护美日韩的既得利益。幸好,中国凭借本土加亚非拉的巨量人口市场和汇集大量经济型理工人才的巨型公司的内部产业链,还能支持有志气的上游技术公司的发展,例如在手机芯片领域,大量山寨智能手机支持了展讯的崛起,而华为公司凭借自有品牌手机的市场支撑,扶植了海思的成功,这两家中国半导体领军企业是在互联网模式公司之外维系了中国的技术传承的。当前互联网产业的深层次软肋2013年以来,互联网在美国资本市场股价一再高涨,在中国也得到了舆论的追捧。2014年8月,一部名为《互联网时代》的纪录片在中国播出,引发了资本市场和舆论界的极大轰动。这部纪录片的思索是比较深入的,它反复进行一种隐喻,那就是相对于二战前后贯彻科层制的工业时代,互联网时代具有“去中心化”的重要特征。由于中心化暗示着权威,那么去中心化就暗示着“平等”。所以,互联网时代也是平等的时代、大众的时代。这种“互联网是平的”论点过于笼统,有点像互联网的对外形象宣传。“去中心化”网络结构,符合技术趋势性,是存在的,会带来什么样的影响,需要再进行严肃的探讨。笔者相信真正有价值的网络――它很可能是去中心化的,必定是有极深的技术支持而且又能促进技术进步的网络。但当前这种媒体性很强的互联网模式,已经表现出若干深层次软肋。
  • 第一,应用大放异彩的同时基础科技储备开始吃紧。
互联网产业追求个体成功,追求明星效应,所以整天思考的就是大卖,就是用户体验,如何快速想到新的创意,快速变成现实产品,它加大了应用层面创新的动力。因为互联网,信息领域各种新产品、新服务层出不穷,软层面的变化很快。美国风投涉足的其他领域,如生物医药和新能源因为技术原因,更新速度远不如互联网,给风投带来的财富也远不如互联网,可见其中互相强化的机制。但正因为创新集中在应用层面,乃至越来越表层化,当年AT&T进行基础科学知识创造和储备的机制就基本上消失了。基础科技研发时间过长,不确定,吃利润,一旦研究成功又有很强的外部性,与资本市场逻辑存在直接冲突。在工业时代,基础科技只能在不求回报的公立学术机构、国家实验室、富裕商人身后捐赠的实验室(如卡文迪许实验室)、超大型公司内部研发部门里发展出来。二十世纪七八十年代的新型“极客企业”的逆反及其与资本市场的融合好比来了次性格大转变,开始急速消化之前几十年储备的基础科技财富,但是新增补充的基础科技知识资源越来越少。当然,谷歌在发达以后也开始支持很多不赚钱的大型基础性项目(比如谷歌地球、街景等)。但和过去的基础科技机制相比,这些项目通常看起来比较“好玩”,满足极客的好奇心,要让大部分人看得懂,多集中在应用层面(收购并大放光彩的安卓系统也靠近应用层)。而且这些项目更多集中在信息科技领域。对于其他大型制造业、工业系统则无能为力。这正是西方科技进步机制的深层次危机。
  • 第二,放大财富分化。互联网“明星企业”模式会进一步放大财富的分化。
“互联网是平的”论,与世纪之交全球化兴起时“世界是平的”论一样,过于模糊,所指不清。如果从经营的效果上看,互联网让知识门槛相对变低,“变平了”;从经营者角度出发,它让普通人更有上升的机会,普通人的小团队能够通过明星产品一举成名并发展成大公司,“变平了”。但就该经济模式造就的财富分配结果而言,它趋向于更加不平等的结果。1990年代,在资本市场慢牛的带动下,上市企业高管通过期权形态获得的高薪已经为当时的评论家瞠目。但到互联网时代尤其是2010年以来的移动互联网时代,一方面这个行业规模经济的需求更加强烈,强者一统天下的局面更加突出(如百度阿里腾讯),另一方面又叠加了资本市场赋予互联网企业的特权而放大估值(市值财富),互联网上市公司股权市值增值造成的财富分化比传统行业上市公司更加夸张。而这种财富分化会产生激励效应,主导人才流动方向,人才大量流向互联网行业,媒体性的互联网行业的聚焦又不在于钻研积累,结果增加了未来科技基础的脆弱性。
  • 第三,UGC平台泡沫。UGC(User Generated Content,用户生产内容的平台模式)是2004年前后web2.0(博客、维基是第一批2.0产物)的思想精髓,到社交互联网时代放大,是“互联网思维”的核心。
但其实早年的论坛就是UGC的原型。既然互联倾向于走2C的业务模式,UGC就是2C业务最重要的经营哲学。从近十年的效果来看,UGC模式良莠不齐,有些平台网站确实实现了高质量的UGC,但更多的UGC平台,尤其是移动互联网时代大量APP是有问题的。少数高质量的UGC网站,比如维基百科、知乎,扩大了知识网络上的创作源,把大量有才能的人的空余时间利用起来,让他们分享自己的“认知剩余”,进行免费的传播,扩大了这些在纸片时代仅局限于极少数人头脑的知识的影响范围――也就是越来越多的人成为“全能型极客”。但更多UGC的“社交互联网”,出于其利用人类原始本能的动机,刺激人们把碎片化时间都用在闲聊、交朋友、游戏等容易上瘾的“信息消费”上面,对于社会整体并不有利,而且极容易成为谣言的温床。其实,UGC在近两年甚至演化成一种投机懒汉的想法,都希望用户在上面弄内容――但有价值的内容是耗费工夫的,全国的精英也就只能支撑不多的几个UGC平台。
  • 第四,争夺用户导致末日心态。互联网产业重心在于抓用户。
这里又暗藏一个矛盾,首先互联网是免费模式,在实体经济意义上是要大量用户才能支撑起一点收入(比如网络文学,几十个读者才有一人付费)。在广告方面,近几年由于风投蜂拥而至,互联网广告单价有所提高,这其实是在挥霍性的使用用户资源。同时,互联网网站、APP又种类很多,大家都想着占使用者的时间,可是使用者每天就24小时,于是人们很快发现“人的时间”也是一种稀缺资源,成为互联网公司争夺的对象。其结果是:
  • 一方面在资本市场上“打肿脸充胖子”,阅读近几年互联网公司的上市招股说明书,可发现他们特别强调自己的“用户数”,动辄数千万,多则数亿。如果看完若干互联网公司的招股说明书,可能会很奇怪:为什么它们的用户数那么多但我自己却从来没有使用过,也不曾有印象周围的人使用过。答案是利用“用户定义”的伸缩空间,把那种每月登录一次(社交网站)的注册人也算做用户,通过强调用户数来获取估值――实质上大部分用户形成收入的可能性几乎为零。
  • 另一方面,这种对于“人的时间”资源有限的忧虑,又影响了互联网企业家的心态――虽然互联网在不断产生出新的想法,创造出新的边疆,但在“人的时间”这个大陆里面不断加塞新想法将使得互联网世界越来越拥挤,因此对于单个互联网公司来说“资源”是有限的甚至是在萎缩的。


所以从内心上来说,互联网人和19世纪后期以来的那些创业资本家的心态有很大不同,后者相信边疆无限,资源可以不断地被发现并转换成可利用的形态,是乐观派,而前者则认为资源有限,必须先下手为强,而且要主动攻击置对手于死地,在对手处于萌芽状态时就消灭它,这是一种深沉的悲观派,中国互联网圈内崇拜《三体》这部科幻小说就是这种心态的极好反映。而这种基于资源有限的深沉悲观正和资本市场上互联网公司被撑得极大的市值(反映了资本的高期望)形成了巨大的张力。

真正的互联网精神

今天,互联网思维在中国得到了广泛认同。但上文所述的种种偏媒体泡沫化的现象,显然指向其中的问题。那么,有没有互联网精神呢?笔者相信当然是有的,世界上有真正的互联网精神,应该把它同媒体化、泡沫化的伪互联网精神区分开来。那么,真正的互联网精神在哪里?恐怕要从它的源头说起。

正如在互联网UGC的实践中,精英聚在一起还是精英,垃圾聚在一起仍然是垃圾的结果所暗示的,真正的互联网精神是通过共享的网络平台实现的“精英共和制”和“无限边疆观”。当前的互联网模式已经把这种精神在很大程度上掩盖了,变成了比表层工夫――在中国干脆成了比噱头。

需要指出的是,互联网尤其是近几年来的移动互联网带来的对既有资源的更有效利用,是创造了很大价值的。但是,这是在生产力老本上进行资源优化配置,生产力基础还没有被驱动进步。网络本身有可能成为一种先进的生产力(例如,在先进的传输和终端技术的支持下,人们实现知识的快速自动吸收和分享),但这种未来场景靠目前的互联网文化很难实现――因为它意味着生产层面的技术大变革,而当前移动互联网的热门集中在对原有生产力资源的消费上,比如2013年以来最时髦的O2O模式(“线上线下联动”),相当于把人类现代社会中的生活方方面面再用互联网走一遍,按照2012年以来美国资本市场的玩法,每个领域都可以创造一个至少大中型市值水平的上市公司。 收起阅读 »

闲谈集群管理模式

Docker很火很红,简直到了没有道理的地步了。Docker为什么这么红?因为它是一种可以用来掀桌子的技术。在部署自动化这条产业上的工人和机床制造商们,看家护院的 cmdb,分布式脚本执行等所谓核心技术即便不会变成明日黄花,也会沦为二流技术。仅仅把 Docke...
继续阅读 »
Docker很火很红,简直到了没有道理的地步了。Docker为什么这么红?因为它是一种可以用来掀桌子的技术。在部署自动化这条产业上的工人和机床制造商们,看家护院的 cmdb,分布式脚本执行等所谓核心技术即便不会变成明日黄花,也会沦为二流技术。仅仅把 Docker 当成一个轻量级 vmware 来使用,是没法看穿其实质的。要理解 Docker 的意义,不能从 Docker 是什么,能够干什么说起。让我们先来回忆一下集群管理模式的发展历程,以及这些落后的模式的种种弊端。
手工管理时代
IP地址是放在 excel 表里的。管理是靠登陆跳板机,用 SSH 连接服务器。手工执行命令做新的服务器部署,已有服务器的程序版本升级,以及各种配置刷新修改的工作。
弊端不言而喻,主要有这么几点:

  • 缺乏一致性,因为是手工操作所以服务器之间总是有一些差异

  • 效率低下,一个人可以管理的服务器数量非常有限

  • 自动化大跃进时代


业务数量的增长,很快使得机器的数量超过手工操作维护的极限。无论再烂的团队,只要业务长到这个份上了,必然会出现大量的自动化工具用脚本自动化执行的方式快速地支撑业务。这个时代是一个黄金时代,运维真正长脸的时代。因为没有自动化的运维技术,业务就会遇到瓶颈。自动化技术的引入,切实地体现成了业务的收益。
这个时代的特征是两个关键的系统

  • 把本地 excel 表格里的 IP 地址用数据库的方式管理起来,称之为 CMDB

  • 基于 SSH 或者 agent 的分布式脚本执行平台


效率低下了不再是主要问题,主要的弊端变为了:

  • 大量的脚本,杂乱无章,内容重复,质量难以保证,最终给故障留下隐患

  • 没有对现网预期状态的定义和管理,所有的现网状态都是脚本日积月累的产物,导致服务器状态漂移,产生雪花服务器(每个机器都不一样),进而给业务稳定性留下隐患


这些弊端短期对业务来说并没有立竿见影的伤害,属于内伤型的。而且很多隐患即便暴露了也会流于强调纪律,强调运维意识云云。很少会有人去追究背后的运维理念的问题。结果就是大部分公司都停留在这个阶段了。毕竟运维是一个足够用即可的支撑领域。运维搞得再高科技,特高可用,未必和创业公司的成功有多少直接联系。
开发闹革命时代
伴随 DevOps 同时出现的是 infrastructure as code 的提法。简单来说就是一帮开发杀到运维领域之后,看见这些运维居然是这样去管理现网状态的。于是他们把写代码的经验带过来,将现网状态建立成模型(所谓 code),把预期的状态提交到版本控制中。就像写代码一样,去管理服务器配置。
很多后台开发主导的小创业公司直接跳过了上个时代,运维自动化体系从一开始就是基于 puppet 和 chef 来搞的。平心而论,用 puppet 的更多是缺少历史包袱,而不是因为运维问题有多复杂。很多管理的机器数量不超过十台,却在如何使用 puppet/chef 上浪费大把时间的团队也是有的。相反很多大公司因为有沉重的历史包袱,和庞大的传统运维团队,这种开发闹革命的路反而走不通。
这种做法主要是解决了脚本的管理问题,而且因为直接定义了现网状态,服务器之间的一致性也会好很多。但是光鲜亮丽的模型背后本质上还是一堆脚本来驱动的。上个时代的弊端只是经过了包装和改良,并没有办法根除。
应用预期状态到现网依靠的还是跑脚本。而且与之前不同,现在更多的是跑别人写的cookbook了,质量也是良莠不齐的。
虽然定义了预期的现网状态,但是起点不同(比如从a=>c, b=>c)需要做的升级操作可能完全是不同的。要编写一个面面俱到的升级脚本其实非常困难。
还有哪些问题?
一致性和稳定性是最大的问题。服务器开机之后,常年是不重装系统的。无数人在上面跑过脚本,执行过命令,定位过问题。服务器实际的状态是没有办法精确管控的。infrastructure as code 是一种改良,但是仍未根除这个问题。每一次在服务器上跑脚本其实就是一种赌博,因为没有两台服务器是完全一样的。在本地测试可行的脚本,未必在另外一台上不会引起问题。这不是强调一下代码里不能 rm * ,而要 rm path/* 就可以解决的问题。
版本管理其实一直是没有的。做过开发的人,可能还会用 git/svn 来作为部署的基线,基本的版本都会提交到仓库里。更多的一线运维用的还是 rsync 的模式。rsync 的意思就是要安装一个新服务器,需要找一台“与之最像”的服务器。然后把文件拷贝到新服务器上,把配置修改一下,启动完事。携程出事了,我个人猜测应该与版本管理混乱有关系。
故障替换是非常困难的。先不说故障替换,就是故障机剔除就是一个头疼的事情。比如ZooKeeper。各个客户端都硬编码三个 ip 地址。一旦其中一个 ip 挂掉了。zookeepr按照高可用协议可以保持正常,但是长期来说这个挂掉的ip还是要从各个使用方里剔除的。这个就且改了。一旦业务的高可用做得不好,需要运维来搞一些接告警之后替换故障机的事情,那就是各种脚本折腾各种配置文件的节奏了。
Docker 是如何掀桌子的
两点神论,进入到 Docker 时代之后

  • CMDB 不再至关重要了。CMDB 连同IP,以及服务器资源变成底层蓝领工人关心的问题了。上层的后台开发和业务运维不再需要也无法再以 IP 为中心的 CMDB 来管理配置。

  • 分布式脚本执行平台从核心作业系统退居二线。很简单,服务器不再需要变更了,常规的上新服务器,发布新版本都不再依赖脚本在一个已有的服务器上执行去修改状态。而是创建一个新的容器。


Docker的实质是一个真正的版本管理工具。在 Docker 之前版本管理是各种拼凑的解决方案。什么是版本,服务器是由三部分组成:版本、配置、数据。所谓版本就是操作系统,以及操作系统的配置。各种第三方包,开发给的可执行文件,和一部分配置文件。这些的集合是一个版本,其实就是一个完整的可执行环境。除此之外一般就是一个数据库,里面放了两部分内容,一部分是管理员可以从页面上修改的配置,一部分是业务数据。在 puppet 时代的版本,是一个申明文件。这个申明文件执行的时候,需要先从某个 ISO 安装出一个操作系统,然后用 apt-get/yum 从某个镜像源安装一堆系统的包,然后用 pip/bundle 安装一堆 python/ruby 语言层面的包,最后才是开发给你的 git/svn/某个不知名的tar.gz。你以为这些东西每次拼装出来的东西都是同样的版本么?其实未必。想当年某墙干掉 github 的时候,不知道多少人无法做发布了。Docker 打包出的连系统在一起的镜像,其实是对版本的最好阐述。
使用 Docker 之后不再需要修改现网的 container 了。一个 container 如果需要升级,那么就把它干掉,再把预先做好的新的镜像发布成一个新的 container 替换上去。分布式脚本执行,变成了分布式容器替换了。当然这种标准化的操作,用 mesos marathon 已经完美解决了。
使用 Docker 之后,无法再基于 IP 做管理了。倒不是给每个 container 分配一个 IP 分配不过来,而是 IP 代表的静态模型无法跟上时代了。基于 IP 管理,就意味你会基于 SSH 登陆这个 IP 来管理。这种思想从骨子里就是落后的了。进程,进程组,模块,set 这些才是管理的粒度。至于进程是跑在哪个 IP 上的哪个容器里,不再重要了。一图可以说明这个问题:

1.png

上面这个扩容的按钮点完之后有让你填 IP 吗?没有!你只需要告诉marathon,我要32个进程实例。它就会去找这些资源运行这 32 个实例。业务最终需要的是 32 个进程,而不是 32 个 IP。IP只是运行进程需要的资源而已。实际运行的时候进程可能是在一个IP上启动了32个端口,也可能是随机分配了5个IP,每个各跑了一些端口。当然这些分配都是可以通过“约束”的方式表达的。而不是让你去搞32个IP来,再跑个脚本去这些IP上部署这些进程。
The Missing Piece
拼图游戏就差最后这一块了。Docker 做为一个版本工具是绝对合格的。Marathon 以 Docker 的方式托管所有进程也是靠谱的。但是还不完整:
Docker镜像作为版本发布到现网之后是无法运行的,因为任何一个应用起码都有好几个服务要互相访问。这些硬编码在镜像里的 IP 地址换了一个环境是无法执行的。一个版本里任何配置都可以硬编码,就是 IP 地址和端口是没硬编码的。
扩容缩容可以很容易创建和销毁容器,但是引用了这个容器的服务器的其他容器怎么办呢?
发布,故障替换都是同样的问题
解决方案可以看这两张图:

2.png


3.png

方案其实非常简单。把 app1 => app2 的网络访问关系,改成 app1 =local=> haproxy =network=> haproxy =local=> app2。通过在容器本地部署 haproxy “托管所有的端口”,也就是用 haproxy 在进程之间做联线,而不是每个进程自己去负责连接网络上的其他进程。
试想一下之前是在配置文件里硬编码 10.0.0.1:3306 是某台数据库。硬编码是不对的,是要打屁股的。所以我们把硬编码的 ip 地址改成 127.0.0.1:10010。这一次我们不再硬编码任何 IP 了,我们只硬编码一个特殊的端口号。每个进程都有一堆特殊的本地端口号用于访问自己需要的上下游服务。这个端口号背后的进程到底在哪个 IP,哪个 端口,哪个 container 里执行。做为使用方不需要修改任何代码(比如兼容什么 ZooKeeper/etcd 神马的),也不用关心。甚至这个端口后面是多个远程的IP构成一个基于客户端的高可用。代理甚至还可以做一些出错换一个后端再重试的事情。
有了这种神器之后,扩容所容,发布变更,故障替换都很轻松了。容器随便新增,随便删除。网络结构变化了之后,刷新各个地方的 haproxy 配置就是了。各种灰度,各种零停机替换方案都可以搞起。
名字服务与网络
类似的方案有很多。最底层的方案是 SDN/IP 漂移,以及网络的bonding。这种方案的特点是保持 IP 地址作为最传统的名字服务,妄图延续其生命。
上层一点的方案是 DNS。再上层一些的方案是 ZooKeeper。
各种方案争的就是服务如何注册自己,如何彼此发现这个点。各种方案的优缺点可以自己去读:

  • SmartStack: Service Discovery in the Cloud(http://nerds.airbnb.com/smartstack-service-discovery-cloud/)

  • DOCKERCON VIDEO: BUILDING A SMARTER APPLICATION STACK(https://blog.docker.com/tag/smartstack/)btw,airbnb 在 13 年就把这套方案投入生产了。


最有意思的是把这种 haproxy 的方案与基于 SDN 的 IP 漂移方案做对比。haproxy 的就是替网络做应用层进程之间联线的事情,通过引入 haproxy 让这种联线更具有灵活性。 而 SDN 的方案是说,你现在的业务进程之间是通过 IP 之间静态链接的,这种连接不够灵活没关系,路由器帮你整。一个 IP 挂掉了,可以把IP漂移到另外一台机器上去继续使用。其实就是在一个场景下实现两个进程的重新联线,突破两 IP 之间静态互访的限制,给基于 IP 的部署方案续命。
两者底层的技术是相通的。所谓 IP 漂移最后靠的是现代牛逼的CPU,和软件路由技术。最后玩的都是用户态转发,dpdk神马的。所以 haproxy 慢,转发效率有问题神马的,长期来看都不会是问题。用软件来联线,是趋势。连路由器都开始这么玩了,连硬件厂商都开始卖软件了。
The Final Battle
集群管理纯粹变成进程管理,IP不再重要,状态不再重要。CMDB会变得越来越边缘化。
发布变更不再是去修改服务器,而是新建销毁容器,以及更新进程间网络联线关系。分布式作业系统会越来越少用,跳板机就更加不允许使用了。
记住“immutable servers”这个提法吧,它终将会得到历史的认可。
来源:Dockerone.io 收起阅读 »

环信即时通讯云急招 高级Java后台工程师(薪资:20K-40K/月)

高级Java后台工程师(技术开发部)(薪资:20K-40K/月)   1. 5年以上java开发工作经验,具有服务器开发工作经验者优先;   2、深入了解java开发工具及主流开发框架,具有扎实的技术功底,熟悉主流技术架构;   3、熟悉REST架构和HTTP...
继续阅读 »
高级Java后台工程师(技术开发部)(薪资:20K-40K/月)
 
1. 5年以上java开发工作经验,具有服务器开发工作经验者优先;
 
2、深入了解java开发工具及主流开发框架,具有扎实的技术功底,熟悉主流技术架构;
 
3、熟悉REST架构和HTTP协议,以及Nginx等;
 
4、熟悉Cassandra, Kafka,Zookeeper等流行的分布式系统及其架构;
 
5、熟悉TCP/IP协议,熟悉socket和多线程开发,具备高访问量web开发工作经验(10W同时在线或日PV达千万);
 
6、逻辑思维能力强,具有团队意识;
 
7、熟悉linux相关开发优先考虑;
 
8、熟悉ruby, python, bash 等脚本语言优先考虑;
 
9、有开源社区经验者优先考虑;
 
10、全栈工程师,DevOps直接录取;

 
简历请发邮件:steven@easemob.com 
 
 
关于环信:

环信是行业内领先的云服务提供商,国内最大的即时通讯云平台。环信上线半年内,已经完成了3轮融资。截止2015年上半年,环信已经帮助了23763个App加入社交和沟通能力,典型用户包括海豚浏览器、猎聘网、百合相亲、蜻蜓FM、汽车之家车友会、优听、节操精选、华图教育等。同时环信SDK覆盖用户高达2.51亿,日均消息量过亿,是经过真实亿级用户考验的即时通讯云平台。 收起阅读 »

环信即时通讯云急招高级运维开发工程师(薪资:20K-40K/月)

高级运维开发工程师(技术开发部) (薪资:20K-40K/月)   1、精通Linux以及主要Unix系统及原理,了解网络基本技术,熟悉TCP/IP协议工作原理;   2、熟悉nginx, tomcat, redis, cassandra, zookeeper...
继续阅读 »
高级运维开发工程师(技术开发部) (薪资:20K-40K/月)
 
1、精通Linux以及主要Unix系统及原理,了解网络基本技术,熟悉TCP/IP协议工作原理;
 
2、熟悉nginx, tomcat, redis, cassandra, zookeeper等技术的原理,优化,排错;
 
3、熟悉shell, perl, python, java, php, erlang, C/C++等开发语言一种以上;
 
4、熟悉大型网站架构及优化,熟悉分布式系统,大型数据库,缓存,队列等技术原理;
 
5、责任心强,积极沟通,热爱分享;
 
6、有开源社区/项目/大型互联网公司经验者优先考虑;
 
7、全栈工程师,DevOps直接录取;
 
8、最少2年相关工作经验。
 
 
简历请发邮件:steven@easemob.com 

 
关于环信:

环信是行业内领先的云服务提供商,国内最大的即时通讯云平台。环信上线半年内,已经完成了3轮融资。截止2015年上半年,环信已经帮助了23763个App加入社交和沟通能力,典型用户包括海豚浏览器、猎聘网、百合相亲、蜻蜓FM、汽车之家车友会、优听、节操精选、华图教育等。同时环信SDK覆盖用户高达2.51亿,日均消息量过亿,是经过真实亿级用户考验的即时通讯云平台。 收起阅读 »

环信即时通讯云急招前端工程师(薪资:20K-40K/月)

 【前端工程师】 您的责任:  环信web IM  SDK 等的开发设计,环信业务后台的开发设计  我们的要求:  1.3年web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap, html5, css3, saas, less, jq...
继续阅读 »
 【前端工程师】

您的责任: 

环信web IM  SDK 等的开发设计,环信业务后台的开发设计 


我们的要求: 

1.3年web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap, html5, css3, saas, less, jquery, bower, grunt

2.熟悉AJAX, REST等原理和使用方式

3.熟悉HTTP, WebSocket, Spdy等协议

4.熟悉Haml, Jade, Slim等模板语言优先考虑

5.有设计美感者优先考虑

6.深刻理解Web标准,对可用性. 可访问性等相关知识有实际的了解和实践经验;

7.熟悉ruby, python, bash, nodejs 等脚本语言优先考虑;

8.有开源社区经验者优先考虑

9.全栈工程师,DevOps直接录取
 
 
简历请发邮件:steven@easemob.com 
 
 
关于环信:

环信是行业内领先的云服务提供商,国内最大的即时通讯云平台。环信上线半年内,已经完成了3轮融资。截止2015年上半年,环信已经帮助了23763个App加入社交和沟通能力,典型用户包括海豚浏览器、猎聘网、百合相亲、蜻蜓FM、汽车之家车友会、优听、节操精选、华图教育等。同时环信SDK覆盖用户高达2.51亿,日均消息量过亿,是经过真实亿级用户考验的即时通讯云平台。
  收起阅读 »

小窍门: @用户,被@方可以收到提示

直接@就可以了, 新编辑器里面, 回复暂时不能有下拉提示, 评论是有用户提示的.    不信你可以试试

直接@就可以了, 新编辑器里面, 回复暂时不能有下拉提示, 评论是有用户提示的. 
 
不信你可以试试

P2P实时音视频之NAT穿越

在P2P实时音视频领域,NAT穿越是一个非常重要的技术。NAT穿越技术使得客户端和客户端直接进行通讯,从而减少了端到端的延迟,并大大减轻了服务器的压力,降低成本。 NAT是什么 NAT的全称Network Address Translation,通常指的是...
继续阅读 »
在P2P实时音视频领域,NAT穿越是一个非常重要的技术。NAT穿越技术使得客户端和客户端直接进行通讯,从而减少了端到端的延迟,并大大减轻了服务器的压力,降低成本。
NAT是什么
NAT的全称Network Address Translation,通常指的是把内网地址转换成外网地址。一般家用的无线路由器就用到了NAT技术。NAT技术的出现是为了解决IPv4地址不够的问题,而且还能够避免来自网络外部的攻击,隐藏和保护网络内部的计算机。凡事有利必有弊,NAT同样带来了新的问题。
NAT工作原理
我们先看一下NAT的工作过程

41.png


NAT维护一个地址映射表,记录内容为内网主机地址iAddr、映射地址eAddr和外网主机地址hAddr,表初始为空
内网主机主机A发送数据包给服务器A,10.0.1.10:1111 -> 203.22.22.22:6000;
NAT在映射表里没找到源地址等于10.0.1.10:1111的记录,于是新建一条记录1,分配外网端口2000
NAT修改数据包的源地址再发到外网,202.11.11.11:2000 -> 203.22.22.22:6000;
后续所有源地址为10.0.1.10:1111,目标地址为203.22.22.22:6000都做同样的修改
服务器A发送数据包回给内网主机A,203.22.22.22:6000 -> 202.11.11.11:2000
NAT发现外网地址202.11.11.11:2000映射的内网地址为10.0.1.10:1111
NAT修改数据包的目的地址再发到内网,203.22.22.22:6000 -> 10.0.1.10:1111
内网主机B和服务器B通讯的过程也类似A,只是分配的外网端口是3000
从上面NAT的工作过程可以看出,NAT通过修改数据包的源地址或目的地址来实现地址映射的。NAT修改数据包对内网主机是透明的,不需要内网主机做任何配置,方便简单。
NAT工作原理可以总结为:
只有内网主机主动向外网发送数据,外网才有可能发送数据给内网主机
内网发送到外网的数据包会被修改源地址,外网发送给内网的数据包会被修改目的地址

很显然,第1条原理保护了内网主机免受外网的攻击,但却违背了网络端到端的设计原则。如果两台主机在不同的NAT后面,是没有办法穿越NAT直接端到端(P2P))通讯的。幸运的是,在大部分情况下,我们可以在服务器的协助下实现NAT穿越。
NAT类型
在讲NAT穿越之前,我们先来分析NAT的类型。由于没有强制性的NAT标准,在实际应用中NAT有多种类型。根据内网地址到外网地址的映射是1对1,还是1对多,NAT可以分成两大类:Cone NAT(锥型) Symmetric NAT(对称型)

42.png


从图中的淡紫色形状应该可以看出来它们名字的来历(呕心沥血独家原创图)。锥型NAT把一个内网地址固定的转换成一个外网地址,即1对1映射;对称型NAT的一个内网地址可以转换成多个外网地址,即1对多映射。从锥型NAT和对称型NAT的定义我们可以推测出他们的映射表内容。
锥型映射表应该是这样的:

43.png


对称型映射表应该是这样的:

44.png


Cone NAT子类型
锥型NAT还可以再继续细分类型。外网主机发送给内网主机的数据包在通过NAT时,NAT会根据映射表的外网主机地址限制条件来允许或限制数据包通过。根据这个限制条件,锥型NAT还可以分成三种子类型:
Full-cone NAT,全锥型
一旦某内网地址向外网发送过数据包,NAT允许任意外网地址发送数据给此内网地址。

416.png


(Address)-restricted-cone NAT,(地址)限制锥型
一旦某内网地址向某外网主机发送过数据包,NAT允许此外网主机发送数据给此内网地址。换句话说,只限制ip,不限制端口。

45.png


Port-restricted cone NAT,端口限制锥型
只有从内网地址发送过的外网地址,NAT才允许此外网地址发送数据给此内网地址。换句话说,同时限制ip和端口。

46.png


穿越NAT
通过上面对NAT的分析可以看出,在不同NAT后面的两个客户端A和B,如果知道对方的NAT映射后的外网地址,就有可能直接发送UDP包给对方外网地址进行通讯。但是这里有一个问题,客户端不能直接获取自身的NAT外网地址,解决的办法就是引入一个服务器S来协助客户端获取自身的外网地址。NAT的类型有多种,类型两两组合有很多种,不是每种组合都可以被穿越的,我们来分析两个典型的组合。
锥型 vs 锥型

47.png


  1. A发送数据包给S询问自身地址,S把A的外网地址eA返回给A
  2. B发送数据包给S询问自身地址,S把B的外网地址eB返回给B
  3. S把B的外网地址eB发送给A
  4. S把A的外网地址eA发送给B
  5. A发送数据包给eB,B发送数据包给eA,建立P2P通道

端口限制锥型 vs 对称型

48.png


  1. A发送数据包给S询问自身地址,S把A的外网地址eA返回给A
  2. B发送数据包给S询问自身地址,S把B的外网地址eB1返回给B
  3. S把B的外网地址eB1发送给A
  4. S把A的外网地址eA发送给B

A发送数据包给eB1,因为eB1只接受来自S的数据,所以A的数据被NATB丢弃
B通过发送数据包给eA,因为eA是新的目标地址,NATB 创建新的映射地址eB2,而eA只接受来自S和eB1的数据,所以B的数据被NATA丢弃,无法建立P2P通道
这里就不一一分析其他组合,各位看官可以自行分析,这里直接给出结论:

49.png


现实中的NAT
在穿越NAT的结论里,只有两种组合不能穿越,即对称型vs对称型、端口限制锥型vs对称型,占比并不高,看起来结论还不错。但是,理论是美好的,现实是残酷的,生活中对称型NAT的数量并不少。只要是大型组织的网络,一般都采用对称型NAT,因为这类NAT安全性最好。我们团队曾经对常用的网络做过调查研究,以下是调研结果:
有公网IP的宽带:比如联通的ADSL,这类宽带会给每个用户分配一个公网IP,所以其NAT类型取决于用户所选用的路由器,大部分家用路由器都是端口限制锥型NAT;
无公网IP的宽带:比如宽带通,这类宽带给用户分配的是局域网IP,连接公网的NAT是运营商的,一般都是对称型NAT;
移动互联网:跟“无公网IP的宽带”类似,分配给手机的是局域网IP,出口基本都是对称型NAT;
大公司路由器:大部分都把路由器配置成对称型NAT;
比较可惜的是移动互联网也是对称型NAT,也就是说,如果通讯双方都走3G或4G的话,是很难直接P2P通讯的。我们的产品可以穿越部分对称型NAT,当碰到无法穿越的NAT时,为用户提供relay服务,保证接通率。
奇葩的NAT
我们现在知道NAT分为1种对称型和3种锥型,那还有没有其他类型的NAT呢?答案是YES。这个NAT各位看官应该并不陌生,它就是大名鼎鼎的netfilter/iptables。大家接触最多的iptables,是运行在ring3层用户态的配置程序,而运行在ring0内核态的netfilter才是真正实现NAT功能的程序。在大部分情况下,netfilter表现出来的是人见人爱的锥型NAT,但是在某种条件刺激下,它就华丽丽地变身成高贵冷漠的对称型NAT!
先上图:

491.png


在穿越时,假如右边B发给A的包比左边A发给B的包先到达netfilter,netfilter会用之前的映射地址eB把B的包发出去,这时候netfilter表现出来的是锥型NAT,穿越成功。反过来,假如A发给B的包先到达netfilter,那么B发给A的包就会被netfilter映射成新的地址eB’,这时候netfilter表现出来的是对称型NAT,导致穿越失败。见下图。

492.png


netfilter不分内网和外网,它会跟踪内网和外网所有协议的连接(conntrack),包括tcp和udp。当外网的数据先到达netfilter时,netfilter创建一条conntrack,内网的数据后到达netfilter,netfilter发现conntrack1已经占用了端口,就会选择另外一个外网端口作为映射端口。看官如果想了解详细情况,请阅读博大精深的netfilter源码,这里提示一下,看get_unique_tuple函数就可以了。虽然netfilter很奇葩,但我们的产品依然能够轻松的穿越它。作者介绍:
符宁,环信音视频team leader,在音视频客户端/服务器领域拥有多年设计、开发和管理经验。
联系方式:simon.fu@easemob.com 收起阅读 »

实时网络音视频通讯qos的一种解决方案

一、前言 随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为未来移动互联网 发展的一个重要方向。那么如何保证智能终端之间实时音视频通讯的服务质量成为一个必须加以重视的问题。实时音视频通讯包括采集、编码、网络传输、解码、播放...
继续阅读 »
一、前言
随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为未来移动互联网
发展的一个重要方向。那么如何保证智能终端之间实时音视频通讯的服务质量成为一个必须加以重视的问题。实时音视频通讯包括采集、编码、网络传输、解码、播放等环节,其中采集、编解码和播放是不受网络条件影响的,只受限于编解码算法,播放策略等因素,网络传输的丢包、抖动和乱序对qos的影响最为重大,因此本文介绍的qos解决方案
要解决的是网络传输丢包、抖动和乱序因素对服务质量的不好影响。
二、发送端
对于实时音视频通讯,常采用UDP协议来传输多媒体数据,本文是采用基于udp的rtp协议来传输音视频数据。对
于不同格式的编码数据,会有不同的rtp打包协议,比如对于H.264视频数据,文档rfc3984对NAL U的rtp打包封装进行了规范,详情请参考该文档。对于视频数据的打包封装,因为一帧视频数据的数据长度可能大于MTU,所以相关的打包协议都会规定将长度大于MTU的帧进行切割,分块封装到多个rtp包进行传输。为了避免丢包、抖动和乱序对服务质量的影响,本方案在发送端和接收端各建立了节点数相等的一段循环buffer,用于缓存发送端数据和接收端数据。

31.png


发送端在发送数据的时候,某个rtp包的seq为send_seq,发送端把这个包通过udp socket发送出去的同时,把这
个rtp包的数据拷贝到send_seq对应节点的buffer中去,以便这个rtp包接收方没收到时,发送方还能重发这个rtp包。
这里要注意的一点是,发送端和接收端的循环buffer节点数要能被65536整除,这样rtp seq增加到最大值65535时对应最后一个节点,下一个rtp包的seq为0正好对应上第一个节点,避免rtp seq掉头时出现漏洞。
三、接收端
和发送端类似,接收端也开辟了一段节点数能被65536整除的循环buffer,用于缓存接收到的rtp包。接收端收到rtp包时,需要去解析rtp包头,取出接收到的rtp包的seq,对应下图中的received_seq。

33.png


当收到第一个包时,start_seq和end_seq都被设置为received_seq,并把收到的rtp包送到解码单元。后面收到
rtp包时,有两个工作要做,一个工作是接收的模块将接收到的rtp包拷贝到received_seq指向的节点的buffer,并将这个节点的数据flag(用于标记该节点是否填充了数据)设置为true,同时要根据start_seq、end_seq和received_seq的关系来决定要不要将end_seq更新为received_seq的值,如果received_seq对应的包本来应该end_seq对应的包之前到达,则不更新end_seq的值,否则就更新。另一个工作是要每过一段时间都要去扫描start_seq到end_seq对应的每个节点,首先,若当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如500ms),则将start_seq和end_seq之间的每个节点的数据全部丢弃,将每个节点的数据flag设置为false,更新start_seq为end_seq。其次,若start_seq对应的节点的下一个节点的数据falg为true,则将该节点的数据送到解码单元,同时将start_seq更新为该节点的seq,并将该节点的数据flag设置为false;若flag为false,且当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如50ms),则将该节点的seq(lost_seq)发送给发送端,请求发送端将seq对应的rtp数据再发一遍。这样,当有些包很久(大于500ms)都没收到,就认为它来不了,直接将它们丢弃;有些包短时间(小于50ms)没来,则向发送端发送重传请求,请求发送端再发一次该包,试图能补上这些包。
四、结果分析
没加qos模块时,两个手机视频通信在有丢包情况下回出现视频帧不完整,播放出现马赛克的现象,加上qos模块后,视频播放流畅,效果大为改善。同时我们为了测试该方案的作用,在发送端人为地分别丢弃10%和20%的视频rtp包,接收端解码播放效果良好,没有出现马赛克现象。
作者介绍:
彭祖元,环信资深音视频技术专家。拥有多年音视频编解码开发经验,在Android,iOS等平台音视频采集,编码,传输,解码,播放等方面有着丰富的经验,熟悉流媒体服务器开发。 收起阅读 »

Big Data Processing at Easemob – Big Data Processing Platform

Easemob plug-in and its SDK on mobile text and voice messaging communication service become extremely popular in China recently an...
继续阅读 »
Easemob plug-in and its SDK on mobile text and voice messaging communication service become extremely popular in China recently and are used by over 13,000 mobile Apps with millions of registered users in total. One of the driving design principles of Easemob is that better quality of social activities means a better App with a more engaging user experience. With regards to this, Easemob decided to invest into Big Data technology for app developers in the form of an interactive web portal that presents business analytics related to their plug-ins.

21.png


Figure 1 Easemob Social Activity Big Data Analysis Application
Most data on mobile Apps with Easemob’s plug-in is consumed in real-time. Easemob recognized that providing results to app developers in closer to real time could help generate more engaging social activities by allowing more dynamic interaction with users. The finer grained details of a real-time system also provide much more information than a delayed multiple hours aggregate. Hence, the primary goal of Easemob's big data analysis system was to provide analytic results to app developers with minutes or even seconds of latency.
To achieve such target,  we actually  don’t have many options. The original idea is to use apache hadoop to complete the task.  Hadoop MapReduce/Hive based solution certainly can handle such scale required by Easemob with high availability and accuracy.  However, its batch processing logic based upon MapReduce framework means probably it won’t deliver results in real time. Hence Easemob will not be able to provide a specific SLA for its app developers if such idea is implemented.
With some careful research, we came up with a solution that relies on Spark integrated with Cassandra to deliver results. Easemob has been already using Cassandra for its messaging system and the nosql big table database has been proved to be able to scale and meet the requirement of Easemob app clients. In summary, we chose Spark and Cassandra for  the new big data processing system based upon high performance Spark can provide and high availability and scalability from Cassandra. Some details about Spark and Cassandra are discussed in a later section of this article.
About Spark
Spark is an open source cluster computing system developed in the UC Berkeley AMP Lab. Some benchmark results done by Berkeley shows the results of comparison of performance between spark/shark and hadoop/hive combination.

22.png


Figure 2 Benchmark of Spark/Shark and Hadoop/Hive
It is not surprising to see Spark beats Hadoop easily. This is because spark provides primitives for in-memory cluster computing while MapReduce processing can only occur on data stored either in a file system (unstructured) or in a physical database (structured). It can not avoid I/O bottleneck between the individual jobs of an iterative MapReduce workflow. Spark does not have this issue.
Apart from its lighting speed, spark provides a few interesting features which will be needed by Easemob’s big data processing system sooner or later. Shark for Spark is like Hive for Hadoop. It actually builds directly on the Apache Hive codebase. However, it has a “Spark” heart. Underneath, it is using Spark execution engine for its query processing so it appears much faster than Hive. Spark also directly supports real time processing through its streaming framework on which Easemob’s social activity big data engine will be built. The apache machine learning library is tightly integrated into Spark. Interactive data mining is a supported feature by Spark. It is absolutely vital feature for Easemob ‘s future success of its social activity big data analysis system.
As for Cassandra, as mentioned previously in this article, it has been selected by Easemob for its messaging system.  Today in Easemob, more than 100 millions messages a day are being handled by Cassandra database across multiple data centers. The big table database has been proven to be able to scale. The figure 3 as below shows what the big data processing platform for Easemob’s future social activity analysis engine looks like.

23.png


Figure 3 - Platform for Easemob Social Activity Big Data Analysis
There is one thing I particularly like about Spark.  It makes use of the Scala language, which allows distributed datasets to be manipulated like local collections. It combines Object-oriented and functional programming perfectly. It has a very strong static type system. It overcomes many shortcomings of Java language but however, it is still compiled to java bytecode hence it runs on java virtual machine. The Interoperability of the two languages is maintained well. It is perfectly integrated into Spark.
In the big data world, Spark is relatively new technology, however, it is already used by many companies in production such as Yahoo, Sohu and Ebay etc. The long list can be viewed in Apache website.
About the author
Zhi Huang is the Head of Big Data Analytics Department in easemob.com. He can be reached at zhi.huang@easemob.com
Today, in China, our mobile text and voice messaging communication service serves more than 13,000 mobile apps and hundreds of millions of users. We are currently expanding our business globally. If you are app developer, you are more than welcome to use our free service at any time.
作者: 黄智 收起阅读 »

IM客户端数据库加载过程优化

IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。 在系统初始化时,这两部分都要从数据库中加载到内存中。 数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。 每次系统启动的时候,首先查询会话列表,然...
继续阅读 »
IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。
在系统初始化时,这两部分都要从数据库中加载到内存中。
数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。

1.png


每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions()  
FOR (conversation : conversationList) {  
    db.loadMessages(conversation);  
}
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages();
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
针对“1”中每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
对于“2”中每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
 cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');  
  while (cursor.NEXT()) {  
       msg = msgFrom(cursor)  
IF (! msg belong TO conversation) {  
    // 消息不属于当前的会话,所以  
    conversation = NEW Conversation();  
    conversation.ADD(msg);  
    continue;  
}  
 
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {  
    conversation.ADD(msg);  
} ELSE {  
    // 消息个数已经超过会话消息限制  
    continue;  
}  
  }
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。

测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者: 李楠 收起阅读 »

移动开发之语言之美 – 类

现在最受开发者欢迎的两大平台 IOS, android现在主要还使用Objective-C 和 Java 来进行开发,不过苹果公司推出的编程语言 Swift,她吐故纳新,抛弃了Objective C繁琐的语法规则,引入了极为简洁,功能强大的语法。实际上除了平台...
继续阅读 »
现在最受开发者欢迎的两大平台 IOS, android现在主要还使用Objective-C 和 Java 来进行开发,不过苹果公司推出的编程语言 Swift,她吐故纳新,抛弃了Objective C繁琐的语法规则,引入了极为简洁,功能强大的语法。实际上除了平台本身的限制,包括如何调用平台提供的API,如何使用线程,如何启动定时器,如何设计UI界面等等之外,语言本身其实都是大同小异,所以为了方便开发者掌握不同平台的语言,对比学习Swift,Objective-C,Java等移动开发主流语言也是一种不错的选择。
在这个篇章中,我们首先从类定义开始,逐步探讨继承,接口,访问控制

如果你学过面向对象编程的语言,你就知道类是个什么概念,简而言之,我们把某种具有共同属性及功能单元的设计蓝图称之为类,它代表的设计定义,而对象就是根据类定义所产生的内存实体。
我现在用一个简单的需求实例来阐明不同的语言是如何实现这个需求的,以便读者可以了解不同的语言是对类如何定义和实现的。
需求:
实现一个类包含4个变量 
  1. mExampleData 是integer类型
  2. mExampleStr是字符串类型
  3. mExampleBool 布尔类型
  4. exampleProperty 是int类型
  5. 但是exampleProperty是可以通过api进行改变的

包含5个函数
构造函数
  1. exampleFunc函数没有任何返回值和参数
  2. exampleFuncWithParameters 没有返回值但是有一个字符串参数,和 布尔型参数
  3. exampleFuncWithReturnValue 返回值为字符,但没有参数的函数
  4. classExampleFunc 静态或者类函数

Objective-C 定义
@interface YN_Example_Class : NSObject{
@private
int mExampleData; 
 
@protected
NSString* mExampleStr;
 
@public
BOOL mExampleBool;
}
 
-(YN_Example_Class*) init;
 
@property(nonatomic) int exampleProperty;
 
-(void) exampleFunc;
-(void) exampleFuncWithParameters:(NSString*) str withBool:(BOOL) boolP;
-(NSString*) exampleFuncWithReturnValue;
 
+(void) classExampleFunc;
Objective-C 类实现
@implementation YN_Example_Class 
 
-(YN_Example_Class*) init{
mExampleStr = @"YN_Example_Class";
mExampleData = 8;
mExampleBool = YES;
return self;
}
 
-(void) exampleFunc{
NSLog(@"exampleFunc");
}
 
-(void) exampleFuncWithParameters:(NSString *)str withBool:(BOOL) boolP{
mExampleStr = str;
mExampleBool = boolP;
}
 
-(NSString*) exampleFuncWithReturnValue{
return mExampleStr;
}
 
+(void) classExampleFunc{
NSLog(@"classExampleFunc");
}
 
-(void) setExampleProperty: (int) data{
if(_exampleProperty < 100){
_exampleProperty = data;
}
}
 
-(int) getExampleProperty{
_exampleProperty++;
return _exampleProperty;
}
 
@end
Swift
class YN_Example_class{
var mExampleStr:String
var mExampleData:UInt
var mExampleBool:Bool 
 
var exampleProperty:UInt{
get{
mExampleData++
return mExampleData;
}
 
set(data){
if(mExampleData < 100){ mExampleData = data } } } init(){ mExampleStr = "YN_Example_class" mExampleData = 8 mExampleBool = true } func exampleFunc(){ println("exampleFunc") } func exampleFuncWithParameters(str:String, boolP:Bool){ mExampleStr = str; mExampleBool = boolP } func exampleFuncWithReturnValue() -> String{
return mExampleStr
}
 
class func exampleClassFunc(){
println("exampleClassFunc")
}
}
Java​
class YN_Example_Class{
private int mExampleData;
protected String mExampleStr;
public boolean mExampleBool;
private int exampleProperty;
static private String gStr;
 
YN_Example_Class(){
mExampleStr = "YN_Example_Class";
mExampleData = 8;
mExampleBool = true;
setExampleProperty(0);
}
 
void exampleFunc(){
System.out.println("exampleFunc");
}
 
void exampleFuncWithParameters(String str, boolean boolP){
mExampleStr = str;
mExampleBool = boolP;
}
 
String exampleFuncWithReturnValue(){
return mExampleStr;
}
 
static void classExampleFunc(){
System.out.println("classExampleFunc");
}
 
public int getExampleProperty() {
exampleProperty++;
return exampleProperty;
}
 
public void setExampleProperty(int exampleProperty) {
if(this.exampleProperty < 100){
this.exampleProperty = exampleProperty;
}
}
}
 
属性property/成员变量
Objective-C既提供变量也提供属性的概念,实际上属性就是一种语法规则,属性本身并不存在内存中,但编译器为之合成一个内存实体叫_PropertyName,我们可以简单的理解为是用来替代麻烦的setter和getter操作,编译器会自动合成相应的setter和getter函数,不过属性定义的不同会导致setter和getter实现的不同。
为了简洁,去除混淆,Swift已经不再区分成员变量和property,她应经统一了二者,直接用关键字var来定义。
Swift 定义了两种propertyStored Property -- 可以理解为property直接存储数据
Computed Property -- 可以理解为property不能直接存储数据,而是通过getter,setter来间接存取exampleProperty就是个Computed Property
var example:YN_Example_class = YN_Example_class()
example.exampleProperty = 9;
println(example.exampleProperty)
example.exampleProperty 值为10
Java 和 Objective-c 还有 Swift不同之处就是,语言层面没有property的支持,不过无非就是多写两行代码,用来setter和getter成员变量
类函数/静态函数
直接了当的说,就是直接可以通过类名访问
YN_Example_Class.classExampleFunc
类变量/静态变量
用来做所有实例共享的变量,单例模式
swift,和Objective-C不能声明类变量在类里,Java一定要声明在类里。
Objective-C
#import
#import "objc_language_class.h"
static NSString* gStr; 
 
@implementation YN_Example_Delegate
Swift
import Foundation 
 
private var gStr:String?
类属性,所有的实例都共享同一个类属性,类属性一定是个Computed Property(看上述property属性),类属性的实现需要全局属性的支持,上述就是如何定义一个全局属性来存储数据
类属性例子如下:
class var gProperty:String {
set(g){
gStr = g
}
get{
return gStr!
}
}
Java
class YN_Example_Class{
static private String gStr;
接口,继承,重载
Swift
import Foundation 
 
protocol YNExample_Protocol{
func getMe() -> String
}
 
class YN_Example_Delegte : YNExample_Protocol{
var myproto:UInt
var myStr:String
init(){
myproto = 3;
myStr = String()
}
 
func getMe() -> String{
return "YN_Example_Delegte"
}
}
 
class YN_Example_Delegte_derived : YN_Example_Delegte{
// without override, complier will give the error
override func getMe() -> String{
return "YN_Example_Delegte_derived"
}
}
Objective-C
#ifndef objc_language_class_objc_language_class_h
#define objc_language_class_objc_language_class_h 
 
@protocol YN_Example_Protocol
 
-(NSString*) getMe;
@end
 
@interface YN_Example_Delegate : NSObject{
NSString* myStr;
}
 
@property int myprop;
 
-(YN_Example_Delegate*) init;
 
@end
 
@interface YN_Example_DelegateDerived : YN_Example_Delegate;
 
@end
 
#endif
 
static NSString* gStr;
 
@implementation YN_Example_Delegate
 
- (NSString*) getMe{
return @"YN_Example_Delegate";
}
 
-(YN_Example_Delegate*) init{
myStr = [[NSString alloc] initWithUTF8String:""];
return self;
}
 
@end
 
@implementation YN_Example_DelegateDerived
- (NSString*) getMe{
myStr = @"";
return @"YN_Example_DelegateDerived";
}
@end
Java
interface YNExample_Protocol{
String getMe();
}
 
class YN_Example_Delegte implements YNExample_Protocol{
 
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte";
}
}
 
class YN_Example_Delegte_derived extends YN_Example_Delegte{
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte_derived";
}
}
三种语言都不支持多继承,但是都支持多接口实现。
关于重载只有Swift硬性要求在重载的函数前加override,否者complier会报错
构造函数
Swift和Objective-C的默认构造函数都是init(),并且可以定制的构造函,只不过名称都必须命名为init只是参数不同,Java和C++一样都是类名开始的函数,但init有返回值,Java构造函数没有。可以参考上面代码。
Java 和 Objective-C默认都会调用父类的构造函数,但swift则不同,默认不调用除非以下情况:子类没有实现任何init构造函数
子类重载或新实现某些构造函数,但子类所有的property都必须要有默认的初始值,也就是在声明时赋的值
Swift 可以重载构造函数,不过必须要加override前缀
类探测API
Java:
用 instanceof 来探测类type
if(example instanceof YN_Example_Class){
System.out.println("is YN_Example_Class");
}
Swift:
用as来探测类type
delegate as YN_Example_Delegte
Objective-C
用isKindOfClass来探测类type
BOOL isClass = [delegate isKindOfClass:[YN_Example_DelegateDerived class]];
访问控制
Objective-C
其访问控制基本和C++一致

@private:      只有这个类可以访问
@protected:  此类和子类可访问
@public:       任何类都可以访问
默认权限是protected
Swift:
private:        只有在本源文件里的代码可以访问
internal:       只有在本module(例如,app,或framework)里可以访问
public:         没有限制,可以作为对外暴漏的接口,例如创建个library
默认权限是internal
Java:
private:       只有这个类可以访问,不可跨包访问
default:       只有本包得类可以访问,默认的类都是default
protected:本类和此类的子类可以访问,跨包可访问
public:        没有限制,所有类可以访问,跨包可以访问
默认权限是default
此文主要讲解不同的语言对类不同的定义和实现,哪些地方不同并且应该注意什么,不过从结构来看,大体语言都很类似。此文并没有讲解的面面俱到,例如教大家如何熟悉语言的基本语法,如何声明变量,如何写控制流,这些都需要读者自己写程序加以熟悉。
由于时间有限,如果有纰漏之处,还望提醒,我会及时改正,谢谢。
PS:如果需要源码,请发信给mailto : syyorient@outlook.com
作者: 隋云怡
[Reference]
https://itunes.apple.com/cn/book/swift-programming-language/id881256329?mt=11
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/I
 
  收起阅读 »

自组织是不是团队管理的乌托邦?

对于很多管理者,最幸福的事,莫过于做到名义上管理一个团队,但实际上什么都不需要做。他所带的逆天团队还可以成果迭出。对于团队中的成员来讲,如果他可以做什么都不被管,做什么都有人帮,那真是可以做梦也会笑醒的。这样的团队存在么?自组织的团队据说就可以,不管你信不信,...
继续阅读 »

51.jpg

对于很多管理者,最幸福的事,莫过于做到名义上管理一个团队,但实际上什么都不需要做。他所带的逆天团队还可以成果迭出。对于团队中的成员来讲,如果他可以做什么都不被管,做什么都有人帮,那真是可以做梦也会笑醒的。这样的团队存在么?自组织的团队据说就可以,不管你信不信,所以我看了这本书。
我想要谈论它,还有个人变动的原因。我曾经待过养老的公司,也从热衷微管理的公司走过。几个月前又刚从一个大公司出来,到了一个小小的初创企业。环境的变化会让人变得更加敏感,上面那些变化让我得以对比思考管理的各个方面。其实那个大公司也不大,不过大公司很多问题它都有而已。(大公司病嘛,等有时间再讨论)
很多时候,加入一个有问题的团队,就像旅行路上经过的沼泽地。为了生存你只能滚着前进,与沼泽保持尽可能大的接触面,然后弄一身脏泥巴。等到滚多了,再加上勤奋努力,很容易的,你会成为一个滚泥巴专家。
你很可能忘记,本来只是为了经过。你想要去的,是美好的远方。
这只是个提醒。也许你只想研究技术,但你值得知道,哪些是该受的,哪些是该得的。如果你还想管理,不想掉进泥沼里,你更不应该停止对好团队的追求。这本书也就更值得一看。
这是一篇读书笔记,书是《管理3.0》。这本书都很多理论和观点有意思,我会谈谈我喜欢的一些,包括自组织相关的思维方式、创新问题、开发模式和团队管理。也会补充我认为缺失的一环。最后是一个问题,也是对个人最重要的问题,你适合什么样的团队?
思维方式
值得一提的是系统思维,来源于彼得·桑吉的《第五项修炼》,“将问题视为整个系统的一个组成部分,侧重于组织内的循环关系和非线性因果关系”(P47)。其实就是不要将一个运动的整体割裂对待。当一个人作为个体存在于团队内部时,其他人都是环境变量。你做的所有动作,都会对其他人产生影响。要认识到自己能力可以扩展的边界。当然重要的是,不要跟猪做队友。
这本书我也是几年前看过,关于系统内部正向反向反馈的说法印象深刻。只要反馈环建立起来,就会有习惯的力量帮助你或者阻碍你。所以要关心当前团队的运转情况,强化正向反馈。
其次是关于简化论的说明,“找到了心脏病的诱因(简化论),并不意味着可以制造一个不生病的心脏(构建论)”(P9)。这也是很多人知道自己团队的问题,却不知道如何构建一个好团队的原因。简化论是很多人思考停止的地方,因为对于吐槽来说足够了,殊不知后面才是更有价值的。
关于创新
1. 创新需要土壤
在这个人人视抄袭为平常的国家里,创新已经稀有到要比鬼还少了。为什么中世纪后有文艺复兴,为什么有春秋战国可以百家争鸣?我与作者一样,非常赞同土壤的说法,“复杂性系统方法认为创新是可遇而不可求的,它是水到渠成而自然涌现的结果。得先有涌现所需的土壤,才能期待它的发生”(P52)。就在去年提这说法的时候(内部在创新方面一直有讨论),我是希望说明创新是需要多数人参与,发生在少数人身上的,要给工程师们空间,不要太多管理操作。
前文有个关键词是“涌现”,它在做属性时,按照定义是“当一个系统的属性不能追溯到系统的任何独立部分时,我们就称之为涌现属性”,就像“流动性是水的涌现属性,文化是群体的涌现属性”;做动词时,强调的是自然发生,整体性质的体现,比如在说涌现式设计则强调 “最好的架构不是预先定义好的(可能有一个基本的形式),它可以在产品开发过程中逐步浮现出来”(P21)。这里隐含的意思,我理解,创新是一种整体表现,也是需要用结果后验的,我们要做和能做的只是增大其可能性而已。
2. 包容式多样性
作者更进一步,强调了土壤的肥沃问题,“团队的多样性能够显著增加其创造力。但必须有足够的共同点加一些制衡,即包容式多样性”(P60)。做事要找志趣相投的人只是其一,只有能力互补,才容易有火花碰撞出来。这个在我组织过的黑客马拉松、参加过的内部创新大赛表现得十分明显,如果只是技术人组队,很容易陷在 Geek 圈里,玩玩原型做技术储备可以,离产品化还是甚远。
说起来,此次微博的升级视频,我自认为是个小的正向例子。那是有一天,设计师跑过来问说如果想利用每个人的信息来生成个性化的视频,能不能搞得定。如果没有服务的话,他只能每个人手动生成了,可能几千个。我说没有经验,但可以试试看。于是有了最后的 AppleScript + Shell + JavascriptX 的奇怪组合。我想说的是,由于有设计师的参与,由于不同领域的人共同对技术应用边界的摸索,才有了产品质量的成果出来。事实上效果还是比较令人满意的,没看过的可以看看这里:http://www.weibo.com/1819367693/Bp6II3a1V
我说这个例子是偶然,因为从想法到实现还有相当长的路要走。大多数想法都像书中所说,"一个很好地想法可能在公司内被踢来踢去好几年而得不到利用,这不是因为大家没有看到它的好处,而是因为不知道应该由谁来负责将它踏实地落到行动上”(P63)。我是负责后端团队,通常不会与设计师打交道,与这位设计师认识纯粹是由于组织黑客马拉松的时候他帮忙设计制作了宣传视频。
3. 勒曼第六法则
对于一个业务基本稳定的公司,创新难还有一个重要原因,就是勒曼第六法则,"许多软件产品并没有演化使自身变得更好,他们演化之目的只是为了尽量不被用户抛弃(不可避免)”(P310),大多时候,它们 “为了将客户满意度维持在相同的水平,要不断增加产品的功能性”。制定 KPI 当然要 SMART 不是么?你见过 KPI 说要将创新水平提高几个百分点么?。。。
说回创造力,还有个有趣的三阶段理论。先是前习俗创造力,属于儿童的,是自发性的;然后是习俗创造力,一般在7-11岁左右,开始是真正的思考,了解世界的边界;最后是后习俗创造力,也是创新开始的地方(P67)。最后一阶段的时候,便是讲初心的地方,“即使知道有实实在在的约束,也可以像小孩子一样充满想象力”(P70)。这个理论很类似的一个学习理论,会在后面人员培养处再讲。这里主要说明边界学习对创新的影响。边界客观存在,不要让它制约你的想法。
4. 罗杰斯创新曲线
罗杰斯有个创新曲线,说的是团队人群分布。“创新者2.5%、早期采用者13.5%、早期采用人群34%、后期采用者34%、迟缓者16%。改变要从渴望尝试新事物的创新者入手,一次努力推动后面人群。忽略行动迟缓者,他们会一直抵制改变,直到其他所有人都采用”(P335)。这个比例在不同团队肯定会有所差异,你只要记住,不要想当然所有人都愿意改变就行。然后在尝试新事物的时候,不要怕反对声音。
管理者要做的就是发现这种萌芽,帮助其成长而不被恶劣环境影响。对那些不愿意改变的力量进行驱赶,甚至可以让他们适当痛苦。“让停滞痛苦不堪”(P336)说的就是这种迂回方式,有些人吃一堑才能长一智。
手中的鞭子和驴头前面的胡萝卜同样有用。
开发模式
书中提了两家的宣言:
敏捷软件开发(P19)
“个人和交互 胜于 流程和工具;可以工作的软件 胜于 复杂的文档”;
“虽然右项也具有价值,我们认为左项具有更大的价值”

精益软件开发(P24)
“可以工作的软件犹不足,尚需精益求精;响应变化犹不足,尚需稳步增加价值”
“在追求左项的过程中,我们发现右项亦不可或缺”

说实话我对这些模式都不感冒,这方面我更功利,我觉得不同发展阶段需要不同的开发方式。特别在互联网行业,一个团队的开发活动需要在高速度和高质量方面反复切换。
在团队人员紧缺的时候,你要有速度意识,这是质量下降几乎是必然的;如果人员稍微充沛,就要重新思考质量问题,承认低质量软件的存在与不合理之处,持续改进。因为当用户请求量上来,几乎所有的问题都会显现出来(墨菲定律),你要做的是在Bug 酿成灾难之前消灭掉。
我在心里是渴望精益的。
关于不同开发方式其实网上有很多争吵,极端言论也比比皆是,但看两者的宣言,你很难产生针锋相对的感觉。为什么?因为很多人在交流时急于表达自己的观点,(或有意或无意地)不会强调与对方观点的相同之处,这在沟通中造成了很多无谓的争执。相互熟悉的人之间也无法避免这种情况,更别说背景差异的团队成员之间了。
因此我也更加赞同敏捷宣言里对个人和交互的强调。有效沟通才可能让改进发生。
团队管理
1. 动机管理,以人为本
“人是任何软件项目中最复杂的元素”(P62),确实所有对时间和项目的管理,都是对人的管理。而其中最难的部分,是人员的动机管理,“所有管理者首先关注的应该是激励员工以确保他们愿意全心全意做事,而这一切,都需要动力”(P56)。如果一个人已经与团队贰心,那你实质上已经失去他了。所有的CEO(大忽悠)都会谈价值观,谈企业文化,也是这个目的。
我们把只画饼的宣传叫做忽悠,是在强调奖励的必要性。但奖励最重要的目的是要变成激励,书里提醒 “不要把为团队聚餐买单当成庆祝里程碑式胜利表达谢意的方式。为团队买单是为了解决人们社交和相互联系的需求”(P85)。而且“不要应某些员工的要求而取悦他们,从而引入规则、实践和政策。真正的目的必须是引入秩序和稳定性”。
后面这句话我不知道是否理解得准确,我只能说部分同意。我同意规则和秩序,只要他们是公平的,而不在意是否为了取悦员工。事实上我认为,除了真正的团队目标,其他所有事情都应该为了成员愉悦而做,这样才能最大发挥成员的价值。
在某种程度上,管理应该是服务性的。
2. 适当授权,解除电网
如果说动机管理搞定了人员能动性的问题,那么授权管理就是为解决工作效率的问题。“智慧的控制要做到无形中的影响。决策的制定也应该来自人员之间的互动,而非来自我的权威”(P109),这方面英文Empowerment更容易理解一些。把权力下放给员工,但不要忘记目的是“不是增强激励,而是改善管理”。当然,授权也是分级别的:告知、贩卖、咨询、商定、建议、征询、委托(P125)。当然,“这并不意味着需要分级的架构”。
授权不当就会造成隐形电网的存在。“当管理者对下属赋能时,他们并不会清楚地点明其职权界限,这意味着人们往往只能摸着石头过河,磕磕绊绊,中间还伴随着情感伤害”,“它是时间和资源的双重浪费。屡屡触及隐形电网的经历会扼杀人们的积极性”(P173)。这方面我倒是体会颇深,因为不同等级的信息不对称和相互之间的沟通问题,会造成很多理解上的偏差,碰上电网是非常可能发生的。这个事故的预防(降低频次)以及处理好坏(减轻伤害),不同的管理者之间差距非常明显。坏的结果多了,就很容易造成多一事不如少一事的氛围,离自组织也必然是越来越远。
3. 运用同侪压力
适当授权之后,就可以放手团队去解决问题了,也是时候做好准备接受成员犯错的考验了。“若要有所作为,必先磨练耐性”(P130),而且“当已授权的团队走进办公室请求你决定某件事情时,要想办法让他们自己解决问题”(P135)。不要做保姆,不止是减轻当前的负担,重要的是要防止成员思维惰性,养成依赖别人思考的习惯。
 团队可以自行运转,来源于成员想一起共事的意愿,但意愿能促进协作么?书里提到的“同侪压力”,很好的说明了这一点。当你做事的时候,大家都在看着你。你的能力展现无遗,好与不好都在每个人心里。这种社会压力让成员之间可以互相驱动,也是规则发挥作用的地方。
要让同侪压力转化成动力,要注意“与那些试图破坏这些团队的人做斗争”,因为“只有在人们想要加入某个团体时,该团体的社会压力才会起作用”(P224)。最好的方式是,“建立团队、设定目标、再退后一步”。如果有人要破坏团队的规则,那么“找他谈一谈”,不奏效就“再谈一谈”,还不行就“请他离开”。团队是作为团队而存在,而不是为某个人。我的经验是,在这方面越果断越好。
如果怕请人离开,那就把好入门的关。“在允许某人加入(有挑战性的)项目之前,必须对他进行适当的测试”(P185),这是书中根据荷兰的低交通事故率总结的经验。我在微博也一直坚持,没有经过内部新兵训练以及高性能服务设计演练的人,再忙也不会让其进入正式的开发序列。这是对当事人负责,如果他因为经验缺乏酿成事故,以后做事也会畏手畏脚;更是对团队其他人负责,团队不应该因为某个人的问题而受拖累。互联网应用,完成功能只是基本的,还要保证性能、可用性等很多方面。问题在上线前解决要远比上线后简单。而一旦上线,就是7x24小时的服务,出了问题基本是不搞定不眠不休的(也是苦逼之处)。
一句话,宁肯一堆人累,也不要被一个人坑。
4. 保护团队成员
“管理者既要促进自组织,还要保护大家不受伤害”(P175)。我本来觉得这是对管理者基本的要求,但是在一次内部讨论中,出现了截然不同的相反观点。有人反问说,如果一个人不能很好地保护自己,总需要人呵护,如何期望他在对外合作中,甚至在公司外部的行业洗礼中生存下来?
如果说从个人角度来看待这个问题,尚可以理解的话,从团队角度看来我是完全不赞同。组成团队的主要目的是为了完成共同的目标,而不是为了让个人接受锻炼。而在完成目标过程中成员之间互相配合互相帮助,也是团队存在的价值所在。让个人成长,在团队中发挥更大价值,跟让个人接受磨练以成长到更高层次,是两件事。
特别是当管理者不是领导整个公司的时候(个人还没体验过),如果不喜欢随波逐流,期望自己的团队在公司内可以变得更好,举例来讲先变成自组织模式,更要注意这点。如果让团队中的普通成员去接受不必要的压力,要么他觉得太困难无法做事,要么他适应下来,结果把外界的习惯带回当前小团队,所有这些都会对团队向自组织转变起到负面作用。这就是环境的力量。用系统思维方式来看,如之前所述,就是不要让环境对团队改进产生负向反馈。
5. 开始,不要停止改进
开始改变并不意味着就从此一路向前了。一切都在变化之中,环境、人员甚至是目标都有可能随时改变。而整个团队也很可能进入局部优化的状态,事情做得马马虎虎,也总是问题不断。这时候就要考虑模拟退火了。“先加热再冷却会改变金属的性质,比如强度和硬度,这种技术叫退火。复杂性系统中,错误和噪声-通常由环境引发,会扰乱系统并使之拜托局部优化,在这之后,系统更容易找到更好地位置安定下来。”(P339)
用错误和噪声来改善团队,说起来有些奇怪,再看一下模因论可能就好理解了:“不要求模因组内所有思想、概念和实践都必须是有益的。其中的一些有害处,但由于同属于一个模因组,负面思想也会帮助正面思想互相复制,这样便可以中和负面影响。危险范例:没有确凿证据能够证明代码集体所有制的价值,但是此实践似乎能增强其他敏捷实践,所以复制它也不会有什么坏处。”(P204)
如果发生了局部优化,我理解很可能是某些方法措施有了什么副作用。这时就要重新审视团队,是否有规则没有实施或者被错误的实施了;如果发现新规则不适应当前的团队,要继续尝试其他可能。
不恰当的错误补救例子很多,比如弹性制度下一个人钻空子不好好工作变为全部人强制坐班,或者一个人在项目过程中出现沟通问题,全部人天天一起开会。这种情况还有很多,说个亲身经历的创新相关的例子。
曾经有一次大家想搞创新,但是在如何创新上分化成了两派。一派就是我刚才提的土壤论,希望解放大多数同学的创造力;另一派则是规划论,认为创新是可以规划出来,用项目来管理的。讨论结果是决定先用后者。不出意料的是,执行规划论的结果就是,大家只是将其看做是另一项工作任务而已。问题是本来都忙不过来了,还要再进行一项自己毫不在意的事情,即使它的名字叫创新,会有什么动力呢。因此这个行动基本上还未开始就结束了。然后,整个事情最奇怪的地方出现了,再没有人谈论创新问题,好像不存在这件事情一样。
举这个例子,不是说理念分歧的问题,而是整个团队在失败尝试之后的总结和使用方式,也就是如何获取经验的问题。当一件事情失败之后,不要怕把事情放在台面上讨论,特别是尝试,大部分都是要失败的。如果失败了而不总结,才是对自己最大的不负责任。总结可以得到经验,但要时刻对经验保持警惕。好的经验固然让人成熟进步,错误经验却让人裹足不前。
攻打威虎山的时候,正面土匪多有坦克,小分队又从背面上山了不是?
这就够了么
思想是好思想,行动也有方法,就能实现么?在我看来,至少还有两个问题需要解决才行,也是书上没有讲的。一是目标管理,二是人员培养。
1. 目标管理
这个目标管理不同于前面说的动机管理。动机管理是解决愿不愿意做的问题,目标是解决要做什么的问题。一个团队是自组织的,意味着每个人都是自主的而不是被管理的,好的时候会像大雁一样跟随头雁,不好的时候就各自为政,一盘散沙。
因此目标管理是不可忽略的。咦,那还怎么自组织?我个人理解,这不是像KPI 一样的设定与分解,而是一起来讨论团队目标,然后自我选择与设定的过程。事情一样,但规则不同。
2. 人员培养
并不是每个人都适合自组织团队。有的人能力不足,无法自主完成任务;有的人驱动不足,需要监督和指导;有的人悟性不够,无法自己发现问题。你也许会说,那就不是我们需要的人啊。
那只是理想。什么都是变化的,市场是变的,目标是变的,人也是如此。好的方面是,他的能力会提高,你有可能培养出一个合格的人;不好的方面是,他可能离开,你会流失已有的成员。
所以现实是,在你集合一群合格的人之前,你有漫长的路要走,而你的船不能停或掉头。因此,人员培养是必须要重视的。但其实团队能给的帮助有限,只有足够的压力和适当的指导。适当的指导让其找到方向,适当的压力让其不要懈怠,如此而已。
人必先自救,而后人救之。这句话放在这里依然是适用的。
自组织是不是团队管理的乌托邦
你可能会想,有个先进社会还说达到之后人人富足,衣食无忧,我不也天天在初级阶段生活么?自组织会不会成为团队管理的乌托邦?坦率来讲,我也不知道。我曾经看过(自以为)自组织的影子,但是没有完全实现。
每个人有特定适应的团队,如果你怀疑自组织,你可以不去实践,但你肯定需要寻找自己心仪的团队,不管是加入还是组建。我只是知道我喜欢这样的团队,我也愿意尝试。
你要考虑的问题其实是,你适合什么样的团队。想一想,
你是否能够把握自己的方向?
你是否希望不再被指手画脚?
你是否能够自学愿意帮助他人?
你希望找到一群跟你一样的人?
...
如果是的话,那就值得一试。
我们正在试。
原文: http://ericliang.info/is-self-organization-utopia-of-group-management/
作者: 梁宇鹏|
 


ea790d9dgw1ew6fkfe8mfj20sj0j1tbn.jpg




ea790d9dgw1ew6fof7nwcj20zk0nptbr.jpg


 
  收起阅读 »

环信SDK与Apple Watch的结合系列讲解(3)

第3章主要介绍怎样在Watch App的页面上显示iPhone程序里的数据。主要操作的是“EMWatchOCDemo WatchKit Extension”这个文件夹,附源码EMWatchOCDemo。 如果你已经看过我在第1章推荐的blog,应该明白这个t...
继续阅读 »
第3章主要介绍怎样在Watch App的页面上显示iPhone程序里的数据。主要操作的是“EMWatchOCDemo WatchKit Extension”这个文件夹,附源码EMWatchOCDemo。
如果你已经看过我在第1章推荐的blog,应该明白这个target主要是负责逻辑的,从iPhone App中获取数据,调动Watch App显示数据。
默认是这个样子的

41.png


一、WathKit定义了一些专门用于Watch App的类,与UIKit的对比如下图

42.png


二、整合Watch App和iPhone App
1、新建Controller
根据Interface.storyboard,我需要新建5个Controller。右键---New File---Cocoa Touch Class

43.png


新建的类默认有三个方法,[-awakeWithContext:]相当于[-viewDidLoad],[-willActivate]相当于[-viewWillAppear],[-didDeactivate]相当于[-viewDidDisappear],“相当于”一下是不是就很容易理解每个方法中能进行什么操作了?
建好这5个Controller之后,再次打开Interface.storyboard,在每个storyboard Controller的Class属性中填写对应的类名,这种操作对于熟悉storyboard的开发者来说,应该都不陌生。
附图一张

44.png


2、将自定义的类与storyboard关联起来之后,继续关联其他的控件。
声明插件变量Table,并在storyboard中进行关联。
@property (weak, nonatomic) IBOutlet WKInterfaceTable *table;
创建自定义的Table Row Controller,右键---New File---Cocoa Touch Class---Subclass of “NSObject”,声明插件变量Label,在storyboard中将Table Row Controller和Label进行关联。要记得填写Table Row Controller的Identifier,在加载数据时会用到这个属性。
3、接下来要进行每个页面的数据获取了,我是在[-awakeWithContext:]中进行的数据获取。
WKInterfaceController有个类方法[+ openParentApplication: reply:],用于向对应的iPhone App发起申请。
而对应的iPhone App要想检测到这个请求,需要在AppDelegate中监听 [- application: handleWatchKitExtensionRequest: reply:].
以菜单页面MenuController为例,当页面加载时要先向iPhone App发起获取是否登录的申请,iPhone App收到申请,将是否登录的值返给WatchKit Extension;如果没有登录,页面上显示“登录”选项,如果登录了,显示“会话”“好友”“群组”三个选项。
MenuController:
[WKInterfaceController openParentApplication:@{@"action":@"isLogined"} reply:^(NSDictionary *replyInfo, NSError *error) {
         BOOL isLogined = NO;
 
         if ([replyInfo count] > 0) {
            isLogined = [[replyInfo objectForKey:@"isLogined"] boolValue];
         }
 
          if (isLogined) {
              NSDictionary *conversationInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"会话", @"title", nil];
             NSDictionary *friendInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"好友", @"title", nil];
             NSDictionary *groupInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"群组", @"title", nil];
             [self.dataSoure addObject:conversationInfo];
             [self.dataSoure addObject:friendInfo];
             [self.dataSoure addObject:groupInfo];
 
             NSInteger count = [self.dataSoure count];
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
            [self.table setNumberOfRows:[self.dataSoure count] withRowType:@"RowType2Controller"];
             for (int i = 0; i < count; i++) {
                 RowType2Controller *rowController = [self.table rowControllerAtIndex:i];
                 NSDictionary *dic = self.dataSoure[i];
                 NSString *title = dic[@"title"];
                 [rowController.titleLabel setText:title];
             }
        }
        else{
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
             [self.table setNumberOfRows:1 withRowType:@"RowType2Controller"];
             RowType2Controller *rowController = [self.table rowControllerAtIndex:0];
             [rowController.titleLabel setText:@"登录"];
       }
  }];
 
AppDelegate
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
    if ([userInfo count] > 0) {
        NSString *actionString = [userInfo objectForKey:@"action"];
 
        EaseMob *easemob = [EaseMob sharedInstance];
        if ([actionString isEqualToString:@"isLogined"]) {
            reply(@{@"isLogined":[NSNumber numberWithBool:[easemob.chatManager isLoggedIn]]});
        }
}
4、获取到了数据,接下来要调用Watch App显示数据了。
显示数据主要用到了WKInterfaceTable。WKInterfaceTable相对于UITableView而言,能调用的接口少的可怜
WKInterfaceTable.h
WK_CLASS_AVAILABLE_IOS(8_2)
@interface WKInterfaceTable : WKInterfaceObject
 
- (void)setRowTypes:(NSArray *)rowTypes;                                         // row names. size of array is number of rows
- (void)setNumberOfRows:(NSInteger)numberOfRows withRowType:(NSString *)rowType; // repeating row name
 
@property(nonatomic,readonly) NSInteger numberOfRows;
- (id)rowControllerAtIndex:(NSInteger)index;
 
- (void)insertRowsAtIndexes:(NSIndexSet *)rows withRowType:(NSString *)rowType;
- (void)removeRowsAtIndexes:(NSIndexSet *)rows;
 
- (void)scrollToRowAtIndex:(NSInteger)index;
 
@end
WKInterfaceController中
上一步中的代码示例已经给出了WKInterfaceTable使用方式,具体代码请看demo。
5、每个单独的页面都写好了,现在要让他们动起来。
WatchKit提供了三类页面导航方式。
第一种UINavigationController 控制的类似栈的导航方式,相应接口
- (void)pushControllerWithName:(NSString *)name context:(id)context;  // context passed to child controller via initWithContext:
- (void)popController;
- (void)popToRootController;
第二种 modal 形式,相应接口
- (void)presentControllerWithName:(NSString *)name context:(id)context; // modal presentation - (void)dismissController;
第三种 类似 UIPageController 的分页式导航,相应接口
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts; // modal presentation of paged controllers. contexts matched to controllers - (void)becomeCurrentPage;
其中的“WithName(s):”参数就是每个控件在storyboard中设置的Identifier属性。
好了,就先写这么多吧,后期有时间会继续补充。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(2)
 
  收起阅读 »

环信SDK与Apple Watch的结合系列讲解(2)

这一篇主要是介绍怎么拖apple watch上的相关页面,附源码EMWatchOCDemo。 需要在工程中的“EMWatchOCDemo WatchKit App”中进行操作,该文件夹的结构如图 WatchKit几乎不允许直接coding页面,只能在...
继续阅读 »
这一篇主要是介绍怎么拖apple watch上的相关页面,附源码EMWatchOCDemo。
需要在工程中的“EMWatchOCDemo WatchKit App”中进行操作,该文件夹的结构如图

31.png


WatchKit几乎不允许直接coding页面,只能在storyboard上拖来拖去,对于我这种习惯直接coding页面的人来说,真真是极痛苦的。
一、确定apple watch上的操作流程
首先,我想要一个菜单页面,跟iPhone程序对应,在环信SDK未登录情况下,显示登录选项;在环信SDK登录情况下,有三项:会话,好友,群组。
然后,登录选项点击之后,能启动iPhone进行登录操作,因为watch的页面实在是太小了,没有键盘,环信SDK也不支持指纹或者声音登录。
再然后,登录操作成功之后菜单页面显示三项选项,每项点击都可以进入对应页面,显示相应的数据。
最后,进入聊天页面,可以显示已有的聊天记录,可以发送表情。本来想加入发送心跳的功能,但是发现WatchKit不支持watch app获取硬件传感器参数,而且找到了一篇不错的自问自答文章http://www.cocoachina.com/ios/20150323/11396.html
二、在storyboard上拖来拖去
1、点击文件夹下的interface.storyboard文件,右边会出现相应的视图显示。根据第一步的思路,直接拖4个Interface Controller, duang~,duang~,duang~, duang~,就成这个样子了

32.png


每个Interface Controller的Identifier属性强烈建议写上,为什么请见第3章。为了便于区分每个controller,个人习惯给每个Interface Controller都填写Title属性。
2、想了一下菜单,会话,好友,群组的功能,显示用table是再好不过的了,直接再拖table到每个controller,然后你就会发现一些奇怪的东西,比如这样的

33.png


Table Row Controller是类似于自定义UITableViewCell的东西,默认是继承于NSObject。
Group应该算是个新概念,它的作用是将页面上的控件分块。WatchKit不支持设置frame,目前我知道的,只是支持在横向上“左中右”,在纵向上“上中下”,支持设置大小。如果你想实现一个九宫格样式的页面,比较讨巧的方式是直接用三行Table Row Controller,每行三个button。不讨巧的方法,抱歉,我发现不管怎样排列组合这些属性,都出不来九宫格。
3、在会话页面显示和谁聊天的username,在好友页面显示好友的username,在群组页面显示群组的名字,用Label通通能搞定。
我在group里拖一个label,默认就是这个样子

34.png


自己选下不同的选项,看下效果,应该就能理解了。
4、聊天页面比较特殊,需要自己发送的在左边显示,对方发的在右边显示。不过这个很容易实现,只需要发送的和接收的放在两个Table Row Controller里,然后设置空间的Position即可,示意图:

35.png


拖完上边这些,页面就差不多了 。在Scheme中选择EMWatchOCDemo WatchKit App,编译 OK,可以进行第3章了。
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »

环信SDK与Apple Watch的结合系列讲解(1)

该系列是记录在apple watch上开发IM,用到了最近挺流行的环信IM SDK。 一、先来一段网上随处可查到的信息: 1、两种分辨率 1.65寸 312*390 1.5寸 272*340 2、开发环境 Xcode 6.2 or later OS X 10...
继续阅读 »
该系列是记录在apple watch上开发IM,用到了最近挺流行的环信IM SDK。
一、先来一段网上随处可查到的信息:

1、两种分辨率
1.65寸 312*390
1.5寸 272*340
2、开发环境
Xcode 6.2 or later
OS X 10.9.4 or later
Watchkit
3、三种展现类型
a>标准的watch app,拥有自身的界面和功能
b>Glances,纯信息浏览,样式也很固定。这一方式适合新闻、天气、股票、运动数据等信息。
c>Notification,用于显示用户的本地通知和远程通知,它包括 Short-Look 和 Long-Look 两种形式。
4、官方文档
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/index.html
5、怎么在工程中加入apple watch
关于这部分,网上的资料很多,在这外链一些不错的blog:
http://www.tuicool.com/articles/MFJFNny
http://blog.jobbole.com/79984/
 
二、WatchKit和环信SDK的结合

1、在工程中添加WatchKit扩展
你可以新建一个工程,在target上添加,也可以在已有的工程的target上添加,操作步骤是一样一样的。
为了记录的完整性,我新建了一个工程EMWatchOCDemo,看名字可以知道,这是一个ObjC写的工程。

21.png


2、EMWatchOCDemo是iphone的程序,先在这个target上接入环信SDK。
按照环信官网上的ios集成文档下载环信sdk,加入依赖库,配置好属性。我不准备实现实时语音,所以只用了lite.a。导入环信sdk之后的工程变成了这个样子

22.png


我比较习惯每个步骤都编译一下,如果编译通过,继续进行下边的步骤。
3、初始化环信SDK
环信SDK的初始化几乎都在AppDelegate.m中实现,如注册app,配置apns证书和昵称,进入前台和进入后台的操作,这些在环信官网的ios初始化文档中都有描述,不再赘述。
4、实现一些简单的功能
ConversationViewController 会话获取,展示。
FriendsViewController 好友获取,展示。
GroupsViewController 群组获取,展示。
HomeViewController是首页,Tab容器,实现了登录等操作。
DXEMIMHelper是IM的管理类,定义了全局的宏定义,包括默认登录账号KDEFAULT_USERNAME。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(2)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »

怎样在Apple Watch上集成环信SDK

本文简单的讲述下如何用Apple Watch Kit集成环信SDK. 首先:需要升级xcode到version 6.2,和 IOS SDK8.2 然后下载环信SDK从官网 打开XCode->new project->new target->选择WatchKit...
继续阅读 »
本文简单的讲述下如何用Apple Watch Kit集成环信SDK.
首先:需要升级xcode到version 6.2,和 IOS SDK8.2
然后下载环信SDK从官网
打开XCode->new project->new target->选择WatchKit App

1.png


xcode 会自动给你创建几个targets,例如下图:

2.png


把EaseMobSDK文件夹拖拽到HxAppleWatchDemo Target里

3.png


选择target HXAppleWatchDemo,加入下图所有的Linked Frameworks and Libraries里的库文件

4.png


在HXAppleWatchDemo target 创建bridging header文件

5.png


设置bridging header文件

6.png


设置other linker flags 以保证SDK Lib category的扩展方法可用

7.png


所有环境设置都已完成,试着build下看有啥问题么
接着开始写代码:
1. 打开HXAppleWatchDemo WatchKit App 里的interface.storyboard然后加个button 叫load contacts

8.png


2. 找到HXAppleWatchDemo WatchKit Extension里的文件InterfaceController.swift,然后把上述的button关联到 @IBOutlet weakvar open:WKInterfaceButton!
InterfaceController.swift代码如下
//
// InterfaceController.swift
// HXAppleWatchDemo WatchKit Extension
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
// 
 
import WatchKit
import Foundation
 
class InterfaceController: WKInterfaceController {
 
@IBOutlet weak var open: WKInterfaceButton!
 
@IBAction func openApp() {
InterfaceController.openParentApplication(["action":"getcontact"], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
if info != nil{
if info.count > 0{
self.getContacts(info!["contacts"] as [String])
}
}
})
}
 
func getContacts(contacts:[String]){
presentTextInputControllerWithSuggestions(contacts, allowedInputMode: WKTextInputMode.Plain, completion: {(result:[AnyObject]!)-> Void in
if result != nil{
var id:String = result[0] as String
var date:NSDate = NSDate()
var now:NSTimeInterval = date.timeIntervalSinceNow
 
self.sendMessage(id,text: now.description)
}
}
)
}
 
func sendMessage(id:String,text:String){
InterfaceController.openParentApplication(["action":"send","name":id,"message":text], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
 
})
}
 
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
 
// Configure interface objects here.
}
 
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
 
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
 
}
InterfaceController.openParentApplication是用来和IOS的程序通讯的接口,大部分的业务逻辑需要在parent application实现也就是上述说的HXAppleWatchDemo Target
我们看下HXAppleWatchDemo是如何实现和Apple Watch App通讯的
//
// AppDelegate.swift
// HXAppleWatchDemo
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
// 
 
import UIKit
 
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,IChatManagerDelegate{
 
var window: UIWindow?
 
var callback:(([NSObject : AnyObject]!) -> Void)!
 
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let apnsCertName:String = "chatdemoui";
 
EaseMob.sharedInstance().registerSDKWithAppKey("easemob-demo#chatdemoui", apnsCertName: apnsCertName)
 
EaseMob.sharedInstance().chatManager.addDelegate(self, delegateQueue: nil)
 
EaseMob.sharedInstance().chatManager.asyncLoginWithUsername("tt1", password: "1", completion: { (loginInfo,error) -> Void in
 
NSLog("login callback : ")
 
HXSDKHelper.instance.sendTextMessage("uni3", textMessge:"test from")
}, onQueue: nil)
 
return true
}
 
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
 
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
 
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
 
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
 
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
 
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
 
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
 
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
 
func didSendMessage(message: EMMessage!, error: EMError!) {
if(error != nil){
callback!(["send":error!.errorCode.value])
}else{
callback!(["send":"ok"])
}
}
 
func didReceiveMessage(message: EMMessage!) {
 
}
}
这个就是和Apple WatchKit App实现通讯的接口:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) { 
 
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
 
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
HXSDKHelper就是对环信一个简单的封装,现在里面只实现了一个函数
//
// HXSDKHelper.swift
// swittest
//
// Created by youni on 15/3/15.
// Copyright (c) 2015年 youni. All rights reserved.
// 
 
import Foundation
private var gInstance = HXSDKHelper()
 
class HXSDKHelper : NSObject{
class var instance:HXSDKHelper{
return gInstance
}
 
func sendTextMessage(to : String, textMessge : String){
var latestMessage:EMMessage = EMMessage()
var chatText:EMChatText = EMChatText(text: textMessge)
var txtBody:EMTextMessageBody = EMTextMessageBody(chatObject: chatText)
 
latestMessage.addMessageBody(txtBody);
latestMessage.to = to;
EaseMob.sharedInstance().chatManager.asyncSendMessage(latestMessage, progress: nil);
}
}
大功告成,完成以上步骤,你就能够做个简单的Watch App 可以用环信的SDK发消息了。
由于没有真机,以上都是在模拟器上测试通过。
如果需要工程代码请联系我:syyorient@outlook.com
或者从github上获取
https://github.com/youniworld/AppleWatchDemo-HuanXin
如果其他问题可以登陆http://www.easemob.com/docs/gettingstart/
作者:隋云怡 收起阅读 »

IM客户端数据库加载过程优化

IM
IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。 在系统初始化时,这两部分都要从数据库中加载到内存中。 数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。 [datalist] 每次系统...
继续阅读 »
IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。
在系统初始化时,这两部分都要从数据库中加载到内存中。

11.png


数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。
[datalist]
每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions() 
FOR (conversation : conversationList) { 
db.loadMessages(conversation);
 }
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages()
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
1. 每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
2. 对于每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
 cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');  
  while (cursor.NEXT()) {  
       msg = msgFrom(cursor)  
IF (! msg belong TO conversation) {  
    // 消息不属于当前的会话,所以  
    conversation = NEW Conversation();  
    conversation.ADD(msg);  
    continue;  
}  
 
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {  
    conversation.ADD(msg);  
} ELSE {  
    // 消息个数已经超过会话消息限制  
    continue;  
}  
  }
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。
测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
[]一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
[]mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
[]SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者:李楠 收起阅读 »

io操作中常见的几个概念

1. 概述 web应用中,io这块是一个重点,不同的策略对系统的性能影响很大. 对于io操作来说,用户线程发起io请求,内核负责完成此请求,并反馈结果,这两个角色之间需要进行协调: 1. 系统挂起用户线程,操作完成之后,系统返回结果,唤醒用户线程 2. 系统返...
继续阅读 »
1. 概述
web应用中,io这块是一个重点,不同的策略对系统的性能影响很大.
对于io操作来说,用户线程发起io请求,内核负责完成此请求,并反馈结果,这两个角色之间需要进行协调:
1. 系统挂起用户线程,操作完成之后,系统返回结果,唤醒用户线程
2. 系统返回状态码,用户线程轮询结果,直到操作完成.
3. 用户线程发起请求时附带回调信息,内核接受请求后返回,用户线程执行别的逻辑;系统会在操作完成之后回调,有的系统自动创建新用户线程完成io处理,有的系统会利用已有的用户线程执行处理逻辑
4. 用户系统注册一个大的信号处理线程,用户线程发起请求后直接返回,系统在操作完成之后发送信号,信号处理线程接手处理.
可以看出,有的方式会造成线程挂起等待,有的会造成线程空转;一个线程好不容易等到系统分配了时间片,却又无奈的交出自己的时间片,浪费系统资源,我们的目标就是尽量少的线程,尽量少浪费线程的时间片,尽量少的线程切换,要做到这点,还是先来说说io中较常见的同步、异步、阻塞和非阻塞这几个概念.
2. 同步
我理解的io同步是指线程做io请求时需要等待内核将数据准备好,浪费了自己的cpu时间片.比如read,如果数据没准备好则线程直接挂起,浪费了一次线程调度;如果数据准备好了,需要将数据从内核空间拷贝到用户空间,可能分给自己1ms,结果等待拷贝数据花了了0.5ms,,实际只花费了0.5ms在业务上.
也有人是从当前线程是否需要等待处理结果来考量,当前线程需要等待,那就是同步,这个和我的观点实际没差别,到了异步这儿就不太一样,我们下面说.
3. 异步
与同步模式相反,异步模式下用户线程只需要注册相关信息,操作系统在操作完成后,将数据从内核空间拷贝到用户空间,然后根据注册信息通知用户进程处理io结果.整个流程中无需用户线程干预,用户线程可以充分利用分配给自己的时间.
有的io调用返回的是状态码,用户线程再根据状态码做相应的处理,但实际去做io操作时还是要主动的发起系统调用去获取数据,这是同步模型;还有的io操作是通过信号驱动,内核在操作完成后发送信号通知用户进程处理,用户进程捕获到信号后再发起系统调用去读取数据,实际上还是同步模式.
回到前面的问题,当前线程不等待io的操作结果这是否可以认为是异步?
我是这样想的,io数据从内核空间拷贝到用户空间这一步所花费的时间,不花在这个用户线程就花在别的用户线程,,总是消耗了用户线程的cpu时间片,除非由内核来驱动用户线程.
4. 阻塞
阻塞是指进行io操作时是否引起线程挂起,挂起了就是阻塞模式.很多时候会把同步和阻塞混淆,主要是因为同步一般都是由阻塞实现的.仔细想想,非阻塞也可以是同步,创建socket时如果指定BLOCK为false,那所有的操作都变成非阻塞,此时可能还是同步模式.
阻塞模式既有有点又有缺点,因为会阻塞,在请求不活跃时会节约cpu;因为会阻塞,也就造成了线程切换,也就浪费了cpu.
5. 非阻塞
没啥好说的,线程一路畅通无阻,看起来挺好,可如果忙着做状态检测,那就极大的浪费了cpu资源.
5 四种理论模型
同步异步,阻塞非阻塞,交叉组合,共有四种模型
5.1 同步阻塞
最经典的使用方式,最简单的,最喜欢的....
io操作会引起线程阻塞,只有系统准备好了时才会有返回,可以说不会浪费任何cpu资源.一般会有少量的线程接入请求,再来一个线程池处理接入的请求,简单有效.如果你的系统处理的连接不多,或者大部分不活跃,不用犹豫就它了.
在请求频繁时,同步阻塞会放大线程调度的成本,如果总得连接数超过线程池大小还会造成请求排队,此时还是尽早调整策略.
5.2 同步非阻塞
建立socket时,可以指定no block,此时所有的操作都会立刻返回,线程再根据返回值做相应的处理,这是一种轮询的方案,相比于同步阻塞,会多出若干次的系统调用,很不合算.
另外还有一种多路复用的io模式,线程先向内核注册若干个感兴趣的事件,然后一直等待,在某个或若干个事件符合条件后,内核将其打包返回,线程接到返回值,再去处理事件.
相比于直接在read/write上block住,同步阻塞多了一个获取事件的调用,因此相比于同步阻塞会有额外的系统开销;不过,因为一个线程可以同时监听多个连接,也就能一次处理若干个连接,在连接数较多时可以节约线程调度的成本,优势明显.
5.3 异步阻塞
这个没啥说的,略
5.4 异步非阻塞
用户线程先发起io请求,内核立刻返回,于是用户线程就可以做其它的事.在数据准备好之后,内核会将数据拷贝到用户空间,再给用户线程发送操作完成的通知.这个模型下,可以每个事件一个线程,也可以每个连接一个线程,相比于其他模式,能够最大化的节省线程数;另外,由于用户线程不需要去主动检查,每个用户线程都能用满自己的时间片,整个系统的性能值得期待.
理论说得这么好可别轻易动心,能用够用省力气才是王道,一般的用个同步阻塞就够了.真要用异步io,最好测测,以前的linux的异步io实现的不是太好,不知道现在啥状况了.
6 java中的模型
jdk一开始仅支持bio模式,也就是同步阻塞模式,在1.4中提供了nio,支持多路复用,1.7中又引入了aio,支持异步io.
作者:李春进 收起阅读 »

崩溃引发的思考:怎样才算真正的互联网技术公司?

阿里、携程接力两天的服务故障,沸腾了整个码农界。 看热闹都不怕事儿大。光纤被挖,引得异地双活被嘲讽,逼着支付宝最后有人出来打保票,年底我们一定搞完异地双活;线上服务被删更是报复论、Bug论频频,还出现了异司双活的崇拜,携程只是道了个歉,但是各种开人、睡人的小道...
继续阅读 »
阿里、携程接力两天的服务故障,沸腾了整个码农界。
看热闹都不怕事儿大。光纤被挖,引得异地双活被嘲讽,逼着支付宝最后有人出来打保票,年底我们一定搞完异地双活;线上服务被删更是报复论、Bug论频频,还出现了异司双活的崇拜,携程只是道了个歉,但是各种开人、睡人的小道消息也没停下。
热闹之余,人民群众也没闲着,纷纷忆苦思甜,言必称曾经。
这个说,“曾经我把数据库truncate了,老大当时不给解决方案只给目标,修了一晚上,期间各种惊心动魄,第二天恢复了竟然没人发现”。
那个说,“曾经有个新员工,没分清线上环境和测试环境,把线上数据库Rebuild了,导致某网站下线几小时”。(注:此次携程下线8个小时,据公开资料显示,其每宕机一秒会损失人民币5000元)
。。。
就像说起苦日子,都是自己小时候家里穷,你说你吃过糠我就说挖过树皮。事实上,大多数互联网服务都是7x24的,线上问题肯定是天天有。@TimYang 更是写过一篇黑天鹅来表达这种见怪不怪,见 http://timyang.net/service/black-swan-of-service/
这些苦又有什么可说呢?用来在自己遇到问题的某一天,聊以自慰么?
我们经常说,“吃一堑,长一智”,而我在每个团队里都在说的是,“真正聪明的人,可以从别人的问题里学到东西”。在我看来,更重要的其实在后面,后来又发生了什么?
依携程现在流言四出的状况,公关已然疯掉,明确的事故报告是很难了。但这不妨碍我们思考,要是发生在自己身上会发生什么?如果分析出来原因是程序Bug,如果原因是人为操作失误,如果是故意操作,结果会有区别么?我会在文后回答这个问题。

8.jpg


@左耳朵耗子 说得很直接
“技术上出故障是必然的。能否体现一个公司是技术公司,重要看这几点:1)故障的恢复是否有技术含量,2)公司对故障的处理方式,如果是通过加更多的流程,或是通过加更多的权限管控,或是通过处罚犯错误的人,或是上升到员工意识形态上,而不是去用更好的技术来解决,那么这个公司不会是个技术公司。”
但是遗憾的是,增加流程基本上算是大多数国产公司的首选,来个层层审批,更有甚者,来个线下审批什么的,让你一夜回到有纸办公的原始社会。
说一个前几天的故事。
我们服务升级,灰度部署一台机器后,把一个存储集群搞挂了。这件事的直接原因,是改了一行代码,没有压测就上了线。
服务最后通过降级扶住了,但是大家还是各出了一身汗,于是开会总结。总结的目的肯定是,如何避免同类事情的再次发生?有人提建议说,增加产品经理来做最后一级上线审批吧?
我当然是拒绝的。
为什么?我当然也知道增加审批的好处。首先,后续措施如果是加流程,那其实在暗示整个事情的发生受累于流程缺失,其他原因可以不再深究。
其次,再加一道审批,说明以后也不怕出问题了,因为最大的责任总会落在最后一个人身上,即使后面还是问题不断。
最后一点,对领导更有交待,如果你只是说Fix了一个服务的Bug,总有可能显得对问题不太上心,听起来也不太可靠。比较而言,增加流程就让人放心多了。
但是,我们聚在一起是为了做事,而不是为逃避责任不是吗?但是但是,人又是懒惰的。Larry Wall说,“好的程序员有三个宝贵品德,懒惰、没耐心和骄傲”。这话经过了一个又一个优秀的程序员的验证。
你要做事,又很懒惰。那除了让事情更快更好地完成,似乎别无他法。
加流程让事情更快了么?明显没有。每次服务上线多一层沟通,沟通意味着要协商另一个人的时间,然后你向他解释当前的情况。如果对方再不了解背景,那就足以让你崩溃死机了。
加流程让事情更好了么?没有。它对真正问题毫无益处,不幸的是,它还让整件事情变得更坏了。问题的根本原因在加流程的掩盖下蒙混过关,然后蠢蠢欲动在未来的某一天重新爆发。到了那时候,你依然需要分析整个事情,不同的是已经有了惯例,而这个惯例又会带来新的流程。
这就像纸尿裤。一个小孩尿了裤子,可以给他穿上纸尿裤。但是,健康成年人有穿的么?更危险的还在于,你不知道什么时候可以脱下来,因为是别人给你穿的,别人才不管你什么时候可以自禁了。
这也是很多公司为什么流程越来越重的原因,取消一个流程比增加一个流程要难得多。如果你能证明不需要一个流程也许在最开始就可以通过证明不增加它了。
回到总结会,我们又继续讨论了下去。经过复盘确认,当时的情况是,压测环境创建耗时费力,开发进度一再推迟下,经过团队讨论觉得没有问题,于是就跳过了压测步骤。
为什么可以跳过?因为整个技术团队都讨论了,评估这个压测可以不做(为什么我后文会讲)。这也是我拒绝产品经理审核的一个原因,因为他肯定没有技术了解整个服务,而风险管控最大的依据在于技术团队的评估。如果技术评估没有风险,他再来也是不会对结果有所改变的。
为什么决定这个压测不做了呢?因为我们两个负责人(本来也只有两个人),其中看了一眼文档(你没有看错),然后跟对方说似乎有个性能改进。另一个听到对方这么说,于是也看了一眼文档,下意识觉得那肯定是妥妥的了嘛。
这是什么鬼?思维惰性导致的循环依赖啊。实际上,只要再看一眼源代码,就知道不应该做这个改动了。但是两个人都没有去看,真是群愚的又一个例子。
选择不做压测,还有个原因,压测过程迟迟不能开展,而服务又着急上线。压测进展慢,因为压测没有计划,且在功能回归之后才进行。压测环境部署没有自动化,全部服务部署一次需要近十个小时,再跑完压测基本需要额外一天。而服务着急上线,是因为服务交付日期延迟了一周又一周。归根到底,整个团队研发效率的低下,已经导致完整流程跑不完了。
所以最后会议的决定变成了:
1. 确定压测步骤的必要性,再少的代码改动都不可以绕过;
2. 明确上线责任,而不是让责任继续分散,开发人员自己需要对新版本性能负责;
3. 提前制定压测计划,在服务开发完后立即进行压测,与功能测试同时进行;
4. 自动化压测环境部署,我们在云上构建服务,主机初始化和服务部署都使用脚本或管理工具自动化;
就在本文截稿之时,经过运维同事的努力,我们从零完整部署基准压测环境(支持100w同时在线)的时间,已经缩短到两小时以内。整个压测回归可以在三个小时内搞定。压测再也不是一个负担了。
当然研发效率的提高也在改进中,这是一个更大的话题稍后有机会再展开。
从这方面看,我们算一个技术公司么?我觉得是的,因为这样的公司里,不管遇到什么问题,技术的总会归技术。因为我们都相信,技术问题都可以通过技术解决。这句话也是对开始问题的回复。
所以亲爱的兄弟姐妹们,那个故障之后,你们得到的是流程还是技术改进呢?你们还好么?

9.jpg


如果有一天,你忽然发现,增加了太多流程,却依然做不好服务。请不要忘记,我们欢迎你。
作者: 梁宇鹏 收起阅读 »

回顾:电商抢滩618,Docker帮大忙!7个你不可错过的Docker分享!

618电商大战由来已久,巨大的数据和流量对于各电商平台来说,无疑是一项巨大挑战。如何在挑战中保证服务器的流畅和稳定,是这场大战胜负的关键性因素。Docker的出现正好能缓解这些因素,下边就盘点下几位全国顶级专家的Docker演讲,帮助你更全面系统的了解和学习D...
继续阅读 »
618电商大战由来已久,巨大的数据和流量对于各电商平台来说,无疑是一项巨大挑战。如何在挑战中保证服务器的流畅和稳定,是这场大战胜负的关键性因素。Docker的出现正好能缓解这些因素,下边就盘点下几位全国顶级专家的Docker演讲,帮助你更全面系统的了解和学习Docker在云计算时代,开发者将应用转移到云上已经解决了硬件管理的问题,然而软件配置和管理相关的问题依然存在。Docker的出现正好能帮助软件开发者开阔思路,尝试新的软件管理方法来解决这个问题。通过掌握Docker,开发人员便可享受先进的自动化运维理念和工具,无需运维人员介入即可顺利运行于各种运行环境。
基于 Docker 的开发与运维一体化

1.png


平安健康互联网技术平台资深架构师 王延炯
Docker经过一段时间的发展,已快速进入以互联网公司为主的技术界。但对非互联网企业而言,传统业务复杂、IT设施薄弱、软件研发过程冗长,开发、测试、运维组织关系松散。应如何借鉴互联网企业经验,使得IT系统更好、更快速帮助企业开展业务、提升整体运营效率?本演讲将对比Docker与传统企业的IT过程,并以具体实践为参考,帮助传统企业向互联网化迈出探索性的一步。
基于Docker的云计算平台搭建实战

2.jpg


北京数人科技有限公司CTO 肖德时
CTO 肖德时在以往的云计算平台搭建过程,我们首选的解决方案是OpenStack解决方案,它非常成熟,让业界很放心。 随着新一代容器技术Docker生态圈的兴起,基于Docker的混合云计算平台搭建成为可能。我们为什么现在 这么需要容器技术,其原因是虚拟化计算已经把我们的网络环境从现实的物理机时代拉到虚拟机时代,部署在虚拟机上 的应用开始走向分布式架构,MicroService模式的应用越来越受到用户的喜爱。复杂的网络环境、软件环境让 开发者越来越无法承受。通过类似Docker技术,把内部构建、分发、部署都定制为统一的标准,正好是容器天然的优势。
Gaia—万台规模的Docker应用实战

3.jpg


腾讯数据平台部高级工程师 罗韩梅
作为底层的资源调度平台,Gaia(盖娅)能够让应用开发者像使用一台超级计算机一样使用整个集群,极大地简化了资源管理逻辑。Gaia提供高并发任务调度和资源管理,实现集群共享,具有高度可伸缩性和可靠性,能够支持MR等离线业务,甚至是实时计算、在线service业务。通过一系列的优化,Gaia可以支持到单cluster万台规模,毫秒级的作业下发效率以及更加完善的资源管理,同时,我们引入了弹性内存管理,增加了网络和磁盘带宽管理。Docker轻量、可移植、跨平台的特性将彻底改变程序的交付方式,并充分释放了虚拟化的威力,大有掀起一场容器革命之势。云计算、大数据经常意味着需要调动数据中心大量的资源,如何能够快速的匹配合适资源,需要一个聪明的“大脑”——Gaia。通过Docker on Gaia实现的Docker 云,将会让Docker的能量发挥到极致。
面向混合云的容器服务实践

4.jpg


IBM中国开发中心云平台服务部架构总监 杨博
演讲主要以企业应用与混合云的视角来审视容器技术,并分享IBM使用Docker容器技术来构建针对企业的混合云平台的实践与思考。通过本次分享将了解到企业应用在向云尤其是混合云迁移时会遇到哪些困难与挑战,而容器技术又是如何解决这些问题的,以及IBM云平台Bluemix里的容器服务是如何为用户带来提升的。
Docker在雪球的技术实践

5.png


球SRE团队高级工程师 高磊
基于Mesos和Docker的分布式计算

6.png


数人科技CEO 王璞
本次演讲主要介绍了如何基于Mesos和Docker搭建企业级分布式计算平台。Mesos和Docker都是最近非常热门的技术。国内企业使用Mesos的并不多,而国外主要是Twitter和Airbnb之类的新兴互联网公司在使用Mesos。Docker在2014年蓬勃发展,但是基于Docker真正开发企业应用的客户也并不多。基于Mesos和Docker打造的企业级分布式计算平台比Hadoop和Spark等常见分布式数据处理平台更具灵活性,能够处理实时请求、离线批处理请求、同构异构数据等等。
使用Docker构建项目

7.png


七牛任职七牛公司全栈架构师 李兆海
Docker的火热众所周知,那么到底该如何使用Docker来构建项目呢?本次演讲将分析利用Docker来构建项目可以解决哪些问题,以及分享七牛在使用Docker构建项目时的一些实践经验。
内容来源:StuQ 收起阅读 »

传微软要收购Docker了,真的假的?

传微软要收购Docker!以目前Docker的市值来看,微软收购Docker绝对是绰绰有余。但微软收购Docker之后的意义是什么了?对于Docker而言又意味着什么了? 如果微软能成功收购Docker对于微软的Azure服务来说会是一个很好的举措,可以同时提...
继续阅读 »
传微软要收购Docker!以目前Docker的市值来看,微软收购Docker绝对是绰绰有余。但微软收购Docker之后的意义是什么了?对于Docker而言又意味着什么了?
如果微软能成功收购Docker对于微软的Azure服务来说会是一个很好的举措,可以同时提高其在开源领域与容器领域的声望。
当然,如果微软还没有着手收购Docker的话,可能它马上要行动了。
这个迷人而大胆的举措是上周末微软CEO Satya Nadella爆出来的,他正在努力让Azure云成为商业云的一个不错选择;同时,使Azure成为容器运行与开源项目托管的“头等选择”也是其努力实现的目标。
Docker是一个开源的容器技术,它可以让用户将其应用程序打包,并以轻量级的方式将其运行在不同的基础设施(infrastructure)之上,而且可以保持部署上的最小配置变化。同时也不要混淆了,Docker还是一个有融资背景的初创公司,它曾经叫DotCloud,并将Docker容器技术进行商业化运作,而如今它已经价值10亿美金。
商业界对容器技术——一种新的虚拟化技术十分热衷,因为容器技术让部署应用程序变得更为简单,同时也使数据中心的运维更为顺畅。
Docker与微软这两家公司都没有对这个收购传闻做出回应,但是“容器圈子”对此事的评价是:微软会从这个收购案中抢占到容器领域的制高点;同时也可以使微软在容器领域不再“落后”于Google;而后者在容器领域已经发布了自己的容器管理服务——Kubernetes。
“Google虽然在容器方面与其它公司进行了广泛的合作,但是它还是在努力独吞这个市场。如果微软成功收购Docker,保持Docker开源并为其打造一个高质量的编排服务,微软就能既在舆论上也能在技术上获取成功,”一位行业内人士透露,“微软也在用Kubernetes,但是其肯定不会将自己的未来赌在Google的开源技术上,毕竟这些都不是自己的。”
微软与Google在公有云市场上是竞争对手,但是他们又都想挑战这个行业的龙头Amazon Web Service;当然他们互相竞争的领域还包括移动操作系统与搜索。
虽然Docker与微软达成协议确保Docker能在Azure上运行,但是,Docker这个春季刚刚加入10亿美元估值俱乐部的公司,同样跟其他平台提供商有相似的协议。
据投资领域专家称,Docker愿意出售,但是投资商不会将其以低于35亿美金的价格出售。尽管如此,微软还是能轻松在这个价位上买下 Docker,毕竟这家科技巨擎的现金储备达到了953亿美金。相比四月传出的流言——微软将以500亿美金收购Saleforce来说,这个数目还是相当便宜的。
对于Docker来说,这笔交易未尝不可。Docker尽管在开发圈子里已经十分流行——它的源代码已经累计下载了3亿次;但是它的核心产品也是开源的,也就是免费的,从一个侧面来说,Docker存在商业模式盈利的潜在问题。
但是Docker也在研发付费版本,这个版本将在容器安全方面有增强功能,同时也会加入很多企业级用户关心的功能。这个版本还处于预览版阶段,但是将会在下周的Docker开发者大会上作大力度宣传。同时,Docker还将发布能使Docker容器更容易的在大规模集群下安装部署与管理的特性;这些容器编排服务将使用户能同时管理调度多个容器,并最终为客户节约运维成本。
在容器编排服务领域,Docker的竞争对手像CoreOS与Mesosphere,还有前面介绍的Google Kubernetes,都是开源且免费的产品,他们也提供相似的功能与产品。一些分析师认为他们的产品在某种程度上要优于Docker最开始的水准。
一个投资者在四月Docker完成D轮9500万美金融资后说,相对于其他免费的容器服务,Docker跟他们竞争会很难,Docker的这个风险也是广泛认同的。
“显然Docker在商业模式上的风险会更大些,”Insight Venture Partners公司的Jeff Horing在对Docker完成D轮融资时也说。
同样也有人说微软与Docker什么都不会发生;对收购持消极态度者称:微软并不需要买下Docker,只需要跟Docker合作就能得到其想要的;其他反对者也说微软在管理开源项目缺乏经验,同时微软也在为Windows服务器引入容器,并兼容Docker。
Janakiram & Associates的创始人Janakiram说:“Windows是商业领域Linux唯一的替代品,微软会推自己的容器服务来支持Docker而不会卖Linux操作系统。”
但是,微软在Docker估值不断增加的时候买下Docker真的好吗?而且IBM与Red Hat会随时跳出来横刀夺爱,毕竟Red Hat也表示过“会尽一切可能将Docker揽入怀里。”
这个传闻会有另一个结果。就像拍卖一样,大家都会对Docker进行轮番报价,Docker的价值水涨船高,对Docker来说何乐而不为呢? 收起阅读 »

解读:开发者可以使用Docker做什么?

有些开发者可能还是不明白 Docker 对自己到底有多大的用处,因此翻译 Docker 个人用例 这篇文章中来介绍 Docker 在普通开发者开发过程中的用例。 Docker 如今赢得了许多关注,很多人觉得盛名之下其实难副,因为他们仍然搞不清 Doc...
继续阅读 »
有些开发者可能还是不明白 Docker 对自己到底有多大的用处,因此翻译 Docker 个人用例 这篇文章中来介绍 Docker 在普通开发者开发过程中的用例。

f_9c4bfbc389e72fe6e6fdc3bdb7e5ce0f.jpg


Docker 如今赢得了许多关注,很多人觉得盛名之下其实难副,因为他们仍然搞不清 Docker 和普通开发者到底有什么关系。许多开发者觉得 Docker 离自己很远,Docker 是生产环境中的工具,和自己无关。我也是花了很长时间才想清楚作为普通开发人员如何在自己的开发中使用 Docker。坦率地说,我仍处在学习的过程中。
这篇文章提供了一个 Docker 用例列表,我希望它能更好地帮助你理解 Docker 并引发你的思考。本文只是描述 Docker 在普通开发者日常的应用,并不提供完整的解决方案。
在介绍用例之前,我希望你能先记住这句话:“Docker 是一个便携的应用容器”。你可以不知道 Docker 所说的的“便携式容器”到底是什么意思,但是你必须清楚 Docker 在日常中能带来非常大的效率提升。
当你需要在容器内运行自己的应用(当然可以是任何应用),Docker 都提供了一个基础系统镜像作为运行应用时的基础系统。也就是说,只要是 Linux 系统上的应用都可以运行在 Docker 中。
可以在 Docker 里面运行数据库吗?当然可以。
可以在 Docker 里面运行 Node.js 网站服务器吗?当然可以。
可以在 Docker 里面运行 API 服务器吗?当然可以。
Docker 并不在乎你的应用程序是什么、做什么,Docker 提供了一组应用打包、传输和部署的方法,以便你能更好地在容器内运行任何应用。
下面的例子我自己经常使用,当然你有更好的案例也可以分享给我。
尝试新软件
对开发者而言,每天会催生出的各式各样的新技术都需要尝试,然而开发者却不太可能为他们一一搭建好环境并进行测试。时间非常宝贵,正是得益于 Docker,让我们有可能在一条或者几条命令内就搭建完环境。Docker 有一个傻瓜化的获取软件的方法,Docker 后台会自动获得环境镜像并且运行环境。
并不仅仅是新技术环境搭建用得到 Docker。如果你想快速在你的笔记本上运行一个 MySQL 数据库,或者一个 Redis 消息队列,那么使用 Docker 便可以非常容易地做到。例如 Docker 只需要一条命令便可以运行 MySQL 数据库:docker run -d -p 3306:3306 tutum/mysql。
译者注:虽然使用命令也能非常快地安装 MySQL 数据库,但是当用到最新的技术或者非常复杂的技术时,使用 Docker 便会是个非常好的选择,例如 Gitlab,普通用户大概需要一天的时间去搭建 Gitlab 平台,而 Docker 则只需要一条命令。
进行演示
现在我经常需要在周末用自己开发的成果对客户活着别人做一两个演示。搭建演示环境的过程非常麻烦。现在我发现 Docker 已经成为我演示这些工具的最合理的方式。同时,对于客户来说,我可以直接将 Docker 镜像提供给他们,而不必去做任何环境配置的工作,工作的效果也会和在他们演示中所看到的一模一样,同时不必担心他们的环境配置会导致我们的产品无法运行。
避免“我机器上可以运行”
无论是上一篇介绍的企业部署 Docker 还是本文的个人 Docker 用例,都提到了这个情况。因为环境配置不同,很多人在开发中也会遇到这个情况,甚至开发的软件到了测试人员的机器上便不能运行。但这都不是重点。重点是,如果我们有一个可靠的、可分发的标准开发环境,那么我们的开发将不会像现在这么痛苦。Docker 便可以解决这个问题。Docker 镜像并不会因为环境的变化而不能运行,也不会在不同的电脑上有不同的运行结果。可以给测试人员提交含有应用的 Docker 镜像,这样便不再会发生“在我机器上是可以运行的”这种事情,很大程度上减轻了开发人员测试人员互相检查机器环境设置带来的时间成本。
另一个 Docker 可以发挥用处的地方是培训班。除了 Docker 容器的隔离性之外,更能体会到 Docker 优势的地方在于环境搭建。培训班的新手每个人都要在环境搭建上花费很多时间,但是如果在这里应用到 Docker 的话,那么我们只需要把标准的运行环境镜像分发下去,然后就可以开始上课了。使用 Docker 和使用虚拟机一样简单,但是 Docker 要更方便、更轻量级。同时,我们也可以告诉学员:“在培训的同时,我们还将学到当下最流行的技术——Docker”,这种双赢的结局,何乐而不为呢。
学习 Linux 脚本
当然这个原因看起来可能很奇怪,但是对不不熟悉 Linux 操作系统和 Shell 脚本的人来说,确实是一个好机会。即便本文并不是在讲 Linux,Linux 的重要度仍然不言而喻。如果你用的是 Windows,那么我给你一个建议:从云主机提供商那儿租用一台云主机:我推荐使用 CoreOS 系统的云主机。虽然这样并不会让你成为专业的 Linux 运维,但是可以让你快速地学到 Linux 基础知识,爱上命令行操作,并且慢慢开始熟悉和欣赏 Linux。
更好地利用资源
虚拟机的粒度是“虚拟出的机器”,而 Docker 的粒度则是“被限制的应用”,相比较而言 Docker 的内存占用更少,更加轻量级。
对我来说这是 Docker 的一个优势:因为我经常在自己电脑中运行多个 Docker 应用,使用 Docker 比使用虚拟机更加简单,方便,粒度更细,也能持续地跟踪容器状态。
为微服务定制
如果你一直在关注科技新闻的话,那么你应该听说过“微服务(Microservices)”的概念。Docker 可以很好地和微服务结合起来。从概念上来说,一个微服务便是一个提供一整套应用程序的部分功能,Docker 便可以在开发、测试和部署过程中一直充当微服务的容器。甚至生产环境也可以在 Docker 中部署微服务。
在云服务提供商之间移植
大多数的云主机提供商已经全面支持 Docker。对于开发人员来说,这表示你可以很方便地切换云服务提供商,当然也可以很方便地将你本地的开发环境移动到云主机上,不需要本地上配置一次运行环境、在云主机上还配置一次运行环境。全面部署 Docker (Docker here and Docker there) 作为标准运行环境可以极大地减轻应用上线时的工作量和产生 BUG。
API 端
API 是应用之间的粘合剂,一个合格开发者肯定使用过别人提供的 REST API,或者自己开发过 REST API。需要指出的是,无论是客户端还是 API 提供端,在开发之前都需要先定义一组公共的 API 接口,写成文档,然后才能进行编码。如果服务端和客户端是共同开发的话,那么服务端通常会先实现能返回固定字符串的 API 接口,在以后的开发中再慢慢去实现 API 的功能。
虽然有人会认为在这里 Docker 被滥用了,完全可以用 sample.json 这种文件去实现虚拟 API,但是下面有个实例可以更好地解决前后端分离开发时的 API 问题。
为了更好地解释我的意思,给大家提供一个实例:JSON Server,一个用于提供 JSON 数据的 REST API。使用过这个容器的人就会知道,既然有这么好用的 Docker JSON Server,我们没有理由不用 Docker。
译者注:
运行示例的 JSON Server,同时使用示例中提供的 JSON 文件,只需执行一条命令便可以创建一个服务端的 API 应用。
使用 curl http://127.0.0.1:80/posts 即可获取示例文件中的 posts 段,这样在后端没有开发完 API 的时候,前端一样可以进行协同开发。
技术的创新
这点应该算不上是用例,但是我还是来写一下。Docker 正在快速发展,工具也在不断更新,没有人能预见到未来 Docker 会是什么样子的。你在复杂的系统中 Docker 使用的越多,越是可能会发现技术上的空白和未来技术发展的方向。现在还处在 Docker 的发展期,任何你使用 Docker 创建的工具都有可能成为社区关注的热点。这是 Docker 的机会,也是成就你自己的机会。
你的用例
最后一条便不再是我的用例了,而是 Docker 在你手中能发挥多大的作用。我也很希望看到你能提供更多使用 Docker 的方式,欢迎留言。
其他
还有两个技巧可以分享给你们。在学习 Docker 的过程中因为有了这两个的帮助,我才得意不断地提升自己。
一:Docker Hub Registry。这是 Docker 的官方镜像仓库,除了托管着 Docker 官方的镜像外,和 Github 一样,你可以在上面上传自己的镜像,也可以在上面搜寻其他有用的镜像,极大地节省自己的时间。例如 Oracle-XE-11g 镜像,所有的一切都是现成的,完全不需要自己去下载 Oracle XE 11g 安装。这样为你和团队节约了大量的时间成本。
如果你不太确定的话,可以去 Docker Hub 上搜有一下有没有自己用得到的镜像。大部分情况下你所需要的镜像在 Docker Hub 上都已经有人构建了。
二:多参考 IaaS 供应商的新闻,虽然我们不能像在他们会议室里那样完全了解他们的公司动态,但是仍然可以从新闻中可以了解到 Docker 最新的发展方向和技术趋势。可以肯定的是,容器化技术是未来的热点,我们不仅可以在本机运行 Docker,不仅仅在一家云服务提供商的主机上运行 Docker,未来所有的云服务提供商都会支持 Docker。
Docker 前景很明确,采用 Docker 只会让开发变得更方便。
内容来源:OPEN资讯 收起阅读 »

没有头像的发帖人不是好的开发者

有没有头像,是衡量提问者是否对回复者尊重的标志 有没有头像,是衡量提问者是否专业的标志 有没有头像,是衡量提问者是不是有耐心的标志 有没有头像,是衡量提问者是不是电脑里存了头像的标志 有没有头像,是衡量提问者有没有获得20积分的标志   (有人接下去么.......
继续阅读 »
有没有头像,是衡量提问者是否对回复者尊重的标志
有没有头像,是衡量提问者是否专业的标志
有没有头像,是衡量提问者是不是有耐心的标志
有没有头像,是衡量提问者是不是电脑里存了头像的标志
有没有头像,是衡量提问者有没有获得20积分的标志
 
(有人接下去么......) 收起阅读 »

基于Mesos和Docker的分布式计算平台

Docker及其相关技术的出现和发展,给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。 针对“互联网+”时代的业务增长、变化速度及大规模计算的需求,廉价的、高可扩展的分布式x...
继续阅读 »
Docker及其相关技术的出现和发展,给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。
针对“互联网+”时代的业务增长、变化速度及大规模计算的需求,廉价的、高可扩展的分布式x86集群已成为标准解决方案,如Google已经在几千万台服务器上部署分布式系统。Docker及其相关技术的出现和发展,又给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。
分布式系统设计准则可伸缩性
首先分布式系统一定是大规模的系统,有很好的Scalability。出于成本的考虑,很多大规模的分布式系统一般采用廉价的PC服务器,而不是大型的高性能服务器。没有单点失效
廉价的PC服务器在大规模使用中经常会遇到各种各样的问题,PC服务器的硬件不可能是高可靠的,比如Google的数据中心每天都会有大量的硬盘失效,所以分布式系统一定要对硬件容错,保证没有任何的单点失效。在这种很不稳定、很不可靠的硬件计算环境下,搭建一个分布式系统提供高可靠服务,必须要通过软件来容错。分布式系统针对不允许有单点失效的要求有两方面的设计考虑,一种是服务类的企业级应用,每个服务后台实例都要有多个副本,一两台硬件故障不至于影响所有服务实例;另外一种数据存储的应用,每份数据也必须要有多个备份,保证即使某几个硬件坏掉了数据也不会丢失。
高可靠性
除了单点失效,还要保证高可靠性。在分布式环境下,针对企业级服务应用,要做负载均衡和服务发现来保证高可靠性;针对数据服务,为了做到高可靠性,首先要按照某种算法来把整体数据分片(因为一台服务器装不下),然后按照同样的算法来进行分片查找。
数据本地性
再一个分布式设计理念是数据本地性,因为网络通信开销是分布式系统的瓶颈,要减少网络开销,应当让计算任务去找数据,而不是让数据去找计算。
分布式系统与Linux操作系统的比较
由于纵向拓展可优化空间太小(单台服务器的性能上限很明显),分布式系统强调横向扩展、横向优化,当分布式集群计算资源不足时,就要往集群里面添加服务器,来不停地提升分布式集群的计算能力。分布式系统要做到统一管理集群的所有服务器,屏蔽底层管理细节,诸如容错、调度、通信等,让开发人员觉得分布式集群在逻辑上是一台服务器。
和单机Linux操作系统相比,虽然分布式系统还没有成熟到成为“分布式操作系统”,但它和单机Linux一样要解决五大类操作系统必需的功能,即资源分配、进程管理、任务调度、进程间通信(IPC)和文件系统,可分别由Mesos、Docker、Marathon/Chronos、RabbitMQ和HDFS/Ceph来解决,对应于Linux下的Linux Kernel、Linux Kernel、init.d/cron、Pipe/Socket和ext4,如图1所示。

1.jpg


图1 分布式系统与Linux操作系统的比较 
基于Mesos的分布式计算平台
Mesos资源分配原理
目前我们的Mesos集群部署在公有云服务上,用100多台虚拟机组成Mesos集群。Mesos不要求计算节点是物理服务器还是虚拟服务器,只要是Linux操作系统就可以。Mesos可以理解成一个分布式的Kernel,只分配集群计算资源,不负责任务调度。基于Mesos之上可以运行不同的分布式计算平台,如Spark、Storm、Hadoop、Marathon和Chronos等。Spark、Storm和Hadoop这样的计算平台有任务调度功能,可以直接使用Mesos SDK跟Mesos请求资源,然后自行调度计算任务,并对硬件容错。Marathon针对服务型分布式应用提供任务调度,比如企业网站等这类需要长时间运行的服务。通常网站应用程序没有任务调度和容错能力,因为网站程序不太会处理某个后台实例挂掉以后要在哪台机器上重新恢复等这类复杂问题。这类没有任务调度能力的服务型分布式应用,可以由Marathon来负责调度。比如,Marathon调度执行了网站服务的一百个后台实例,如果某个实例挂掉了,Marathon会在其他服务器上把这个实例恢复起来。Chronos是针对分布式批处理应用提供任务调度,比如定期处理日志或者定期调Hadoop等离线任务。
Mesos最大的好处是能够对分布式集群做细粒度资源分配。如图2所示,左边是粗粒的资源分配,右边是细粒的资源分配。

2.jpg


图2 Mesos资源调度的两种方式
图2左边有三个集群,每个集群三台服务器,分别装三种分布式计算平台,比如上面装三台Hadoop,中间三台是Spark,下面三台是Storm,三个不同的框架分别进行管理。右边是Mesos集群统一管理9台服务器,所有来自Spark、Hadoop或Storm的任务都在9台服务器上混合运行。Mesos首先提高了资源冗余率。粗粒资源管理肯定带来一定的浪费,细粒的资源提高资源管理能力。Hadoop机器很清闲,Spark没有安装,但Mesos可以只要任何一个调度马上响应。最后一个还有数据稳定性,因为所有9台都被Mesos统一管理,假如说装的Hadoop,Mesos会集群调度。这个计算资源都不共享,存储之间也不好共享。如果这上面跑了Spark做网络数据迁移,显然很影响速度。然后资源分配的方法就是resource offers,是在窗口的可调度的资源自己去选,Mesos是Spark或者是Hadoop等等。这种方法,Mesos的分配逻辑就很简单,只要不停地报告哪些是可用资源就可以了。Mesos资源分配方法也有一个潜在的缺点,就是无中心化的分配方式,所以有可能不会带来全局最优的方式。但这个数据资源缺点对目前来讲并不是很严重。现在一个计算中心资源贡献率很难达到50%,绝大部分计算中心都是很闲的状态。
Mesos资源分配示例
下面具体举例说明怎么用Mesos资源分配。如图3所示,中间是Mesos Master,下面是Mesos Slave,上面是Spark和Hadoop运行在Mesos之上。Mesos Master把可用资源报告给Spark或Hadoop。假定Hadoop有一个任务想运行,Hadoop从Mesos Master上报的可用资源中选择某个Mesos Slave节点,然后这个任务就会在这个Mesos Slave节点上执行,这是任务完成一次资源分配,接下来Mesos Master继续进行资源分配。

3.jpg


任务调度
Mesos只做一件事情,就是分布式集群资源分配,不管任务调度。Marathon和Chonos是基于Mesos来做任务调度。如图4所示,Mesos集群混合运行来自Marathon和Chronos的不同类型的任务。Marathon和Chonos基于Mesos做任务调度时,一定是动态调度,也就是每个任务在执行之前是不知道它将来在哪一台服务器上执行和绑定哪一个端口。如图5所示,9台服务器组成的Mesos集群上混合运行各种Marathon调度的任务,中间一台服务器坏掉以后,这台服务器上的两个任务就受影响,然后Marathon把这两个任务迁移到其他服务器上,这就是动态任务调度带来的好处,非常容易实现容错。

4.jpg


图4 Mesos集群运行不同类型的任务

5.jpg


图5 Marathon动态任务调度
为了减少硬件故障对应用服务的影响,应用程序要尽量做到无状态。无状态的好处是在程序受到影响时不需要进行任何恢复,这样这个程序只要重新调度起来就可以。无状态要求把状态数据放到存储服务器或者是消息队列里面,这样的好处是容错时恢复起来会变得很方便。
服务类的高可靠性
对于服务类型的任务,分布式环境保证服务的高可靠性,这需要负载均衡和服务发现。在分布式环境下做负载均衡有一个难点就是后台这些实例有可能发生动态变化,比如说某一个节点坏掉了,这个节点上的实例会受到影响,然后迁移到其他节点上。然而传统负载均衡器的后台实例地址端口都是静态的。所以在分布式环境下,为了做负载均衡一定要做服务发现。比如,某个服务之前有四个事例,现在新添加了两个实例,需要告诉负载均衡器新增加的实例的地址和端口。服务发现的过程是由几个模块配合完成,比如说Marathon给某个服务增加了新的实例,把新调度的实例地址端口写到Zookeeper,然后Bamboo把Zookeeper里存放的该服务新的实例的地址端口信息告诉负载均衡器,这样负载均衡器就知道新的实例地址端口,完成了服务发现。
数据类的高可靠性
对于服务类型的应用,分布式系统用负载均衡器和服务发现来保证高可靠性的服务。对于数据类型的应用,分布式系统同样要保证高可靠的数据服务。首先要做数据分片,一台服务器存不下所有数据就分成多份来存,但对数据进行分片必须按照某个规则来进行分片,后面查找时要按照同样的规则来进行分片查找,就是一致性。假定最原始的方案我们用Hash计算做成方法,在线性空间上分了三份以后,我要在数据分成三块机器来存,三台机器都存满了时,再把数据进行分配的时候不再把它分配到直线线性空间上,而是把它分配到环状空间上,把起点和终点连接起来,连成一个数据环,如图6所示,这样相应的数据点就放在这一块。如果要添加一个新的数据中心就在环上新切出来这块,这样很方便,切出来这一部分代表这一部分数据都应该放到新的芯片上,所以把原来子数据分片挪到嵌入式的分片上。

6.jpg


图6 数据分片
还有可能删除数据,我们把黄色的数据放到红色的数据上,这是环的好处。实际为了做到高可靠性,任何一个数据可能假定映射到黄色部分以后,这些黄色的部分只要映射到任何一个黄色的区域都会存在同一片机器上,同一片机器底层会有多个副本和做数据的备份,这是实际数据分片的一个实例。这是怎么做数据的高可靠性。这些数据分片,还有负载均衡,都是为了对应分布式分片硬件带来的不可靠和失效,这是我们用分布式系统最大的特点。
基于Docker的分布式计算平台
Docker工作流
我们主要用Docker来做分布式环境下的进程管理。Docker工作流如图7所示,我们不仅把Docker应用到生产阶段,也应用到开发阶段,所以我们每天编辑Dockerfile,提升Docker Images,测试上线,发Docker镜像,在我们内部私有Docker regis里面,再调到我们Docker集群生产环境里面,这和其他的Docker工作流没有什么区别。

7.jpg


在Mesos提交Docker任务
因为Mesos和Docker已经是无缝结合起来。通过Marathon和Chronos提交服务型应用和批处理型应用。Marathon和Chronos通过RESTful的方式提交任务,用JSON脚本设定应用的后台实例个数、应用的参数、以及Docker Images的路径等等。
分布式环境下的进程通信
在分布式环境下应用服务之间通信,是用分布式消息队列来做,我们用的是RabbitMQ。RabbitMQ也是一个分布式系统,它也要保证高可靠性、解决容错的问题。首先RabbitMQ也有集群,如图8所示,六个节点组成了一个RabbitMQ的集群,每个节点之间是互为备份的关系,任何一个坏掉,其他五个还可以提供服务,通过冗余来保证RabbitMQ的高可靠性。

8.jpg


图8 RabbitMQ集群
其次,RabbitMQ也有数据分片机制。因为消息队列有可能很长,长到所有的消息不可能都放到一个节点上,这时就要用分片,把很长的消息队列分为几段,分别放到不同的节点上。如图9所示是RabbitMQ的联盟机制,把一个消息队列打成两段,一段放在上游一段放在下游,假定下游消息队列的消息被消费完了就自动把上游消息队列里的消息移到下游,这样一个消息队列变成非常长的时候也不怕,分片到多个节点上即可。

9.jpg


图9 消息队列分片
分布式文件系统
最后讲一下分布式文件系统HDFS和Ceph。Hadoop文件系统HDFS,如图10所示,每个数据块有三个备份,必须放在不同的服务器上,而且三个备份里面每个机架最多放两份,这么做也是为了容错。Ceph是另一种流行的开源分布式文件系统。Ceph把网络存储设备抽象成一张逻辑硬盘,然后“挂载”到分布式集群的每台服务器上,原理上非常像是Linux操作系统Mount一块物理硬盘。这样一来,用户程序访问Ceph的文件系统就跟访问Linux本地路径一样,非常方便。

10.jpg


分布式环境下的监控
分布式环境下,程序不是运行在本地,而是在集群上面,没有监控就等于程序运行在黑盒子下,无法调优,必须要有监控。分布式环境下的监控分为两个部分,一是性能监控,另一个是报警。性能监控要知道每个应用程序运行状态是什么样,即每一个应用程序占了多少CPU内存、服务的请求处理延迟等。我们是用Graphite来做应用程序性能监控;还有其他系统,比如MongoDB、Hadoop等开源系统,我们用Ganglia来做性能监控,比如CPU内存硬盘的使用情况等。报警是要在关键服务出现故障时,通知开发运维人员及时排解故障,我们用Zabbix来做报警。(责编/周建丁)
内容来源:《程序员》 收起阅读 »

【经验分享】教你写Android网络框架之基本架构

前言 在开发过程中,网络是我们很重要的一部分,因此我们就以网络框架或者说网络模块开始。在这个框架开发过程中,我会整理开发思路、以及遇到一些设计问题时会有怎么样的考虑、解决方案,当然这只是我个人的观点,大家也可以有自己的实现。除了网络框架,后续的系列还想更新I...
继续阅读 »
前言
在开发过程中,网络是我们很重要的一部分,因此我们就以网络框架或者说网络模块开始。在这个框架开发过程中,我会整理开发思路、以及遇到一些设计问题时会有怎么样的考虑、解决方案,当然这只是我个人的观点,大家也可以有自己的实现。除了网络框架,后续的系列还想更新ImageLoader框架、ORM框架,如果有时间也会增加动画框架和微博开发的系列文章。当然这些框架只是一些简单的框架基础,本人水平、时间有限,而且已经有现成、成熟的很多框架,我们在这里只是以重复造轮子的态度去学习轮子构建过程,从而达到能够造轮子的地步。至于很多细节的问题,我们这里就不过多讨论了,如果有兴趣,各位可以自行研究。
最后,我们暂且把这个框架命名为SimpleNet,下面我们一起进入主题吧。
基本结构

1.jpg


图1 ( SimpleNet的基本结构 )
SimpleNet框架的基本结构类似于Volley,包括一些命名上也有跟Volley一致。它主要分为四个部分,最上面的部分为Request,即各种请求类型。例如返回的数据类型为json的对应为JsonRequest,返回数据字符串的为StringRequest,如果需要上传文件,那么你需要使用MultipartRequest,该请求只支持小文件的上传,如果上传的文件过大则会产生OOM。

第二部分为消息队列,消息队列维护了提交给网络框架的请求列表,并且根据相应的规则进行排序。默认情况下更具优先级和进入队列的顺序来执行,该队列使用的是线程安全的PriorityBlockingQueue,因为我们的队列会被并发的访问,因此需要保证访问的原子性。

第三部分是Executor,也就是网络的执行者。该Executor继承自Thread,在run方法中循环访问第二部分的请求队列,请求完成之后将结果投递给UI线程。为了更好的控制请求队列,例如请求排序、取消等操作,这里我们并没有使用线程池来操作,而是自行管理队列和Thread的形式,这样整个结构也变得更为灵活。

第四部分则是Response投递类,在第三部分的Executor中执行网络请求,Executor是Thread,但是我们并不能在主线程中更新UI,因此我们使用
ResponseDelivery来封装Response的投递,保证Response执行在UI线程。
每个部分职责都相对单一,这样便于日后的升级和维护。
框架分析
图1中看起来有点像是分层架构,其实不是,这个图更多的是表达了它的逻辑顺序,而不是结构。而在我们的应用开发中,分层架构是一个重要的手段,如图2所示。

2.png


但在开发过程中,我们往往会把UI和业务层耦合起来,因为它们的关系太密切了,分解起来并不是那么容易。高手能够把复杂的事情简单化,而分解就是简单化的重要手段,分解这个过程在开发过程中我们成为重构。

那么我们就引入了一个分层概念,为了便于理解你也可以按照如图1的结构来加深理解。那么分层有什么优缺点呢?
优点:
复杂问题分解简单化,每一层负责自己的实现,并向外提供服务;
职责分离,复杂的系统都有很多人员进行开发,这些功能开发的管理和集成是个很严重的问题,分层设计实现之后,每层只需定义好自己的对外接口,其他依赖层服务的就可以进行开发;
每一层对其他层都是独立的,对外隐藏实现细节,上层无需知道下层的细节,只需调用接口即可;
有利于标准化。
缺点:
分层之后对于领域业务的修改有可能需要修改很多层;
过多的层次影响性能。
如上所说,我们的SimpleNet并不是分层的,而是简单的模块化,但是理论基础都是类似的,依赖于抽象而不依赖于实现、单一职责……这里引入分层的概念,这是便于理解,同时也是希望大家在开发过程中能够尽量保证模块的内聚性、耦合性。
再看SimpleNet,Request是一个抽象的泛型类,泛型类型就是返回的Response类型,例如StringRequest就是继承自Request。第二部分的RequestQueue依赖于Request,Request是抽象的,因此任何Request的子类都可以传递到请求队列中来,它依赖的是抽象Request,而不是具体的某个实现,因此保证了可扩展性。你可以自己实现自己所需的Request,例如大文件的上传Request。同理,第三部分的NetworkExecutor也只是依赖于Request抽象,但这里又引入了一个类型HttpStack,这个网络请求的真正执行者,有HttpClientStack和HttpUrlConnStack,两者分别为Apache的HttpClient和java的HttpURLConnection,关于这两者的区别请参考:Android访问网络,使用HttpURLConnection还是HttpClient?。HttpStack也是一个抽象,具体使用HttpClient还是HttpURLConnection则由运行系统版本来定,HttpStackFactory会根据系统版本给框架返回对应的HttpStack。最后的ResponseDelivery比较简单了,只是通过Handler将结果投递给UI线程执行,也就是执行RequestListener的onComplete方法,此时网络执行完成,用户即可在该方法中更新UI或者相关的其他的操作。
下面我们再看看SimpleNet的工程结构,如图3所示。

3.jpg


图3
这就是SimpleNet框架的基本结构了~
  收起阅读 »

新版imgeek.org社区公测

各位geekers, 我们重新输理和构建了了imgeek.org开发者社区,邀请各位测试,现在的样子看起来有点像stackoverflow. 对于新用户,直接注册即可进入 对于已经注册的用户,由于你的密码是加密存放的,我们无法迁移你的密码, 你需要通过&q...
继续阅读 »
各位geekers,

我们重新输理和构建了了imgeek.org开发者社区,邀请各位测试,现在的样子看起来有点像stackoverflow.

对于新用户,直接注册即可进入
对于已经注册的用户,由于你的密码是加密存放的,我们无法迁移你的密码, 你需要通过"找回密码"进入:http://www.imgeek.org/?/account/find_password/

fat1

---------------------------------------------
2015.6.16  imgeek.org release note
 . 调整问题及回复的展现形式
 . 增加发布“文章”功能
 . 新增“活动”功能
 . 新增“工单功能
 . 增强提醒功能
 . 新增“知识库”/“帮助”功能
 . 新增提问前“问题提示”功能
 . 新增“简历”功能及实名认证功能
 . 增加“个人空间”及用户信誉体系
 . 增加与客服对接的交换格式
 . 增强搜索功能
 . 更好的手机端访问体验
 . .......(更多好功能等你体验)

to do list :
1. 社交账号(微博、微信、QQ、facebook ,twitter,google)登录
2. "活动"板块优化
3. "话题"/标签优化
4. 提醒功能优化
5. 推出英文版本
6. 开源项目托管协作托管平台

原来的社区站仍然可以通过:http://www.imgeek.org/bbs 访问
 
  收起阅读 »

Meteor 获 2000 万美元融资,Galaxy 是新重点

JavaScript 开发框架 Meteor 的开发团队 Meteor Development Group (简称 MDG)为它的框架堆栈、Web 开发工具和 JavaScript 移动应用融资 2000 万美元。此轮融资由经纬创投、安德森·霍洛维茨基金和 T...
继续阅读 »
JavaScript 开发框架 Meteor 的开发团队 Meteor Development Group (简称 MDG)为它的框架堆栈、Web 开发工具和 JavaScript 移动应用融资 2000 万美元。此轮融资由经纬创投、安德森·霍洛维茨基金和 Trinity 风险投资领导,并提供了 MDG 开发开源框架所需要的资源。

MDG 的 CEO Geoff Schmidt 表示,下一步的重点是 Galaxy,它是基于谷歌开源项目 Kubernetes 的 Meteor 运行应用程序的系统。另外,Meteor 也增加处理 UI 的库,比如对 Angular 和 React 的支持。 收起阅读 »