注册
环信即时通讯云

环信即时通讯云

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

环信开发文档

环信FAQ

环信FAQ

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

RTE开发者社区

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

技术讨论区

技术交流、答疑
资源下载

资源下载

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

iOS Library

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

Android Library

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

开发技巧: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 的支持。 收起阅读 »