出家如初,成佛有余

从卓越亚马逊网页404错误想到的

Posted in Uncategorized by chuanliang on 2009/10/30

    前段时间有朋友推荐《经理人财务知识一本通》写得浅显易懂,打算买本看看。直接用google.com搜索“经理人财务知识一本通”,点击卓越亚马逊amazon.cn的链接,没想到竟然报错:

电子商务,用户体验,ued,卓越亚马逊,运营

    点击链接http://amazon.cn/o/ASIN/B002EENM7I/ref=nosim/betteraddons-20. 跳转到amazon.cn的404错误页面:“对不起,您输入的地址页不存在,请尝试其他方法!”错误。直接在http://www.amazon.cn上搜索“经理人财务知识一本通”没有问题,实际地址为http://www.amazon.cn/mn/detailApp?prodid=bkbk959919&source=baidubkbkbk959919sk0。baidu“经理人财务知识一本通 amazon”也存在类似问题。开始以为是amazon.cn改版将图书的url地址改变了,但搜索引擎索引尚未更新,导致404错误引起的。比较了一下amazon.com与amazon.cn的url规范发现amazon.com图书的url规范是http://www.amazon.com/o/ASIN/”,看来应该是amazon.cn升级到amazon.com导致的问题。amazon.cn未来的url应该会采用http://www.amazon.cn/o/ASIN/ 。升级中…。记得十一时候就存在这样的问题,当时也没在意。

    登录amazon.cn后发现原来购买过、收藏过、浏览过的所有商品的地址都类似为“http://amazon.cn/o/ASIN/”,点击查看详情,也报“对不起,您输入的地址页不存在,请尝试其他方法!”错误。

    觉得卓越亚马逊的运营水平也真够差的,这样的问题这么久也没有人发现。于是打算告知一下,只不过找了半天没发现投诉的入口。不像当当直接在首页的下方有“顾客投诉”的入口。后来终于找到了投诉入口:帮助->联系我们->邮件联系,每一步都极其需要好的眼神,想为人民服务都不容易。用户体验啊,用户体验。

    仔细一想,发觉思考这样的小问题其实还蛮有趣味的。在小的电子商务网站运营过程中,怎样才能够避免出现类似的问题呢?

    1、运营流程细节执行 :在遇到这样的问题时候,第一反应是运营流程的问题。只要运营人员或SEO推广人员每天执行搜索一下搜索操作就可以发现问题;只要开发测试能够再测试充分一点也可以避免此问题。因此可以通过完善运营流程来保证这一点。当然对于大部分小公司而言,对于运营中涉及的方方面面都要以制度方式来保证,那管理成本也太高了。最理想的莫过于员工们在所谓的企业文化的感召下以xxxx主人公精神来积极主动做这些事情。运营的重要价值之一就是对制度流程持续的完善。   

    2、运营工具设计:一般网站都有专门的页面来处理http 404错误,记得有很多人总结了“xx个最具有个性的404错误页面”。以前一般觉得404错误页面的价值只不过是用户体验而已。其实404错误在运营中可以发挥更多的价值。在web analytics分析中,可以重点加入对源自搜索引擎、用户点击、用户输入等操作的404错误分析,将这些分析成果作为工具提供给运营人员,降低运营人员每日手工检查的劳动量,同时也降低通过制度来约束所带来的监管及考核成本。

    3、社区互动的价值:通过营造良好的社区氛围,让用户成为网站义务的运营及测试人员,第一时间反馈系统的各种问题,降低企业运营的成本。

    4、问题是最好的老师:404错误->运营流程完善->产品设计的运营工具设计->社区互动,每一个环节发挥作用就可以避免问题的发生。但如果其中一个环节真的起了作用,那么可能更多深层的问题反而被掩埋了,我们也失去了思考和前进的动力,也失去了持续改进的机会。所以有的时候,当下的风平浪静恰恰蕴含了灾难,问题的及早暴露反而是一种成长机会。

淘宝历史订单的设计

Posted in Uncategorized by chuanliang on 2009/10/26

    2009-9-25日上淘宝,打算在2009年初买过宝宝用品的一家店铺里再买点东西,于是想从历史订单里面把店铺地址找到。登陆后,在“已买到的宝贝”的“所有订单”里发现只有到2009-7-24日以后购买的东西,也即只有近3个月的交易数据。在上方的查询区中修改“成交时间”的查询区间为2009-01-01到2009-09-25,在“所有订单”中查询出来的数据仍然只是2009-7-24到2009-09-25间的数据。切换到“历史订单”tab,也只有2009-06-27至2009-07-26日之间的订单。偶然发现上方查询区的“成交时间”变为“2009-06-27到2009-07-26”,也即切换到“历史订单”tab后缺省查询的只是一个月的历史数据。于是在切换到“历史订单”后再修改“成交时间”为“2009-01-01到2009-07-01,能够得到想要的历史数据。 倒腾了半天才搞明白淘宝的历史订单的逻辑。晕,不是一般的晕;乱,不是一般的乱。

    淘宝,历史订单,ucd,ued,用户体验,交互设计,支付宝

    这算是一个典型的用户体验问题,很奇怪淘宝那些整天满口UED、UCD的高手们竟然对于这样典型的问题竟然视而不见。之所以乱,我觉得至少有两方面的原因:

    1、界面术语的混乱:对于“所有订单”、“历史订单”等术语由于没有明确定义及说明,存在很大的歧义。

    像我理解“所有订单”就认为是我有交易历史以来的所有记录,而淘宝工程师们的定义应该是“近三个月的交易数据”。对于这些关键术语的解释在“查看帮助”中也没有找到答案。“所有订单”应该包含了诸如“等待付款“、“等待确认收货”、“退款中”、“需要评价”、“成功的订单”这样的订单。从淘宝目前的逻辑来看这些订单都属于“非历史订单”的类别,历史订单与其他们并列为同一层级的tab并不是很恰当,如果能够为“历史订单”提供单独查询入口,同时在界面上再清晰解释一下“历史订单”的时间范围就能避免很多问题。

    特意看了一下支付宝的订单查询页面设计:

淘宝,历史订单,ucd,ued,用户体验,交互设计,支付宝

    支付宝没有像淘宝中那样将历史订单和非历史订单并列在同一层次上,在其交易管理中,明确提供了历史交易数据的查询入口链接。很明显就比淘宝的清晰多了。

    2、操作模式的混乱:

    目前淘宝订单的页面操作模式是:页面上端是查询条件,下面是多tab的查询结果。按理说来,一般用户的操作习惯是“点击查询条件”->“查看查询结果”,这样上端的查询条件本质上只需要一个tab就能够满足用户日常查询的需要。预计是淘宝的工程师们怕用户没有明白上述查询条件所提供的强大功能,于是又将上述查询条件按结果进行分类罗列。对于在3个月内的数据,可以根据上端的查询条件来自动切换到对应的tab上,但对于历史订单的查询用户首先要切换到历史订单的tab后再点击上面的查询条件才能够得到结果。

    同时由于历史交易和非历史交易数据混杂在一起,用户输入的“成交时间”跨度可能涵盖3个月前和3个月内的交易数据,导致用户输入的查询条件可能没有查询结果。例如我输入2009-01-01到2009-10-25,同时交易状态为“交易成功”的订单,查询结果自动切换到了“成功的订单”,但查询结果只有2009-07-24以来的成功订单。

    操作模式混乱的根本原因还是因为将历史数据和非历史数据的查询混在一起,其实在界面设计或技术实现上稍加调整,就能避免如此混乱的状态。按照淘宝目前的设计思路大致有如下一些方案可供选择:

    a、提供类似支付宝的历史订单查询入口,这样从查询入口来控制对不同数据库的查询。

    b、在“成交时间”输入上控制用户输入时间跨度,避免跨时间段(3个月前后3个月后)查询。

    c、不区分历史归档库和非历史归档库,后台查询时候通过统一的DAL(Data-Access-Layer)层来聚合不同数据源的数据后再展现给用户。 很奇怪淘宝自己的Amoeba 项目其实就非常适合这样的场合,不知为何弃而不用。

   

Row-Oriented Database 、Column-Oriented Database 、Key-Value Store Database 、Document-Oriented Database

Posted in Uncategorized by chuanliang on 2009/10/04

    在研究Key-Value Database数据库时候,不可避免地遇见了几种类型的数据库划分方式:Row-Oriented Database 、Column-Oriented Database 、Key-Value Database 、Document-Oriented Database,简单总结一下。

1、Row-Oriented Database:

    行导向的数据库实际上就是传统的关系数据库(RDBMS),关系数据库的数据是按行来存储的,即把一条记录的所有属性(列)存储在一起。传统关系数据库的典型应用场合是OLTP(On-Line Transaction Processing )领域,这些领域以事务处理为主,包括数据的增加、删除、修改、查询。

key-value store,Row-Oriented Database ,Column-Oriented Database,Document-Oriented Database,行数据库,列数据库,文档数据库

 

2、Column-Oriented Database:

    列导向的数据库的典型应用场合是OLAP(On-Line Analysis Processing),因此以列导向的架构设计非常适合于数据仓库的应用。对于大多数数据仓库的分析场景中,实际关注的数据很多时候都只有那么一列或者少数几列的数据。所以在以列为导向的设计中,大部分的分析查询都只需要读取某一个(或者几个)表的几列,而不需要像传统以行为导向的数据库那样需要扫描整个表的数据,这两者IO量的差距是非常大的。

    要理解列导向的数据库可以参考下图(摘自What’s a Column-Oriented DBMS?)。

key-value store,Row-Oriented Database ,Column-Oriented Database,Document-Oriented Database,行数据库,列数据库,文档数据库

    参考文章及Wikipedia的划分方法,常见的Column-Oriented Database如下(不一定准确,仅供参考)

 

3、Column-Oriented Database VS. Row-Oriented Database

key-value store,Row-Oriented Database ,Column-Oriented Database,Document-Oriented Database,行数据库,列数据库,文档数据库

 

OLTP, OLAP,key-value store,Row-Oriented Database ,Column-Oriented Database,Document-Oriented Database,行数据库,列数据库,文档数据库

4、Key-Value Store Database

    在distributed key-value store 杂思 对一些常见Key-Value Store数据库进行了总结,其实精确来说,不应当都叫Key-Value Store Database,而应当叫做Anti-RDBMS或NOSQL Database。

5、Document-Oriented Database

    文档数据库一般用于存储半结构化的数据,在文档数据库中文档是处理信息的基本单位。文档数据库允许创建许多不同类型的非结构化的或任意格式的字段。文档数据库一般采用XML、YAML 、JSON作为存储协议。由于文档数据库中的文档也需要一个唯一的key来标识文档(当然key可以是JSON、XML结构),文档的内容可以看做value,因此一般把文档数据库也归到Key-Value Store Database中,当然所有的XML数据库其实也可以归成文档数据库。

    文档数据库很适合CMS、知识库、OA系统,用于存储新闻稿件、电子邮件、书籍、Web页面、多媒体文件等半结构化的数据。   

    除了Lotus Notes 、Amazon SimpleDB 这样的商用文档数据库外,常见的开源文档数据库:

6、参考文档

    http://ronaldbradford.com/data-store-products/

    http://internetmindmap.com/database_software

    http://en.wikipedia.org/wiki/Column-oriented_DBMS

    http://en.wikipedia.org/wiki/Document-oriented_database

 

Consistent Hashing算法

Posted in Uncategorized by chuanliang on 2009/10/02

    在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括:  轮循算法(Round Robin)、哈希算法(HASH)、最少连接算法(Least Connection)、响应速度算法(Response Time)、加权法(Weighted )等。其中哈希算法是最为常用的算法.

    典型的应用场景是: 有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均分发到每台服务器上,每台机器负责1/N的服务。

    常用的算法是对hash结果取余数 (hash() mod N):对机器编号从0到N-1,按照自定义的hash()算法,对每个请求的hash()值按N取模,得到余数i,然后将请求分发到编号为i的机器。但这样的算法方法存在致命问题,如果某一台机器宕机,那么应该落在该机器的请求就无法得到正确的处理,这时需要将当掉的服务器从算法从去除,此时候会有(N-1)/N的服务器的缓存数据需要重新进行计算;如果新增一台机器,会有N /(N+1)的服务器的缓存数据需要进行重新计算。对于系统而言,这通常是不可接受的颠簸(因为这意味着大量缓存的失效或者数据需要转移)。那么,如何设计一个负载均衡策略,使得受到影响的请求尽可能的少呢?
    在Memcached、Key-Value Store、Bittorrent DHT、LVS中都采用了Consistent Hashing算法,可以说Consistent Hashing 是分布式系统负载均衡的首选算法。

1、Consistent Hashing算法描述

    下面以Memcached中的Consisten Hashing算法为例说明(参考memcached的分布式算法)。

    由于hash算法结果一般为unsigned int型,因此对于hash函数的结果应该均匀分布在[0,232-1]间,如果我们把一个圆环用232  个点来进行均匀切割,首先按照hash(key)函数算出服务器(节点)的哈希值, 并将其分布到0~232的圆上。

    用同样的hash(key)函数求出需要存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器(节点)上。

Consistent hashing,memcached,load balancing,负载均衡,算法,key-value store Consistent Hashing原理示意图

    新增一个节点的时候,只有在圆环上新增节点逆时针方向的第一个节点的数据会受到影响。删除一个节点的时候,只有在圆环上原来删除节点顺时针方向的第一个节点的数据会受到影响,因此通过Consistent Hashing很好地解决了负载均衡中由于新增节点、删除节点引起的hash值颠簸问题。

Consistent hashing,memcached,load balancing,负载均衡,算法,key-value store Consistent Hashing添加服务器示意图

    虚拟节点(virtual nodes):之所以要引进虚拟节点是因为在服务器(节点)数较少的情况下(例如只有3台服务器),通过hash(key)算出节点的哈希值在圆环上并不是均匀分布的(稀疏的),仍然会出现各节点负载不均衡的问题。虚拟节点可以认为是实际节点的复制品(replicas),本质上与实际节点实际上是一样的(key并不相同)。引入虚拟节点后,通过将每个实际的服务器(节点)数按照一定的比例(例如200倍)扩大后并计算其hash(key)值以均匀分布到圆环上。在进行负载均衡时候,落到虚拟节点的哈希值实际就落到了实际的节点上。由于所有的实际节点是按照相同的比例复制成虚拟节点的,因此解决了节点数较少的情况下哈希值在圆环上均匀分布的问题。

Consistent hashing,memcached,load balancing,负载均衡,算法,key-value store

虚拟节点对Consistent Hashing结果的影响

    从上图可以看出,在节点数为10个的情况下,每个实际节点的虚拟节点数为实际节点的100-200倍的时候,结果还是很均衡的。

2、Consistent Hashing算法实现:

    文章Consistent Hashing中描述了Consistent Hashing的Java实现,很简洁。

import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;

public class ConsistentHash<T> {

 private final HashFunction hashFunction;
 private final int numberOfReplicas;
 private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();

 public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
     Collection<T> nodes) {
   this.hashFunction = hashFunction;
   this.numberOfReplicas = numberOfReplicas;

   for (T node : nodes) {
     add(node);
   }
 }

 public void add(T node) {
   for (int i = 0; i < numberOfReplicas; i++) {
     circle.put(hashFunction.hash(node.toString() + i), node);
   }
 }

 public void remove(T node) {
   for (int i = 0; i < numberOfReplicas; i++) {
     circle.remove(hashFunction.hash(node.toString() + i));
   }
 }

 public T get(Object key) {
   if (circle.isEmpty()) {
     return null;
   }
   int hash = hashFunction.hash(key);
   if (!circle.containsKey(hash)) {
     SortedMap<Integer, T> tailMap = circle.tailMap(hash);
     hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
   }
   return circle.get(hash);
 }

}

文章Consistent hashing implemented simply in Python描述了Consistent Hashing算法的python 实现

 

3、参考文档

    http://weblogs.java.net/blog/2007/11/27/consistent-hashing
    http://michaelnielsen.org/blog/consistent-hashing/
    http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/

    http://tech.idv2.com/2008/07/24/memcached-004/ 

    http://amix.dk/blog/viewEntry/19367

    http://amix.dk/blog/viewEntry/19369 

    http://www.javaworld.com/javaworld/jw-10-2008/jw-10-load-balancing-1.html