<<上篇 | 首页 | 下篇>>

频次和密度:SNS战争的秘密 - 一个VC的胡思乱想 - 知乎专栏

先不说话,看图:

这是我随手整理的几个社交网络的“DAU比MAU”(日活跃用户除以月活跃用户),日期不一样,不过都尽量找了最新的数据,大家凑合着看。这个数字越接近1,说明用户使用该应用越频繁。仔细看看这些应用的属性,可以简单归纳几个特点:

  1. 需求本身的频次越高,使用频次越高。这个不解释了。
  2. 移动设备用户越多,使用频次越高。这个也不解释了。
  3. 熟人关系链越多,使用频次越高。解释一下:Instagram有一半的社交关系是真实好友。据说Momo的用户其实有相当多的好友关系是手机通讯录好友,而且每天所有消息中,80%的消息是双向关注的好友发的(你小看它了吧?)。

以上的总结基本是废话,因为大家基本已经知道了,这里我想讨论的其实是背后隐藏的战争规律。

好了,我开始抛砖引玉。

战争规律一:社交关系图相似,需求类型不互斥 ==> 高频打低频

我在知乎上曾经回答过一个问题“facebook社交网站会受到诸如微信等新的社交工具的影响吗?”。

我的基本观点是:如果WhatsApp做一个类似微信朋友圈的东西,对Facebook会有巨大的威胁。逻辑如下:

  1. “发朋友圈”的核心体验是“发了以后立即有好友赞或回复”。WhatsApp的“永久在线+高频使用”属性,让它对Facebook有战略优势,如果做了类似朋友圈的东西,用户发了内容可以比Facebook更快地获得更多好友的反馈。
  2. “看朋友圈”的核心体验是“有很多好友发的新鲜、真实而有趣的内容”。基于第1条,WhatsApp的朋友圈的会有更多的好友发内容,而且更加新鲜,而且因为楼主在线概率更高,留言以后更容易和“楼主”形成互动。
  3. 朋友圈的这两个核心体验都对用户密度和使用频次高度依赖,第1条和第2条互相促进,形成良性循环。

不久以后,Facebook就以190亿美金的可怕的价格收购了WhatsApp。其实WhatsApp在美国的渗透率还不是特别高(而且一直没有上线朋友圈的功能,这点我真想不通),否则很可能挺着不卖,和FB好好干一仗。

这第一条规律可以总结为“高频需求能够带来用户密度拉动低频需求”。

战争规律二:社交关系图相似,频次相仿 ==> 原创为王

80后们应该都还记得2008年上演的SNS大战。先是程炳皓的开心网利用熟人社交小游戏(以及MSN通讯录)像火箭一样成了都市白领的“上班开机必刷”的网站。与此同时,陈一舟的千橡集团收购了校内网(后改名人人网),在校园市场送鸡腿,在白领市场用假开心网阻击真开心网的扩张,而且一下子融了4亿多美金。

面对激烈的竞争,开心网继续开发好玩的社交游戏,并且开发了神奇的“转帖”功能,一时间来势汹汹,占领了用户大量的时间。人人网一开始扛着还是坚持以UGC为主,后来还是决定跟进。“转帖”和各种社交游戏的消息渐渐占据了用户的主feed(用户信息流)。虽然看上去用户使用时长和PV数在增加,但是慢慢地用户行为发生了变化:发照片发状态也开始没人赞也没人留言了,因为好友们的feed里面的转帖和社交游戏的消息淹没了用户原创的内容。

这时一个神奇的网站“新浪微博”伴随着智能手机出现了。自己关注的名人以及好友发布的新鲜信息流和即时的社交交互体验,迅速PK掉开心网和人人网的充满了转帖的信息流。一时间微博红透了半边天,去餐馆吃饭都要打开微博“互粉”一下,吃到美食买了漂亮衣服也都先到新浪微博上给朋友们汇报一下。当时新浪微博气势之汹涌,腾讯砸重金加无限流量支持“腾讯微博”,还是扛不过它。

后来,随着微博feed被公众账号和段子手(甚至机器人)占领,活跃度大幅下降。有人也许要说,微博活跃度下降是因为微信朋友圈的替代效应,我也同意,不过我想说两点。1)微博的社交图谱和微信不一样,其实有独特存在价值,其实不应该这么快被替代(Twitter比它活的好多了),是它自己的feed衰老过快才让竞争对手有可乘之机;2)微信朋友圈里也在feed里引入了大量公众账号和转帖,其实是伤敌一千自损八百(仗着自己有能“无限补血”的高频的通信功能),也许时过境迁,它也要自食其果的。在美国的青少年中,Instagram的使用率已经比Facebook要高了,原因之一就是嫌Facebook的feed里面乱七八糟的东西太多。

这第二条规律可以总结为:基于用户原创内容的交互的需求更“基础”、更“普遍”、更不可替代,而转帖和社交游戏实际上稀释了基于用户原创内容的交互,降低了真实的社交行为的密度,让竞争对手有可乘之机。

战争规律三:社交关系图类似的UGC社区 ==> 内容生产成本越低越好

观察社交网络的变迁,还发现一个现象,就是内容生产成本越低的网站用户越多,使用越频繁。举两个例子:

1) 图片社交。美国以前有个流行的图片社交网站叫Flickr,可流量一直在降。Flickr里面聚集了许多摄影高手用高级摄影器材拍的好照片,可是Instagram让普通人用手机就能拍出还不错的照片出来,不但能洋洋自得,还有好友来点个赞,如果按DAU或者使用时长,Instagram比Flickr高一个数量级。Instagram其实做了大量工作来降低内容生产成本: a) 用方框(容易取景);b) 提供一些滤镜让照片看上去有逼格;c) 用手机就可以拍,随时可以拍。还有个更奇葩的公司Pinterest,连拍照片都省了,用户直接从别的网站扒图片过来“钉”到自己的墙上,就“生产”了内容,还真有人愿意看,不过它是不是个真的SNS其实值得商榷,这里就不展开聊了。

2) 博客和微博(Twitter)。微博出来以后,写博客的人大幅下降。一个重要原因就是写博客门槛太高了,能写高质量博客的人其实不多,而且要隔很久才写一篇,所以基本上只能逛逛网络红人的博客(名人都未必写得好博客)。微博出来以后,不但名人们都能写微博,而且自己认识的朋友也能写出让自己感兴趣的微博。

这第三条规律可以总结为:通过降低内容生产成本可以提高用户关系密度,进而促进内容生产和用户活跃。

总结:频次和密度

其实这些规律背后都是“密度”。补充定义一下“密度”的概念:使用频次,其实是时间维度上的密度,而用户渗透率,代表了用户关系维度上的密度。SNS的核心体验是用户之间的交互,而高密度的用户关系和使用频次能够带来更好的社交交互体验。

最后,按惯例给创业者提几个建议:

1)社交类的创业者,请关注你们自己的DAU/MAU,不断思考需求够不够刚性,有没有保护用户原创内容的积极性,高质量内容的生产成本是不是还可以继续降低,熟人关系比例是不是还可以继续提高?(顺便说一句,如果计划未来利用通讯录引入熟人关系的计划,那么一开始就要搞用户的手机号,要搞手机号,要搞手机号......)

2)许多工具型的应用的开发者想转社区,建议先仔细想想你的用户是否有真实的社交需求,然后再从社交关系和使用频次的不同维度来计算一下自己的有效用户密度,如果需求不强烈或者密度不够,还是别把宝押在这上面。

阅读全文……

标签 :

java Jackson 库操作 json 的基本演示 - joyous的专栏 - 博客频道 - CSDN.NET

核心库下载地址

http://repo1.maven.org/maven2/com/fasterxml/jackson/core/

jackson-annotations-2.2.2.jar

jackson-core-2.2.2.jar

jackson-databind-2.2.2.jar

 

文件类型支持模块

http://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/

jackson-dataformat-xml-2.2.2.jar

 

导入库

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonParseException;

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.  * Map 转换为 json 
  3.  */  
  4. public static void MyTest01()  
  5. {  
  6.   Map<String, String> hashMap = new HashMap<String, String>();  
  7.   hashMap.put("name""zhang");  
  8.   hashMap.put("sex""1");  
  9.   hashMap.put("login""Jack");  
  10.   hashMap.put("password""123abc");  
  11.   
  12.   try  
  13.   {  
  14.     ObjectMapper objectMapper = new ObjectMapper();  
  15.     String userMapJson = objectMapper.writeValueAsString(hashMap);  
  16.   
  17.     JsonNode node = objectMapper.readTree(userMapJson);  
  18.   
  19.     // 输出结果转意,输出正确的信息  
  20.     System.out.println(node.get("password").asText());  
  21.     // 输出不转意,输出结果会包含"",这是不正确的,除非作为json传递,如果是输出结果值,必须如上一行的操作  
  22.     System.out.println(node.get("name"));  
  23.   }  
  24.   catch (IOException e)  
  25.   {  
  26.   }  
  27. }  

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.    * 解析 json 格式字符串 
  3.    */  
  4.   public static void MyTest03()  
  5.   {  
  6.     try  
  7.     {  
  8.       String str = "{\"data\":{\"birth_day\":7,\"birth_month\":6},\"errcode\":0,\"msg\":\"ok\",\"ret\":0}";  
  9.   
  10.       ObjectMapper mapper = new ObjectMapper();  
  11.       JsonNode root = mapper.readTree(str);  
  12.   
  13.       JsonNode data = root.path("data");  
  14.   
  15.       JsonNode birth_day = data.path("birth_day");  
  16.       System.out.println(birth_day.asInt());  
  17.         
  18.       JsonNode birth_month = data.path("birth_month");  
  19.       System.out.println(birth_month.asInt());  
  20.   
  21.       JsonNode msg = root.path("msg");  
  22.       System.out.println(msg.textValue());  
  23.     }  
  24.     catch (IOException e)  
  25.     {  
  26.     }  
  27.   }  

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.    * json 直接提取 值 
  3.    */  
  4.   public static void MyTest05()  
  5.   {  
  6.     try  
  7.     {  
  8.       // 演示字符串  
  9.       String str = "{\"data\":{\"hasnext\":0,\"info\":[{\"id\":\"288206077664983\",\"timestamp\":1371052476},{\"id\":\"186983078111768\",\"timestamp\":1370944068},{\"id\":\"297031120529307\",\"timestamp\":1370751789},{\"id\":\"273831022294863\",\"timestamp\":1369994812}],\"timestamp\":1374562897,\"totalnum\":422},\"errcode\":0,\"msg\":\"ok\",\"ret\":0,\"seqid\":5903702688915195270}";  
  10.   
  11.       ObjectMapper mapper = new ObjectMapper();  
  12.       JsonNode root = mapper.readTree(str);  
  13.   
  14.       // 提取 data  
  15.       JsonNode data = root.path("data");  
  16.       // 提取 info  
  17.       JsonNode info = data.path("info");  
  18.   
  19.       System.out.println(info.size());  
  20.   
  21.       // 得到 info 的第 0 个  
  22.       JsonNode item = info.get(0);  
  23.       System.out.println(item.get("id"));  
  24.       System.out.println(item.get("timestamp"));  
  25.   
  26.       // 得到 info 的第 2 个  
  27.       item = info.get(2);  
  28.       System.out.println(item.get("id"));  
  29.       System.out.println(item.get("timestamp"));  
  30.   
  31.       // 遍历 info 内的 array  
  32.       if (info.isArray())  
  33.       {  
  34.         for (JsonNode objNode : info)  
  35.         {  
  36.           System.out.println(objNode);  
  37.         }  
  38.       }  
  39.   
  40.     }  
  41.     catch (Exception e)  
  42.     {  
  43.   
  44.     }  
  45.   }  

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.  * 创建一个 json,并向该 json 添加内容 
  3.  */  
  4. public static void MyTest07()  
  5. {  
  6.   try  
  7.   {  
  8.     ObjectMapper mapper = new ObjectMapper();  
  9.     ObjectNode root1 = mapper.createObjectNode();  
  10.   
  11.     root1.put("nodekey1"1);  
  12.     root1.put("nodekey2"2);  
  13.   
  14.     System.out.println(root1.toString());  
  15.   
  16.   //Create the root node  
  17.     ObjectNode root = mapper.createObjectNode ();  
  18.     //Create a child node  
  19.     ObjectNode node1 = mapper.createObjectNode ();  
  20.     node1.put ("nodekey1"1);  
  21.     node1.put ("nodekey2"2);  
  22.     //Bind the child nodes  
  23.     root.put ("child", node1);  
  24.     //Array of nodes  
  25.     ArrayNode arrayNode = mapper.createArrayNode ();  
  26.     arrayNode.add (node1);  
  27.     arrayNode.add (1);  
  28.     //Bind array node  
  29.     root.put ("arraynode", arrayNode);  
  30.   
  31.     System.out.println (mapper.writeValueAsString (root));  
  32.   }  
  33.   catch (Exception e)  
  34.   {  
  35.   
  36.   }  
  37. }  


 

参考资料

http://wiki.fasterxml.com/JacksonInFiveMinutes

阅读全文……

标签 : ,

如何用Mule创建动态的HTTP代理服务

 

目的

引入动态http代理的主要目的是在加入新的http代理而不需要重启Mule代理服务。注意,要真正的动态代理,需要注入实现了路径与服务器地址映射的检索服务的Spring的Bean,在enricher位置通过groovy获得Bean的实例进行调用。

 

实例程序的限制:

这个例子程序缺乏生产环境中使用处理:

  • 错误处理
  • 从数据库检索路径的映射配置信息

            这个例子将HTTP相对路径和目标服务器之间的映射写在XML配置文件里。这当然不能允许动态修改代理配置。

  • 支持更多的HTTP方法

             只支持HTTP get和post方法

  • 处理的HTTP参数。

             实例程序没考虑HTTP参数但这些都被认为是在HTTP相对路径的一部分。

  • 支持HTTPS。

 

为了能够有一个服务代理,一个简单的SOAP问候服务使用一个Mule配置文件和一个Java类实现。

Mule配置包含以下配置:

01.<?xml version="1.0" encoding="UTF-8"?>
02.<mule
09.xsi:schemaLocation="
14. 
15.<spring:beans>
16.<spring:bean id="helloService" class="com.ivan.mule.dynamichttpproxy.HelloService"/>
17.</spring:beans>
18. 
19.<flow name="GreetingFlow">
20.<inbound-endpoint address="http://localhost:8182/services/GreetingService"
21.exchange-pattern="request-response"/>
22. 
23.<cxf:jaxws-service serviceClass="com.ivan.mule.dynamichttpproxy.HelloService"/>
24.<component>
25.<spring-object bean="helloService"/>
26.</component>
27.</flow>
28.</mule>

 

服务类:

01.package com.ivan.mule.dynamichttpproxy;
02. 
03.import java.util.Date;
04.import javax.jws.WebParam;
05.import javax.jws.WebResult;
06.import javax.jws.WebService;
07. 
08./**
09.* SOAP web service endpoint implementation class that implements
10.* a service that extends greetings.
11.*
12.* @author Ivan Krizsan
13.*/
14.@WebService
15.public class HelloService {
16./**
17.* Greets the person with the supplied name.
18.*
19.* @param inName Name of person to greet.
20.* @return Greeting.
21.*/
22.@WebResult(name = "greeting")
23.public String greet(@WebParam(name = "inName") final String inName) {
24.return "Hello " + inName + ", the time is now " + new Date();
25.}
26.}

 

 

服务器信息Bean,用于存储原始HTTP请求路径与对应目标服务器ip端口的映射信息:

01.package com.ivan.mule.dynamichttpproxy;
02. 
03./**
04.* Holds information about a server which to forward requests to.
05.*
06.* @author Ivan Krizsan
07.*/
08.public class ServerInformationBean {
09.private String serverAddress;
10.private String serverPort;
11.private String serverName;
12. 
13./**
14.* Creates an instance holding information about a server with supplied
15.* address, port and name.
16.*
17.* @param inServerAddress
18.* @param inServerPort
19.* @param inServerName
20.*/
21.public ServerInformationBean(final String inServerAddress,
22.final String inServerPort, final String inServerName) {
23.serverAddress = inServerAddress;
24.serverPort = inServerPort;
25.serverName = inServerName;
26.}
27. 
28.public String getServerAddress() {
29.return serverAddress;
30.}
31. 
32.public String getServerPort() {
33.return serverPort;
34.}
35. 
36.public String getServerName() {
37.return serverName;
38.}
39.}

 

动态HTTP 代理Mule配置:

001.<?xml version="1.0" encoding="UTF-8"?>
002.<!--
003.The dynamic HTTP proxy Mule configuration file.
004. 
005.Author: Ivan Krizsan
006.-->
015.version="CE-3.4.0"
016.xsi:schemaLocation="
 
022. 
023.<spring:beans>
024.<!--
025.Mappings from path to server represented by a hash map.
026.A map has been choosen to limit the scope of this example.
027.Storing data about mappings between path to server in a database
028.will enable runtime modifications to the mapping data without
029.having to stop and restart the general proxy Mule application.
030.-->
031.<util:map id="pathToServerAndPortMapping" map-class="java.util.HashMap">
032.<!-- Entry for MyServer. -->
033.<spring:entry key="services/GreetingService">
034.<spring:bean class="com.ivan.mule.dynamichttpproxy.ServerInformationBean">
035.<spring:constructor-arg value="localhost"/>
036.<spring:constructor-arg value="8182"/>
037.<spring:constructor-arg value="MyServer"/>
038.</spring:bean>
039.</spring:entry>
040.<!-- Entry for SomeOtherServer. -->
041.<spring:entry key="services/GreetingService?wsdl">
042.<spring:bean class="com.ivan.mule.dynamichttpproxy.ServerInformationBean">
043.<spring:constructor-arg value="127.0.0.1"/>
044.<spring:constructor-arg value="8182"/>
045.<spring:constructor-arg value="SomeOtherServer"/>
046.</spring:bean>
047.</spring:entry>
048.</util:map>
049.</spring:beans>
050. 
051.<flow name="HTTPGeneralProxyFlow">
052.<!--
053.Note that if you increase the length of the path to, for instance
054.generalProxy/additionalPath, then the expression determining
055.the outgoing path need to be modified accordingly.
056.Changing the path, without changing its length, require no
057.modification to outgoing path.
058.-->
059.<http:inbound-endpoint
060.exchange-pattern="request-response"
061.host="localhost"
062.port="8981"
063.path="dynamicHttpProxy" doc:name="HTTP Receiver"/>
064. 
065.<!-- Extract outgoing path from received HTTP request. -->
066.<set-property
067.value="#[org.mule.util.StringUtils.substringAfter(org.mule.util.StringUtils.substringAfter(message.inboundProperties['http.request'], '/'), '/')]"
068.propertyName="outboundPath"
069.doc:name="Extract Outbound Path From Request" />
070. 
071.<logger message="#[string:Outbound path = #[message.outboundProperties['outboundPath']]]"level="DEBUG"/>
072. 
073.<!--
074.Using the HTTP request path, select which server to forward the request to.
075.Note that there should be some kind of error handling in case there is no server for the current path.
076.Error handling has been omitted in this example.
077.-->
078.<enricher target="#[variable:outboundServer]">
079.<scripting:component doc:name="Groovy">
080.<!--
081.If storing mapping data in a database, this Groovy script
082.should be replaced with a database query.
083.-->
084.<scripting:script engine="Groovy">
085.<![CDATA[
086.def theMap = muleContext.getRegistry().lookupObject("pathToServerAndPortMapping")
087.def String theOutboundPath = message.getOutboundProperty("outboundPath")
088.def theServerBean = theMap[theOutboundPath]
089.theServerBean
090.]]>
091.</scripting:script>
092.</scripting:component>
093.</enricher>
094. 
095.<logger
096.message="#[string:Server address = #[groovy:message.getInvocationProperty('outboundServer').serverAddress]]"
097.level="DEBUG"/>
098.<logger
099.message="#[string:Server port = #[groovy:message.getInvocationProperty('outboundServer').serverPort]]"
100.level="DEBUG"/>
101.<logger
102.message="#[string:Server name = #[groovy:message.getInvocationProperty('outboundServer').serverName]]"
103.level="DEBUG"/>
104. 
105.<!-- Log the request and its metadata for development purposes, -->
106.<test:component logMessageDetails="true"/>
107. 
108.<!--
109.Cannot use a MEL expression in the value of the method attribute
110.on the HTTP outbound endpoints so have to revert to this way of
111.selecting HTTP method in the outgoing request.
112.In this example, only support for GET and POST has been implemented.
113.This can of course easily be extended to support additional HTTP
114.verbs as desired.
115.-->
116.<choice doc:name="Choice">
117.<!-- Forward HTTP GET requests. -->
118.<when expression="#[message.inboundProperties['http.method']=='GET']">
119.<http:outbound-endpoint
120.exchange-pattern="request-response"
121.host="#[groovy:message.getInvocationProperty('outboundServer').serverAddress]"
122.port="#[groovy:message.getInvocationProperty('outboundServer').serverPort]"
123.method="GET"
124.path="#[message.outboundProperties['outboundPath']]"
125.doc:name="Send HTTP GET"/>
126.</when>
127.<!-- Forward HTTP POST requests. -->
128.<when expression="#[message.inboundProperties['http.method']=='POST']">
129.<http:outbound-endpoint
130.exchange-pattern="request-response"
131.host="#[groovy:message.getInvocationProperty('outboundServer').serverAddress]"
132.port="#[groovy:message.getInvocationProperty('outboundServer').serverPort]"
133.method="POST"
134.path="#[message.outboundProperties['outboundPath']]"
135.doc:name="Send HTTP POST"/>
136.</when>
137.<!-- If HTTP method not recognized, use GET. -->
138.<otherwise>
139.<http:outbound-endpoint
140.exchange-pattern="request-response"
141.host="#[groovy:message.getInvocationProperty('outboundServer').serverAddress]"
142.port="#[groovy:message.getInvocationProperty('outboundServer').serverPort]"
143.method="GET"
144.path="#[message.outboundProperties['outboundPath']]"
145.doc:name="Default: Send HTTP GET"/>
146.</otherwise>
147.</choice>
148.</flow>
149.</mule>

 

 

注意:

  • 一个名称为“pathToServerAndPortMapping”用Spring XML配置了路径与服务器地址的映射
  • <set-property>元素设置了outboundPath属性的值,outboundPath即服务的相对路径,用于检索目标服务器,并对目标服务器发起http:outbound-endpoint.
  • <enricher>元素部分是使用outboundpath检索对应的ServerInformationBean实例
  • <choice>元素包含了多个outbound HTTP endpoints。

 

启动Mule工程服务后,现在访问真正的目标服务

http://localhost:8182/services/GreetingService?wsdl

应该能看到WSDL文件内容了。通过soapUI访问,应该能收到包含日期和时间的问候了。

 

接下来,通过soapUI访问代理服务

http://localhost:8981/dynamicHttpProxy/services/GreetingService

 

控制台上会打印如下内容:

1.... Outbound path = services/GreetingService
2.... Server address = localhost
3.... Server port = 8182
4.... Server name = MyServer

 

转载请注明原创首发地址:如何用Mule创建动态HTTP代理服务