.NET技术+25台服务器怎样支撑世界第54大网站

英文原文:StackOverflow Update: 560M Pageviews A Month, 25 Servers, And It’s All About Performance

StackOverflow 是一个 IT 技术问答网站,用户可以在网站上提交和回答问题。当下的 StackOverflow 已拥有 400 万个用户,4000 万个回答,月 PV5.6 亿,世界排行第 54。然而值得关注的是,支撑他们网站的全部服务器只有 25 台,并且都保持着非常低的资源使用率,这是一场高有效性、负载均衡、缓存、数据库、搜索及高效代码上的较量。近日,High Scalability 创始人 Todd Hoff 根据 Marco Cecconi 的演讲视频“ The architecture of StackOverflow”以及 Nick Craver 的博文“What it takes to run Stack Overflow”总结了 StackOverflow 的成功原因。

意料之中,也是意料之外,Stack Overflow 仍然重度使用着微软的产品。他们认为既然微软的基础设施可以满足需求,又足够便宜,那么没有什么理由去做根本上的改变。而在需要的地方,他们同样使用了 Linux。究其根本,一切都是为了性能。

另一个值得关注的地方是,Stack Overflow 仍然使用着纵向扩展策略,没有使用云。他们使用了 384GB 的内存和 2TB 的 SSD 来支撑 SQL Servers,如果使用 AWS 的话,花费可想而知。没有使用云的另一个原因是 Stack Overflow 认为云会一定程度上的降低性能,同时也会给优化和排查系统问题增加难度。此外,他们的架构也并不需要横向扩展。峰值期间是横向扩展的杀手级应用场景,然而 他们有着丰富的系统调整经验去应对。该公司仍然坚持着 Jeff Atwood 的名言——硬件永远比程序员便宜。

Marco Ceccon 曾提到,在谈及系统时,有一件事情必须首先弄明白——需要解决问题的类型。首先,从简单方面着手,StackExchange 究竟是用来做什么的——首先是一些主题,然后围绕这些主题建立社区,最后就形成了这个令人敬佩的问答网站。

其次则是规模相关。StackExchange 在飞速增长,需要处理大量的数据传输,那么这些都是如何完成的,特别是只使用了 25 台服务器,下面一起追根揭底:

状态

  • StackExchange 拥有 110 个站点,以每个月 3 到 4 个的速度增长。
  • 400 万用户
  • 800 万问题
  • 4000 万答案
  • 世界排名 54 位
  • 每年增长 100%
  • 月 PV 5.6 亿万
  • 大多数工作日期间峰值为 2600 到 3000 请求每秒,作为一个编程相关网站,一般情况下工作日的请求都会高于周末
  • 25 台服务器
  • SSD 中储存了 2TB 的 SQL 数据
  • 每个 web server 都配置了 2 个 320G 的 SSD,使用 RAID 1
  • 每个 ElasticSearch 主机都配备了 300GB 的机械硬盘,同时也使用了 SSD
  • Stack Overflow 的读写比是 40:60
  • DB Server 的平均 CPU 利用率是 10%
  • 11 个 web server,使用 IIS
  • 2 个负载均衡器,1 个活跃,使用 HAProxy
  • 4 个活跃的数据库节点,使用 MS SQL
  • 3 台实现了 tag engine 的应用程序服务器,所有搜索都通过 tag
  • 3 台服务器通过 ElasticSearch 做搜索
  • 2 台使用了 Redis 的服务器支撑分布式缓存和消息
  • 2 台 Networks(Nexus 5596 + Fabric Extenders)
  • 2 Cisco 5525-X ASAs
  • 2 Cisco 3945 Routers
  • 主要服务 Stack Exchange API 的 2 个只读 SQL Servers
  • VM 用于部署、域控制器、监控、运维数据库等场合

平台

  • ElasticSearch
  • Redis
  • HAProxy
  • MS SQL
  • Opserver
  • TeamCity
  • Jil——Fast .NET JSON Serializer,建立在 Sigil 之上
  • Dapper——微型的 ORM

UI

  • UI 拥有一个信息收件箱,用于新徽章获得、用户发送信息、重大事件发生时的信息收取,使用 WebSockets 实现,并通过 Redis 支撑。
  • 搜索箱通过 ElasticSearch 实现,使用了一个 REST 接口。
  • 因为用户提出问题的频率很高,因此很难显示最新问题,每秒都会有新的问题产生,从而这里需要开发一个关注用户行为模式的算法,只给用户显示感兴趣的问题。它使用了基于 Tag 的复杂查询,这也是开发独立 Tag Engine 的原因。
  • 服务器端模板用于生成页面。

服务器

  • 25 台服务器并没有满载,CPU 使用率并不高,单计算 SO(Stack Overflow)只需要 5 台服务器。
  • 数据库服务器资源利用率在 10% 左右,除下执行备份时。
  • 为什么会这么低?因为数据库服务器足足拥有 384GB 内存,同时 web server 的 CPU 利用率也只有 10%-15%。
  • 纵向扩展还没有遇到瓶颈。通常情况下,如此流量使用横向扩展大约需要 100 到 300 台服务器。
  • 简单的系统。基于 .Net,只用了 9 个项目,其他系统可能需要 100 个。之所以使用这么少系统是为了追求极限的编译速度,这点需要从系统开始时就进行规划,每台服务器的编译时间大约是 10 秒。
  • 11 万行代码,对比流量来说非常少。
  • 使用这种极简的方式主要基于几个原因。首先,不需要太多测试,因为 Meta.stackoverflow 本来就是一个问题和 bug 讨论社区。其次,Meta.stackoverflow 还是一个软件的测试网站,如果用户发现问题的话,往往会提出并给予解决方案。
  • 纽约数据中心使用的是 Windows 2012,已经向 2012 R2 升级(Oregon 已经完成了升级),Linux 系统使用的是 Centos 6.4。

SSD

  • 默认使用的是 Intel 330(Web 层等)
  • Intel 520 用于中间层写入,比如 Elastic Search
  • 数据层使用 Intel 710 和 S3700
  • 系统同时使用了 RAID 1 和 RAID 10(任何4+ 以上的磁盘都使用 RAID 10)。不畏惧故障发生,即使生产环境中使用了上千块 2.5 英寸 SSD,还没碰到过一块失败的情景。每个模型都使用了 1 个以上的备件,多个磁盘发生故障的情景不在考虑之中。
  • ElasticSearch 在 SSD 上表现的异常出色,因为 SO writes/re-indexes 的操作非常频繁。
  • SSD 改变了搜索的使用方式。因为锁的问题,Luncene.net 并不能支撑 SO 的并发负载,因此他们转向了 ElasticSearch。在全 SSD 环境下,并不需要围绕 Binary Reader 建立锁。

高可用性

  • 异地备份——主数据中心位于纽约,备份数据中心在 Oregon。
  • Redis 有两个从节点,SQL 有 2 个备份,Tag Engine 有 3 个节点,elastic 有 3 个节点,冗余一切,并在两个数据中心同时存在。
  • Nginx 是用于 SSL,终止 SSL 时转换使用 HAProxy。
  • 并不是主从所有,一些临时的数据只会放到缓存中
  • 所有 HTTP 流量发送只占总流量的 77%,还存在 Oregon 数据中心的备份及一些其他的 VPN 流量。这些流量主要由 SQL 和 Redis 备份产生。

数据库

  • MS SQL Server
  • Stack Exchange 为每个网站都设置了数据库,因此 Stack Overflow 有一个、Server Fault 有一个,以此类推。
  • 在纽约的主数据中心,每个集群通常都使用 1 主和 1 只读备份的配置,同时还会在 Oregon 数据中心也设置一个备份。如果是运行的是 Oregon 集群,那么两个在纽约数据中心的备份都会是只读和同步的。
  • 为其他内容准备的数据库。这里还存在一个“网络范围”的数据库,用于储存登陆凭证和聚合数据(大部分是 stackexchange.com 用户文件或者 API)。
  • Careers Stack Overflow、stackexchange.com 和 Area 51 等都拥有自己独立的数据库模式。
  • 模式的变化需要同时提供给所有站点的数据库,它们需要向下兼容,举个例子,如果需要重命名一个列,那么将非常麻烦,这里需要进行多个操作:增加一个新列,添加作用在两个列上的代码,给新列写数据,改变代码让新列有效,移除旧列。
  • 并不需要分片,所有事情通过索引来解决,而且数据体积也没那么大。如果有 filtered indexes 需求,那么为什么不更高效的进行?常见模式只在 DeletionDate = Null 上做索引,其他则通过为枚举指定类型。每项 votes 都设置了 1 个表,比如一张表给 post votes,1 张表给 comment votes。大部分的页面都可以实时渲染,只为匿名用户缓存,因此,不存在缓存更新,只有重查询。
  • Scores 是非规范化的,因此需要经常查询。它只包含 IDs 和 dates,post votes 表格当下大约有 56454478 行,使用索引,大部分的查询都可以在数毫秒内完成。
  • Tag Engine 是完全独立的,这就意味着核心功能并不依赖任何外部应用程序。它是一个巨大的内存结构数组结构,专为 SO 用例优化,并为重负载组合进行预计算。Tag Engine 是个简单的 windows 服务,冗余的运行在多个主机上。CPU 使用率基本上保持在2-5%,3 个主机专门用于冗余,不负责任何负载。如果所有主机同时发生故障,网络服务器将把 Tag Engine 加载到内存中持续运行。
  • 关于 Dapper 无编译器校验查询与传统 ORM 的对比。使用编译器有很多好处,但在运行时仍然会存在 fundamental disconnect 问题。同时更重要的是,由于生成 nasty SQL,通常情况还需要去寻找原始代码,而 Query Hint 和 parameterization 控制等能力的缺乏更让查询优化变得复杂。

编码

  • 流程
  • 大部分程序员都是远程工作,自己选择编码地点
  • 编译非常快
  • 然后运行少量的测试
  • 一旦编译成功,代码即转移至开发交付准备服务器
  • 通过功能开关隐藏新功能
  • 在相同硬件上作为其他站点测试运行
  • 然后转移至 Meta.stackoverflow 测试,每天有上千个程序员在使用,一个很好的测试环境
  • 如果通过则上线,在更广大的社区进行测试
  • 大量使用静态类和方法,为了更简单及更好的性能
  • 编码过程非常简单,因为复杂的部分被打包到库里,这些库被开源和维护。.Net 项目数量很低,因为使用了社区共享的部分代码。
  • 开发者同时使用 2 到 3 个显示器,多个屏幕可以显著提高生产效率。

缓存

  • 缓存一切
  • 5 个等级的缓存
  • 1 级是网络级缓存,缓存在浏览器、CDN 以及代理服务器中。
  • 2 级由 .Net 框架 HttpRuntime.Cache 完成,在每台服务器的内存中。
  • 3 级 Redis,分布式内存键值存储,在多个支撑同一个站点的服务器上共享缓存项。
  • 4 级 SQL Server Cache,整个数据库,所有数据都被放到内存中。
  • 5 级 SSD。通常只在 SQL Server 预热后才生效。
  • 举个例子,每个帮助页面都进行了缓存,访问一个页面的代码非常简单:
  • 使用了静态的方法和类。从 OOP 角度来看确实很糟,但是非常快并有利于简洁编码。
  • 缓存由 Redis 和 Dapper 支撑,一个微型 ORM
  • 为了解决垃圾收集问题,模板中 1 个类只使用 1 个副本,被建立和保存在缓存中。监测一切,包括 GC 操。据统计显示,间接层增加 GC 压力达到了某个程度时会显著的降低性能。
  • CDN Hit 。鉴于查询字符串基于文件内容进行哈希,只在有新建立时才会被再次取出。每天 3000 万到 5000 万 Hit,带宽大约为 300GB 到 600GB。
  • CDN 不是用来应对 CPU 或I/O负载,而是帮助用户更快的获得答案

部署

  • 每天 5 次部署,不去建立过大的应用。主要因为
  • 可以直接的监视性能
  • 尽可能最小化建立,可以工作才是重点
  • 产品建立后再通过强大的脚本拷贝到各个网页层,每个服务器的步骤是:
  • 通过 POST 通知 HAProxy 下架某台服务器
  • 延迟 IIS 结束现有请求(大约 5 秒)
  • 停止网站(通过同一个 PSSession 结束所有下游)
  • Robocopy 文件
  • 开启网站
  • 通过另一个 POST 做 HAProxy Re-enable
  • 几乎所有部署都是通过 puppet 或 DSC,升级通常只是大幅度调整 RAID 阵列并通过 PXE boot 安装,这样做非常快速。

协作

  • 团队
  • SRE (System Reliability Engineering):5 人
  • Core Dev(Q&A site)6-7 人
  • Core Dev Mobile:6 人
  • Careers 团队专门负责 SO Careers 产品开发:7 人
  • Devops 和开发者结合的非常紧密
  • 团队间变化很大
  • 大部分员工远程工作
  • 办公室主要用于销售,Denver 和 London 除外
  • 一切平等,些许偏向纽约工作者,因为面对面有助于工作交流,但是在线工作影响也并不大
  • 对比可以在同一个办公室办公,他们更偏向热爱产品及有才华的工程师,他们可以很好的衡量利弊
  • 许多人因为家庭而选择远程工作,纽约是不错,但是生活并不宽松
  • 办公室设立在曼哈顿,那是个人才的诞生地。数据中心不能太偏,因为经常会涉及升级
  • 打造一个强大团队,偏爱极客。早期的微软就聚集了大量极客,因此他们征服了整个世界
  • Stack Overflow 社区也是个招聘的地点,他们在那寻找热爱编码、乐于助人及热爱交流的人才。

编制预算

  • 预算是项目的基础。钱只花在为新项目建立基础设施上,如此低利用率的 web server 还是 3 年前数据中心建立时购入。

测试

  • 快速迭代和遗弃
  • 许多测试都是发布队伍完成的。开发拥有一个同样的 SQL 服务器,并且运行在相同的 Web 层,因此性能测试并不会糟糕。
  • 非常少的测试。Stack Overflow 并没有进行太多的单元测试,因为他们使用了大量的静态代码,还有一个非常活跃的社区。
  • 基础设施改变。鉴于所有东西都有双份,所以每个旧配置都有备份,并使用了一个快速故障恢复机制。比如,keepalived 可以在负载均衡器中快速回退。
  • 对比定期维护,他们更愿意依赖冗余系统。SQL 备份用一个专门的服务器进行测试,只为了可以重存储。计划做每两个月一次的全数据中心故障恢复,或者使用完全只读的第二数据中心。
  • 每次新功能发布都做单元测试、集成测试盒 UI 测试,这就意味着可以预知输入的产品功能测试后就会推送到孵化网站,即 meta.stackexchange(原 meta.stackoverflow)。

监视/日志

  • 当下正在考虑使用 http://logstash.net/做日志管理,目前使用了一个专门的服务将 syslog UDP 传输到 SQL 数据库中。网页中为计时添加 header,这样就可以通过 HAProxy 来捕获并且融合到 syslog 传输中。
  • Opserver 和 Realog 用于显示测量结果。Realog 是一个日志展示系统,由 Kyle Brandt 和 Matt Jibson 使用 Go 建立。
  • 日志通过 HAProxy 负载均衡器借助 syslog 完成,而不是 IIS,因为其功能比 IIS 更丰富。

关于云

  • 还是老生常谈,硬件永远比开发者和有效率的代码便宜。基于木桶效应,速度肯定受限于某个短板,现有的云服务基本上都存在容量和性能限制。
  • 如果从开始就使用云来建设 SO 说不定也会达到现在的水准。但毫无疑问的是,如果达到同样的性能,使用云的成本将远远高于自建数据中心。

性能至上

  • StackOverflow 是个重度的性能控,主页加载的时间永远控制在 50 毫秒内,当下的响应时间是 28 毫秒。
  • 程序员热衷于降低页面加载时间以及提高用户体验。
  • 每个独立的网络提交都予以计时和记录,这种计量可以弄清楚提升性能需要修改的地方。
  • 如此低资源利用率的主要原因就是高效的代码。web server 的 CPU 平均利用率在5% 到 15% 之间,内存使用为 15.5 GB,网络传输在 20 Mb/s到 40 Mb/s。SQL 服务器的 CPU 使用率在5% 到 10% 之间,内存使用是 365GB,网络传输为 100 Mb/s到 200 Mb/s。这可以带来 3 个好处:给升级留下很大的空间;在严重错误发生时可以保持服务可用;在需要时可以快速回档。

学到的知识

1. 为什么使用 MS 产品的同时还使用 Redis?什么好用用什么,不要做无必要的系统之争,比如 C# 在 Windows 机器上运行最好,我们使用 IIS;Redis 在*nix 机器上可以得到充分发挥,我们使用*nix。

2. Overkill 即策略。平常的利用率并不能代表什么,当某些特定的事情发生时,比如备份、重建等完全可以将资源使用拉满。

3. 坚固的 SSD。所有数据库都建立在 SSD 之上,这样可以获得 0 延时。

4. 了解你的读写负载。

5. 高效的代码意味着更少的主机。只有新项目上线时才会因为特殊需求增加硬件,通常情况下是添加内存,但在此之外,高效的代码就意味着 0 硬件添加。所以经常只讨论两个问题:为存储增加新的 SSD;为新项目增加硬件。

6. 不要害怕定制化。SO 在 Tag 上使用复杂查询,因此专门开发了所需的 Tag Engine。

7. 只做必须做的事情。之所以不需要测试是因为有一个活跃的社区支撑,比如,开发者不用担心出现“Square Wheel”效应,如果开发者可以制作一个更更轻量级的组件,那就替代吧。

8. 注重硬件知识,比如 IL。一些代码使用 IL 而不是C#。聚焦 SQL 查询计划。使用 web server 的内存转储究竟做了些什么。探索,比如为什么一个 split 会产生 2GB 的垃圾。

9. 切勿官僚作风。总有一些新的工具是你需要的,比如,一个编辑器,新版本的 Visual Studio,降低提升过程中的一切阻力。

10. 垃圾回收驱动编程。SO 在减少垃圾回收成本上做了很多努力,跳过类似 TDD 的实践,避免抽象层,使用静态方法。虽然极端,但是确实打造出非常高效的代码。

11. 高效代码的价值远远超出你想象,它可以让硬件跑的更快,降低资源使用,切记让代码更容易被程序员理解。

使用MiniProfiler调试ASP.NET MVC网站性能

MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC的性能分析的小程序。可以对一个页面本身,及该页面通过直接引用、Ajax、Iframe形式访问的其它页面进行监控,监控内容包括数据库内容,并可以显示数据库访问的SQL(支持EF、EF CodeFirst等 )。并且以很友好的方式展现在页面上。

该Profiler的一个特别有用的功能是它与数据库框架的集成。除了.NET原生的 DbConnection类,profiler还内置了对实体框架(Entity Framework)以及LINQ to SQL的支持。任何执行的Step都会包括当时查询的次数和所花费的时间。为了检测常见的错误,如N+1反模式,profiler将检测仅有参数值存在差 异的多个查询。

MiniProfiler是以Apache License V2.0协议发布的,你可以在NuGet找到。配置及使用可以看这里:http://code.google.com/p/mvc-mini-profiler

为建立快速的网站黄金参考标准,雅虎2007年为网站提高速度的13个简易规则

mvcminiprofiler

Stack Overflow 用MVC Mini Profiler来促进开源,而在把每一页的右上角服务器渲染时间的简单行来迫使我们解决我们所有的性能衰退和遗漏。如果你在使用.NET开发应用,一定要使用上这个工具。

包括以下核心组件:

  • MiniProfiler
  • MiniProfiler.EntityFramework

如何安装?

一、环境准备
  • Visual Studio 2010
  • ASP.NET MVC项目
  • 如果需要调试EF,建议升级到Entity Framework 4.2
二、安装

推荐使用NuGet方式进行安装,参考文章《使用 NuGet 管理项目库

  • 第一步:在引用上右键选择“Manage NuGet Packages”
  • 第二步:Online搜索miniprofiler

image

MiniProfiler、MiniProfiler.EF、MiniProfiler.MVC3,同时会自动安装依赖组件:WebActivator, 同时也会自动在项目里面添加代码文件:MiniProfiler.cs

  • 第三步:修改代码使MiniProfiler生效

在global.cs的Application_Start事件里面增加代码: StackExchange.Profiling.MiniProfilerEF.Initialize();
修改View的layout文件,在head区域增加如下代码:@StackExchange.Profiling.MiniProfiler.RenderIncludes()

 

如果安装步骤一切顺利的话,打开站点的时候,就可以在左上角看到页面执行时间了,点开可以看到更详细的信息,如果有SQL的话,还会显示SQL语句信息,非常的方便。 页面上如果有ajax请求,也会同时显示到左上角。如果左上角显示红色提示,则表示可能存在性能问题需要处理:

image

点开SQL部分,可以看到详细的SQL语句

image

标记为duplicate的部分,代表在一次请求当中,重复执行了查询,可以优化。

问题:

1、在结合使用EF 4.3的时候发生如下错误:

Invalid object name ‘dbo.__MigrationHistory’.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at MvcMiniProfiler.Data.ProfiledDbCommand.ExecuteDbDataReader(CommandBehavior behavior) in \mvc-mini-profiler\MvcMiniProfiler\Data\ProfiledDbCommand.cs:line 155
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)

需要在EF 4.3上关闭数据库初始化策略:

public class SettingContext : DbContext
{
static SettingContext()
{
Database.SetInitializer<SettingContext>(null);
}

A simple but effective mini-profiler for .NET and Ruby.

A simple but effective mini-profiler for .NET, Ruby, Go and Node.js.

Where do I get support?

Bugs, Feature Requests, Discussion and KB live at community.miniprofiler.com or on Stack Overflow. View Releases and feel free to submit Pull Requests and Issues on GitHub.

Where is the current Ruby documentation?

See Github and Sams blog post.

What does it profile?

MiniProfiler does not attach itself to every single method call; that would be too invasive and wouldn’t focus on the biggest performance issues. Instead, it provides:

  • An ADO.NET profiler, capable of profiling calls on raw ADO.NET (SQL Server, Oracle, etc), LINQ-to-SQL, EF (including Code First), Lightspeed and a range of other data access scenarios
  • A pragmatic Step instrumentation that you can add to code you want to explicitly profile

Simple. Fast. Pragmatic. Useful.

Getting Started on .Net:

Hook up your application with at least one of the following packages are available on nuget:

PM> Install-Package MiniProfiler

Write out our css and javascript includes just before closing the body tag:

@using StackExchange.Profiling;
<head>
 ..
</head>
<body>
  ...
  @MiniProfiler.RenderIncludes()
</body>

For requests that aren’t being profiled, nothing will be written.

  • Decide when you want to profile; perhaps for local requests, perhaps for special users (developers, admins, test team, whatever).

The sample project starts profiling in Global.asax.cs Application_BeginRequest:

using StackExchange.Profiling;
...    
protected void Application_BeginRequest()
{
    if (Request.IsLocal)
    {
        MiniProfiler.Start();
    } 
}

For the code you want to profile, use (regardless of whether the profiler is null or not):

using StackExchange.Profiling;
...
var profiler = MiniProfiler.Current; // it's ok if this is null
using (profiler.Step("Set page title"))
{
	ViewBag.Title = "Home Page";
}
using (profiler.Step("Doing complex stuff"))
{
	using (profiler.Step("Step A"))
	{ // something more interesting here
		Thread.Sleep(100);
	}
	using (profiler.Step("Step B"))
	{ // and here
		Thread.Sleep(250);
	}
}

You can make this as granular or high-level as you like; passing a MiniProfiler in as an optional argument (defaulting to null) to downstream methods works well.

Stop the profiler; the sample project does this in its Global.asax.cs:

protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}

If all goes well, you’ll see something like this:

demo

Profiler Security

Accessing last profiled request

Profiling information can be viewed for the last profiled request by navigating to

~/mini-profiler-resources/results

This resource is accessible to all by default, so you should assign a predicate delegate toMiniProfiler.Settings.Results_Authorize to restrict access. A good place to do this is inGlobal.asax.cs:

protected void Application_Start(object sender, EventArgs e) 
{
   MiniProfiler.Settings.Results_Authorize = IsUserAllowedToSeeMiniProfilerUI;
}
private bool IsUserAllowedToSeeMiniProfilerUI(HttpRequest httpRequest)
{
    // Implement your own logic for who 
    // should be able to access ~/mini-profiler-resources/results
    var principal = httpRequest.RequestContext.HttpContext.User;
    return principal.IsInRole("Developer");   
}

Accessing last 100 profiled requests

Similarly, profiling information for the last 100 profiled requests can be viewed at

~/mini-profiler-resources/results-index

Access to this resource is disabled by default but this can be changed by assigning a predicate delegate to MiniProfiler.Settings.Results_List_Authorize:

protected void Application_Start(object sender, EventArgs e) 
{
   MiniProfiler.Settings.Results_List_Authorize = IsUserAllowedToSeeMiniProfilerUI;
}
private bool IsUserAllowedToSeeMiniProfilerUI(HttpRequest httpRequest)
{
    // Implement your own logic for who 
    // should be able to access ~/mini-profiler-resources/results-index
    var principal = httpRequest.RequestContext.HttpContext.User;
    return principal.IsInRole("Developer");   
}

In using these profiling resources, it is possible to profile requests for any/all users but restrict access to viewing the results only to certain users.

Database profiling

The profiler includes powerful and comprehensive database profiling capabilities. To enable wrap your database connection with a profiling connection.

The built in database profiler supports any kind of DbConnection. It also supports Entity Framework and Linq-2-SQL.

Usage:

Use a factory to return your connection:

public static DbConnection GetOpenConnection()
{
    var cnn = CreateRealConnection(); // A SqlConnection, SqliteConnection ... or whatever

    // wrap the connection with a profiling connection that tracks timings 
    return new StackExchange.Profiling.Data.ProfiledDbConnection(cnn, MiniProfiler.Current);
}

Various features

(N+1) and duplicate query detection===

The profiler is able to detect and highlight areas where you are executing the same query multiple times with different parameters. This allows you to quickly find queries you may be able to batch.

N+1

What about AJAX?

The profiler is also able to log all ajax calls:

ajax calls

You will get one profile timing per ajax call.

Abandoning a profiler session

Often at the beginning of a session you may not know if a user should or should not be allowed to profile, a common pattern for dealing with such issues is:

protected void Application_BeginRequest()
{
   MvcMiniProfiler.MiniProfiler.Start();  
}
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
  if(!CurrentUserIsAllowedToSeeProfiler())
  {
	MvcMiniProfiler.MiniProfiler.Stop(discardResults: true);
  }
}

See the Global.asax.cs file in the Sample.MVC project for a demonstration of more config options that can be used.

Basic Troubleshooting

MiniProfiler isn’t showing

  • Run through the Getting Started steps again and double-check
  • If your web.config has the setting runAllManagedModulesForAllRequests=false then add the following to your web.config too:
<system.webServer>
  ...
  <handlers>
    <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
  </handlers>
</system.webServer>

Where is MiniProfiler used?

MiniProfiler was designed by the team at Stack Overflow. It is in production use there and on the Stack Exchange family of sites.

Also used at:

(if you are using this in production please let us know and we will add you to the list)

Frequently Asked Questions

If you have other questions, please ask them on Stack Overflow or community.miniprofiler.com.

Dapper使用笔记

基本操作

1,执行查询映射到一个强类型的列表

public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

2,映射到动态类型的列表

public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

3,返回受影响行数

public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null)

4,执行多次

connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)",

new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }

)

5,public static IDataReader ExecuteReader

6,public static T ExecuteScalar

参数化

1,DynamicParameters 集合

创建集合对象,通过Add添加项,然后传递给调用方法

var p = new DynamicParameters();

p.Add("@PriceIds", "123", DbType.String);

输出参数和返回值

var p = new DynamicParameters();

p.Add("@a", 11);

p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);

p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);

cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); 

int b = p.Get<int>("@b");

int c = p.Get<int>("@c"); 

2,匿名类型

connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = 1, Id = 2 });

3,列表:

connection.Query<int>("select id from X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 });

高级

1,映射多个对象

public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(

this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null

)

还有其它更多对象的重载

示例:

var sql = 

@"select * from #Posts p 

left join #Users u on u.Id = p.OwnerId 

Order by p.Id";

var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});

2,返回多个结果

public static GridReader QueryMultiple(

this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null

)

示例:

var sql = 

@"

select * from Customers where CustomerId = @id

select * from Orders where CustomerId = @id

select * from Returns where CustomerId = @id";

using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))

{

var customer = multi.Read<Customer>().Single();

var orders = multi.Read<Order>().ToList();

var returns = multi.Read<Return>().ToList();

} 

存储过程

cnn.Query<User>("spGetUser", new {Id = 1}, commandType: CommandType.StoredProcedure);

使用免费工具WCAT为Web应用程序进行压力测试

原文地址:http://blogs.msdn.com/alikl/archive/2008/03/09/stress-test-asp-net-web-application-with-free-wcat-tool.aspx

步骤:
1.安装WCAT
2.创建配置文件
3.运行测试
4.检查结果

安装WCAT
下载并安装Internet Information Services (IIS) 6.0 Resource Kit Tools,其中包含WCAT。

创建配置文件
有三个文本文件需要创建和配置:
1.script.txt
此文件定义了请求,换句话说就是请求哪些页面和如何请求。下面是一个简单的script.txt示例:

NEW TRANSACTION
classId = 1
NEW REQUEST HTTP
Verb = “GET”
URL = “http:// localhost / BankingShmanking / Default.aspx”

2.distribution.txt
定义了不同请求的权重。例如,我需要我需要在1.aspx上生成2倍于2.aspx的请求,我就会在 此文件中进行描述。只请求一个页面的时候,这个文件是没什么作用的。如下是一个distribution.txt文件的示例(1代表设置classId为 1的请求,50代表50%的请求被分配到了以此classId为标识的请求上):
1 50
3.config.txt
定义了测试间隔和生成多少客户端进行请求Web应用程序,下面是一个config.txt文件的示例:
Warmuptime 5s
Duration 30s
CooldownTime 5s
NumClientMachines 1
NumClientThreads 20

这些文件保存于”C:\Program Files\IIS Resources\WCAT Controller”文件夹。

运行测试

在命令行模式下,进入到”C:\Program Files\IIS Resources\WCAT Controller”文件夹,执行如下命令:
wcctl -c config.txt -d distribution.txt -s script.txt -a localhost
再打开令一个命令行窗口,到C:\Program Files\IIS Resources\WCAT Client文件夹下,执行命令:
wcclient.exe localhost

检查结果
结果会直接显示到屏幕上:


同时在C:\Program Files\IIS Resources\WCAT Controller目录下生成日志文件。

使用WCAT对你的Asp.net进行测试

WCAT – Simple Performance Test Tool for your .NET web app

By 16 Sep 2011

WCAT (Web Capacity Analysis Tool) is a tiny but excellent tool from Microsoft to perform load test your web application on IIS.  This tool enables you to do performance analysis on various scenarios of your web application.  All “perfmon” performance counters (like processor time, private bytes usage,  disk queue length,  total bytes sent or received in network) can be specified for the performance testing.  A performance test is for a business scenario, hence a test needs to include all servers (application server, database server) involved in the business scenario.  This is tool is as handy as possible to perform a load test even on single machine.

Once you specify the scenario in your application (i.e. appropriate URLs), a number of virtual clients from various client machines  will visit the URL with appropriate request data.  Like other performance tools, the following players are involved in a performance testing:

  • Server – machines on which your web application components are running
  • Client – A virtual client on a machine which acts as end-user to visit the URL
  • Controller – This coordinates a test among the virtual clients on various machines.  It also capture and collate the performance counters from appropriate servers.

I’ve created a small ASP.NET MVC3 application with following actions:
* http://localhost/myapp/load/create – to insert a simple “comment” field  value into the database (using POST data COMMENT =  “<value>”)
* http://localhost/myapp/load – to lists all comments

WCAT uses Windows Scripting and you can use JavaScript syntax to define the scenario for your test.  Below is a sample scenario in a file scenario.ubr:

scenario
 {
 name    = "IIS Home Page";

warmup      = 30;
 duration    = 50;

default
 {
 // send keep-alive header
 setheader
 {
 name    = "Connection";
 value   = "keep-alive";
 }

// set the host header
 setheader
 {
 name    = "Host";
 value   = server();
 }

// HTTP1.1 request
 version     = HTTP11;

// keep the connection alive after the request
 close       = ka;
 }

//
 // This script is made for IIS7
 //
 transaction
 {
 id = "My App Load Test";
 weight = 1;

request
 {
 url = "/myapp/load/create";
 verb = POST;
 postdata = "Comment=Tested";
 statuscode= 200;
 }

request
 {
 url         = "/MyBooks/load";
 statuscode  = 200;
 }

close
 {
 method      = reset;
 }
 }
 }

A scenario file normally contains warmup time (ramp up), test duration, cooldown time (ramp down).  The default section enables you to specify default HTTP headers for the test.  The transaction section is used to specify the actual business scenarios.  The weight property is used to set the priority of this transaction.  The request section is used to specify individual page request in the transaction.  A “request” contains

  • URL of the page
  • Optionally the HTTP verb (default GET).  In case of posting data, you have to specify the POST as verb.
  • POSTDATA for POST
  • Status code which is normally 200, but for some cases you may need to specify 300 or 302 for moving request

The other test details like servers and client machine names,  number of virtual clients, performance counters can be specified to WCAT through another script file called “setting file”.  Below is a sample setting file named “setting.ubr”:

settings
 {
 clientfile     = "scenario.ubr";
 server         = "localhost";
 clients        = 2;
 virtualclients = 10;

counters
 {
 interval = 10;

counter = "Processor(_Total)\\% Processor Time";
 counter = "Processor(_Total)\\% Privileged Time";
 counter = "Processor(_Total)\\% User Time";
 counter = "Processor(_Total)\\Interrupts/sec";

counter = "Memory\\Available KBytes";

counter = "Process(w3wp)\\Working Set";

counter = "System\\Context Switches/sec";
 counter = "System\\System Calls/sec";

counter = "Web Service(_Total)\\Bytes Received/sec" ;
 counter = "Web Service(_Total)\\Bytes Sent/sec" ;
 counter = "Web Service(_Total)\\Connection Attempts/sec" ;
 counter = "Web Service(_Total)\\Get Requests/sec" ;
 }

// other settings
 }

A setting file usually contains the following:

  • The scenario file the clients need to execute
  • Server machines
  • Number of physical client machines for this test
  • Number of virtual clients for this test
  • Performance counters
  • Registry settings

Note that you can use any file extension for scenario and setting files.  I have followed the convention”ubr” is used in WCAT sample.

Initial Setup

  1. Download the tool from http://snip.udooz.net/wcat63.   It has x64 version also.
  2. Add the installed folder path into system PATH.

This setup has following three exeutables and one Windows script file along with documentation and samples folders:

  • wcctl.exe – controller
  • wcclient.exe – client
  • wcutil.exe – small utility to view a brief report of a test
  • wcat.wsf – used to update, terminate and run wcclient on various client machines.

Initially, you need to update WCAT setup on all client machines by

 wcat.wsf –terminate –update –clients {comma separated list of WCAT client machines}

Steps for the Testing

To perform a test, you need a machine for “controller”, one or more machines for “clients” and the server machines.  You can do the testing even within a single machine by specifying “localhost”.

  1. Once you prepare the scenario and setting files, create a folder  in the controller machine for your test and copy these files on to it.
  2. Open the command prompt as Administrator.
  3. Run wcctl -t scenario.ubr -f settings.ubr -x on the controller machine.  The output will be like

4. Run wcclient.exe on all client machines.

After the test run, the output in the controller command prompt would be like

After all the test run completed, result will be stored in log.xml file in the controller’s  machine current directory.  WCAT has a XSLT “report.xsl” to transform this XML into readable in its installed folder.  Copy the file into log.xml.  Open the log.xml in the browser to see the output.  A part of log.xml file is

<?xml version='1.0'?>
 <?xml-stylesheet type='text/xsl' href='report.xsl'?>
 <report name="wcat" version="6.4.0" level="1" top="100">
 <section name="header" key="90000">
 ...

<item>
 <data name="counter" >\Processor(_Total)\% Processor Time</data>
 <data name="avg" >1.82</data>
 <data name="min" >1.82</data>
 <data name="max" >1.82</data>
 <data name="delta" >0.00</data>
 <item>
 <data name="time" >0.00</data>
 <data name="current" >1.82</data>
 </item>
 </item>
 <item>
 <data name="counter" >\Process(w3wp)\Private Bytes</data>
 <data name="avg" >98455552.00</data>
 <data name="min" >98455552.00</data>
 <data name="max" >98455552.00</data>
 <data name="delta" >0.00</data>
 <item>
 <data name="time" >0.00</data>
 <data name="current" >98455552.00</data>
 </item>
 </item>
 </table>

...
 </report>

Final Words

WCAT might not help to test an entire application which normally has so many user interaction which are not simply captured by single POST request.  However, it would help to perform load testing on every atomic part of your application or to perform technology evaluation as part of prototype engineering.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License

一次移动记账 App 的设计探索

现代智能手机给我们提供了非常多丰富实用的功能,比如平时颇为在意金钱去向的我,就是一个 手机记账 App 的使用者,曾经尝试过各种记账 App,但好像都有那么一点不满意,于是我就想如果是我来设计这么一款 App 的话,我会怎么去设计呢?本文记录的就是一次移动记账 App 的设计探索过程。

一. 设计前的思考

简单、高效和安全是设计之初就想到的三个关键词

在一天的使用过程中,我很可能存在多次记账的需求,这也意味着我会频繁打开这个产品,简单和高效可以让我更快的完成任务。记账因为涉及到资金这个问题,所以它同样需要一定的安全性。如果要给这个 App 一个定位的话,我认为它是一款 “能满足大众基本记账需求的轻便的移动记账 App ” 。

基于以上的思考,很快就产出了以下稿子:

一次移动记账 App 的设计探索

视觉呈现上,我把它做的非常简洁干净,在进入密码解锁以后直接呈现一个列表,左下角则是有一个常驻的悬浮着的绿色添加按钮,用来添加一笔支出或者收入。

当然也有比按钮更优雅一点的方案:

一次移动记账 App 的设计探索

通过手势下拉的操作展开一笔新的支出或收入,因为手势的优势在于没有固定的需要点按的操作位置,能有更高的效率。

尽管看似合理的初步方案就这么出现了,但是如果仔细深入思考的话,会发现它并没有实际解决你希望能够更高效记账这个行为。出于安全的考虑,你始终在打开记账 App 后需要输入四位的密码,然后才能进入记账的行为,这个过程会让你在每次记账的时候都需要多按几次手机。

为了解决这个痛点,我从最基本的使用场景出发,思考了以下几个问题:

  1. 我打开记账 App 的目的是干什么?
  2. 为什么记账?
  3. 什么时候记账?

针对第一个问题,答案很显然:目的就是记账或者查看收入和支出的报表。 第二个问题,记账的目的则是因为想了解自己的财务状况 / 收支分布。 而针对第三个问题:一般情况下则是在完成一笔消费后,或者可能是晚上回忆一下整天的消费统一记录。

在重新整理完这些思考以后,我最终决定重新拟一套设计方案。

二. 思考后的重设计

非首要功能,不应成为主路径上的障碍

在我常试过的很多记账 App 中,它们都提供了密码保护的功能,但也却因为这个密码保护功能使得记账的操作多了很多的步骤。在这一点上,金蝶的 “随手记” 做的不错,在密码保护界面提供了快捷入口。然而从我个人的角度来看,我并不是非常喜欢在密码保护界面多那么几个快捷入口的方式,至少我希望在不影响实际体验的情况下,能使界面显得更简洁一点。我反复思考后得出的结论是:密码保护需要保护的是账单隐私,而不是 “记账” 这个行为。

可以做一个大胆的设定,打开 App 后,如果直接进入记账界面,可以为我节省很多操作的步骤,甚至减去了密码保护界面点击一次快捷入口的行为。

一次移动记账 App 的设计探索

于是重新设计的界面出炉了。

当我打开 App,呈现在我眼前的就是记账界面,我可以直接输入数字以后点击对勾就完成一笔记账操作。右上角有个查看报表的功能入口,当我点击这个图标的时候,会弹出密码框,输入密码就可以进入报表界面。

智能一点点,就能提升用户体验

回到此前思考的第二个问题,我记账的目的是为了 “了解我的财务状况” 。 所以预算功能及与其相关联的提醒功能就变得尤为必要。我设想了一个叫做 “预算” 的功能,当我设置了我每月的预算,并且每月的支出超出我设定的预算以后,那么在首页的记账界面,Titlebar 会变成红色以警示你应该省着点花钱。

一次移动记账 App 的设计探索

至于我何时会记账这个问题,因为在很多时候可能会存在我完成一笔消费的时候记账,因此我的想法是根据打开的时间来预判,比如午餐时间打开的话,分类图标则默认选中 Food 选项。周末下午茶时间打开的话,分类图标则默认自动选中休闲类的选项,如果在程序开发上合入了智能学习的模块,它甚至可以根据用户的个人习惯,为每个记账的时间作分类的选择。通过使用预判的情况来减少我可能会去点击的操作,从而减少不必要的步骤。

为效率服务,在交互细节上的优化

在查看报表界面的时候,会遇到一些需要切换年份,月份或者周为单位的时候,因为手机屏幕在不断变大的情况下,如果将 Tab 放置在顶部,单手操作的时候切换会变得不太容易。因此对于切换时间这个维度的操作,我也重新思考了一下,利用下拉手势来解决。

一次移动记账 App 的设计探索

三. 视觉设计

我给这款产品起名叫 Save+,也就是给予更节省的寓意,产品虽然是一个以记账为目的的 App,但是我希望传达给用户的并不是我每个月花掉了多少钱,而是省下多少钱的感觉。因此在 icon 的设计上使用了储蓄罐作为隐喻。

一次移动记账 App 的设计探索

轻快、亲和

简单,高效是设计这个 App 的初衷,视觉呈现上需要传递给用户 “轻” 的感觉,简明清晰的图标和键盘作为主界面的元素符合直观的感受,让人在打开后就能直接进行操作。对每一枚分类图标都做了一个彩色选中态的处理,温和的颜色使界面显得不单调的同时更具亲和力。

一次移动记账 App 的设计探索 一次移动记账 App 的设计探索 一次移动记账 App 的设计探索

图标中的图形奥义

分类 icon 众多,视觉设计上保持统一的语言可以使整体感更强。对分类图标类型进行视觉上的归纳后主要分为三种类型:圆形、方形和斜形,以此为依据保持图标的整体统一性和饱满度。

一次移动记账 App 的设计探索

结语

当设计给定一个命题后,我相信从不同的使用场景和需求角度来解读往往会给出不同的设计解,从实际的使用场景中出发,瞄准一个哪怕很细微的痛点进行思考,往往也能发现一个新的突破点,而这个突破点则更可能是直接起到决定产品差异化走向的关键因素。当思路不够开阔的时候,不妨暂时先放下手中的设计任务,先找出这个突破点,以此为设计的依据和导向来设计产品。

感谢你的阅读,本文由 腾讯ISUX 版权所有,转载时请注明出处,违者必究,谢谢你的合作。
注明出处格式:腾讯ISUX (http://isux.tencent.com/finance-mobile-app-design.html)

git学习总结

从svn到git

两者都是优秀的版本管理工具(giter请不要鄙视svner)

  • svn上手非常容易,操作简单
  • git功能强大,但是要熟练使用分支不算容易

git因为是分布式的,所以其核心就是分支(只有master分支情况下和svn差不多?),分支的意义在于,可以将项目代码按照功能、模块拆分成不同的分支。比如这个产品要加一个支付功能和一个登陆功能,可以创建两个分支,交给不同的开发人员并行开发。登陆功能先开发完,测试无误后合并改分支到master分支,master分支部署上线。支付功能虽然没有开发完成,但是在另一条分支上,所以产品上线和功能开发完全不受影响。这才是分布式开发的高效模式。

被git坑了一个星期之后决心把官方文档看一下,结合实践经验进行了整理。


新手常见问题

  1. 如何查看有哪些分支?
    git branch -a
  2. 如何强制检出/切换分支或分支指定文件
    git checkout <branch> [file] [-f]
    强制更新,以branch版本的代码为主
  3. 提交代码出现冲突冲突怎么办?
    hint: Updates were rejected because the tip of your current branch is behind
    hint: its remote counterpart. Integrate the remote changes (e.g.
    hint: 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    先切换分支,然后拉取分支上最新的代码覆盖到本地
    git pull
    添加或者提交代码,解决冲突之后
    git push

  4. 如何新建分支
    git checkout -b <branch_name>
    本地建立 branch 並立即切換到新分支
    git branch -m <new_name>
    修改分支名称
  5. 从远程仓库拉取代码到本地仓库,并建立跟踪关系
    git clone http://xxx.git
    或者
    get clone git@xxx.git
    然后
    git checkout -b <本地新分支名> <对应的远程分支名>
  6. 远程仓库新建了一个分支,如何更新远程分支信息
    git fetch <remote base>
  7. 如何在远程仓库新建一个分支
    git branch <branch name>
    新建一个本地分支,按照正常流程提交完代码后,推送到远程
    git push <remote base> <local branch>:<remote branch>

实用指令

reset

git reset [file]
取消暂存

remote

查看远程仓库名

git remote -v
查看远程仓库url

git remote add <basename> <url>
新增远程仓库

git remote show <basename>
查看远程仓库详细信息

git remote rename <old basename> <new basename>
重命名远程仓库

pull

相当于fetch和merge

push

git push [remote_branch] [local_branch]
推送本地仓库代码到远程仓库,相当于svn的commit

git push <remote base> [tag name]
推送本地标签到远程仓库

git push <remote base> <remote branch>:<local branch>
将本地分支推送到指定的远程分支

git push <remote base> --delete <remote branch>
删除远程分支

tag

查看标签(用来标记标志性的稳定版本信息)

git tag -l '[expression]'
查看那符合正则表达式的

git tag -a <tag name> -m <comment>
添加带注释的标签

git tag -a <tag name> <md5>
对某个版本打标签

git tag [tag name]
如果没有标签名,则为查看所有标签。带标签名则为新建标签

merge

git merge <branch name>
将其他分支合并到本分支

commit

git commit -a -m 'xx'
暂存并提交

branch

git branch
查看本地仓库分支

git branch -v
查看本地仓库分支最后一次提交情况

git branch -vv
查看分支跟踪情况

git branch <branch name>
新建分支

git branch -d <branch name>
删除分支

git branch [--merged | --no-merged]
查看已合并|未合并的本地仓库分支

git branch -u <remote base>/<remote branch>
修改当前跟踪分支

commit

git commit -a -m 'xx'
提交并且暂存暂存的方法

checkout

git checkout -- [file]
恢复文件

git checkout -b [branchname] [tagname]
在特定的版本上创建一个新的分支并切换到此分支

git checkout -b [local branch] [remote base]/[remote branch]
将远程分支检出到本地分支

git checkout --track <remote base>/<remote branch>
让当前分支跟踪远程分支

git checkout --track <remote base>/<remote branch>
git checkout -b <local branch> <remote base>/<remote branch>
让当前分支跟踪到远程分支。两条命令作用基本一致,不同的是第二条命令可以重命名检出的分支。

rebase

git rebase [basebranch]
变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

小技巧

查看配置

git config -1

设置别名

git config --global alias.<name> <commend>

保存用户名和密码

对于http(s)协议,可以用下面命令临时缓存

git config --global credential.helper cache
开启linux缓存
git config --global credential.helper wincred
开启windows缓存

对于ssh协议,可以用ssh key,具体教程网上很多

权威教程

博客:http://yalishizhude.github.io
作者:亚里士朱德

如何设计类似微信的多终端数据同步协议 | Grouk实践分享

本文由王渊命在高可用架构群所做的分享整理而来,转载请注明高可用架构公众号:ArchNotes。

王渊命,团队协作IM服务Grouk联合创始人及CTO,技术极客,曾任新浪微博架构师、微米技术总监。2014年作为联合创始人创立团队协作IM服务Grouk,长期关注团队协作基础工具和研发环境建设,Docker深度实践者。

我们Grouk是一个创业团队,面向团队通讯的主打产品应用刚开始公测,因此我们也需要实现类似微信的多端数据同步功能,下面我主要从技术和产品的结合场景进行一些心得分享,感觉我们在这方面的探索还是值得和大家探讨的,这种需求业界也没有非常成熟的公开解决方案。

、移动互联网时代多终端数据同步面临的挑战

首先要讲的是,多终端同步的含义及应用场景。

多终端同步是指用户在多个终端切换时可获得一致性体验,不丢失上下文,同时隐含的一个含义是,如果用户多个终端同时在线要能做到实时同步。

举几个应用的例子:

1 Trello 看板应用

用户打开后,其他人操作看板,要能实时变化,不依赖用户刷新页面。如果用户在多个终端操作,也需要做到实时变化。通过测试,我发现Trello在移动端和PC同步的时候还是有Bug的。例如,PC设置离线,手机上操作card。PC连网,不刷新页面,数据经常无法同步。

2 Quip这样的多人协作编辑

某人的编辑结果,其他人要能实时看到。同时还支持离线编辑、冲突合并。例如,Evernote多人协作是文档锁定模式的,冲突很难自动合并,体验上就差些。

这几种典型的应用场景是移动互联网爆发以来,应用富客户端化带来的一种趋势性变化。

移动端上是独立应用,PC端是基于JavaScript的应用,和原来PC互联网时代刷页面的交互体验完全不一样。这个当然原来也有,但应用没现在这么广泛。

移动客户端的爆发和应用富客户端化带来以下几个挑战:

  • 服务器端的输出由HTML变成结构化数据(json等)。原来基于浏览器和HTTP协议的缓存规则机制失效,客户端需要针对具体的业务场景设计缓存。没有缓存的话,每次全量拉取很浪费流量。
  • 用户习惯从多个终端进行操作,对跨屏操作的体验要求比较高了。
  • 多人协作场景下,数据需要实时同步到不同用户的不同设备上。

、多终端数据同步与传统消息投递协议的差异

接下来说一下多终端数据同步和IM的关系

虽然数据同步机制和IM消息投递是两个问题,但如果实现了实时同步,基本上就实现了一种特殊的IM。所以先说一下传统的IM投递机制。

传统的IM投递协议,大家应该都比较熟悉,5月的时候群里沈剑分享过一次。

这里借用其中的一句话:

消息可达性即消息的可靠投递,有一个著名的定理:SMC定理,Single-Message Communication,Published in :Communications, IEEE Transactions on (Volume:24 , Issue: 2 ) ,很短的一个论文。文章的结论是:任何端到端的消息传递协议,消息既不丢失,也不重复是不可能的。

也就是说传统的IM消息投递要么接受消息丢失,要么接受消息确认重试导致的重复问题。当然可以通过应用层面的排重机制来解决一问题。这里不再细说传统IM的投递机制,大家想了解可以看本公众号中沈剑的分享(【直播全文记录】 从零开始搭建高可用IM系统)。

虽然传统的IM投递机制+历史记录,也可以实现多终端同步:

  • 所有设备都在线的情况下,直接投递。
  • 离线→在线时,拉取历史记录补充缺失记录。

但这样做比较困难的地方在于:

  • 变更如何同步?我们的消息不像传统IM,是不可变对象,我们的消息是可变的。同时,群组列表,联系人列表,这些都是可变的,如何同步?
  • 投递确认机制的缺陷会导致一致性不好控制,如果出现多个终端不一致的情况,客户端无法自修复。只能提供特殊的刷新机制,由用户自己刷新。

比如,我前面提到的Trello的情况。当然,Trello的具体实现没分析过,这里只是推测。于是,我们考虑使用同步协议。说到同步机制当然要说一下微信,今天说分享的时候有人就提到了微信的SYNC。

微信的SYNC协议没有详细的公开分享,按照公开说明,是参考Activesyec实现的。据我的观察,当然,以下微信协议的说明不保证正确性,群里也有微信的同学,可以纠正。

  • 同步机制是通过服务器通知,客户端拉取机制实现的。IM协议投递的是新消息的通知,拉取是根据版本号增量同步,将消息投递转换为基于状态同步的协议。(这个有公开说明)
  • 每个Folder的版本号是严格有序递增的,Folder不是按照会话划分的。
  • 微信投递消息和邮件类似,是将消息投递到每个人的收件箱中,每投递一个消息增加一个版本号。

以上只是我个人的简单分析,不确定微信的服务器端是如何存储的。也不确定微信是如何处理变更的,比如,通讯录的同步。所以我们还是得自己设计一个同步协议。

三、Grouk在多终端数据同步协议上的探索实践

到这里,先总结下我们设计的该同步协议的目标 :

  • 解决接口数据做本地缓存需要根据具体接口单独设计的问题,设计一种统一的客户端缓存数据机制。这个问题应该是所有App类应用都会遇到的问题。
  • 实现消息的多终端增量同步,然后通过同步机制确保不丢消息。同步机制必须避免流量浪费,所以需要做增量。
  • 消息同步和联系人/群组等同步使用同一套机制。这个也为以后的业务数据类型扩充做准备。
  • 客户端数据能自修复达到最终一致性。
  • 不解决冲突合并问题。因为我们的消息比较轻量,不需要像文档一样考虑冲突问题,降低复杂性。

有了目标后,我们首先想到了Git等版本管理系统。因为二者要解决的问题是类似的,区别在于实时性上,还有Git的Server和Client是对等的,而我们这里的Client只是Server的子集。

因为时间关系,关于Git等版本管理系统的机制这里不细说了。直接说一下我们的抽象和解决方案 。

数据结构图,如下:

  • 每个需要同步的数据集抽象成一个Folder,Folder可能是多人共享的,也可能是某人专用的。这里的Folder相当于一个索引表,引用的是对象ID。
  • 每个Folder维护一个变更集(ChangeSet),增量同步通过变更实现,变更的版本号有序递增。变更是每次操作生成的,每一次Folder索引或者Folder引用对象的操作都生成一个变更。
  • 变更(Change)有对应的操作(OP)。如:新增、更新、删除等。包括索引变更和索引引用对象的变更。携带变更数据。客户端根据操作要在本地实现重放逻辑。
  • 每个Folder中的索引对象会被分配一个该Folder中的有序递增ID。每个索引对象也可以拥有自定义属性。
  • 所有的数据对象都统一定义,有更新时间,等基本字段。抽象出通用的操作接口(ObjectStore)。
  • 客户端会通过Change将服务器的Folder及对象库同步下去,不过同步的只是服务器上的一个子集,并不是全量。

客户端可以通过对象的更新时间来确定本地缓存的有效性。

下图是我们利用这套机制的流程:

  • 用户发消息、修改消息、修改个人信息等操作,都触发一次相关Folder的变更,存到变更集中,实时投递到在线客户端。
  • 在线客户端收到变更后,检查本地的版本号和当前版本号是否连续。如果不连续说明有消息丢失,从服务器拉取二者之间丢失的变更。然后客户端根据操作定义将变更应用(apply change)到本地的Folder和对象库。
  • 离线客户端上线后,带上本地的Folder的版本号,发起sync request,去服务器端同步变更。同步后需要进行的操作同上。
  • 所有的对象通过统一的接口获取。支持类似于HTTP的ETag,变更更新模式,不过是针对每个对象的版本,以增强本地缓存机制。

可以说,相当于实现一个服务器和客户端实时同步的轻型数据库。

以下是我们这样设计的优缺点。

优点:

  • 用户在线的情况下,大多数情况变更是直接投递下去的。比通知→拉取模式的和服务器的交互少,更省资源。
  • 离线缓存比较容易实现,离线浏览的体验会比较好。
  • 能保证终端和服务器的数据一致性。
  • 相对比较通用,可以适用于多个业务场景。

缺点:

  • 本地客户端的实现逻辑比较重。微信的思想是轻客户端,重服务器。我估计我们在这里还得踩些坑。
  • 只能保证同一个Folder的最终一致性。

基本协议设计就讲这里了,再说一下我们的技术栈。

主要还是基于Java+Netty研发。我们撸了一个简单的前端框架,主要是为了实现用同一套逻辑,服务多个接入层。

我们的接入层支持的协议有HTTP,自定义TCP,WebSocket。通过接入层转换后,变为内部的request/response,后面共享同一套逻辑。也就是说同一个请求,可以通过HTTP发送,也可以通过长连接(TCP/WebSocket)发送。

数据对象上,我们接口支持JSON/Protobuf两种。根据客户端的accept自动适配。接口输出格式统一定义对象,客户端可以和服务器端共用。

总结下当前应用,尤其是工具应用的一种趋势。

IM已经变得不像IM,不是IM的要变成IM。前半句是说,当前的IM已经逐渐不像传统的IM了,无论是微信,还是Slack,还是我们的Grouk,和传统的IM区别越来越大。后半句是说,不是IM的应用因为要做多终端实时同步,协议越来越靠近IM机制了。

另外个人感觉这种趋势不一定仅局限在工具类。哪怕是电商网站,如果能同步用户的购物车到多个终端,用户的体验也会更进一步。

我们应用使用同步协议已实现的效果 :

  • 多终端数据保持一致,用户切换后不会丢失上下文(比如QQ的消息只投递到一个终端)。
  • 允许多个终端登录,比如,多个手机、多个Web。
  • 历史记录可以在任何一个端获取,也可以通过搜索从任何一条历史消息开始上下回溯。
  • 未读数/收藏实时同步。
  • 联系人信息/群组信息实时同步。
  • 更多欢迎注册体验 https://grouk.com(顺便打个广告)。

最后,再说一个题外话,就是创业公司做技术类的创新是否值得?

我们也讨论过,假设当初直接拿现成的XMPP来做,估计我们的推出时间也可以早几个月。我们在这套机制上花费的时间也不少。但我们还是觉得当前IM这么多,用户的体验已经被QQ、微信等工具教育的情况下,如果体验不能更进一步,估计用户连尝试的愿望都没有。但到底花多少时间,估计要做个平衡。

Q&A

Q1:为什么不采用XMPP协议呢?多人协作时后端出现用户不在一台服务器上如何同步?


XMPP由于众所周知的原因,XML不太适合移动使用。一般移动上使用都要做压缩,比如WhatsApp。另外就是前面描述的,做变更同步比较麻烦。

Q2:客户端所有请求都是通过和服务器的长连接过来吗?没有走比如短连接HTTP协议之类的?


不是所有,有走短连接的。我们采用一种动态机制,长连接优先。消息上我们没有采用长轮询的方式。客户端是TCP长连接,Web版本是WebSocket。

Q3:这个版本号必须是有序的吗?是否可以跟Git一样用随机字符+链表的方式做?


我们这个方案里版本号必须是有序严格递增的,因为要靠这个判断是否丢失消息。Git的方案是因为需要离线写操作,我们当前没这个需求,写都是通过服务器中心写的。

Q4QQ的消息只投递到一个终端?这是多年前了吧?

QQ是对移动端做了写优化,离线登陆后会补充投递一部分消息,但做不到全终端一致同步。

Q5:如何选择客户端服务器之间心跳的时长?有哪些选择的因素考虑,怎么权衡?


这个说实话我们也在摸索。没有太多数据支撑的经验。移动端其实心跳已经不是很重要了,大家的使用习惯基本上是查看消息回复,然后就沉后台了。我们做了点优化就是所有的消息都视为一种心跳。心跳其实是服务器端判断客户端是否在线的一种方式。移动客户端网络变化能收到通知,一般是几分钟一次。 Web版本没有这种功能,所以要靠心跳来判断网络,一般是几秒钟一次。

Q6:发现消息版本不连续后,是全量拉取吗?还是可以判断拉去到哪?


发现消息不连续后,由于版本号是有序递增的,可以计算出中间的差距,直接拉缺失的即可。当然服务器的版本是有限的,如果发现客户端的本地数据太旧,是需要重新全量拉取的。全量拉取的机制不同,Folder的规则不一样。

想和群内专家继续交流有关高可用架构的问题,请关注公众号后,回复arch,申请进群。

阿里云组建医疗大数据:未来数据化一个人是否有可能?

9 月 18 日报道  (文/吴艳梅)

埃里克托普在《颠覆医疗》一书中有过数字化人类的描述:

通过将肉体置于我们外延的神经系统中,通过电子媒体的方式,我们建立起一个动态系统,将快速成熟的数字化、非医学领域的移动设备、云计算和社交网络与蓬勃发展的基因组学、生物传感器和先进成像技术的数字化医学领域合为一体,将这些工具和能力加以综合,我们就能为每一个人获取关于 ta 的解剖学、生理和生物数据。

但这些数据如何在一个更长的时间维度上,对人体进行更全面的数据采集和汇总,如糖尿病、高血压、心脏病、体检、运动、睡眠数据等多个场景,并让医疗健康生态链上的各个角色都能够使用上这份完整的数据,是否能造福每一个生命?

9 月 17 日,阿里云同深圳中瑞奇、杭州金卫健康宣布,三方将合作组建“云上安心”联盟。通过联合社区医院、三级综合医院、医疗硬件厂商、医疗健康 APP、健康体检中心、医疗健康分析模型提供商,在患者知情并授权下,将散落各处的健康医疗数据进行汇聚打通,以期实现基于数据的精准医疗。

阿里云

以心脏病治疗为例,通过将“云上安心”联盟中的心电设备产品“好朋友”接入人体,并连接“好朋友心电图”APP 后,用户便可查看自身心电数据。同时,数据同医院同步,如出现异常,医生或急救中心可提前介入。中瑞奇创始人汪远思表示,“该产品直接面向个人用户开发,并提供专业医护服务。” 也就是说,每一个用户都相当于拥有了一位私人医生。

在今年 7 月初的试点中,阿里云和中瑞奇向杭州米市巷社区 2 万老人中的心脑血管疾病患者发放了相关设备,金卫健康提供面向居民、社区医院的心电数据采集及心脏健康管理服务,帮助社区居民能够及早发现心脏方面潜在风险,将高危人群纳入监护体系。

“云上安心”联盟的生态参与方所有数据都将存放于阿里云上,借助阿里云强大的计算能力和开放的心电算法,实现对亿万级数据的并行处理。诸如心电图的记录过程、心电噪音的过滤,特征值抓取(如p波,r波,st 段)等。通过阿里云大数据处理平台 ODPS、分析型数据库 ADS 还可以进行离线数据和实时数据处理,快速接入更多标准的应用及第三方医疗数据。

阿里云高级产品专家武凯表示,联盟将以心电数据为中心,打通医院生化指标、诊断数据和医院外心电、血糖数据,以及其它体征、运动等各种碎片数据,以期更加全面立体的描述一个患者的健康情况,从而达到精准医疗的目的。

据介绍,心电数据收集类似声音数据采集,对传感器数据的采集频率高,且心电数据量非常大,一个人 24 小时的心电数据大概在 150M 左右,一个人一生心脏跳动 25-30 亿次,会产生人均 4394G 心电数据量。在当前的医疗工程界,无论是院内的静态心电数据,还是动态心电数据,用于疾病检测完之后,都会因数据量巨大而被闲置或清除。

武凯表示,这些数据如果被利用起来,将对中国整个心脏疾病的检测产生不可估量的价值。“挑战在于,海量的心电数据不仅需要无限扩容的储存空间,更需要强大的数据处理分析能力,这两者都是阿里云的优势。”

据悉,心电数据库在美国和欧洲已经成为普遍的医学指标,但在中国,目前并无完整心电数据库。且心电算法处理模式还仅仅是基于小样本特征识别的心电信号分类。“希望在未来建立基于大数据能力的统计分析心电信号的分类方法,以及面对医疗行业的大数据样本,逐步实现人体数据化。”武凯表示。