关于新公司

加入新公司已经差不多有一个多月的时间了,最初么,也接过几个HR的电话,跟其他创业公司的头碰过,犹豫过,也徘徊了一下,还是决定留下来看看吧。这个团队也算一个中型公司下面的一个创业型公司,也可以说是一个创业项目吧。也是一个社区型产品。包括开发、运营在内的,团队基本上也算是草根形式的一帮人吧。我呢,依旧是干服务器这块的开发。目前公司也主要从智能手机客户端这块入手,刚起步,总归还算是慢慢进入状态吧。

创业,或者小项目刚开始还是在于人。目前这个团队,内部分的还是太细,有时候团队内部沟通也不是很有效。最最讨厌的事情,还是在于有很多会议要开。团队还是越小越有效率,三五个人一个团队规模,基本上也算合适。当然,也是因为大多基本没在三五个人的创业团队呆过的,目前这种状态,基本上对于我来说算是很清闲的了。工作强度也不大,我呢,也好好恢复一下。顺便多看看,多学学吧。

这个客户端产品,目前还是有很多地方需要改进,产品这块的出发点我很赞同,功能尽量少、能做到快速迭代,跟踪运营情况及时调整后续产品设计。最初的时候,我想也是快速开发、推出产品。一开始的时候,iOSAndroid客户端还是外包出去做的。现在么,收回来自己开发,也算是有点吃力吧。等过了磨合期,我想,也应该是逐渐进入状态的。

我呢,刚过了迷茫期,也算逐渐进入自己该有的状态吧。目前么,基本上靠自己摸索,然后慢慢前进了。琢磨着,业余再搞点啥项目什么的。想想现在自己也没啥拿得出手的东西,只能慢慢积累了。

脸谱之死

很早就想写点东西了,吐吐槽,发发牢骚。离开脸谱换换这个创业团队之后,没多久,投资人也就宣布解散团队了,剩下的几个成员归入到他投资的另一个创业公司。其实,几个核心成员早已经走的差不多了。产品起起伏伏,或者,根本就没怎么起来过吧。我么也算经历了一个移动互联网产品的生存周期。

一直以来,脸谱想做的事情总是很泛泛。我想投资人应该跟CEO说过,想做成啥样子,应该描绘过这张饼。其实,应该投资人自己来做CEO,这样或许更好点,脸谱也不会有这样的结局吧。CEO呢,在这里的角色其实很像接活干、接项目干,接了这个活呢,一个字,“抄”。当然,我不清楚CEO自己到底有没有什么概念,想把脸谱做成什么样子,或者,泛泛的讲做社交。有时候,“抄”也是一种学问。当然,做外包,也有学问。做创业项目外包,那学问更大了。

从宽泛的角度切入,做出来的产品,有时候就不那么纯粹了。其实啊,就是四不像。什么都有点,却什么都不是。于是,没法有效的留住用户。做产品还是要有产品经理,当然,最完美的事情,莫过于CEO就是干产品经理出身,有丰富的背景经历,受过苦、吃过亏,走过很多弯路。现实往往没有这么美好,一个干外包项目出身的技术人员,来担当这个产品经理的角色,那么,接下去的路途是非常遥远的。首先一个是思维方式,其次,就是会走很多弯路,还有就是设计产品的方式,实现产品的方式,就是来回折腾,最后么,能赶出来,但是质量又不是那么过得去的。美其名曰,敏捷,其实啊,一点都不敏捷。劳心劳力,疲惫不堪。我见过最高效的产品设计规划,一天时间,就把接下去一个版本要实现的功能都定完细化,接下去几天实现,最后出来的产品,我个人觉得还是很棒的,真心觉得很赞。

所以啊,你要是想敏捷,那么就花最短的时间把有限的产品功能以及具体功能流程理清楚,当然,我说,有限的产品功能,其实很多时候你以为很必须的功能,但是又相对来讲实现比较复杂的,往往都是些没必要的功能。最简单,即最实用。这些,算是经验之谈了。

还有啊,老板千万得大肚量。产品设计,由产品经理说了算,这对于创业公司来讲,是必须遵守的天条。不管结果怎样,就算产品设计再怎么烂,你可以跟产品经理提,但是如果不被接受,则必须执行实现,不过如果你提出的异议很合理,我想任何人都是会接受的。这个如果对于产品经理即CEO的团队,那倒还好,如果产品经理非CEO的创业团队,那么,这里对CEO的肚量是个很大的考验。如果撑得过去,那么,男人的胸怀,是冤枉撑大的嘛。如果过不去,那么最终结果还是不说也罢。细细品来,其实矛盾也会在此滋生。创业团队有摩擦,其实很正常,关键是很多时候,有些人并不是那么容易释怀一些事情的。于是,就会有挣、有抢。最后呢,也就速速散了吧。当然,人非圣人,要能够做到对事不对人,的确还是有些难度的。

真心怀念脸谱后台架构调整的那段时光啊,是我收获最多的时候,也得感谢那位架构师大哥啦。自从他断断续续过来,到最后不来,我们这,人心也就那么散掉了。创业还是需要激情的,到最后变成了一种煎熬,那么这个团队,这个项目也差不多该结束了。

还有一点,想吐槽的,其实就是人情味了。最初的时候,也是最最重要的时候,虽然压力大,但是一起努力。毕竟大家也是一起有点憧憬,有点想法,有点激情,想做出点事情来的。也算朋友,互相帮助,互相学习,然后一起成长。当然,此时如果积累下去,那么逐渐形成的一种氛围,渐渐的影响力越大,那么,公司传承的东西,也会茁壮成长。这种事,等到大了,渐渐体会的到,如果后知后觉。影响力,其实还是一点点积累出来的东西,总归还是需要大家认同的。(2012-03-20增)

最终呢,还有一点,我真的是很不服气的,就是工资了。一月份的时候发了一部分,说得好好的,余下的会发的,然后呢,拖到现在才发。期间呢,在讨论工资的问题上,对于我这样在2月底走的耍了些手段。七扣八扣的,到最后也没几个钱了。拿到手,总共的10分之一差不多。原来,这人啊,就是这么对待我们这些一起奋斗过那么久的战友的,难怪脸谱就这么死了。想想自己也算幸运见识了这个人吧。总会碰到这么几个,当面一套,背后一套的。总不清楚投资人知不知道这事情,我想,总归是知道点的吧,他又不用当恶人,少亏点钱,他也乐的。就怕啊,某些人中饱私囊!

我呢,最后也算花钱买个教训吧!

最近这段创业生活

自从去年6月份进入目前这家公司,到现在差不多也有将近9个月的时间了。从刚开始基情四射,到现在天天敲钟,这次创业,也就差不多这么快结束了。我呢,也算得上有些收获吧。Paul Graham说,创业最重要的还是看前5个人,现在看来,这句话确实是非常正确的。反正以后如果创业,要是老板外包行业出身,那我是得三思而后行了的。对于那些工作多年的,特别是国内一些外包公司的人,多半是带些外包从业气息的,这些人,如果再进入外包行业么,那也算对路。要是来互联网行业创业,多半,还是要死的。

前面一家公司的文化,特别是呆过3-5年的公司,绝对深入骨髓的。包括一些行事风格,除非么,有自我觉悟,懂得看些书,学习点经验,慢慢改么,否则,多半下场还是可以预测的。

看完《启示录》,对比一下我们做产品的过程,人家犯过的错,我们也在犯,感慨万千啊。自问,我们的产品根本不能解决什么问题,于是,为何要用这个?专注于解决小问题,如果可以解决好Ctrl+C以及Ctrl+V的问题(听说过这么一则笑话,一个老板,在公司电脑上对一份文件Ctrl+C,复制了一下,然后在家里电脑上Ctrl+V,粘贴,可惜,什么都没有),那么你就可以挣钱了(Dropbox—>可惜被墙)。

创业刚起步阶段,最重要的,还是大家相互激发,相互促进,而不是说,你说得话就一定要听,不听么,好吧,这事你不用做了,走吧。很多事情,还是在于团队,但是产品,拍板的还在于产品经理。如果带项目的,或者做测试的去为产品功能、需求拍板,那么最终的产品可想而知了。哎,总是会有些无奈的。

再者,还是效率了。本以为,创业公司的效率还是会很高的。比如,一天确定接下去版本的产品需求,一星期左右实现,基本上没多大问题。可惜啊,定个产品需求,要考虑这个,考虑那个,考虑以后要做什么什么,最终么,定义的实现,为了兼容上一版本的,还得来回折腾。期间呢,开发过程经常性质的被打断。come on!!!还一定要扯你,说你效率不够,开发进度老是拖。然后么,就接着定死你的上班时间,时间不到?早走?好吧,扣钱!于是么,一整套外包做法就出来了。童鞋,别忘了咱是一起再创业啊,咱也承担一定风险的啊。你要想想,如果公司逐渐壮大了,你要传承下去的是什么?就是这中东西么?come on!!!

这么一段时间,也算是对我自身成长的一种帮助吧。从刚开始的,对web开发的一些技术基本上不懂,到目前的,也算做过点项目了(我们自己开发的即时聊天系统,以及一些业务的api)。对于java web开发的一些道道呢,也算熟悉了(springstruts2ibatistomcat)。期间呢,也算折腾过mongodb。本来的,接下去想把业务上一些单点的逻辑给去掉。目前呢,我们API(运行在tomcat环境下)是单点的,其中还包含一块文件存储的,打算用mongodb master/slave存储文件,以及一些空间数据,不过mongodb 1.8存文件有limit,最大只支持16M,我们用2.0+,也就存存小文件,基本没啥问题。API呢,做一下apache+tomcat的负载均衡,不过个人还是倾向于用nginx,我们的即时聊天呢,目前来讲是没有单点的。

即时聊天还是需要优化,把memcacheq给去掉,包括后台处理消息的应用,全部使用erlang来写,基本上的话,那就是自有协议的ejabberd了。以前我们即时聊天使用的是openfire,我来的时候也还在用,不过,经常性质的会出现问题,我估计么,可能是因为没有用好。

接下去么,还是得继续找工作啊,简历么,海投了。。。找个技术氛围浓厚,有没有那么多边边框框的工作,真不容易啊!

关于又一次辞职

昨天跟头提了离职,是啊,这是迟早的事情么。我们这个创业项目啊,也差不多快死了。团队散了,真的,这么折腾,真疲了。没有做产品经验的人主导做产品,而且还是做外包项目出身。这么做,首先最重要的问题都没有解决,还想,这个里面要加这个、加那个。哎,最终啊,还是这么死死的,没啥特色。在他们眼里,移动客户端的产品就那样子,这边抄一点,那边抄一点,就行了。从来不曾觉得为什么要加这个,能解决什么问题?有什么帮助?

我呆着,也算有半年多了,不怎么甘心这么折腾,基本上啊,都要走了,年底了,散伙饭都没吃。算是一种遗憾了。半年里,看着好几个同事走,又来了好几个新同事,这回,都走了。

最近,又买了几本书,《软件随想录》《点石成金》,这两本书都是经验啊,一本算是创业以及软件行业的心得经验,另一本是网页设计的经验之谈,都蛮不错的。人到了一定时候是不是真就不看书?看别人怎么做就可以了?我觉着,活到老学到老嘛。总要做点和别人不一样的事情出来,现在么,算是一点点积累吧。这半年时间,做得这个项目,我还是非常受益的,虽然,经验尚浅,但是折腾点事情还是靠谱的。

过完年,还得开始找工作啊,想做服务器端的开发,一直打算写个消息队列来着,由于前段时间一直忙,接下去应该有时间写了,基于memcached协议的消息队列,用erlang写,嘿嘿,存储引擎使用leveldb或者直接文件存储。然后用python写个客户端,顺便学习学习Twisted,事件驱动模式的python库,事件驱动对于服务器端的开发确实高效,该干嘛时干嘛,有人搭讪,立马回答,完了继续该干嘛干嘛,不需要耗着资源一直等着搭讪。

忽然觉着,自己能折腾的事情越来越多,但是,还是不够沉淀。接下去,得找个能沉淀些东西的活。外包神马的,最讨厌了啊。

今天,回家过年,这两天,有点小插曲,注定着2012过得不是很顺啊。

人生么,在于折腾啊!:)

年终总结

今年换了份工作,原先是搞GIS项目二次开发的,说白了就是拿别人的SDK根据业务需求方做些定制开发,对于一些特定的业务流程做一下优化,处理一下特定的数据,对于GIS来说,数据就是业务,业务就是数据。也就一些小项目,自己不是很喜欢搞这个,干到4月底于是就辞职了。

呆了一个来月,觉着自己是该找找工作了,于是,海发简历,你懂得。不过,发的都是些互联网创业公司。因为自己对创业神马的很感兴趣,在大学里一直看一些赢在中国这样的视频,导致骨子里一味的想创业,所以就一直寻寻觅觅。后来就到了现在这家创业公司。面试的时候,是轮着面,一个面完接着另一个,想想创业公司也总该招人谨慎点吧。也是因为自己的背景和他们要求的不是很符合,所以就认认真真的回答问题。后来呢,我花了一个下午,做了一个wap页面,是用php写的,用了jquery mobile这个还是alpha版本的js框架,那时我对php也是刚刚入门,js神马的是早期看过一点,也不熟,更不用说CSS和jquery mobile这个框架了。当然啦,我是照着产品经理的原型做的,最终结果模样瞅着还行,于是就这么pass了,第二天就正是上班啦。那天刚好是6月1号,儿童节啊。现在想想,有点诧异啊,当时我怎么就能找到jquery mobile这么个东西呢?也算幸运啊,就这么pass了。

我本来就冲着java web开发这个岗位做的,刚开始对spring,struts,ibatis这些东西都不熟,当然因为对php有点了解,于是开始就写php,做手机访问页面。当时对于java也算稍微了解一点了,就自个摸索这折腾折腾。当时有个架构师,不过经常性质的不来,还有两个开发人员,一个是leader,还有一个就是员工了。后来又来了个实习的。我差不多花了一个多星期的时间,把要求使用的wap页面写完,期间,修修,改改,来回折腾了很多回。咱头的性子很急躁,脾气也不咋滴,于是,期间还经常性质的和产品经理“聊聊天、谈谈心”,于是,设计也是来回折腾,不提也罢啊。

一个星期后,正式开始接触spring,struts,ibatis这些东西,还有tomcat,也算干上了java web开发了。那是主要是用struts包装一些url,用户iphone、android手机客户端调用,就是api。一开始,调试那个配置,实在是件很累的事情,也是,本身那些东西不知道从哪里搞来的,就这么一直用着,天知道啊。那时我们的聊天系统使用的是openfire,基于xmpp协议的时时聊天系统。这个东西,间歇性质的会出些问题,虽然是到3.X的版本了,不过大部分原因还是因为我们都不熟悉他的缘故吧。而且,openfire会有消息丢失的现象,当时,由于搞iphone开发的人就一个,还是菜鸟,所以iphone版本也是很糟糕的版本,android版本有三个人开发稍微好点,不过,设计也是很烂很烂的,一点都不易用。有两个UI,但是纯粹只做UI图,基本不写CSS及其他东西,单纯的只用ps做图,称为设计。产品经理会用axure做原型。

过了一段时间,搞iphone开发的来了一个牛人,同时,头又从原先呆的公司找来几个人做iphone和android,基本都是有好几年工作经验的人。产品经理还是原先那个,继续搞他的设计。iphone和android的都重新架构设计,代码都重写了一遍。这期间服务器开发api这边走了两个人,那架构师还是间歇性质的过来一下。于是,就剩下我和一个实习的了。恰好,新来一个高级架构师,打算用erlang自己写个消息系统,我一听,立马来了兴趣,这架构师人蛮好的,过来之后带我和那个实习生。我们么,也算努力,分工的时候,我们给这个消息系统取了个名字“擎天柱(Optimus Prime)”,名字很拉风。我分工做memcacheq通信框架,用spring包装一下,开放给后台java应用做driver。同时,我也用这个框架做了几个处理特定消息的程序,我们称她为后台应用app,因为消息都是通过memcacheq做中转,所以,前台有高并发特性的erlang支持,后台可以慢慢做事情,这种模式,对于增加和横向扩展都非常容易。

本来我想参与iphone客户端协议开发,但是想想自己对于位运算这种玩意玩不转,于是就放弃了。这个消息系统大概花了1个月的时间,我们服务器从原先的2台云主机,一台兼容机,一台dell服务器,扩展到4台dell服务器,一台兼容机。期间学习到了很多东西,包括使用iptables,apache与tomcat做负载均衡,用iptables做负载均衡,tcp/ip通讯,等等,对我帮助很多的还要感谢新来的架构师,原先那个,这期间也就不来了。这期间,android组开发,也走了一个人,iphone组,开发来了个实习生。服务器组开发这边就我们三。等到我们新消息系统上线,iphone新版也推出来了。

接下去就是客户端改版啦,我呢,也参与者做wap页面和我们网站主页的改版。这期间,主要就是谢谢php,js还有一些客户端使用的api业务更改,修改一下原有的bug。改版还是主要由于投资人的影响。说实话,我真不觉的我们头对这产品想做成啥样有个大概想法。很笼统的,说做熟人社交。嗯,对,熟人社交是不错。关键是切入点,一直看米聊、微信、path、linkedin。。。都不知道想啥,最后还是在这IM、幸会神马的动作迅速,老想着抄别人点啥。。。最后,哎。。。然后产品经理由iphone开发的那个经验丰富的人来担任了,原先那个么,也就走了。

然后,下一版本,又改版啦,之后就是坚定的走商务路线,开始上名片,上邀请好友。同时要上传通讯录给你自动匹配好友,期间使用http协议做上传,然后又改成tcp,接着又改回http,这事情不是一般的折腾。期间就碰到一个memcacheq最大消息体64K的问题,于是,抽空学习了一下他的源代码,还有就是memcachedb的。基本上两个实现差不多。光光名片接口讨论,刚开始说,很简单的,只要能取到,然后能下载就成。接着说,这个恐怕不行吧,不能这样做。邀请接口讨论也是这么折腾。接着么,由于头的不信任。产品经理最后,只能换到头自己身上了。你说,你让人家做产品经理,好,那设计什么的,都让他去做,你要信任他吧?不用,他弄了一套,然后你自己又回去弄一套方案,接着,跟他说你那个不行,还是用我的吧。于是,那你自己去弄呗。

设计么,至画个ui,然后说,我都画在ui上了,你照做吧。还好,会给我们讲讲ui。好吧,你狠,我照做。接口设计照着ui做吧,你不行,不能这么干,不照做吧,不行你不能这么干。总是在讨论接口实现的业务时,经常纠结的,这个也要加,那个也要做,总说,以后我们可能会这样的,你不能这么草率,以后我们需要神马神马的,你不能这么照ui设计。

接着,就因为一些问题,我们架构师还和头争执,于是,产品神马的,都成浮云了。头有个毛病,就是做产品时不知道怎么取舍。在他眼里,我们客户端应用的版本,因为一个小问题,就得重新发布一个新版本。当然,这期间,名片下载上传与好友邀请与推荐是我设计的,从中也学到了很多东西。做开发的,关键还得看产品设计啊。因为接口目前的用户就是客户端。客户端的业务逻辑就是产品设计决定的。如果说,没有一个清晰的产品体验逻辑,任何设计都是浮云啊。

最近刚刚做完活动的接口,哎,这段时间真的是折腾死人了。有几个接口差不多开发完成的时候,突然告知,不能这么干,逻辑还得改,改就改吧,反正我是习以为常了。幸好,这事有一个新来的架构师扛着。当然,期间也有些很坑爹的事情。QA,知道吧,现在QA不单单要测试产品,连产品需求咋整的,都要QA来弄,我们这QA也可怜,天天被折腾,苦逼啊。还有么,就是经常性质的会听到,你去看看别人怎么做的么,然后再来跟我谈这个事情。尼玛,从没听过,产品功能要参考别人,然后自己没有想法做到什么程度的,直接抛给你一句,先去看别人怎么做的。我们开发么,经常性的被打断,来查查这个问题,来看看这个是怎么回事。还要死定个时间,硬要在这段时间弄完,弄不完,你想办法,好吧,我是想不到办法了,最后么,这个跟我原先想的不一样啊,这个又问题啊。你早干嘛去了?早干嘛不来说说清楚?那些不做,那些做,为啥什么都一期做了?为啥不考虑考虑这么做了有啥用?为啥要这么做?

原先那个,现在是公司顾问,就周五来一天。哎,乐得悠闲啊。真怀念开发“擎天柱”那段时光啊。我估摸着,我是不是得换份工作?到年底了,总感觉头对任何人都有中监视心理,总觉着你干活不怎么卖力,也不想想为啥进度会拖慢。我想,只是旁观者清吧。我呢,也犯过几个错误,对于scheme free有了更深刻的感触,对于mysql应用,也加小心。对于业务变更,要更改字段,要千万小心。nsql还是很有必要深入学习一下。

犯过错,才会成长么,如果一个公司的头,没有这种远见,总想着,谁谁谁尽快把活干完,不在乎谁谁谁成长多少,那么,是不是该考虑换个公司?

前段时间,买了好几本书,都没有看完。kindle里面还有很多资料也没有看,对于感兴趣的事情我得专注啊。

琐碎的事情,罗列于此。总的来说,这大半年的时光,自己还是成长了很多,特别是开发“擎天柱”那段时间,了解了压力测试,写过loadrunner脚本,本打算学习一个tsung,服务器产品性能,以及tcp/ip通讯的东西,同时也接触到了erlang这们相当优雅的并发编程语言。Joe老头还是很牛叉的,虽然没有把他写得书《Programming Erlang》看完,但是看完了他写的那篇论文,还是很有成就感的。本打算开发完“擎天柱”,乘有空的时候,用erlang写一个基于memcached协议的消息队列,参考bitcask的实现,直接用文件存储。但是看到FQueue的实现之后,发觉,用RandomAccessFile实现性能不错的文件型持久化消息队列也挺简单,用jmemcached做缓存,实现memcached协议。这个计划,暂时搁浅了,总感觉用erlang写一个应该性能更不错。

新的一年,找一份新工作,期待宽松的工作氛围,老板不会一天到晚督促你干活干活,老板信任每个员工,给予充分的自有,有自由的发挥空间,最好不要严格的考勤,最后一点,还是我喜欢的,高并发、数据存取、high performance,最好可以使用nsql产品。

看书、看书、看书,把前段时间买的几本书看完。同时重新学习一下C语言,把《Programming Erlang》看完,用erlang写一个消息队列,学习一下linux内核,把《Professional Linux Kernel Architecture》这本书给看了。

centos中yum无法使用问题

今天碰到一台服务器无法使用yum install命令,是centos 5.5的系统,自己瞎折腾了一下,居然被我解决了,神奇啊,解决方法贴一下。
显示错误如下:

Loaded plugins: fastestmirror
Determining fastest mirrors
YumRepo Error: All mirror URLs are not using ftp, http[s] or file.
/Eg.
removing mirrorlist with no valid mirrors: /var/cache/yum/addons/mirrorlist.txt
Error: Cannot find a valid baseurl for repo: addons

不知道什么时候把mirrors.txt文件给去掉了。
后来查了一下有这么一串。

1./var/cache/yum/base/mirrorlist.txt
url:

http://mirrors.163.com/centos/5.7/os/x86_64/

http://centos.ustc.edu.cn/centos/5.7/os/x86_64/

http://mirror.neu.edu.cn/centos/5.7/os/x86_64/

http://mirrors.ta139.com/centos/5.7/os/x86_64/

http://ftp.nsysu.edu.tw/CentOS/5.7/os/x86_64/

http://ftp.tc.edu.tw/Linux/CentOS/5.7/os/x86_64/

http://ftp.isu.edu.tw/pub/Linux/CentOS/5.7/os/x86_64/

http://ftp.stu.edu.tw/Linux/CentOS/5.7/os/x86_64/

http://ftp.twaren.net/Linux/CentOS/5.7/os/x86_64/

http://ftp.cs.pu.edu.tw/Linux/CentOS/5.7/os/x86_64/

http://mirror01.idc.hinet.net/CentOS/5.7/os/x86_64/

http://centos.mirror.cdnetworks.com/5.7/os/x86_64/

http://centos.tt.co.kr/5.7/os/x86_64/

http://data.nicehosting.co.kr/os/CentOS/5.7/os/x86_64/

http://mirror.khlug.org/centos/5.7/os/x86_64/

http://mirror.yongbok.net/centos/5.7/os/x86_64/

http://ftp.daum.net/centos/5.7/os/x86_64/

http://mirror.nus.edu.sg/centos/5.7/os/x86_64/

http://mirror.averse.net/centos/5.7/os/x86_64/

http://mirrors.sin1.sg.voxel.net/centos/5.7/os/x86_64/

2./var/cache/yum/updates/mirrorlist.txt
url:

http://mirror.neu.edu.cn/centos/5.7/updates/x86_64/

http://mirrors.ta139.com/centos/5.7/updates/x86_64/

http://mirrors.stuhome.net/centos/5.7/updates/x86_64/

http://centos.ustc.edu.cn/centos/5.7/updates/x86_64/

http://ftp.stu.edu.tw/Linux/CentOS/5.7/updates/x86_64/

http://mirror01.idc.hinet.net/CentOS/5.7/updates/x86_64/

http://ftp.tc.edu.tw/Linux/CentOS/5.7/updates/x86_64/

http://ftp.isu.edu.tw/pub/Linux/CentOS/5.7/updates/x86_64/

http://ftp.cs.pu.edu.tw/Linux/CentOS/5.7/updates/x86_64/

http://ftp.daum.net/centos/5.7/updates/x86_64/

http://mirror.khlug.org/centos/5.7/updates/x86_64/

http://data.nicehosting.co.kr/os/CentOS/5.7/updates/x86_64/

http://centos.tt.co.kr/5.7/updates/x86_64/

http://mirror.averse.net/centos/5.7/updates/x86_64/

ftp://ftp.oss.eznetsols.org/linux/centos/5.7/updates/x86_64/

http://mirror.nus.edu.sg/centos/5.7/updates/x86_64/

http://mirrors.sin1.sg.voxel.net/centos/5.7/updates/x86_64/

http://centos.vr-zone.com/5.7/updates/x86_64/

http://rsync.atworks.co.jp/centos/5.7/updates/x86_64/

http://ftp.nara.wide.ad.jp/pub/Linux/centos/5.7/updates/x86_64/

3./var/cache/yum/addons/mirrorlist.txt
url:

http://mirror.centos.org/centos/5/addons/x86_64/

4./var/cache/yum/extras/mirrorlist.txt
url:

http://centos.ustc.edu.cn/centos/5.7/extras/x86_64/

http://mirrors.stuhome.net/centos/5.7/extras/x86_64/

http://mirror.neu.edu.cn/centos/5.7/extras/x86_64/

http://mirrors.ta139.com/centos/5.7/extras/x86_64/

http://ftp.cs.pu.edu.tw/Linux/CentOS/5.7/extras/x86_64/

http://ftp.stu.edu.tw/Linux/CentOS/5.7/extras/x86_64/

http://ftp.tc.edu.tw/Linux/CentOS/5.7/extras/x86_64/

http://ftp.isu.edu.tw/pub/Linux/CentOS/5.7/extras/x86_64/

http://mirror01.idc.hinet.net/CentOS/5.7/extras/x86_64/

http://data.nicehosting.co.kr/os/CentOS/5.7/extras/x86_64/

http://mirror.khlug.org/centos/5.7/extras/x86_64/

http://ftp.daum.net/centos/5.7/extras/x86_64/

http://centos.tt.co.kr/5.7/extras/x86_64/

http://centos.vr-zone.com/5.7/extras/x86_64/

http://mirror.averse.net/centos/5.7/extras/x86_64/

http://mirrors.sin1.sg.voxel.net/centos/5.7/extras/x86_64/

ftp://ftp.oss.eznetsols.org/linux/centos/5.7/extras/x86_64/

http://mirror.nus.edu.sg/centos/5.7/extras/x86_64/

http://ftp.yz.yamagata-u.ac.jp/pub/linux/centos/5.7/extras/x86_64/

http://ftp.jaist.ac.jp/pub/Linux/CentOS/5.7/extras/x86_64/

对应的把这些url拷贝到mirrorlist.txt文件中去就ok啦。
have fun! :)

阅读memcacheq源码记录

越来越觉着大学里学的谭浩强的《C语言程序设计》太狗血了,哎,悔不当初啊。

今天下午闲着看了一下memcacheq这个消息队列的源代码,存储引擎用得bdbbdb支持队列方式的数据存储,只不过一个record必须fixed-length,具体能存多大的长度,我没再官方的doc中找到,不过,memcacheq作者说最大消息长度不能超过64K,而且,如果你超过了64K,消息队列就挂了,嗯,这个我碰到过了,超过64K,存入消息队列之后,整个消息队列就得重启。所以,如果你不确定以后业务变更会增加消息体的大小,那么我不建议你使用memcacheq :)

memcacheq在内存里维护一个队列的hash,memcacheq.c文件实现了memcached协议,bdb.c文件实现了对于bdb的读写,发觉消息队列持久化用bdb实现实在是很轻量、很简洁,item.c对协议层的封装,操作结构体item。

libevent对收到的通信包做callback,包括对bdb做增、删、存操作。bdb也用libevent,对libevent没有研究,略过N字 :)

ps memcacheq测试使用的是python写的memcache.py。

简单,有效的持久化消息队列,性能过得去,抽空我得好好看看C语言,学习一下 :) 复习一下数据结构,以前的早忘记了,而且也没怎么好好学。老外写的《C语言程序设计》这本,不错的。

memcached protocol

Protocol

——–

Clients of memcached communicate with server through TCP connections.

(A UDP interface is also available; details are below under “UDP

protocol.”) A given running memcached server listens on some

(configurable) port; clients connect to that port, send commands to

the server, read responses, and eventually close the connection.

There is no need to send any command to end the session. A client may

just close the connection at any moment it no longer needs it. Note,

however, that clients are encouraged to cache their connections rather

than reopen them every time they need to store or retrieve data.  This

is because memcached is especially designed to work very efficiently

with a very large number (many hundreds, more than a thousand if

necessary) of open connections. Caching connections will eliminate the

overhead associated with establishing a TCP connection (the overhead

of preparing for a new connection on the server side is insignificant

compared to this).

There are two kinds of data sent in the memcache protocol: text lines

and unstructured data.  Text lines are used for commands from clients

and responses from servers. Unstructured data is sent when a client

wants to store or retrieve data. The server will transmit back

unstructured data in exactly the same way it received it, as a byte

stream. The server doesn’t care about byte order issues in

unstructured data and isn’t aware of them. There are no limitations on

characters that may appear in unstructured data; however, the reader

of such data (either a client or a server) will always know, from a

preceding text line, the exact length of the data block being

transmitted.

Text lines are always terminated by \r\n. Unstructured data is _also_

terminated by \r\n, even though \r, \n or any other 8-bit characters

may also appear inside the data. Therefore, when a client retrieves

data from a server, it must use the length of the data block (which it

will be provided with) to determine where the data block ends, and not

the fact that \r\n follows the end of the data block, even though it

does.

Keys

—-

Data stored by memcached is identified with the help of a key. A key

is a text string which should uniquely identify the data for clients

that are interested in storing and retrieving it.  Currently the

length limit of a key is set at 250 characters (of course, normally

clients wouldn’t need to use such long keys); the key must not include

control characters or whitespace.

Commands

——–

There are three types of commands.

Storage commands (there are six: “set”, “add”, “replace”, “append”

“prepend” and “cas”) ask the server to store some data identified by a key. The

client sends a command line, and then a data block; after that the

client expects one line of response, which will indicate success or

faulure.

Retrieval commands (there are two: “get” and “gets”) ask the server to

retrieve data corresponding to a set of keys (one or more keys in one

request). The client sends a command line, which includes all the

requested keys; after that for each item the server finds it sends to

the client one response line with information about the item, and one

data block with the item’s data; this continues until the server

finished with the “END” response line.

All other commands don’t involve unstructured data. In all of them,

the client sends one command line, and expects (depending on the

command) either one line of response, or several lines of response

ending with “END” on the last line.

A command line always starts with the name of the command, followed by

parameters (if any) delimited by whitespace. Command names are

lower-case and are case-sensitive.

Expiration times

—————-

Some commands involve a client sending some kind of expiration time

(relative to an item or to an operation requested by the client) to

the server. In all such cases, the actual value sent may either be

Unix time (number of seconds since January 1, 1970, as a 32-bit

value), or a number of seconds starting from current time. In the

latter case, this number of seconds may not exceed 60*60*24*30 (number

of seconds in 30 days); if the number sent by a client is larger than

that, the server will consider it to be real Unix time value rather

than an offset from current time.

Error strings

————-

Each command sent by a client may be answered with an error string

from the server. These error strings come in three types:

- “ERROR\r\n”

means the client sent a nonexistent command name.

- “CLIENT_ERROR <error>\r\n”

means some sort of client error in the input line, i.e. the input

doesn’t conform to the protocol in some way. <error> is a

human-readable error string.

- “SERVER_ERROR <error>\r\n”

means some sort of server error prevents the server from carrying

out the command. <error> is a human-readable error string. In cases

of severe server errors, which make it impossible to continue

serving the client (this shouldn’t normally happen), the server will

close the connection after sending the error line. This is the only

case in which the server closes a connection to a client.

In the descriptions of individual commands below, these error lines

are not again specifically mentioned, but clients must allow for their

possibility.

Storage commands

—————-

First, the client sends a command line which looks like this:

<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n

cas <key> <flags> <exptime> <bytes> <cas unqiue> [noreply]\r\n

- <command name> is “set”, “add”, “replace”, “append” or “prepend”

“set” means “store this data”.

“add” means “store this data, but only if the server *doesn’t* already

hold data for this key”.

“replace” means “store this data, but only if the server *does*

already hold data for this key”.

“append” means “add this data to an existing key after existing data”.

“prepend” means “add this data to an existing key before existing data”.

The append and prepend commands do not accept flags or exptime.

They update existing data portions, and ignore new flag and exptime

settings.

“cas” is a check and set operation which means “store this data but

only if no one else has updated since I last fetched it.”

- <key> is the key under which the client asks to store the data

- <flags> is an arbitrary 16-bit unsigned integer (written out in

decimal) that the server stores along with the data and sends back

when the item is retrieved. Clients may use this as a bit field to

store data-specific information; this field is opaque to the server.

Note that in memcached 1.2.1 and higher, flags may be 32-bits, instead

of 16, but you might want to restrict yourself to 16 bits for

compatibility with older versions.

- <exptime> is expiration time. If it’s 0, the item never expires

(although it may be deleted from the cache to make place for other

items). If it’s non-zero (either Unix time or offset in seconds from

current time), it is guaranteed that clients will not be able to

retrieve this item after the expiration time arrives (measured by

server time).

- <bytes> is the number of bytes in the data block to follow, *not*

including the delimiting \r\n. <bytes> may be zero (in which case

it’s followed by an empty data block).

- <cas unique> is a unique 64-bit value of an existing entry.

Clients should use the value returned from the “gets” command

when issuing “cas” updates.

- “noreply” optional parameter instructs the server to not send the

reply.  NOTE: if the request line is malformed, the server can’t

parse “noreply” option reliably.  In this case it may send the error

to the client, and not reading it on the client side will break

things.  Client should construct only valid requests.

After this line, the client sends the data block:

<data block>\r\n

- <data block> is a chunk of arbitrary 8-bit data of length <bytes>

from the previous line.

After sending the command line and the data blockm the client awaits

the reply, which may be:

- “STORED\r\n”, to indicate success.

- “NOT_STORED\r\n” to indicate the data was not stored, but not

because of an error. This normally means that either that the

condition for an “add” or a “replace” command wasn’t met, or that the

item is in a delete queue (see the “delete” command below).

- “EXISTS\r\n” to indicate that the item you are trying to store with

a “cas” command has been modified since you last fetched it.

- “NOT_FOUND\r\n” to indicate that the item you are trying to store

with a “cas” command did not exist or has been deleted.

Retrieval command:

——————

The retrieval commands “get” and “gets” operates like this:

get <key>*\r\n

gets <key>*\r\n

- <key>* means one or more key strings separated by whitespace.

After this command, the client expects zero or more items, each of

which is received as a text line followed by a data block. After all

the items have been transmitted, the server sends the string

“END\r\n”

to indicate the end of response.

Each item sent by the server looks like this:

VALUE <key> <flags> <bytes> [<cas unique>]\r\n

<data block>\r\n

- <key> is the key for the item being sent

- <flags> is the flags value set by the storage command

- <bytes> is the length of the data block to follow, *not* including

its delimiting \r\n

- <cas unique> is a unique 64-bit integer that uniquely identifies

this specific item.

- <data block> is the data for this item.

If some of the keys appearing in a retrieval request are not sent back

by the server in the item list this means that the server does not

hold items with such keys (because they were never stored, or stored

but deleted to make space for more items, or expired, or explicitly

deleted by a client).

Deletion

——–

The command “delete” allows for explicit deletion of items:

delete <key> [<time>] [noreply]\r\n

- <key> is the key of the item the client wishes the server to delete

- <time> is the amount of time in seconds (or Unix time until which)

the client wishes the server to refuse “add” and “replace” commands

with this key. For this amount of item, the item is put into a

delete queue, which means that it won’t possible to retrieve it by

the “get” command, but “add” and “replace” command with this key

will also fail (the “set” command will succeed, however). After the

time passes, the item is finally deleted from server memory.

The parameter <time> is optional, and, if absent, defaults to 0

(which means that the item will be deleted immediately and further

storage commands with this key will succeed).

- “noreply” optional parameter instructs the server to not send the

reply.  See the note in Storage commands regarding malformed

requests.

The response line to this command can be one of:

- “DELETED\r\n” to indicate success

- “NOT_FOUND\r\n” to indicate that the item with this key was not

found.

See the “flush_all” command below for immediate invalidation

of all existing items.

Increment/Decrement

——————-

Commands “incr” and “decr” are used to change data for some item

in-place, incrementing or decrementing it. The data for the item is

treated as decimal representation of a 64-bit unsigned integer. If the

current data value does not conform to such a representation, the

commands behave as if the value were 0. Also, the item must already

exist for incr/decr to work; these commands won’t pretend that a

non-existent key exists with value 0; instead, they will fail.

The client sends the command line:

incr <key> <value> [noreply]\r\n

or

decr <key> <value> [noreply]\r\n

- <key> is the key of the item the client wishes to change

- <value> is the amount by which the client wants to increase/decrease

the item. It is a decimal representation of a 64-bit unsigned integer.

- “noreply” optional parameter instructs the server to not send the

reply.  See the note in Storage commands regarding malformed

requests.

The response will be one of:

- “NOT_FOUND\r\n” to indicate the item with this value was not found

- <value>\r\n , where <value> is the new value of the item’s data,

after the increment/decrement operation was carried out.

Note that underflow in the “decr” command is caught: if a client tries

to decrease the value below 0, the new value will be 0.  Overflow in

the “incr” command will wrap around the 64 bit mark.

Note also that decrementing a number such that it loses length isn’t

guaranteed to decrement its returned length.  The number MAY be

space-padded at the end, but this is purely an implementation

optimization, so you also shouldn’t rely on that.

Statistics

———-

The command “stats” is used to query the server about statistics it

maintains and other internal data. It has two forms. Without

arguments:

stats\r\n

it causes the server to output general-purpose statistics and

settings, documented below.  In the other form it has some arguments:

stats <args>\r\n

Depending on <args>, various internal data is sent by the server. The

kinds of arguments and the data sent are not documented in this vesion

of the protocol, and are subject to change for the convenience of

memcache developers.

General-purpose statistics

————————–

Upon receiving the “stats” command without arguments, the server sents

a number of lines which look like this:

STAT <name> <value>\r\n

The server terminates this list with the line

END\r\n

In each line of statistics, <name> is the name of this statistic, and

<value> is the data.  The following is the list of all names sent in

response to the “stats” command, together with the type of the value

sent for this name, and the meaning of the value.

In the type column below, “32u” means a 32-bit unsigned integer, “64u”

means a 64-bit unsigner integer. ’32u:32u’ means two 32-but unsigned

integers separated by a colon.

Name              Type     Meaning

———————————-

pid               32u      Process id of this server process

uptime            32u      Number of seconds this server has been running

time              32u      current UNIX time according to the server

version           string   Version string of this server

pointer_size      32       Default size of pointers on the host OS

(generally 32 or 64)

rusage_user       32u:32u  Accumulated user time for this process

(seconds:microseconds)

rusage_system     32u:32u  Accumulated system time for this process

(seconds:microseconds)

curr_items        32u      Current number of items stored by the server

total_items       32u      Total number of items stored by this server

ever since it started

bytes             64u      Current number of bytes used by this server

to store items

curr_connections  32u      Number of open connections

total_connections 32u      Total number of connections opened since

the server started running

connection_structures 32u  Number of connection structures allocated

by the server

cmd_get           64u      Cumulative number of retrieval requests

cmd_set           64u      Cumulative number of storage requests

get_hits          64u      Number of keys that have been requested and

found present

get_misses        64u      Number of items that have been requested

and not found

evictions         64u      Number of valid items removed from cache

to free memory for new items

bytes_read        64u      Total number of bytes read by this server

from network

bytes_written     64u      Total number of bytes sent by this server to

network

limit_maxbytes    32u      Number of bytes this server is allowed to

use for storage.

threads           32u      Number of worker threads requested.

(see doc/threads.txt)

Item statistics

—————

CAVEAT: This section describes statistics which are subject to change in the

future.

The “stats” command with the argument of “items” returns information about

item storage per slab class. The data is returned in the format:

STAT items:<slabclass>:<stat> <value>\r\n

The server terminates this list with the line

END\r\n

The slabclass aligns with class ids used by the “stats slabs” command. Where

“stats slabs” describes size and memory usage, “stats items” shows higher

level information.

The following item values are defined as of writing.

Name                   Meaning

——————————

number                 Number of items presently stored in this class. Expired

items are not automatically excluded.

age                    Age of the oldest item in the LRU.

evicted                Number of times an item had to be evicted from the LRU

before it expired.

outofmemory            Number of times the underlying slab class was unable to

store a new item. This means you are running with -M or

an eviction failed.

Note this will only display information about slabs which exist, so an empty

cache will return an empty set.

Item size statistics

——————–

CAVEAT: This section describes statistics which are subject to change in the

future.

The “stats” command with the argument of “sizes” returns information about the

general size and count of all items stored in the cache.

WARNING: This command WILL lock up your cache! It iterates over *every item*

and examines the size. While the operation is fast, if you have many items

you could prevent memcached from serving requests for several seconds.

The data is returned in the following format:

<size> <count>\r\n

The server terminates this list with the line

END\r\n

‘size’ is an approximate size of the item, within 32 bytes.

‘count’ is the amount of items that exist within that 32-byte range.

This is essentially a display of all of your items if there was a slab class

for every 32 bytes. You can use this to determine if adjusting the slab growth

factor would save memory overhead. For example: generating more classes in the

lower range could allow items to fit more snugly into their slab classes, if

most of your items are less than 200 bytes in size.

Slab statistics

—————

CAVEAT: This section describes statistics which are subject to change in the

future.

The “stats” command with the argument of “slabs” returns information about

each of the slabs created by memcached during runtime. This includes per-slab

information along with some totals. The data is returned in the format:

STAT <slabclass>:<stat> <value>\r\n

STAT <stat> <value>\r\n

The server terminates this list with the line

END\r\n

Name                   Meaning

——————————

chunk_size             The amount of space each chunk uses. One item will use

one chunk of the appropriate size.

chunks_per_page        How many chunks exist within one page. A page by

default is one megabyte in size. Slabs are allocated per

page, then broken into chunks.

total_pages            Total number of pages allocated to the slab class.

total_chunks           Total number of chunks allocated to the slab class.

used_chunks            How many chunks have been allocated to items.

free_chunks            Chunks not yet allocated to items, or freed via delete.

free_chunks_end        Number of free chunks at the end of the last allocated

page.

active_slabs           Total number of slab classes allocated.

total_malloced         Total amount of memory allocated to slab pages.

Other commands

————–

“flush_all” is a command with an optional numeric argument. It always

succeeds, and the server sends “OK\r\n” in response (unless “noreply”

is given as the last parameter). Its effect is to invalidate all

existing items immediately (by default) or after the expiration

specified.  After invalidation none of the items will be returned in

response to a retrieval command (unless it’s stored again under the

same key *after* flush_all has invalidated the items). flush_all

doesn’t actually free all the memory taken up by existing items; that

will happen gradually as new items are stored. The most precise

definition of what flush_all does is the following: it causes all

items whose update time is earlier than the time at which flush_all

was set to be executed to be ignored for retrieval purposes.

The intent of flush_all with a delay, was that in a setting where you

have a pool of memcached servers, and you need to flush all content,

you have the option of not resetting all memcached servers at the

same time (which could e.g. cause a spike in database load with all

clients suddenly needing to recreate content that would otherwise

have been found in the memcached daemon).

The delay option allows you to have them reset in e.g. 10 second

intervals (by passing 0 to the first, 10 to the second, 20 to the

third, etc. etc.).

“version” is a command with no arguments:

version\r\n

In response, the server sends ”VERSION <version>\r\n”, where <version> is the version string for the

server.

“verbosity” is a command with a numeric argument. It always succeeds,

and the server sends “OK\r\n” in response (unless “noreply” is given

as the last parameter). Its effect is to set the verbosity level of

the logging output.

“quit” is a command with no arguments:

quit\r\n

Upon receiving this command, the server closes the

connection. However, the client may also simply close the connection

when it no longer needs it, without issuing this command.

UDP protocol

————

For very large installations where the number of clients is high enough

that the number of TCP connections causes scaling difficulties, there is

also a UDP-based interface. The UDP interface does not provide guaranteed

delivery, so should only be used for operations that aren’t required to

succeed; typically it is used for “get” requests where a missing or

incomplete response can simply be treated as a cache miss.

Each UDP datagram contains a simple frame header, followed by data in the

same format as the TCP protocol described above. In the current

implementation, requests must be contained in a single UDP datagram, but

responses may span several datagrams. (The only common requests that would

span multiple datagrams are huge multi-key “get” requests and “set”

requests, both of which are more suitable to TCP transport for reliability

reasons anyway.)

The frame header is 8 bytes long, as follows (all values are 16-bit integers

in network byte order, high byte first):

0-1 Request ID

2-3 Sequence number

4-5 Total number of datagrams in this message

6-7 Reserved for future use; must be 0

The request ID is supplied by the client. Typically it will be a

monotonically increasing value starting from a random seed, but the client

is free to use whatever request IDs it likes. The server’s response will

contain the same ID as the incoming request. The client uses the request ID

to differentiate between responses to outstanding requests if there are

several pending from the same server; any datagrams with an unknown request

ID are probably delayed responses to an earlier request and should be

discarded.

The sequence number ranges from 0 to n-1, where n is the total number of

datagrams in the message. The client should concatenate the payloads of the

datagrams for a given response in sequence number order; the resulting byte

stream will contain a complete response in the same format as the TCP

protocol (including terminating \r\n sequences).

哎,人生;哎,产品

前段时间由于项目上面的业务变更,导致memcacheq这个消息队列使用有点捉襟见肘。由于其对于消息体大小有限制,而且最大不能超过60K,就萌生了自己写一个消息队列的想法。总是掌握在自己手中,能应对现有的业务需要的东西用着才爽啊。本来计划是有空就改写的,但是由于产品上面一而再再而三的修改功能、修改方向,最坑爹的事情是没有功能定义的情况下得去照搬别的产品的功能。悲剧ing。于是这个计划一直没有实施。

哎,技术架构方案的选择真得是件麻烦的事情,得遇见今后一段时间的业务变更与增长,又要考虑现有资源。这样做出合理的,可以实现的方案。我们那架构师也辛苦,总得和老板设计的产品做pk。心力交瘁啊。有时候想想,也迷茫,咱这是做了啥东西?用来干啥?对自己也没多大帮助啊?更坑爹的事情是,把外包那套管理项目的方式折腾过来,每天都是下班时过来给你折腾点东西,杯具ing。产品设计上面没有明确的功能性设计,只是笼统的说,我要这个功能,其他你可以去参考别人怎么做的。你妹啊!

一个ui图,再加个说明,给你,你去设计接口吧。好吧,设计完接口又说,你这样不行,客户端没法调用的,两个接口,怎么可以这样子用,客户端调用很麻烦的,你如果做客户端,你怎么去弄?哎,坑爹啊!

神马浮云啊,没做过产品经理的,把项目管理那套东西搬过来,好吧,你行的。我算是清楚了。我只写代码。

坑爹的产品设计

坑爹的产品设计会造成神马后果,哎,我总算见识了。从没见过时间被扣的这么死的,好吧,你是老板,我认了。也不想想,万一有啥问题要查,要调试,这不还是被打断?

搞项目的,以为看了些别人的产品就来折腾产品设计,好吧,你牛。这么好的一个产品经理摆在那里,不要,成,那你自己弄出点好的事情来啊?一要个功能,就来,你看看别人怎么做的么,你看过么?没吧?那你去看看然后给出一个解决方案来吧?行不?什么时间可以有?明天?

坑爹啊,我一个搞技术的人给你去折腾功能设计方案,你妹啊!一会要查这个问题,一会要做这个事情,我又不是神。行吧行吧,总会有个解决方法的。打工的,我忍了。看吧,看别人怎么做的呗。

好吧,你只要有这个功能,功能怎么样的,你不管。成啊,最后呢?还不是需要实现去改?来回折腾累不累啊?硬要短时间内完成,行啊,那你别介意有什么意外问题啊。又要没有别的问题,又要快、好的实现,但是你有好好的去设计这个功能么?还不就是画了一下uiflow?能解决问题么?坑爹啊!

哎,看别人有的功能,你也想加,ok,可以,你得想想我们这个产品用来做啥的吧?迷茫啊,用来做啥都不知道,亮点呢?好的产品一般都是功能简单,但是都是必须的功能,而且都是能抓住用户粘度的功能。我们呢?有想过么?用户的粘度?

这个总得好好考虑吧?行,我一个码农,不知道说啥,嘿,你说干啥就干啥呗,反正也讲不通,就这样吧!

无觅相关文章插件,快速提升流量