10月 26

说一下定义冷启动的难度及考虑要点

我把冷启动话题分为如下四种情况,单点启动,单边启动,双边启动,多边启动,难度和成本依次递增,而一旦启动成功,其竞争门槛则从低到高。而我们创业之初,在设计项目的时候,这个问题就应该考虑清楚,因为这涉及重大的成本核算和推广策略。

1、单点启动

项目特征,个体用户使用你的产品和服务,不受其他人,服务商的影响。简单说就是,一个人,一个客户,也能用起来。

常见的工具类产品,比如美图秀秀,用户只要自己觉得好用就可以了,不需要很多人在线,也不需要有一大堆服务商选择。 比如各种安卓桌面,壁纸产品等等等。

常见的企业服务产品,比如财务软件,销售管理软件,客户对自己的业务有帮助就可以。

常见的单机休闲游戏。

这样的产品 ,启动推广可以慢慢来,不用担心说,我用户不多,我没有很多的用户和玩家,就会导致大量的流失,可以慢慢磨产品,针对每一个用户,客户的反馈去调整优化,直到产品打磨成熟,然后逐步推广做大。

对于草根创业者或资金不是很大的创业者,从事这个领域的创业,市场可以慢慢做,可以一点点发展,一步步走。对于企业服务而言,甚至可以一单一单的做。

2、单边启动

项目特征,依赖于一定人群同时使用,才可以启动,一旦无法满足人群同时使用的指标,现有用户会迅速流失。

简单说就是,一群人才能玩起来。

典型,社交类产品,即时通讯,社交网络,联机游戏。

这样的产品,你必须批量发起用户,短时期达到一个用户密度门槛,才可以让项目进行下去,比如一个交友网站,你必须让这个地区有足够多的男性和女性,这个网站才会有持续发展的可能性,那么第一批用户,必须是有规模的批量导入,而不是一个个发展,否则就是发展一个,流失一个。

所以我们说,做社交比做工具难,因为启动所需的用户密度非常高,联机游戏有时候还好点,人少点也能玩的下去。

有些草根创业者的玩法是,做一些系统机器人来维持活跃度,让用户以为有很多人在线,比如有些无节操交友网站的很多美女,都是机器人,其实就是程序抓取的数据而已,以及一些自动应答或自动回复。有些联机游戏的对手,也是机器人。哎呀哎呀,是不是话又说多了。。。。

3、双边启动

项目特征,需要供需双方都有足够的参与密度,才能同时启动,任何一方达不到足够的参与度,均无法形成有效稳定的发展,参与用户均会流失。

典型案例,

电商平台, 比如淘宝

o2o,比如百度外卖,美团,饿了么

租车平台,比如滴滴打车,uber

你需要有足够的用户,也需要有足够的商家,如果商家够多而用户没有,商家一定流失,用户够多而商家不足,用户也会流失。(自营电商和自己采购货源的属于单边启动,比如京东,唯品会,都是单边启动)

双边启动所需启动资金更大,所需启动难度更高,而一旦建立起来,其竞争门槛更高,因为对手想要颠覆的成本更高更高。

4、多边启动

项目特征,除了供需双方,还需要第三方甚至第四方的参与密度,才能同时启动。任何一方达不到足够的参与度,均无法形成有效稳定的发展,参与用户均会流失。

淘宝目前看上去有一点这样的感觉,但是其实是双边启动后第三边才开始逐渐热起来,并形成很强大的竞争门槛,这里的第三边就是,物流服务商对接。 但实际上淘宝启动的时候这块并不强,都是商家自己解决,也没考虑的很多,所以我认为淘宝启动的时候,也不属于三边启动。

目前,实话说,三边启动或多边启动还没看到成功案例,

但我看到了这样的创业策划

搭建利用闲置人力提供送餐服务的平台,这还真不是泄露创业者商业机密,因为跟我说这话的至少3个人了,好多人都提到这个项目。

那我们想想

第一,你需要有大量的商家加入。

第二,你需要足够的用户,否则商家肯定没兴趣维护这玩意。

第三,你需要足够的送货人在线,才能保证及时的把东西送出去。

先不说第三的风控怎么处理。

我觉得连百度和阿里要做这样的事情都要掂量掂量,这咋同时启动啊啊啊啊! 饿了么搞了几千人的快送员,难道说你一个点子就秒了人家?

现在我们回顾一下,就会发现,大规模烧钱运营推广,各种补贴的,多半是第三类,双边启动,因为启动门槛实在太高,靠口碑冷启动基本不太可能起来,淘宝当年打易趣花钱也是杠杠的,铺天盖地的弹窗当时几乎覆盖了大半草根网站,因为你需要两边同时高密度项目才有可持续的价值。

又要说百度的案例,百度有啊刚开始的时候,商家极为踊跃,百度在商家这块号召力还是很强的,但是错估了自己用户方面的影响力,不肯砸钱买用户(如果有现在做百度外卖的气魄,这项目还真有一些机会),销售量上不去,商家觉得投入产出不值,也就开始撤退,然后,就成了百度一桩不愿意提及的历史。连百度做双边启动,想省点钱都做不起来,可想而知,双边启动的项目有多难。

我看到很多创业项目,动不动就要做一个商家和用户的大平台,先拿美团和淘宝说事,最近还有人给我发这样的计划书,真心说,你要是李嘉诚儿子我不拦着你。年景好你团队背景够牛逼拿了几千万美元先试试水再去融下一轮我也不拦着你,你别出心裁找个垂直领域做专一点也未尝不可,一上来就要和美团淘宝pk,口口声声找痛点,歇歇吧,这启动成本绝对比你以为的高几个数量级。

再说一个挺典型的案例,还是百度

最早的搜索引擎,其实是单点启动产品,百度开始的时候没打什么推广,你只要采集的数据够全,搜索结果够好,用户多点少点,彼此没影响,所以百度可以慢慢做,一点点优化,一个点一个点的提升市场占有率,现在说百度搜索质量不好的,很多人是真不知道15年前谷歌的中文搜索啥样的。 但百度的战略上还是做了一件牛逼的事情,从贴吧开始,百度从单点产品变成单边产品,用户群有交流了,到了知道和百科,以及文库,从单边变成双边,积淀了一大批内容贡献者,再想和百度pk,这就成了门槛,你说这其他搜索引擎也可以收录啊,这要看你对百度有没有威胁,你看百度连google的蜘蛛都不拦,却直接把360的蜘蛛封掉,你威胁我了,我封你蜘蛛,你要再抓取,我就去法院告你侵权,这案例多典型。从百度这个做法起,中文搜索引擎的市场从单点启动硬是变成了多边启动。 靠贴吧,知道,百科,文库的UGC组合,硬是把竞争门槛往上提了一大截。

再说一下有赞,微店这样的平台,其实本质上是单点启动的项目,因为这个平台只对商家服务,而且商家彼此交互不多,但系统开始出现供应商平台后,单点开始走向单边,不过目前据说供应商在其中销售占比不高,后续会不会走向双边,也就是会不会把用户端做起来,还需要观察。

那么说一下结论,一个产品,假设一个简单的产品,单点启动做的非常成功,如果想提高门槛,减少竞争对手的威胁,就要从单点转向单边,构筑人群壁垒,你产品再好你人群不够你就是不如我,如果可能就构筑双边,你搞定一波人群你还是pk不过我,如果再牛逼就去搭建三边壁垒,四边壁垒,比如淘宝的物流体系牛逼了,别人更没法玩了,然后再弄一个金融体系出来,那对手就只有绝望了。

目前典型如美图秀秀,本来是一个单点启动的成功典范,到了美拍,就是单边了,甚至可以说是双边,因为美拍并不是贴吧或qq这样平等的社区,而是一群特别会show的贡献者和一大批浏览者,可以认为是两个群体各取所需,这时候竞争门槛就高多了,你再搞一个美颜工具你没有足够用户积淀也颠覆不了对方了。

创业者,做项目设计和规划的时候,单点启动的最容易冷启动,所需成本最低,单边启动的需要一定的号召力,影响力,就需要思考你有没有足够的资金和影响力支持;双边启动的,特别特别难,不要认为想到一个痛点就可以启动,这里的启动成本是天文数字,一定要考虑清楚!

written by ocean

10月 23

服务器是 win2008 r2 +iis 7.5

开发用的mvc5

一直报错 

重新注册

%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i

各种折腾都不行,抓狂了  

最后居然是

添加 global.asax解决问题. 

written by ocean

10月 21

这里使用到了croppic这个插件

官方网址

http://www.croppic.net/

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="croppic/assets/css/bootstrap.css" rel="stylesheet" />
    <link href="croppic/assets/css/croppic.css" rel="stylesheet" /> 
    <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="croppic/croppic.min.js"></script> 
</head>
<body>  
    	<div id="croppic"></div>
					<span class="btn" id="cropContainerHeaderButton">click here to try it</span> 
    <script>
        var croppicHeaderOptions = {
            uploadUrl:'dopic.aspx',
            cropData: {
                "dummyData": 1,
                "dummyData2": "asdas"
            }, 
            cropUrl: 'img_crop_to_file.php',
            customUploadButtonId: 'cropContainerHeaderButton',
            modal: false,
            processInline: true,
            loaderHtml: '<div class="loader bubblingG"><span id="bubblingG_1"></span><span id="bubblingG_2"></span><span id="bubblingG_3"></span></div> ',
            onBeforeImgUpload: function () { console.log('onBeforeImgUpload') },
            onAfterImgUpload: function () { console.log('onAfterImgUpload') },
            onImgDrag: function () { console.log('onImgDrag') },
            onImgZoom: function () { console.log('onImgZoom') },
            onBeforeImgCrop: function () { console.log('onBeforeImgCrop') },
            onAfterImgCrop: function () { console.log('onAfterImgCrop') },
            onReset: function () { console.log('onReset') },
            onError: function (errormessage) { console.log('onError:' + errormessage) }
        }
        var croppic = new Croppic('croppic', croppicHeaderOptions);

    </script>

</body>
</html>

后台处理代码

    public partial class DoPic : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            var imgUrl = Request["imgUrl"].ToString();

            // original sizes
            var imgInitW = Request["imgInitW"];
            var imgInitH = Request["imgInitH"];


            // resized sizes
            var imgW =GetInt( Request["imgW"]);
            var imgH =GetInt( Request["imgH"]); 
            // offsets
            var imgY1 = GetInt(Request["imgY1"]);
            var imgX1 = GetInt(Request["imgX1"]); 
            // crop box 
            var cropW = GetInt(Request["cropW"]);
            var cropH = GetInt(Request["cropH"]);
            // rotation angle 
            var rotation = GetInt(Request["rotation"]); 
            imgUrl = imgUrl.Substring(imgUrl.IndexOf(',')+1);
             byte[] arr = Convert.FromBase64String(imgUrl);

             MagickImage mi = new MagickImage(arr);
             mi.Resize( new MagickGeometry(imgW + "x" + imgH));

             mi.Rotate(rotation);
             mi.Crop(new MagickGeometry(imgX1, imgY1, cropW, cropH));

             mi.Write(@"D:\Test\TestDemo\WebApplication1\1.png");

        }


        public static int GetInt(string input){
            double d=double.Parse(input);
            int r=(int)d;
            return r;
        }
    }

written by ocean

10月 16

关注Docker很久了,这次刚好配了新电脑,自己配个试试。

我的是win10系统,首先安装了Hyper-v

Ubuntu选择了16.04版本(ubuntu-16.04.3-server-amd64.iso)

下载地址

http://releases.ubuntu.com/16.04/

按照惯例,更新Ubuntu包索引

sudo apt-get update

让服务器可以使用Https更新仓库

sudo apt-get install apt-transport-https ca-certificates curl software-properties-common

添加Docker官方的密钥对,如果没问题,会返回“OK”

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

设置官方稳定版的仓库来源

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

安装好系统之后,按照教程安装docker

首先更新一下包索引

sudo apt-get update

安装最新版本的Docker-CE社区版

sudo apt-get install docker-ce

查看是否安装成功

docker version

试着运行一个测试镜像

sudo docker run hello-world

因为一些总所周知的原因,在国内使用docker拉取镜像会很慢,经常超时,因此需要添加国内的镜像源

/etc/docker/daemon.json

添加如下内容

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

值得注意的是,该镜像库只包含流行的公有镜像。私有镜像仍需要从美国镜像库中拉取。

然后记录几个docker常用的命令

sudo docker pull microsoft/aspnetcore   //这个是用来跑web的
sudo docker pull microsoft/dotnet    //从docker仓库拉取一个名称为“microsoft/dotnet”的镜像
sudo docker images    //查看所有镜像
sudo docker rmi 63sf86332e    //删除一个imageid的镜像
sudo docker rmi $(docker images -q)    //删除所有镜像


sudo docker run microsoft/dotnet    //创建microsoft/dotnet镜像的一个容器
sudo docker ps -a    //查看所有容器
sudo docker rm 6f0c67de4b72    //删除一个containerid的容器
sudo docker rm $(sudo docker ps -a -q)   //删除所有容器

运行容器

docker start ebb86823ddf9   //后面跟的是容器id

停止容器

docker stop app1   //后面跟的是容器别名

查看运行的容器

docker ps

进入容器

docker attach ebb86823ddf9   //后面跟的是容器id

另外一种进入容器的方法,退出的时候容器可以继续执行

docker exec -it mainapp bash   //mainapp是容器别名

退出容器

exit

把主机的文件copy到容器中

docker cp /www/ocean/default.conf mainapp:/etc/nginx/conf.d/default.conf    //mainapp是容器别名

把容器中的文件copy到主机中

docker cp mainapp:/etc/nginx/conf.d/default.conf  /var/ocean/   //mainapp是容器别名

接下来进入实战阶段

首先将asp.net core的程序通过winscp发布到Linux主机的 /var/ocean/ 这个路径下

然后执行下面这个命令

docker run -p 8001:80 -v /var/ocean:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/DockerTest.Web.dll

浏览器打开  http://172.28.3.125:8001/ 即可以看到运行成功

172.28.3.125是Linux主机的IP, 8001是对外暴露的端口,这些都是可以变的

这个命令可以再改变一下

docker run --name a1 -d -p 8001:80 -v /var/ocean:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/DockerTest.Web.dll

–name a1 是给容器命名叫a1,之后就可以直接拿a1来控制容器,不需要使用记不住的Id了

-d 命令是设置detach为true,根据官方的文档,意思是让这个命令在后台运行

还有一个,可以指定容器的hostname,参数是

-h 名字

例如

docker run --name appp -d  -h HOSTNAME  -p 8005:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/Ascend.FrameworkCore.Authorization.dll

另外一种方式是Dockerfile,可以用Dockerfile来创建镜像

文件名 Dockerfile ,必须是这个,注意大小写

FROM microsoft/aspnetcore
WORKDIR /app
COPY . /app 
ENTRYPOINT ["dotnet", "/app/DockerTest.Web.dll"]

跟随asp.net core的程序同时copy到  /var/ocean/目录, 当然了,目录随意

Linux命令进入 /var/ocean/目录,然后输入命令打包

docker build -t oceanfirstimage .

成功之后查看镜像就可以看到一个叫coeantestapp的镜像了,居然有328M…

启动这个镜像

sudo docker run --name app1 -d  -p 8008:80 oceanfirstimage

浏览器中输入 http://172.28.3.125:8008/ 即可访问

接下来记录如何把本地做好的镜像传到hub.docker.com

登录到docker

docker login

上传之前需要对本地的镜像打标签,依然以oceantestapp这个本地镜像为例

docker tag oceantestapp oceanheyang/oceantestapp

推送到hub.docker.com

docker push oceanheyang/oceantestapp

其中oceanheyang是我在docker hub上的用户名。经过等待,我们本地的镜像就传到docker hub上了。我可以在任何机器上直接使用。值得注意的是这是公共的,所有人都可以使用我的镜像。所以一些私有的东西就不能这样传了。

再次使用非常简单,只需要一句话

sudo docker run --name app1 -d  -p 8001:80  oceanheyang/oceantestapp

好了,打开 http://172.28.3.125:8001/ 可以尽情享受了

继续完善它,当我们重启server的时候,也是希望容器自动重启的。因此要使用到 –restart参数,命令如下

sudo docker run --restart=always

不过我已经创建了的容器可以直接更新,而不用销毁重建

docker update --restart=always app1

继续记载docker中的nginx,做负载均衡

先运行起来5个容器,等下通过nginx负载分发到这5个容器上

sudo docker run --name app1 -d  -p 8001:80 oceanfirstimage
sudo docker run --name app2 -d  -p 8002:80 oceanfirstimage
sudo docker run --name app3 -d  -p 8003:80 oceanfirstimage
sudo docker run --name app4 -d  -p 8004:80 oceanfirstimage
sudo docker run --name app5 -d  -p 8005:80 oceanfirstimage

然后获取nginx的镜像

sudo docker pull nginx

运行

docker run --name mainweb -d -p 80:80  -v /var/ocean/d2.conf:/etc/nginx/conf.d/default.conf nginx

我直接把配置文件放在外面了

具体可以参考这篇文章  linux下,用jexus配合nginx的使用

浏览器中输入 http://172.28.3.125  就可以看到了,随机访问

记录一下docker中启动redis,一句话搞定

docker run -p 6379:6379 --name oceanredis -d redis

一定要加 -p 6379:6379 才能将容器内的端口映射到外面,否则永远也连不上。
如果要自定义配置文件,则使用下面这句话

docker run -d -p 6379:6379 -v  /ocean/redis.conf:/usr/local/etc/redis/redis.conf --name oceanredis redis redis-server /usr/local/etc/redis/redis.conf

安装php

docker run -d -p 80:80 –name a1 -v /ocean/dokuwiki:/var/www/html php:7.2-apache

没有写入权限的话

chmod -R 777 /ocean/dokuwiki

written by ocean

10月 10

IIS里面限制,需要做调整

位置在 

IIS–>Request Filtering–>Edit Feature Settings

修改Maximum allowed content length

默认值是30M

written by ocean

10月 04

“先卖10000台再说!”201189日,小米网负责人黎万强在公司内部大会上这样说道。时间回到4年多前,在小米“标配”产品发布日816日前几天,小米公司100多号人沉浸在即将到来的第一款产品诞生的亢奋中,但是,没有人能告诉我们:未来,我们将能走多远\做多大。彼时,小米网仅有三位开发工程师,在经过两个多月的紧张开发后,小米网将要第一次面对公众在线销售产品,接受大考。由于工程师资源极度紧张,我们甚至考虑过使用ECSHOP之类的开源系统搭建小米网,不过幸好我们很快放弃了这一想法。因为在3个月之后,我们就发现不得不对系统进行一轮重构了。如果使用了第三方开源系统,为适应原系统架构,我们将长期被迫“迁就”原架构而放弃很多更优的设计,并为学习这个系统而付出时间成本。

第一代的小米网架构非常简单,如图1所示。

1
第一代小米网架构

我们实现了一个最基本的电商网站基本组件:在线销售系统、订单处理系统、仓储和物流系统,其中物流只对接了兄弟公司凡客的子公司如风达(现已独立)。所有的业务系统共用一个数据库。这样运行了几个月,发现网站访问量越来越大,当有新品销售时,面对突然激增的大量访问,数据库压力陡增,造成后端业务系统几乎无法使用。

2012年上半年,在小米网运行半年多后,我们决定将业务系统进行拆分,首先将销售系统剥离,之后逐步将越多越多的子系统拆分。拆分后各业务系统相对独立,各自使用自己的数据库,这样就完美解决了不同系统抢占数据库资源的问题,也让模块更清晰,程序员也能专注于自己负责的业务系统的开发,如图2所示。

2
拆分业务系统

这种结构随着小米网子系统的增多,只运行了几个月,我们就发现灾难开始显现了:我们需要维护的接口越来越多。系统间接口调用图变成了图3这样。

3
系统间接口调用图

这张网越来越复杂,从而使系统越来越难以维护,问题层出不穷。为了让各子系统尽量解耦,我们开发了小米网异步消息服务系统(Notify),让它作为所有子系统异步通信的中间人,所有子系统只需与中间人通信,接口标准化,将网状结构变为星状结构,大大降低了系统间通信成本,提高了开发效率,如图4所示。

4 小米网异步消息服务系统(Notify

此时,我们各子系统的网络架构也进行了相应的升级,大体分三层:调度层、业务层、数据库。在调度层,我们主要使用LVSHAProxy做流量转发和故障转移;业务层则五花八门,不同语言,不同框架百花齐放;数据层主要使用MySQLNoSQL存储及缓存服务(RedisMemcache)。

经过以上改造,我们从结构上,让整个流程更清晰了。然而,流量在继续增大,特别是小米网的爆品非常多,由于供应链及硬件产业特性,导致新品上市时供应量无法满足用户需求,用户的热情又远超我们的期望。大量请求导致前端销售系统的数据库开始告急。我们急需采取一种方案,将峰值抹平。我们是一家电商网站,在交易时会有大量在线联机事务处理,对数据一致性要求极高,所以经过讨论,我们决定采用淘宝开源的数据库中间件产品Cobar来实现数据库的水平切分。一共部署了32个实例,按用户ID实现数据的均匀读写,每个实例都做了MM双主高可用实现,如图5所示。

小米网技术架构变迁实践

5 采用淘宝开源的数据库中间件产品Cobar

这种架构保证了我们日常的在线销售稳定运行无压力,但是,每逢重大产品发布时,仍然要面对数百万QPS的抢购并发压力。不光是对数据库,对前端的应用程序服务器一样造成巨大的冲击。非抢购时间和抢购时间的流量差距可达几十倍到上百倍,如果我们按抢购峰值流量部署服务器,那将是一笔巨大的硬件资产浪费。在这个背景下,我们组织专门的人员开发了小米网的大型秒杀系统:BigTap。其实道理很简单,大家都去银行办过业务,在银行窗口排队的多,但是我们几乎很少看到有人在银行的取号机面前排过队。大秒系统其实就是充当银行的取号机的角色。它的业务逻辑非常简单:判定用户是否合法,合法则给这个用户购买资格,用户抢购成功;不合法则拒绝用户请求,抢购失败,如图6所示。

小米网技术架构变迁实践

6 小米网的大型秒杀系统BigTap

由于平时不抢购,大秒系统没有任何流量,所以,我们将大秒系统整体迁移至AWS云上,抢购前一天,将系统扩容,抢购后,将服务器再下架,实现完美伸缩,大大节约了成本。

除了大秒系统以外,我们还额外开发了众多小米网特色服务以支撑自己的业务。其中之一就是基于RedisTwitter开源的Twemproxy开发的小米通用缓存服务(内部代号MCC),集群中单节点达到14QPS,支持自动分片,热加载,全Redis协议支持。由于MCC支撑了小米网全业务线的缓存服务,所以我们还将此服务设计成双机房高可用架构,如图7所示。

小米网技术架构变迁实践

7 小米双机房架构(图中M表示主Redis实例,S表示从Redis实例)

常态下,双机房同时工作,读写机房1的主和从实例。机房2Mi-Twemproxy也读写机房1的主从实例。当机房1故障时,只需修改机房2Mi-Twemproxy读写机房2的从实例,并将此实例提升为主实例。当机房2出现故障时,不需要做任何改动。不足之处是当机房1出现故障时,机房2短期内只有一个主实例工作,无冗余。

在搭建电商网站中,我们还要时刻考虑的一个业务问题是:如何尽快地将货物售出,实现最快的库存周转,同时还要有好的购物体验,在这个问题上,库存系统的设计是一个很大的挑战。我们尝试考虑过很多电商的做法:按仓库库存卖商品。这种设计的好处是:仓与仓之间不用调拨,省去物流费用。缺点是,可能某个仓库存过高卖不出,某个仓又缺货,导致用户无法下单购买。最终导致库存周转周期太长,降低了整体效率。小米网结合自身实际情况,设计了一套虚拟库存分配系统,将各个仓作为库存渠道,可以自由合并,拆分供给不同的销售渠道,且可以自由调配。这种方法的缺点是订单可能会跨仓发货,增加物流成本,但是优点也显而易见:大大提高了库存周转率,用户也获得了较好的购物体验,在以用户为中心的小米网,这是我们首先会考虑的问题,设计如图8所示。

小米网技术架构变迁实践

8 小米虚拟库存分配系统

跨仓调拨既然无法避免,那我们能做的是尽量减少跨仓调拨频次,在多个仓库之间调拨时尽量合理规划。小米网的跨仓调拨问题实际上是一个多目标线性规划求最优解的问题,我们将各仓当前需求量,未来预测需求量,调拨线路和时间均考虑了进去。

在任何一家互联网公司中,都必须要重视的一件事就是:监控。服务器、应用程序、我们的业务,都存在监控需求。然而当今好的监控方案几乎是空白,每个公司业务不同,特点不同,通用的监控方案全都收效甚微。监控的意义在于出故障时,责任人应该第一时间知道并能采取措施。这要求监控系统做到一是及时,二是准确。当监控对象是一家大型公司时,还要求监控系统做到极其重要的一点是——有效。下面我会分析何为有效监控。小米网一路走过来,做过很多监控,当业务触发异常时就开始通过短信、邮件等告警。就算监控点很少时也不足以构成威胁,一旦触发告警,马上处理。然而,业务量激增后,监控点非常多,责任人往往会在短时间内收到大量重复的告警邮件和短信,时间一长,人都会疲劳,此时极容易忽略重大告警。所以,之前监控系统设计的最大问题在于没有区分异常和告警的关系,没有设定异常的策略和告警的策略。如果一条告警送到了,但是没有引起责任人的重视,那么这就是一条失败的、无效的告警。遇到异常马上触发告警是不科学的设计。我们新设计的监控系统的目的是大大提高有效告警量,其核心思想如下。

异常判断策略:

异常判定函数

val,监控结果取值

val(key),结果若有多个字段,如Server各参数,取指定 key 的值

count,结果若有多条,取结果集条数

exist,监控结果是否存在

empty,监控结果是否为空

异常判定表达式

val>3 ,取值大于3时判定为异常

count>10,结果集条数大于10时判定为异常

exist,结果不为空即判定为异常

empty,结果为空即判定为异常

告警判断策略:

告警判定函数

times,当前连续异常次数 percent(num),最近n次监控结果中异常的百分比

limit(num),告警次数限制,num=0为不限制

snooze(minutes),不限制告警次数时,异常周期内每隔minutes分钟告警一次 告警

判定策表达式

times>3,连续异常3次则告警

percent(10)>0.8,最近10次监控结果中异常数大于80%则告警

times>3 && limit(0) && snooze(30),大于3次异常开始告警,异常周期内,每隔30分钟告警一次 
通过这两个策略组合,能够实现最大限度不打扰人的有效告警。

最后,我在此稍提一下小米网的服务化。这是目前我们正在进行的技术架构大升级,采用Thrift+ETCD+Go+PHP实现。小米SOA框架完全自行开发,框架本身由Go语言实现。非Go语言的项目,我们通过两个小插件:服务发现助手和服务注册助手帮助接入到我们的服务平台。关于服务化的意义,网上有非常多的详细的分析,在此不再赘述。对于越来越大的技术架构体系,服务化将是未来的趋势。

 

written by ocean