#
从零开始。
>1 下载Android SDK
,然后带着不求甚解的态度去阅读 SDK 里的官方文档。
没有手机软件开发经验、Java 以及 eclipse
使用经验的朋友不必担心,我也是一样。
刚开始接触不可能一下就看明白,先大体扫一下官方文档,了解一下基本概念,不需要完全理解,
有点印象就行了。
>2 去
http://www.eoeandroid.com/viewthread.php?tid=772&extra=page%3D1
下载 eoe 特刊~ 写的很不错,你首先需要看完第一期,就可以把开发环境搭建好了。
其他几期可以稍后再看。
>3
查阅官方文档,按照里边的 hello world 教程一步一步做,很快你的第一个 android 项目就可以
成功运行了!
>4
接下来你应该学习官方文档里的记事本了,这个要稍微花点时间,做的过程可能有一些疑问,没关系,
大胆忽略,只要可以成功运行即可。
>5
看了一大堆英文的东西,也有不少疑问了,应该看一些中文的文档来加深理解了。
强烈推荐 深入淺出 Android --
Google 手持設備應用程式設計入門
http://code.google.com/p/androidbmi/wiki/DiveIntoAndroid
虽然是繁体的,但是文章的质量相当高,看完之后基本上就可以算是入门了!
>6 接下来,建议把 eoe
特刊的其它几期都看完,相信你对一些常见的开发需求就很有把握了。
>7 请加入hong老大建的群,这里有很多对android
感兴趣 的兄弟,大家可以一起探讨。
http://groups.google.com/group/china-android-dev
>8
推荐几个不错的博客,
http://haric.javaeye.com/
http://rayleung.javaeye.com/
>9 可以经常去
http://www.eoeandroid.com/forumdisplay.php?fid=27 这里逛逛。
>10
想开始做app赚钱了? 呵呵,把 javaeye 里移动编程板块的精华良好贴都翻一遍吧~ 祝福你!
转自:http://www.javaeye.com/topic/473980
在使用<c:out value="${map[key]}" /> 求对应的KEY中的,value
必须注意:key的类型必须和map中存放的key的类型一致,否则不能得到预期的结果
1、迭代
当forEach
的items属性中的表达式的值是java.util.Map时,则var中命名的变量的类型就是java.util.Map.Entry。这时
var=entry的话,用表达式${entry.key}取得键名。用表达式${entry.value}得到每个entry的值。这是因为
java.util.Map.Entry对象有getKey和getValue方法,表达式语言遵守JavaBean的命名约定。
例:
- <c:forEach items="${map}" var="entry">
- <c:out value="${entry.key}" />
- <c:out value="${entry.value}" />
- </c:forEach>
<c:forEach items="${map}" var="entry">
<c:out value="${entry.key}" />
<c:out value="${entry.value}" />
</c:forEach>
2、根据key求值
如果事先知道key那么很容易根据${map.key值}就可以得到值对象,但是如果key是一个变量呢?有一个问题,如果给定一个key的变量如何使用
EL得到对象呢,这里需要使用EL表达式中的[]来解决。
例:
- <c:out value="${map[key]}" />
- <!-- 这里的map就是 java.util.Map对像,key是这个map里的一个
key -->
<c:out value="${map[key]}" />
<!-- 这里的map就是 java.util.Map对像,key是这个map里的一个key -->
1、迭代
Map的每个对象以key=value的形式给出
当forEach
tag的item属性中的表达式的值是java.util.Map时,在var中命名的变量被设置为类型是java.util.Map.Entry的
item。这时,迭代变量被称为entry,因此,用表达式${entry.key}取得键名。
在下面的例子中你会看到,可以用表达
式${entry.value}得到每个entry的值。这是因为java.util.Map.Entry对象有getKey和getValue方法,表
达式语言遵守JavaBean的命名约定。
通常,JSP
EL表达式${a.b.c.d}是用代码a.getB().getC().getD()来计算的。这种表达式是对JavaBean属性的相继调用的简化。
示例:
- <%@ page language="java" pageEncoding="utf-8"%>
- <jsp:directive.page import="com.xaccp.vo.BookAdapter"/>
- <%@ taglib prefix="c" uri="/WEB-INF/c.tld" %>
- <jsp:directive.page import="java.util.Hashtable"/>
- <jsp:directive.page import="com.xaccp.vo.Book"/>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html:html locale="true">
- <head>
- <%
- Hashtable table=new Hashtable();
- BookAdapter ba=new BookAdapter();
- table.put("aaa",new Book(1,"abc"));
- table.put("bbb",new Book(2,"bbb"));
- table.put("ccc",new Book(3,"cccc"));
- pageContext.setAttribute("table",table);
- %>
- </head>
-
- <body>
-
- <hr>
- <c:forEach items="${table}" var="aaa">
- <c:set var="key" value="${aaa.key}" ></c:set>
- <c:set var="book" value="${aaa.value}"></c:set>
- ${key }=${book }111 name:${book.bookID}<br>
-
- </c:forEach>
- </body>
- </html:html>
-
-
<%@ page language="java" pageEncoding="utf-8"%>
<jsp:directive.page import="com.xaccp.vo.BookAdapter"/>
<%@ taglib prefix="c" uri="/WEB-INF/c.tld" %>
<jsp:directive.page import="java.util.Hashtable"/>
<jsp:directive.page import="com.xaccp.vo.Book"/>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html:html locale="true">
<head>
<%
Hashtable table=new Hashtable();
BookAdapter ba=new BookAdapter();
table.put("aaa",new Book(1,"abc"));
table.put("bbb",new Book(2,"bbb"));
table.put("ccc",new Book(3,"cccc"));
pageContext.setAttribute("table",table);
%>
</head>
<body>
<hr>
<c:forEach items="${table}" var="aaa">
<c:set var="key" value="${aaa.key}" ></c:set>
<c:set var="book" value="${aaa.value}"></c:set>
${key }=${book }111 name:${book.bookID}<br>
</c:forEach>
</body>
</html:html>
2、根据key变量求值
如果事先知道key那么很容易根据${map.key值}就可以得到值对象,但是如果key是一个变量呢?
有一
个问题,如果给定一个key的变量如何使用EL得到对象呢,这里需要使用EL表达式中的[]来解决,解决方法如示例:
- <%@ page language="java" pageEncoding="utf-8"%>
- <jsp:directive.page import="com.xaccp.vo.BookAdapter"/>
- <%@ taglib prefix="c" uri="/WEB-INF/c.tld" %>
- <jsp:directive.page import="java.util.Hashtable"/>
- <jsp:directive.page import="com.xaccp.vo.Book"/>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html:html locale="true">
- <head>
- <%
- Hashtable table=new Hashtable();
- table.put("aaa",new Book(1,"abc"));
- table.put("bbb",new Book(2,"bbb"));
- table.put("ccc",new Book(3,"cccc"));
- pageContext.setAttribute("table",table);
- %>
- </head>
-
- <body>
- This a struts page. <br>
- <c:set var="keys" value="bbb" ></c:set>
- <c:set var="book" value="${pageScope.table[pageScope.keys]}"></c:set>
- ${keys }=${book}
- <!-- 这里的${book就是对应于key值得book对象} -->
- <br>
- </body>
- </html:html>
google app engin Text字段可保存任意长的字符(可能有人说用String,不好意思engin不支持500字符以上的String)
持久化字段需这样才能保存
@Persistent(defaultFetchGroup="true")
private Text text;
可用text.getValue()获取其保存的字符串
注意:用jstl标签<c:out value="${text}" />会进行转义
如果不想转义,即是原封不动的将字符串输出,用${text}即可
1.对于JSTL先了解下:
sevlet2.4 jsp2.0规范 需使用jstl1.1 tomcat5.X
:使用标签需在jsp头部引用<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core"
%>
sevlet2.5 需使用jstl1.2 tomcat6.X
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" import="java.util.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
isELIgnored="false"表示使用EL表达式
EL表达式可参见:http://www.cnblogs.com/hya1109/archive/2007/10/02/912947.html
2.在google app engine中使用JSTL,需在jsp中加入:
<%@ isELIgnored="false" %>
<%@
taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>(这句代码在myeclipse中会报错不用管)
因为在web.xml中其实sevlet2.5
${article.title}====article.getTile();
http://homelink.javaeye.com/blog/293328#comments
参考文档 http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
comet是HTTP长连接,就是在HTTP发送请求时,服务器不立刻发送响应信息给客户端,
而是保持着连接,等待一定情况发生后才把数据发送回去给客户端。所以用comet可以实现服务器端的数据实时地发送给客户端。
本文主要是用java和js来简单地实现comet,最后附上源码和使用例子。
在客户端用XMLRequest发送请求到服务器,在服务器端用一个servlet来接收XMLRequest的请求,当接收到请
求时,并不立刻响应客户端,而是把该servlet线程阻塞,等到一定事件发生后,再响应客户端。当客户端接收到服务端的响应后,调用自定义的回调函数来
处理服务器发送回来的数据,处理完成后,再发送一个XMLRequest请求到服务端,这样循环下去,就可以实现数据的实时更新,又不必要在客户端不断地
轮循(polling)。
利用该comet的实现(以后简称为keeper)时,只要在客户端注册事件和写一个处理返回数据的回调函数,然后在服务端实现
keeper中的EventListener接口,调用Controller.action(eventListener,eventType)就可以
了。
keeper分成两大部分,第一部分为客户端的javascript,第二部分是服务端的servlet和事件处理。
一.客户端
建立一个XMLRequest对象池,每发送一次请求,从对象池中取一个XMLRequest对象,如果没有可用的对象,则创建一
个,把它加入到对象池中。这部分的代码来自于网络。
为了使用方便,再添加一些方法,用来注册事件。这样只要调用注册函数来注册事件,并且把回调函数传给注册事件函数就行了,处理数据
的事情,交给回调函数,并由用户来实现。
keeper为了方便使用,把客户端的javascript代码集成在servlet中,当配置好keeper的servlet,
启动HTTP服务器时,keeper会根据用户的配置,在相应的目录下生成客户端的javascript代码。
二.服务端
服务端的servlet初始化时,根据配置来生成相应的客户端javascript代码。
servlet的入口由keeper.servlet.Keeper.java中的doGet进入。在Keeper的doGet
中,从请求中获取用户注册事件的名称(字符串类型),然后根据事件的名称,构造一个事件(Event类型),再把它注册到NameRegister中,注
册完成后,该servlet线程调用wait(),把自已停止。等待该servlet线程被唤醒后,从Event中调用事件的EventListener
接口的process(request,response)来处理客户端的请求。
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- String eventName = request.getParameter("event");
- NameRegister reg = NameRegister.getInstance();
- Event event = null;
- try {
- event = reg.getEvent(eventName);
- if(event == null) {
- event = new Event(eventName,this);
- reg.registeEvent(eventName, event);
- }
- if(event.getServlet() == null) {
- event.setServlet(this);
- }
-
- } catch (RegistException e1) {
- e1.printStackTrace();
- }
- synchronized(this) {
- while(!event.isProcess()) {
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- EventListener listener = event.getListener();
- if(listener != null) {
- listener.process(request,response);
- }
- }
在服务端处理事件时,调用了keeper.control.Controller中的静态方法
action(EventListener listener,String eventName)来处理。如下所示。
- public static boolean action(EventListener listener,String eventName){
- NameRegister reg = NameRegister.getInstance();
- HttpServlet servlet = null;
- Event e = null;
- try {
- e = reg.getEvent(eventName,true);
- if(e == null) {
- return false;
- }
- e.setListener(listener);
- servlet = e.getServlet();
- e.setProcess(true);
- synchronized(servlet) {
- servlet.notifyAll();
- }
- } catch (RegistException ex) {
- ex.printStackTrace();
- }
- if(servlet != null && e != null) {
- e = null;
- return true;
- } else {
- return false;
- }
- }
下面开始用keeper来写一个简单的网页聊天程序和基于服务端的时间。
1.客户端设置
注册两个事件,一个用于是时间事件,一个是消息事件。同时还要写两个回调函数,用于处理服务
端返回的时间和聊天消息。如下所于:
- <script type="text/javascript">
- Keeper.addListener('timer',showTime);//注册时间事件
- function showTime(obj){ //时间处理回调函数
- var sp = document.getElementById("dateTime");
- if(sp){
- sp.innerHTML = obj.responseText;
- }
- }
- function startOrStop(obj){
- var btn = document.getElementById("controlBtn")
- btn.value=obj.responseText;
- }
- Keeper.addListener('msg',showMsg,"GBK");//注册消息事
件,最后一个参数是
- //字符串编码
- function showMsg(obj){//处理消息的回调函数
- var msg = document.getElementById("msg");
- if(msg){
-
- msg.value = obj.responseText+""n"+msg.value;
-
- }
- }
- function sendMsg() {
- var msg = document.getElementById("sendMsg");
- if(msg){
- var d = "msg="+msg.value;
- sendReq('POST','./demo',d,startOrStop);
- msg.value = "";
- }
- }
-
- </script>
2.配置服务端
服务端的配置在
web.xml文件中,如下所示
- <servlet>
- <servlet-name>keeper</servlet-name>
- <servlet-class>keeper.servlet.Keeper</servlet-class>
- <init-param>
- <!--可选项,设置生成客户端的JavaScript路径和名字,默认置为
/keeper.js-->
- <param-name>ScriptName</param-name>
- <param-value>/keeperScript.js</param-value>
- </init-param>
- <!--这个一定要设置,否则不能生成客户端代码-->
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>keeper</servlet-name>
- <url-pattern>/keeper</url-pattern>
- </servlet-mapping>
用<script type="text/javascript"
src="./keeperScript.js"></script>在页面包含JavaScript时,这里的src一定要和上面配
置的一至。上面的设置除了<init-param></init-param>为可选的设置外,其他的都是必要的,而且不能改
变。
3.编写事件处理代码,消息的处理代码如下:
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- System.out.println("Post..");
- String msg = request.getParameter("msg");
- Controller.action(new SendMsg(msg),"msg");
- }
- class SendMsg implements EventListener{
- private String msg;
- public SendMsg(String msg) {
- this.msg = msg;
- }
- @Override
- public void process(HttpServletRequest request, HttpServletResponse response) {
- response.setCharacterEncoding("UTF-8");
- PrintWriter out = null;
- try {
- out = response.getWriter();
- if(msg!=null){
- out.write(msg);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- finally{
- if(out != null) {
- out.close();
- }
- }
- }
- }
到这时,一个基本的keeper应用就完成了。其它部分请参考附件中的例子源码。
互联网服务的特点就是面向海量级的用户,面向海量级的用户如何提供稳定的服务呢?这里,对这几年的一些经验积累和平时接触的一些理念做一个总结。
一、原则
1.Web服务的CAP原理
CAP指的是三个要素:一致性(Consistency)、可用性(Availability)、分区容忍性(Partition
tolerance)。CAP原理指的是这三个要素最多只能同时实现两点,不可能三者兼顾,对于海量级服务,一般这是一条常记心中的基准准则。
如下是《 Web服务的CAP
》关于CAP的定义:
- 一致性:可以参考数据库的一致性。每次信息的读取都需要反映最新更新后的数据。
- 可用性:高可用性意味着每一次请求都可以成功完成并受到响应数据
- 分区宽容度:这个是容错机制的要求。一个服务需要在局部出错的情况下,没有出错的那部分被复制的数据分区仍然可以支持部分服务的操作,可以简单的
理解为可以很容易的在线增减机器以达到更高的扩展性,即所谓的横向扩展能力。
面向海量级的分布式服务设计,基本上分区容忍性(Partition
tolerance)是第一要素,因此根据业务的情况,我们需要在一致性(Consistency)和可用性(Availability)之间做取舍。对
于一些业务,譬如支付宝或财付通,一致性会是第一考虑要素,即使是延迟的不一致也是不可接受的,这种时候只能牺牲可用性以保证一致性。对于一些应用,譬如
淘宝或拍拍交易中的评价信息,一般用户是可以接受延迟的一致性,这种时候可以优先考虑可用性,而用最终一致性来保证数据一致,譬如通过某种对帐机制。对于
一些应用,甚至一致性都并非要求,只需要保证差不多一致性即可,譬如Q-zone中的农场游戏中的偷菜。
根据我们应用的业务需求,选择合适的一致性级别,以更好地保证系统的分区容忍性和可用性。
2.柔性可用
面向海量级的分布式服务设计,我们要意识到,一切都是不可靠的,在不可靠的环境的环境中构建可靠的应用,其中最重要的一点就是保持系统的柔性。
1)不可靠的环境
我们可能已经见惯一个远程服务提供不了服务了,运行一段时间后WebServer突然不响应了,数据库随着负载的不断增加再放上一条SQL语句就会垮掉。
但是,硬盘坏掉、电源断掉、光纤中断,听起来似乎多么不可思议,然而,当一个海量服务需要成千上万台服务器、需要部署全国各地的数十个数据中心、需要横跨
电信网通教育网三大网络的时候,一切听起来不可思议的事情会变成常态。一切都是不可靠的,唯一可靠的就是不可靠本身。
2)划分服务级别
我们应该意识到,在这种不可靠的环境中提供完美的服务,本身就是一个神话,即使不是说完全不可能,但至少是代价高昂的,因此,当某些问题发生(环境变地不
可靠的时候),我们必须做出取舍,选择为用户提供用户最关心的服务,这种服务虽然听起来是有损的(至少是不完美的),但却能在一定程度上满足用户大部分的
需求。譬如,当网络带宽无法为用户提供最好的体验而扩容又不是短期可以达到的时候,选择降低一些非重要服务的体验是一个比较好的选择。
在面向海量互联网的设计当中,对服务进行分级,当系统变地不可靠的时候,优先提供重要优先级的服务。
3)尽快响应
互联网用户的耐心是非常有限的,如果一个页面需要3秒以上才能看到,也许大部分用户的第一选择就是关掉浏览器。在构建柔性可用的互联网服务的时候,响应时
间大部分情况下都是需要最优先考虑。还是一句话,环境是不可靠的,当我们无法尽快从远程服务获得数据、当数据库已经慢如蜗牛,也许当后台还在吭哧吭哧干活
的时候,用户老早已经关闭了页面,处理返回的数据也只是在浪费表情,面向互联网用户,响应就是生命。
二、策略
如何让我们的应用提供更高质量的服务呢,这里是一些在日常开发使用到或者观察到的一些策略的总结:
1.数据sharding
海量服务相应也意味着海量的用户和海量的用户数据,大家都知道,即使是再强大的数据库、再强大服务器,在单表上亿规模的数据足够让一条简单的SQL语句慢
如蜗牛(甚至于在百万、千万级别上,如果没有采取合适的策略,都无法满足服务要求),一般处理这种千万上亿级数据的大家基本上都会想到的就是数据
sharding,将数据切割成多个数据集,分散到多个数据库的多个表中(譬如将用户数据按用户ID切割成4个数据库每个数据库100个表共400个
表),由于每个表数据足够小可以让我们的SQL语句快速地执行。而至于如何切割,实际上跟具体的业务策略有关系。
当然,我们要认识到,这种数据sharding并非全无代价的,这也意味着我们需要做出一些折中,譬如可能很难进行跨表数据集的查询、联表和排序也变地非
常困难、同时数据库client程序编写也会变地更加复杂、保证数据一致性在某些情况下会变地困难重重。sharding并非万能药,选择是否
sharding、如何sharding、为sharding如何换用一个近似的业务描述方式,这是业务设计需要仔细考虑的问题。
2.Cache
经验会告诉我们,基本上大部分系统的瓶颈会集中在IO/数据库上,常识也告诉我们,网络和内存的速度比IO/数据库会提升甚至不止一个数量级。面向海量服
务,Cache基本上是一个必选项,分布式Cache更是一个不二选择,根据我们的需要,我们可以选择memcached(非持久化)、
memcachedb/Tokyo Tyrant(持久化),甚至构建自己的cache平台。
在使用Cache上,下面是需要仔细考虑的点:
- 选择合适的Cache分布算法,基本上我们会发现使用取模方式决定Cache位置是不可靠的,因为坏节点的摘除或者节点扩量会让我们的Cache
命中率在短时间内下降到冰点,甚至会导致系统在短期内的负载迅速增长甚至于崩溃,选择一种合适的分布算法非常重要,譬如稳定的一致性哈希
- Cache管理:为每个应用配置独立的Cache通常不是一个好主意,我们可以选择在大量的机器上,只要有空闲内存,则运行Cache实例,将
Cache实例分成多个组,每个组就是一个完整的Cache池,而多个应用共享一个Cache池
- 合理的序列化格式:使用紧凑的序列化方案存储Cache数据,尽量少存储冗余数据,一方面可以最大能力地榨取Cache的存储利用率,另一方面,
可以更方便地进行容量预估。此外,不可避免地,随着业务的升级,存储的数据的格式有可能会变更,序列化也需要注意向上兼容的问题,让新格式的客户端能够支
持旧的数据格式。
- 容量估算:在开始运行之前,先为自己的应用可能使用到的容量做一个容量预估,以合理地分配合适的Cache池,同时为可能的容量扩充提供参考。
- 容量监控:Cache命中率怎么样,Cache的存储饱和度怎么样,Client的Socket连接数等等,对这些数据的采集和监控,将为业务的
调整和容量的扩充提供了数据支持。
- 选择在哪层上进行Cache,譬如数据层Cache、应用层Cache和Web层Cache,越靠近数据,Cache的通用性越高,越容易保持
Cache数据的一致性,但相应的处理流程也越长,而越靠近用户,Cache的通用性越差,越难保证Cache数据的一致性,但是响应也越快,根据业务的
特点,选择合适的Cache层是非常重要的。一般而言,我们会选择将粗粒度、极少变更、数据对用户不敏感(即可以一定程度上接受误差)、并且非针对用户级
的数据,在最靠近用户的层面上Cache,譬如图片Cache、TOP
X等数据;而将一些细粒度、变更相对频繁、用户相对敏感的数据或者是针对用户级的数据放在靠近数据的一段,譬如用户的Profile、关系链等。
3.服务集群
面向海量服务,系统的横向扩展基本上是第一要素,在我的经验和经历中,服务集群需要考虑如下因素:
- 分层:合理地对系统进行分层,将系统资源要求不同的部分进行合理地逻辑/物理分层,一般对于简单业务,Client层、WebServer层和
DB层已经足够,对于更复杂业务,可能要切分成Client层、WebServer层、业务层、数据访问层(业务层和数据访问层一般倾向于在物理上处于同
一层)、数据存储层(DB),太多的分层会导致处理流程变长,但相应系统地灵活性和部署性会更强。
- 功能细粒度化:将功能进行细粒度的划分,并使用独立的进程部署,一方面能更有利于错误的隔离,另一方面在功能变更的时候避免一个功能对其他功能产
生影响
- 按数据集部署:如果每一层都允许对下一层所有的服务接口进行访问,将存在几个严重的缺陷,一是随着部署服务的增长,会发现下一层必须允许数量非常
庞大的Socket连接进来,二是我们可能必须把不同的服务部署在不同的数据中心(DC)的不同机房上,即便是上G的光纤专线,机房间的穿梭流量也会变地
不可接受,三是每个服务节点都是全数据容量接入,并不利于做一些有效的内部优化机制,四是只能采用代码级控制的灰度发布和部署。当部署规模达到一定数量级
的时候,按数据集横向切割成多组服务集合,每组服务集合只为特定的数据集服务,在部署上,每组服务集合可以部署在独立的相同的数据中心(DC)上。
- 无状态:状态将为系统的横向扩容带来无穷尽的烦恼。对于状态信息比少的情况,可以选择将全部状态信息放在请求发器端,对于状态信息比较多的情况,
可以考虑维持一个统一的Session中心。
- 选择合适的负载均衡器和负载均衡策略:譬如在L4上负载均衡的LVS、L7上负载均衡的Nginx、甚至是专用的负载均衡硬件F5(L4),对于
在L7上工作的负载均衡器,选择合适的负载均衡策略也非常重要,一般让用户总是负载均衡到同一台后端Server是一个很好的方式
4.灰度发布
当系统的用户增长到一定的规模,一个小小功能的发布也会产生非常大的影响,这个时候,将功能先对一小部分用户开放,并慢慢扩展到全量用户是一个稳妥的做
法,使用灰度化的发布将避免功能的BUG产生大面积的错误。如下是一些常见的灰度控制策略:
- 白名单控制:只有白名单上的用户才允许访问,一般用于全新功能Alpha阶段,只向被邀请的用户开放功能
- 准入门槛控制:常见的譬如gmail出来之初的邀请码、QQ农场开始阶段的X级的黄钻准入,同样一般用于新功能的Beta阶段,慢慢通过一步一步
降低门槛,避免在开始之处由于系统可能潜在的问题或者容量无法支撑导致整个系统的崩溃。
- 按数据集开放:一般常用于成熟的功能的新功能开发,避免新功能的错误产生大面积的影响
5.设计自己的通信协议:二进制协议、向上/下兼容
随着系统的稳定运行访问量的上涨,慢慢会发现,一些看起来工作良好的协议性能变地不可接受,譬如基于xml的协议xml-rpc,将会发现xml解析和包
体的增大变地不可接受,即便是接近于二进制的hessian协议,多出来的字段描述信息(按我的理解,hessian协议是类似于map结构的,包含字段
的名称信息)和基于文本的http头将会使协议效率变地低下。也许,在开始合适的时候而不是到最后不得已的时候,去设计一个良好的基于二进制的高效的内部
通信协议是一个好的方式。按我的经验,设计自己的通信协议需要注意如下几点:
- 协议紧凑性,否则早晚你会为你浪费的空间痛心疾首
- 协议扩展性,早晚会发现旧的协议格式不能适应新的业务需求,而在早期预留变地非常地重要,基本上,参见一些常用的规范,魔术数(对于无效果的请求
可以迅速丢弃)、协议版本信息、协议头、协议Body、每个部分(包括结构体信息中的对象)的长度这些信息是不能省的
- 向下兼容和向上兼容:但功能被大规模地调用的时候,发布一个新的版本,让所有的client同时升级基本上是不可接受的,因此在设计之处就需要考
虑好兼容性的问题
6.设计自己的Application Server
事情进行到需要自己设计通信协议,自己构建Application Server也变地顺理成章,下面是在自己开发Application
Server的时候需要处理的常见的问题:
- 过载保护:当系统的某个部件出现问题的时候,最常见的情况是整个系统的负载出现爆炸性的增长而导致雪崩效应,在设计application
server的时候,必须注意进行系统的过载保护,当请求可以预期无法处理的时候(譬如排队满载或者排队时间过长),丢弃是一个明智的选择,TCP的
backlog参数是一个典型的范例。
- 频率控制:即便是同一系统中的其他应用在调用,一个糟糕的程序可能会将服务的所有资源占完,因此,应用端必须对此做防范措施,频率控制是其中比较
重要的一个
- 异步化/无响应返回:对于一些业务,只需要保证请求会被处理即可,客户端并不关心什么时候处理完,只要最终保证处理就行,甚至最终没有处理也不是
很严重的事情,譬如邮件,对于这种应用,应快速响应避免占着宝贵的连接资源,而将请求进入异步处理队列慢慢处理。
- 自我监控:Application
Server本身应该具备自我监控的功能,譬如性能数据采集、为外部提供内部状态的查询(譬如排队情况、处理线程、等待线程)等
- 预警:譬如当处理变慢、排队太多、发生请求丢弃的情况、并发请求太多的时候,Application
Server应该具备预警的能力以快速地对问题进行处理
- 模块化、模块间松耦合、机制和策略分离:如果不想一下子面对所有的复杂性或不希望在修改小部分而不得不对所有的测试进行回归的话,模块化是一个很
好的选择,将问题进行模块切割,每个模块保持合理的复杂度,譬如对于这里的Application
Server,可以切分成请求接收/管理/响应、协议解析、业务处理、数据采集、监控和预警等等模块。这里同时要注意块间使用松耦合的方式交互,譬如,请
求接收和业务处理之间则可以使用阻塞队列通信的方式降低耦合。另外还需要注意的是机制和策略的分离,譬如协议可能会变更、性能采集和告警的方式可能会变化
等等,事先的机制和策略分离,策略变更的处理将变地更加简单。
7.Client
很多应用会作为服务的Client,去调用其他的服务,如下是在做为Client应该注意的一些问题:
- 服务不可靠:作为Client永远要记住的一点就是,远程服务永远是不可靠的,因此作为Client自己要注意做自我保护,当远程服务如果无法访
问时,做折中处理
- 超时保护:还是上面所说的,远程服务永远都是不可靠的,永远也无法预测到远程什么时候会响应,甚至可能不会响应(譬如远程主机宕机),请求方要做
好超时保护,譬如对于主机不可达的情况,在linux环境下,有时会让客户端等上几分钟TCP层才会最终告诉你服务不可到达。
- 并发/异步:为了提速响应,对于很多可以并行获取的数据,我们总是应该并行地去获取,对于一些我们无法控制的同步接口——譬如读数据库或同步读
cache——虽然不是很完美,但多线程并行去获取是一个可用的选择,而对于服务端都是使用自构建的Application
Server,使用异步Client接口至关重要,将请求全部发送出去,使用异步IO设置超时等待返回即可,甚至于更进一步异步anywhere,在将
client与application
server整合到一块的时候,请求发送出去之后立即返回,将线程/进程资源归还,而在请求响应回来符合条件的时候,触发回调做后续处理。
8.监控和预警
基本上我们会见惯了各种网络设备或服务器的监控,譬如网络流量、IO、CPU、内存等监控数据,然而除了这些总体的运行数据,应用的细粒度化的数据也需要
被监控,服务的访问压力怎么样、处理速度怎么样、性能瓶颈在哪里、带宽主要是被什么应用占、Java虚拟机的CPU占用情况怎么样、各内存区的内存占用情
况如何,这些数据将有利于我们更好的了解系统的运行情况,并对系统的优化和扩容提供数据指导。
除了应用总体监控,特定业务的监控也是一个可选项,譬如定时检查每个业务的每个具体功能点(url)访问是否正常、访问速度如何、页面访问速度如何(用户
角度,包括服务响应时间、页面渲染时间等,即网页测速)、每个页面的PV、每个页面(特别是图片)每天占用的总带宽等等。这些数据将为系统预警和优化提供
数据上的支持,例如对于图片,如果我们知道哪些图片占用的带宽非常大(不一定是图片本身比较大,而可能是访问比较大),则一个小小的优化会节省大量的网络
带宽开销,当然,这些事情对于小规模的访问是没有意义的,网络带宽开销节省的成本可能都没有人力成本高。
除了监控,有效的预警机制也是必不可少,应用是否在很好地提供服务、响应时间是否能够达到要求、系统容量是否达到一个阀值。有效的预警机制将让我们尽快地
对问题进行处理。
9.配置中心化
当系统错误的时候,我们如何尽快地恢复呢,当新增服务节点的时候,如何尽快地让真个系统感知到呢?当系统膨胀之后,如果每次摘除服务节点或者新增节点都需
要修改每台应用配置,那么配置和系统的维护将变地越来越困难。
配置中心化是一个很好的处理这个问题的方案,将所有配置进行统一地存储,而当发生变更的时候(摘除问题节点或者扩量增加服务节点或新增服务),使用一些通
知机制让各应用刷新配置。甚至于,我们可以自动地检测出问题节点并进行智能化的切换。
三、最后
构建面向海量用户的服务,可以说是困难重重挑战重重,一些原则和前人的设计思路可以让我们获得一些帮助,但是更大的挑战会来源于细节部分,按我们技术老大
的说法,原则和思路只要看几本书是个技术人员都会,但决定一个系统架构师能力的,往往却是对细节的处理能力。因此,在掌握原则和前人的设计思路的基础上,
更深入地挖掘技术的细节,才是面向海量用户的服务的制胜之道。
刚换了项目组,接触到了htmlunit,就把官方示例翻译一下,作为入门:
先下载依赖的相关JAR包:http://sourceforge.net/projects/htmlunit/files/
示例1:获取javaeye网站的title
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
public static void getHomeTile() throws FailingHttpStatusCodeException, MalformedURLException, IOException{
final WebClient webClient = new WebClient();
final HtmlPage htmlPage = webClient.getPage("http://www.javaeye.com");
System.out.println(htmlPage.getTitleText());
System.out.println(htmlPage.getTextContent());
}
可以看见打印出:JavaEye Java编程 Spring框架 AJAX技术 Agile敏捷软件开发 ruby on rails实践 - JavaEye做最棒的软件开发交流社区
和javaeye首页的title属性一样
示例二:获取一个特定的浏览器版本
public void homePage_Firefox() throws Exception {
final WebClient webClient = new WebClient(BrowserVersion.FIREFOX_2);
final HtmlPage page = webClient.getPage("http://htmlunit.sourceforge.net");
assertEquals("HtmlUnit - Welcome to HtmlUnit", page.getTitleText());
}
示例三:获取特定ID的DIV或者锚链接
public void getElements() throws Exception {
final WebClient webClient = new WebClient();
final HtmlPage page = webClient.getPage("http://some_url");
final HtmlDivision div = page.getHtmlElementById("some_div_id");
final HtmlAnchor anchor = page.getAnchorByName("anchor_name");
}
实例四:模拟页面提交一个form
public void submittingForm() throws Exception {
final WebClient webClient = new WebClient();
// Get the first page
final HtmlPage page1 = webClient.getPage("http://some_url");
// Get the form that we are dealing with and within that form,
// find the submit button and the field that we want to change.
final HtmlForm form = page1.getFormByName("myform");
final HtmlSubmitInput button = form.getInputByName("submitbutton");
final HtmlTextInput textField = form.getInputByName("userid");
// Change the value of the text field
textField.setValueAttribute("root");
// Now submit the form by clicking the button and get back the second page.
final HtmlPage page2 = button.click();
}
Eclipse 快捷键(1)
Ctrl+1 快速修复(最经典的快捷键,就不用多说了)
Ctrl+D: 删除当前行
Ctrl+Alt+↓ 复制当前行到下一行(复制增加)
Ctrl+Alt+↑ 复制当前行到上一行(复制增加)
Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)
Alt+↑ 当前行和上面一行交互位置(同上)
Alt+← 前一个编辑的页面
Alt+→ 下一个编辑的页面(当然是针对上面那条来说了)
Alt+Enter 显示当前选择资源(工程,or 文件 or文件)的属性
Shift+Enter 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后)
Shift+Ctrl+Enter 在当前行插入空行(原理同上条)
Ctrl+Q 定位到最后编辑的地方
Ctrl+L 定位在某行 (对于程序超过100的人就有福音了)
Ctrl+M 最大化当前的Edit或View (再按则反之)
Ctrl+/ 注释当前行,再按则取消注释
Ctrl+O 快速显示 OutLine
Ctrl+T 快速显示当前类的继承结构
Ctrl+W 关闭当前Editer
Ctrl+K 参照选中的Word快速定位到下一个
Ctrl+E 快速显示当前Editer的下拉列表(如果当前页面没有显示的用黑体表示)
Ctrl+/(小键盘) 折叠当前类中的所有代码
Ctrl+×(小键盘) 展开当前类中的所有代码
Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代替)
Ctrl+Shift+E 显示管理当前打开的所有的View的管理器(可以选择关闭,激活等操作)
Ctrl+J 正向增量查找(按下Ctrl+J后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes
line中显示没有找到了,查一个单词时,特别实用,这个功能Idea两年前就有了)
Ctrl+Shift+J 反向增量查找(和上条相同,只不过是从后往前查)
Ctrl+Shift+F4 关闭所有打开的Editer
Ctrl+Shift+X 把当前选中的文本全部变味小写
Ctrl+Shift+Y 把当前选中的文本全部变为小写
Ctrl+Shift+F 格式化当前代码
Ctrl+Shift+P 定位到对于的匹配符(譬如{}) (从前面定位后面时,光标要在匹配符里面,后面到前面,则反之)
下面的快捷键是重构里面常用的,本人就自己喜欢且常用的整理一下(注:一般重构的快捷键都是Alt+Shift开头的了)
Alt+Shift+R 重命名 (是我自己最爱用的一个了,尤其是变量和类的Rename,比手工方法能节省很多劳动力)
Alt+Shift+M 抽取方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用)
Alt+Shift+C 修改函数结构(比较实用,有N个函数调用了这个方法,修改一次搞定)
Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候)
Alt+Shift+F 把Class中的local变量变为field变量 (比较实用的功能)
Alt+Shift+I 合并变量(可能这样说有点不妥Inline)
Alt+Shift+V 移动函数和变量(不怎么常用)
Alt+Shift+Z 重构的后悔药(Undo)
Ctrl + Z 返回到修改前的状态(编写代码时的后悔药)
Ctrl + Y 与上面的操作相反 (即刚后悔完又后悔)
Shift + / 自动导入类包 (在你写好的类名的右边用这个可导入包)
Ctrl + Shif + / 自动注释代码
Ctrl + Shif + "自动取消已经注释的代码
Ctrl + Shif +O 自动引导类包
MyEclipse 快捷键(2)[b][/b][color=#FF0000][/color]
(1)Ctrl+M切换窗口的大小
(2)Ctrl+Q跳到最后一次的编辑处
(3)F2当鼠标放在一个标记处出现Tooltip时候按F2则把鼠标移开时Tooltip还会显示即Show Tooltip
Description。
F3跳到声明或定义的地方。
F5单步调试进入函数内部。
F6单步调试不进入函数内部,如果装了金山词霸2006则要把“取词开关”的快捷键改成其他的。
F7由函数内部返回到调用处。
F8一直执行到下一个断点。
(4)Ctrl+Pg~对于XML文件是切换代码和图示窗口
(5)Ctrl+Alt+I看Java文件中变量的相关信息
(6)Ctrl+PgUp对于代码窗口是打开“Show List”下拉框,在此下拉框里显示有最近曾打开的文件
(7)Ctrl+/ 在代码窗口中是这种//~注释。
Ctrl+Shift+/ 在代码窗口中是这种/*~*/注释,在JSP文件窗口中是<!--~-->。
(8)Alt+Shift+O(或点击工具栏中的Toggle Mark Occurrences按钮)
当点击某个标记时可使本页面中其他地方的此标记黄色凸显,并且窗口的右边框会出现白色的方块,点击此方块会跳到此标记处。
(9)右击窗口的左边框即加断点的地方选Show Line Numbers可以加行号。
(10)Ctrl+I格式化激活的元素Format Active Elements。
Ctrl+Shift+F格式化文件Format Document。
(11)Ctrl+S保存当前文件。
Ctrl+Shift+S保存所有未保存的文件。
(12)Ctrl+Shift+M(先把光标放在需导入包的类名上) 作用是加Import语句。
Ctrl+Shift+O作用是缺少的Import语句被加入,多余的Import语句被删除。
(13)Ctrl+Space提示键入内容即Content
Assist,此时要将输入法中Chinese(Simplified)IME-Ime/Nonlme
Toggle的快捷键(用于切换英文和其他文字)改成其他的。
Ctrl+Shift+Space提示信息即Context Information。
(14)双击窗口的左边框可以加断点。
(15)Ctrl+D删除当前行。
|