Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

在 Web 项目中应用 Apache Shiro

$
0
0

Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁? Web 应用程序一般做法通过表单提交用户名及密码达到认证目的。“授权”即是否允许已认证用户访问受保护资源。关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍 Shiro 在 Web Application 中如何实现验证码认证以及如何实现单点登录。

9 评论:

杨 晓晋, 软件工程师, 深圳市雁联计算系统有限公司

2013 年 1 月 31 日

  • +内容

用户权限模型

在揭开 Shiro 面纱之前,我们需要认知用户权限模型。本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。即能证明“你是谁?”、“你能访问多少 受保护资源?”。为实现一个较为灵活的用户权限数据模型,通常把用户信息单独用一个实体表示,用户权限信息用两个实体表示。

  1. 用户信息用 LoginAccount 表示,最简单的用户信息可能只包含用户名 loginName 及密码 password 两个属性。实际应用中可能会包含用户是否被禁用,用户信息是否过期等信息。
  2. 用户权限信息用 Role 与 Permission 表示,Role 与 Permission 之间构成多对多关系。Permission 可以理解为对一个资源的操作,Role 可以简单理解为 Permission 的集合。
  3. 用户信息与 Role 之间构成多对多关系。表示同一个用户可以拥有多个 Role,一个 Role 可以被多个用户所拥有。
图 1. 用户权限模型

图 1. 用户权限模型

回页首

认证与授权

Shiro 认证与授权处理过程

  • 被 Shiro 保护的资源,才会经过认证与授权过程。使用 Shiro 对 URL 进行保护可以参见“与 Spring 集成”章节。
  • 用户访问受 Shiro 保护的 URL;例如 http://host/security/action.do。
  • Shiro 首先检查用户是否已经通过认证,如果未通过认证检查,则跳转到登录页面,否则进行授权检查。认证过程需要通过 Realm 来获取用户及密码信息,通常情况我们实现 JDBC Realm,此时用户认证所需要的信息从数据库获取。如果使用了缓存,除第一次外用户信息从缓存获取。
  • 认证通过后接受 Shiro 授权检查,授权检查同样需要通过 Realm 获取用户权限信息。Shiro 需要的用户权限信息包括 Role 或 Permission,可以是其中任何一种或同时两者,具体取决于受保护资源的配置。如果用户权限信息未包含 Shiro 需要的 Role 或 Permission,授权不通过。只有授权通过,才可以访问受保护 URL 对应的资源,否则跳转到“未经授权页面”。

Shiro Realm

在 Shiro 认证与授权处理过程中,提及到 Realm。Realm 可以理解为读取用户信息、角色及权限的 DAO。由于大多 Web 应用程序使用了关系数据库,因此实现 JDBC Realm 是常用的做法,后面会提到 CAS Realm,另一个 Realm 的实现。

清单 1. 实现自己的 JDBC Realm
 public class MyShiroRealm extends AuthorizingRealm{ 
   // 用于获取用户信息及用户权限信息的业务接口
   private BusinessManager businessManager; 
    
   // 获取授权信息
   protected AuthorizationInfo doGetAuthorizationInfo( 
      PrincipalCollection principals) { 
      String username = (String) principals.fromRealm( 
         getName()).iterator().next(); 
      
      if( username != null ){ 
      // 查询用户授权信息
         Collection<String> pers=businessManager.queryPermissions(username); 
         if( pers != null && !pers.isEmpty() ){ 
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
            for( String each:pers ) 
               info.addStringPermissions( each ); 
            return info; 
         } 
      } 
      return null; 
   } 
   // 获取认证信息
   protected AuthenticationInfo doGetAuthenticationInfo( 
      AuthenticationToken authcToken ) throws AuthenticationException { 
      UsernamePasswordToken token = (UsernamePasswordToken) authcToken; 
      // 通过表单接收的用户名
      String username = token.getUsername(); 
      
      if( username != null && !"".equals(username) ){ 
         LoginAccount account = businessManager.get( username ); 
         if( account != null ){ 
            return new SimpleAuthenticationInfo( 
               account.getLoginName(),account.getPassword(),getName() ); 
         } 
      } 
      return null; 
   } 
 }

代码说明:

  1. businessManager 表示从数据库获取用户信息及用户权限信息的业务类,实际情况中可能因用户权限模型设计不同或持久化框架选择不同,这里没给出示例代码。
  2. doGetAuthenticationInfo 方法,取用户信息。对照用户权限模型来说,就是取 LoginAccount 实体。最终我们需要为 Shiro 提供 AuthenticationInfo 对象。
  3. doGetAuthorizationInfo 方法,获取用户权限信息。代码给出了获取用户 Permission 的示例,获取用户 Role 的代码类似。为 Shiro 提供的用户权限信息以 AuthorizationInfo 对象形式返回。

回页首

为何对 Shiro 情有独钟

或 许有人要问,我一直在使用 Spring,应用程序的安全组件早已选择了 Spring Security,为什么还需要 Shiro ?当然,不可否认 Spring Security 也是一款优秀的安全控制组件。本文的初衷不是让您必须选择 Shiro 以及必须放弃 Spring Security,秉承客观的态度,下面对两者略微比较:

  1. 简单性,Shiro 在使用上较 Spring Security 更简单,更容易理解。
  2. 灵活性,Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
  3. 可插拔,Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。Spring Security 在这方面就显得有些捉衿见肘。

回页首

与 Spring 集成

在 Java Web Application 开发中,Spring 得到了广泛使用;与 EJB 相比较,可以说 Spring 是主流。Shiro 自身提供了与 Spring 的良好支持,在应用程序中集成 Spring 十分容易。

有了前面提到的用户权限数据模型,并且实现了自己的 Realm,我们就可以开始集成 Shiro 为应用程序服务了。

Shiro 的安装

Shiro 的安装非常简单,在 Shiro 官网下载 shiro-all-1.2.0.jar、shiro-cas-1.2.0.jar(单点登录需要),及 SLF4J 官网下载 Shiro 依赖的日志组件 slf4j-api-1.6.1.jar。Spring 相关的 JAR 包这里不作列举。这些 JAR 包需要放置到 Web 工程 /WEB-INF/lib/ 目录。至此,剩下的就是配置了。

配置过滤器

首先,配置过滤器让请求资源经过 Shiro 的过滤处理,这与其它过滤器的使用类似。

清单 2. web.xml 配置
<filter> <filter-name>shiroFilter</filter-name> <filter-class> 
      org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

Spring 配置

接下来仅仅配置一系列由 Spring 容器管理的 Bean,集成大功告成。各个 Bean 的功能见代码说明。

清单 3. Spring 配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.do"/> <property name="successUrl" value="/welcome.do"/> <property name="unauthorizedUrl" value="/403.do"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> 
         /=anon 
         /login.do*=authc 
         /logout.do*=anon 
         # 权限配置示例
         /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] 
         
         /** = authc 
      </value> </property> </bean> <bean id="securityManager" 
   class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"/> </bean> <bean id="myShiroRealm" class="xxx.packagename.MyShiroRealm"> <!-- businessManager 用来实现用户名密码的查询 --> <property name="businessManager" ref="businessManager"/> <property name="cacheManager" ref="shiroCacheManager"/> </bean> <bean id="lifecycleBeanPostProcessor" 
    class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean id="shiroCacheManager" 
   class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean> <bean id="formAuthenticationFilter" 
   class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>

代码说明:

  1. shiroFilter 中 loginUrl 为登录页面地址,successUrl 为登录成功页面地址(如果首先访问受保护 URL 登录成功,则跳转到实际访问页面),unauthorizedUrl 认证未通过访问的页面(前面提到的“未经授权页面”)。
  2. shiroFilter 中 filters 属性,formAuthenticationFilter 配置为基于表单认证的过滤器。
  3. shiroFilter 中 filterChainDefinitions 属性,anon 表示匿名访问(不需要认证与授权),authc 表示需要认证,perms[SECURITY_ACCOUNT_VIEW] 表示用户需要提供值为“SECURITY_ACCOUNT_VIEW”Permission 信息。由此可见,连接地址配置为 authc 或 perms[XXX] 表示为受保护资源。
  4. securityManager 中 realm 属性,配置为我们自己实现的 Realm。关于 Realm,参见前面“Shiro Realm”章节。
  5. myShiroRealm 为我们自己需要实现的 Realm 类,为了减小数据库压力,添加了缓存机制。
  6. shiroCacheManager 是 Shiro 对缓存框架 EhCache 的配置。

回页首

实现验证码认证

验证码是有效防止暴力破解的一种手段,常用做法是在服务端产生一串随机字符串与当前用户会话关联(我们通常说的放入 Session),然后向终端用户展现一张经过“扰乱”的图片,只有当用户输入的内容与服务端产生的内容相同时才允许进行下一步操作。

产生验证码

作为演示,我们选择开源的验证码组件 kaptcha。这样,我们只需要简单配置一个 Servlet,页面通过 IMG 标签就可以展现图形验证码。

清单 4. web.xml 配置
<!-- captcha servlet--> <servlet> <servlet-name>kaptcha</servlet-name> <servlet-class> 
      com.google.code.kaptcha.servlet.KaptchaServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>kaptcha</servlet-name> <url-pattern>/images/kaptcha.jpg</url-pattern> </servlet-mapping>

扩展 UsernamePasswordToken

Shiro 表单认证,页面提交的用户名密码等信息,用 UsernamePasswordToken 类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:

清单 5. CaptchaUsernamePasswordToken
 public class CaptchaUsernamePasswordToken extends UsernamePasswordToken{ 

 private String captcha; 

 // 省略 getter 和 setter 方法

 public CaptchaUsernamePasswordToken(String username, char[] password, 
 boolean rememberMe, String host,String captcha) { 
 super(username, password, rememberMe, host); 
 this.captcha = captcha; 
 } 
 }

扩展 FormAuthenticationFilter

接下来我们 扩展 FormAuthenticationFilter 类,首先覆盖 createToken 方法,以便获取 CaptchaUsernamePasswordToken 实例;然后增加验证码校验方法 doCaptchaValidate;最后覆盖 Shiro 的认证方法 executeLogin,在原表单认证逻辑处理之前进行验证码校验。

清单 6. CaptchaUsernamePasswordToken
 public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter{ 

   public static final String DEFAULT_CAPTCHA_PARAM = "captcha"; 
   private String captchaParam = DEFAULT_CAPTCHA_PARAM; 
   public String getCaptchaParam() { 
      return captchaParam; 
   } 
   public void setCaptchaParam(String captchaParam) { 
      this.captchaParam = captchaParam; 
   } 
   protected String getCaptcha(ServletRequest request) { 
      return WebUtils.getCleanParam(request, getCaptchaParam()); 
   } 
   // 创建 Token 
   protected CaptchaUsernamePasswordToken createToken( 
      ServletRequest request, ServletResponse response) { 
   
      String username = getUsername(request); 
      String password = getPassword(request); 
      String captcha = getCaptcha(request); 
      boolean rememberMe = isRememberMe(request); 
      String host = getHost(request); 
                   
      return new CaptchaUsernamePasswordToken( 
         username, password, rememberMe, host,captcha); 
   } 
   
   // 验证码校验
   protected void doCaptchaValidate( HttpServletRequest request 
      ,CaptchaUsernamePasswordToken token ){ 

      String captcha = (String)request.getSession().getAttribute( 
         com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); 
      
      if( captcha!=null && 
         !captcha.equalsIgnoreCase(token.getCaptcha()) ){ 
         throw new IncorrectCaptchaException ("验证码错误!"); 
      } 
   } 
   // 认证
   protected boolean executeLogin(ServletRequest request, 
      ServletResponse response) throws Exception { 
      CaptchaUsernamePasswordToken token = createToken(request, response); 
       
      try { 
         doCaptchaValidate( (HttpServletRequest)request,token ); 
                 
         Subject subject = getSubject(request, response); 
         subject.login(token); 
                     
         return onLoginSuccess(token, subject, request, response); 
      } catch (AuthenticationException e) { 
         return onLoginFailure(token, e, request, response); 
      } 
   } 

 }

代码说明:

  1. 添加 captchaParam 变量,为的是页面表单提交验证码的参数名可以进行灵活配置。
  2. doCaptchaValidate 方法中,验证码校验使用了框架 KAPTCHA 所提供的 API。

添加 IncorrectCaptchaException

前面验证码校验不通过,我们抛出一个异常 IncorrectCaptchaException,此类继承 AuthenticationException,之所以需要扩展一个新的异常类,为的是在页面能更精准显示错误提示信息。

清单 7. IncorrectCaptchaException
 public class IncorrectCaptchaException extends AuthenticationException{ 
   public IncorrectCaptchaException() { 
      super(); 
   } 
   public IncorrectCaptchaException(String message, Throwable cause) { 
      super(message, cause); 
   } 
   public IncorrectCaptchaException(String message) { 
      super(message); 
   } 
   public IncorrectCaptchaException(Throwable cause) { 
      super(cause); 
   } 
 }

页面展现验证码错误提示信息

清单 8. 页面认证错误信息展示
 Object obj=request.getAttribute( 
   org.apache.shiro.web.filter.authc.FormAuthenticationFilter 
      .DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); 
 AuthenticationException authExp = (AuthenticationException)obj; 
 if( authExp != null ){ 
   String expMsg=""; 
   if(authExp instanceof UnknownAccountException || 
      authExp instanceof IncorrectCredentialsException){ 
      expMsg="错误的用户账号或密码!"; 
   }else if( authExp instanceof IncorrectCaptchaException){ 
      expMsg="验证码错误!"; 
   }else{ 
      expMsg="登录异常 :"+authExp.getMessage() ; 
   } 
   out.print("<div class=\"error\">"+expMsg+"</div>"); 
 }

回页首

实现单点登录

前 面章节,我们认识了 Shiro 的认证与授权,并结合 Spring 作了集成实现。现实中,有这样一个场景,我们拥有很多业务系统,按照前面的思路,如果访问每个业务系统,都要进行认证,这样是否有点难让人授受。有没有一 种机制,让我们只认证一次,就可以任意访问目标系统呢?

上面的场景,就是我们常提到的单点登录 SSO。Shiro 从 1.2 版本开始对 CAS 进行支持,CAS 就是单点登录的一种实现。

Shiro CAS 认证流程

  • 用户首次访问受保护的资源;例如 http://casclient/security/view.do
  • 由于未通过认证,Shiro 首先把请求地址(http://casclient/security/view.do)缓存起来。
  • 然后跳转到 CAS 服务器进行登录认证,在 CAS 服务端认证完成后需要返回到请求的 CAS 客户端,因此在请求时,必须在参数中添加返回地址 ( 在 Shiro 中名为 CAS Service)。 例如 http://casserver/login?service=http://casclient/shiro-cas
  • 由 CAS 服务器认证通过后,CAS 服务器为返回地址添加 ticket。例如 http://casclient/shiro-cas?ticket=ST-4-BWMEnXfpxfVD2jrkVaLl-cas
  • 接下来,Shiro 会校验 ticket 是否有效。由于 CAS 客户端不提供直接认证,所以 Shiro 会向 CAS 服务端发起 ticket 校验检查,只有服务端返回成功时,Shiro 才认为认证通过。
  • 认证通过,进入授权检查。Shiro 授权检查与前面提到的相同。
  • 最后授权检查通过,用户正常访问到 http://casclient/security/view.do。

CAS Realm

Shiro 提供了一个名为 CasRealm 的类,与前面提到的 JDBC Realm 相似,该类同样包括认证和授权两部分功能。认证就是校验从 CAS 服务端返回的 ticket 是否有效;授权还是获取用户权限信息。

实现单点登录功能,需要扩展 CasRealm 类。

清单 9. Shiro CAS Realm
 public class MyCasRealm extends CasRealm{ 
   // 获取授权信息
   protected AuthorizationInfo doGetAuthorizationInfo( 
      PrincipalCollection principals) { 
      //... 与前面 MyShiroRealm 相同
   } 
   
    public String getCasServerUrlPrefix() { 
      return "http://casserver/login"; 
   } 
   public String getCasService() { 
      return "http://casclient/shiro-cas"; 
   } 
   16 
 }

代码说明:

  1. doGetAuthorizationInfo 获取授权信息与前面章节“实现自己的 JDBC Realm”相同。
  2. 认证功能由 Shiro 自身提供的 CasRealm 实现。
  3. getCasServerUrlPrefix 方法返回 CAS 服务器地址,实际使用一般通过参数进行配置。
  4. getCasService 方法返回 CAS 客户端处理地址,实际使用一般通过参数进行配置。
  5. 认证过程需 keystore,否则会出现异常。可以通过设置系统属性的方式来指定,例如 System.setProperty("javax.net.ssl.trustStore","keystore-file");

CAS Spring 配置

实现单点登录的 Spring 配置与前面类似,不同之处参见代码说明。

清单 10. Shiro CAS Spring 配置
<bean id="shiroFilter" 
   class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" 
      value="http://casserver/login?service=http://casclient/shiro-cas"/> <property name="successUrl" value="/welcome.do"/> <property name="unauthorizedUrl" value="/403.do"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="cas" value-ref="casFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> 
         /shiro-cas*=cas 
         /logout.do*=anon 
         /casticketerror.do*=anon 
         # 权限配置示例
         /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] 
          
         /** = authc 
      </value> </property> </bean> <bean id="securityManager" 
   class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"/> </bean> <bean id="lifecycleBeanPostProcessor" 
   class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- CAS Realm --> <bean id="myShiroRealm" class="xxx.packagename.MyCasRealm"> <property name="cacheManager" ref="shiroCacheManager"/> </bean> <bean id="shiroCacheManager" 
   class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean> <bean id="formAuthenticationFilter" 
   class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/> <!-- CAS Filter --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="casticketerror.do"/> </bean>

代码说明:

  1. shiroFilter 中 loginUrl 属性,为登录 CAS 服务端地址,参数 service 为服务端的返回地址。
  2. myShiroRealm 为上一节提到的 CAS Realm。
  3. casFilter 中 failureUrl 属性,为 Ticket 校验不通过时展示的错误页面。

回页首

总结

至此,我们对 Shiro 有了较为深入的认识。Shiro 灵活,功能强大,几乎能满足我们实际应用中的各种情况,还等什么呢?让我开始使用 Shiro 为应用程序护航吧!



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




软件开发工程师的面试应该考察哪些素质,如何做权衡?

$
0
0


首先介绍一下背景
行业:我所在的行业IT很重要但并不是revenue center,属于烧钱部门,地位不能跟纯互联网公司或者游戏公司比
:我们组最近几年的方向都是招一条龙的程序员,以便减少沟通成本和方便问责。这里的一条龙不但包括技术上写前后端和数据库的代码,也包括在公司内部和用户沟通拿需求,测试,发布部署前后的行政工作。基本上出了问题推都推不掉。
个人:我本人从来没有在国内工作过,所以经验上可能会有所偏差。我一般来说都是第二面,第一面主管已经面过了,简历上的问题问的差不多了,所以我很少会问简历上的问题。除非有匪夷所思的经历。

从最近几年招人的经验中得出的结论主要有以下几点。按重要性排序如下:

1 - 态度
这是我最看重的特质,当然这个态度说的不是对我的态度,而是思考问题和解决问题的态度。
个人来说比较偏好的是对自己感兴趣的领域具备‘打破砂锅问到底’的精神,具备这样精神的人往往有一种完美主义,对于自己的代码有强烈的mental attachment,会自己挤时间不断地fix bugs,增加unit tests或者refactoring,长远来看对项目质量非常有好处,组里有这样的人非常省心。(哎,我干嘛帮资本家去想这些)

这样的面试,我一般直接问candidate你最有自信的技术是什么?然后顺着这个不断地深入挖掘。通过“如果你来设计”这样的假设,来考察应聘者对自己自信的技术到底有何种程度的了解,到底有没有想过这些技术为什么会是这样的。

案例1,我对数据库很熟悉
那么“如果你来设计”的问题就会类似这样:
1,如果你来设计数据库,你会怎么管理文件的? 比如我insert了一行数据到employee表,具体在后台发生了什么事情? 会有什么样的写入操作?有哪些东西会被更新
2,好,现在record已经在数据里面了。我跑一句 select * from employee where last_name = 'Smith'。你觉得数据库具体做了哪些事情把这一条数据给返回的? 它是怎么从文件系统中把这一条记录给找出来的?
3,假设我的employee表里已经有一些数据了,这时候我加了一个新的column,会发生什么事情? 文件系统中需要做哪些改动?
4,假设我的employee表里已经有一些数据了,这时候加了一个index,又会发生什么事情? 要写入哪些文件,具备什么特性的数据结构?
5,好,现在已经有index了,那么同样的查询 select * from employee where last_name = 'Smith' 在执行的过程中会有什么区别?看能不能具体展开解释
6,如果你来设计数据库,你会怎么实现join呢?如果两个表都建了索引是怎么做的?如果两个都没有呢?如果一个表有索引?
7, 等等等等。。。。

案例2,我用Spring用得最熟
1,singleton到底有什么用?我用static method为什么不能做同样的事情? 什么情况下会用static method更好一些?
2,dependency injection到底有什么用? interface在这里的作用到底是什么?
3,好,这么看来,spring确实可能在某些情况下有点用,那么如果你来改善spring的话,你会改进哪些点呢?
4,能不能大致讲讲,你觉得autowire具体是怎么实现的?实际使用中会有哪些问题呢?
5,constructor injection和method injection的优劣比较? 什么情况下用哪一个?
6,spring在unit test中起到什么作用,哪些情况下可以不用spring,给一下具体的例子?
7,如果有一天Oracle想在语言层面支持DI,你会怎么设计syntax?为什么?
8,等等等等。。。。

注意:这里主要考察的是思维能力,看看他有没有想过这些问题,而不要纠结于Sybase或者Spring是否真的是这么实现的。我再重复一遍,不要纠结回答的准确性,关键是这个人的思路怎么样,是不是能自圆其说。

这一部分一般持续20分钟到30分钟,取决于candidate的素质和话题本身能不能聊出东西来。可能因为我的这个面试方式比较少见,感觉许多candidate一开始会比较紧张。这时候作为面试官需要不断地给于正面回馈,让他觉得自己还不错,以便放松下来。当然,如果实在是太差的基本就直接“do you have any questions for us”了。

2 - 经验
这个经验不但包括过去公司里从事过哪些项目,也包括平时都做些什么。因为我的行业关系,我感觉纯大公司流或者纯互联网公司流的都有缺憾。前者过于保守死板沉闷,后者过于激进缺乏质量意识,都比较极端。最理想的是在大公司工作一段时间,同时业余维护一个github项目之类的。这种人一般既比较积极主动,又能理解并遵守历史遗留问题。属于兼容并包的好队友。

这一块一般10分钟,取决于有没有东西聊。

3 - 算法
这个可能会引起争议,算法上只要不是太差就行,毕竟我们日常工作牵涉算法的部分不是特别多。 由于一般我们第三面会有别的同时做pair programming,所以我这边都是大概问一个简单的但是实际的问题。举个例子,给你一列区间,如何把它合并起来?
def merge(ranges: List[Int, Int]): List[(Int, Int)] = ???

伪代码写在纸上就行了。就是看看基本的排序递归的使用。具体排序算法也不是非得写出来。

这一块一般15到20分钟。

4 - 对新技术的热情
最近有关注什么新技术吗?angularjs啊,好在哪儿呢?akka啊,解释解释什么是Software Transactional Memory啊?之类的。因为很多人很喜欢聊这些,也算是给自己充充电。

可能是我个人经历的关系,对新技术的热情是一个必要条件,但并不单独构成一个加分项,我见过很多对新技术很有热情,完全只是因为好奇,想尝鲜的想法。对于组和公司的利益却不怎么放在心上。把组里的项目当成试验场,这样的人最后往往需要其他组员帮他擦屁股。

这一块基本就是闲聊,当是收尾了,5到10分钟吧。

— 完 —
本文作者: 庄生

【知乎日报】 你都看到这啦,快来点我嘛 Σ(▼□▼メ)

此问题还有 18 个回答,查看全部。
延伸阅读:
如何面试 iOS 工程师?
作为面试官,你主要考察面试人哪些方面的能力?

Java线程同步中关键字synchronized详述

$
0
0
synchronized关键可以修饰函数、函数内语句。无论它加上方法还是对象上,它取得的锁都是对象,而不是把一段代码或是函数当作锁。

1,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一段时间只能有一个线程得到执行,而另一个线程只有等当前线程执行完以后才能执行这块代码。
2,当一个线程访问object中的一个synchronized(this)同步代码块时,其它线程仍可以访问这个object中是其它非synchronized (this)代码块。
3,这里需要注意的是,当一个线程访问object的一个synchronized(this)代码块时,其它线程对这个object中其它synchronized (this)同步代码块的访问将被阻塞。
4,以上所述也适用于其它的同步代码块,也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,这个线程就获得了object的对象锁。而且每个对象(即类实例)对应着一把锁,每个synchronized(this)都必须获得调用该代码块儿(可以函数,也可以是变量)的对象的锁才能执行,否则所属线程阻塞,方法一旦执行就会独占该锁,直到从方法返回时,也释放这个锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个对象,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个线程可以获取该对象的锁),从而避免了类成员变量的访问冲突。

synchronized方式的缺点:
由于synchronized锁定的是调用这个同步方法的对象,也就是说,当一个线程P1在不同的线程中执行这个方法时,它们之间会形成互斥,从而达到同步的效果。但这里需要注意的是,这个对象所性的Class的另一个对象却可以任意调用这个被加了synchronized关键字的方法。同步方法的实质是将synchronized作用于object reference,对于拿到了P1对象锁的线程才可以调用这个synchronized方法,而对于P2来说,P1与它毫不相干,程序也可能在这种情况下摆脱同步机制的控制,造成数据混乱。以下我们将对这种情况进行详细地说明:
首先我们先介绍synchronized关键字的两种加锁对象:对象和类——synchronized可以为资源加对象锁或是类锁,类锁对这个类的所有对象(实例)均起作用,而对象锁只是针对该类的一个指定的对象加锁,这个类的其它对象仍然可以使用已经对前一个对象加锁的synchronized方法。
在这里我们主要讨论的一个问题就是:“同一个类,不同实例调用同一个方法,会产生同步问题吗?”
同步问题只和资源有关系,要看这个资源是不是静态的。同一个静态数据,你相同函数分属不同线程同时对其进行读写,CPU也不会产生错误,它会保证你代码的执行逻辑,而这个逻辑是否是你想要的,那就要看你需要什么样的同步了。即便你两个不同的代码,在CPU的不同的两个core里跑,同时写一个内存地址,Cache机制也会在L2里先锁定一个。然后更新,再share给另一个core,也不会出错,不然intel,amd就白养那么多人了。
因此,只要你没有两个代码共享的同一个资源或变量,就不会出现数据不一致的情况。而且同一个类的不同对象的调用有完全不同的堆栈,它们之间完全不相干。
以下我们以一个售票过程举例说明,在这里,我们的共享资源就是票的剩余张数。
package com.test;
 
public  class ThreadSafeTest  extends Thread  implements Runnable {
    
   private  static  int  num = 1;
 
     public ThreadSafeTest(String name) {
        setName(name);
    }
 
     public  void run() {
        sell(getName());     
    }
   
     private  synchronized  void sell(String name){
         if ( num > 0) {
            System.  out.println(name + ": 检测票数大于0" );
            System.  out.println(name + ": \t正在收款(大约5秒完成)。。。" );
             try {
                Thread.  sleep(5000);
                System.  out.println(name + ": \t打印票据,售票完成" );
                 num--;
                 printNumInfo();
            }  catch (InterruptedException e) {
                e.printStackTrace();
            }
        }  else {
            System.  out.println(name+": 没有票了,停止售票" );
        }
    }
    
   private  static  void printNumInfo() {
 
        System.  out.println("系统:当前票数:" +  num);
         if ( num < 0) {
            System.  out.println("警告:票数低于0,出现负数" );
        }
    }
 
     public  static  void main(String args[]) {
         try {
             new ThreadSafeTest("售票员李XX" ).start();
            Thread.  sleep(2000);
             new ThreadSafeTest("售票员王X" ).start();
           
        }  catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
运行上述代码,我们得到的输出是:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员王X: 检测票数大于0
售票员王X:  正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X:  打印票据,售票完成
系统:当前票数:-1
警告:票数低于0,出现负数
根据输出结果,我们可以发现,剩余票数为-1,出现了同步错误的问题。之所以出现这种情况的原因是,我们建立的两个实例对象,对共享的静态资源 static  int  num = 1同时进行了修改。那么我们将上面代码中方框内的修饰词static去掉,然后再运行程序,可以得到:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员王X: 检测票数大于0
售票员王X:  正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X:  打印票据,售票完成
系统:当前票数:0
对程度修改之后,程序运行貌似没有问题了,每个对象拥有各自不同的堆栈,分别独立运行。但这样却违背了我们希望多线程同时对共享资源的处理(去static后,num就从共享资源变成了每个实例各自拥有的成员变量),这显然不是我们想要的。
在以上两种代码中,采取的主要是对对象的锁定。由于我之前谈到的原因,当一个类的两个不同的实例对同一共享资源进行修改时,CPU为了保证程序的逻辑会默认这种做法,至于是不是想要的结果,这个只能由程序员自己来决定。因此,我们需要改变锁的作用范围,若作用对象只是实例,那么这种问题是无法避免的;只有当锁的作用范围是整个类的时候,才可能排除同一个类的不同实例对共享资源同时修改的问题。
package com.test;
 
public  class ThreadSafeTest  extends Thread  implements Runnable {
     private  static  int  num = 1;
 
     public ThreadSafeTest(String name) {
        setName(name);
    }
 
     public  void run() {
         sell(getName());     
    }   
    
  private  synchronized  static  void sell(String name){
 
         if ( num > 0) {
            System.  out.println(name + ": 检测票数大于0" );
            System.  out.println(name + ": \t正在收款(大约5秒完成)。。。" );
             try {
                Thread.  sleep(5000);
                System.  out.println(name + ": \t打印票据,售票完成" );
                 num--;
                 printNumInfo();
            }  catch (InterruptedException e) {
                e.printStackTrace();
            }
        }  else {
            System.  out.println(name+": 没有票了,停止售票" );
        }
    }
 
     private  static  void printNumInfo() {
        System.  out.println("系统:当前票数:" +  num);
         if ( num < 0) {
            System.  out.println("警告:票数低于0,出现负数" );
        }
    }
 
     public  static  void main(String args[]) {
         try {
             new ThreadSafeTest("售票员李XX" ).start();
            Thread.  sleep(2000);
             new ThreadSafeTest("售票员王X" ).start();
           
        }  catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
将程序做如上修改,可以得到运行结果:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X: 没有票了,停止售票
对sell()方法加上了static修饰符,这样就将锁的作用对象变成了类,当该类的一个实例对共享变量进行操作时将会阻塞这个类的其它实例对其的操作。从而得到我们如期想要的结果。
总结:
1,synchronized关键字有两种用法:synchronized方法和synchronized块。
2,在Java中不单是类实例,每一个类也可以对应一把锁

在使用synchronized关键字时,有以下几点儿需要注意:
1,synchronized关键字不能被继承。虽然可以用synchronized来定义方法,但是synchronized却并不属于方法定义的一部分,所以synchronized关键字并不能被继承。如果父类中的某个方法使用了synchronized关键字,而子类中也覆盖了这个方法,默认情况下子类中的这个方法并不是同步的,必须显示的在子类的这个方法中加上synchronized关键字才可。当然,也可以在子类中调用父类中相应的方法,这样虽然子类中的方法并不是同步的,但子类调用了父类中的同步方法,也就相当子类方法也同步了。如,
在子类中加synchronized关键字:
class Parent { 
     public  synchronized  void method() {   } 
class Child  extends Parent { 
     public  synchronized  void method () {   } 
}
调用父类方法:
class Parent { 
     public  synchronized  void method() {   } 
class Child  extends Parent { 
     public  void method() {  super.method();   } 
}
2,在接口方法定义时不能使用synchronized关键字。
3,构造方法不能使用synchronized关键字,但可以使用synchronized块来进行同步。
4,synchronized位置可以自由放置,但是不能放置在方法的返回类型后面。
5,synchronized关键字不可以用来同步变量,如下面代码是错误的:
public  synchronized  int n = 0; 
public  static  synchronized  int n = 0;
6,虽然使用synchronized关键字是最安全的同步方法,但若是大量使用也会造成不必要的资源消耗以及性能损失。从表面上看synchronized锁定的是一个方法,但实际上锁定的却是一个类,比如,对于两个非静态方法method1()和method2()都使用了synchronized关键字,在执行其中的一个方法时,另一个方法是不能执行的。静态方法和非静态方法情况类似。但是静态方法和非静态方法之间不会相互影响,见如下代码:
public  class MyThread1  extends Thread { 
     public String methodName ; 
 
     public  static  void method(String s) { 
        System.  out .println(s); 
         while ( true ); 
    } 
     public  synchronized  void method1() { 
         method( "非静态的method1方法" ); 
    } 
     public  synchronized  void method2() { 
         method( "非静态的method2方法" ); 
    } 
     public  static  synchronized  void method3() { 
         method( "静态的method3方法" ); 
    } 
     public  static  synchronized  void method4() { 
         method( "静态的method4方法" ); 
    } 
     public  void run() { 
         try { 
            getClass().getMethod( methodName ).invoke(  this); 
        } 
         catch (Exception e) { 
        } 
    } 
     public  static  void main(String[] args)  throws Exception { 
        MyThread1 myThread1 =  new MyThread1(); 
         for ( int i = 1; i <= 4; i++) { 
            myThread1. methodName = "method" + String. valueOf (i); 
             new Thread(myThread1).start(); 
             sleep(100); 
        } 
    } 
}
运行结果为:
非静态的method1方法
静态的method3方法
从上面的运行结果可以看出,method2和method4在method1和method3运行完之前是不会运行的。因此,可以得出一个结论,如查在类中使用synchronized来定义非静态方法,那么将影响这个类中的所有synchronized定义的非静态方法;如果定义的静态方法,那么将影响这个类中所有以synchronized定义的静态方法。这有点儿像数据表中的表锁,当修改一条记录时,系统就将整个表都锁住了。因此,大量使用这种同步方法会使程序的性能大幅度地下降。

对共享资源的同步访问更加安全的技巧:
1,定义private的instance变量+它的get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象可以在外界绕过同步方法的控制而直接取得它,并且改动它。这也是JavaBean的标准实现之一。
2,如果instance变量是一个对象,如数组或ArrayList等,那上述方法仍然不安全,因为当外界通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且只返回这个private对象的clone()。这样,调用端得到的就只是对象副本的一个引用了。
 
 
 
 
参考资料:

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



FFmpeg获取DirectShow设备数据(摄像头,录屏)

$
0
0

这两天研究了FFmpeg获取DirectShow设备数据的方法,在此简单记录一下以作备忘。本文所述的方法主要是对应Windows平台的。

1.       列设备

ffmpeg -list_devices true -f dshow -i dummy

命令执行后输出的结果如下(注:中文的设备会出现乱码的情况)。列表显示设备的名称很重要,输入的时候都是使用“-f dshow -i video="{设备名}"”的方式。


我自己的机器上列出了以下设备:

[dshow @0388f5e0] DirectShow video devices
[dshow @0388f5e0]  "Integrated Camera"
[dshow @0388f5e0] "screen-capture-recorder"
[dshow @0388f5e0] DirectShow audio devices
[dshow @0388f5e0]  "鍐呰楹﹀厠椋?(Conexant20672 SmartAudi"
[dshow @0388f5e0]  "virtual-audio-capturer"

下文的测试中,使用其中的两个视频输入:"Integrated Camera"和"screen-capture-recorder"。

 注:音频设备出现乱码,这个问题还没有解决。

2.       获取摄像头数据(保存为本地文件或者发送实时流)

2.1. 编码为H.264,保存为本地文件

下面这条命令,实现了从摄像头读取数据并编码为H.264,最后保存成mycamera.mkv。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 mycamera.mkv

2.2. 直接播放摄像头的数据

使用ffplay可以直接播放摄像头的数据,命令如下:

ffplay -f dshow -i video="Integrated Camera"

如果设备名称正确的话,会直接打开本机的摄像头,如图所示。


注:除了使用DirectShow作为输入外,使用VFW也可以读取到摄像头的数据,例如下述命令可以播放摄像头数据:

ffplay -f vfwcap -i 0

2.3. 编码为H.264,发布UDP

下面这条命令,实现了:获取摄像头数据->编码为H.264->封装为UDP并发送至组播地址。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f h264 udp://233.233.233.223:6666

注1:考虑到提高libx264的编码速度,添加了-preset:v ultrafast和-tune:v zerolatency两个选项。

注2:高分辨率的情况下,使用UDP可能出现丢包的情况。为了避免这种情况,可以添加–s 参数(例如-s 320x240)调小分辨率。

2.4. 编码为H.264,发布RTP

下面这条命令,实现了:获取摄像头数据->编码为H.264->封装为RTP并发送至组播地址。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f rtp rtp://233.233.233.223:6666>test.sdp

注1:考虑到提高libx264的编码速度,添加了-preset:v ultrafast和-tune:v zerolatency两个选项。

注2:结尾添加“>test.sdp”可以在发布的同时生成sdp文件。该文件可以用于该视频流的播放。

2.5. 编码为H.264,发布RTMP

下面这条命令,实现了:获取摄像头数据->编码为H.264->并发送至RTMP服务器。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f flv rtmp://localhost/oflaDemo/livestream

2.6. 编码为MPEG2,发布UDP

与编码为H.264类似,指明-vcodec即可。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec mpeg2video -f mpeg2video udp://233.233.233.223:6666

播放MPEG2的UDP流如下。指明-vcodec为mpeg2video即可

ffplay -vcodec mpeg2video udp://233.233.233.223:6666

 

3.       屏幕录制(Windows平台下保存为本地文件或者发送实时流)

Linux下使用FFmpeg进行屏幕录制相对比较方便,可以使用x11grab,使用如下的命令:

ffmpeg -f x11grab -s 1600x900 -r 50 -vcodec libx264 –preset:v ultrafast –tune:v zerolatency -crf 18 -f mpegts udp://localhost:1234

详细时使用方式可以参考这篇文章: DesktopStreaming With FFmpeg for Lower Latency

Linux录屏在这里不再赘述。在Windows平台下屏幕录像则要稍微复杂一些。在Windows平台下,使用-dshow取代x11grab。一句话介绍:注册录屏dshow滤镜(例如screen-capture-recorder),然后通过dshow获取录屏图像然后编码处理。

因此,在使用FFmpeg屏幕录像之前,需要先安装dshow滤镜。在这里推荐一个软件:screen capture recorder。安装这个软件之后,就可以通过FFmpeg屏幕录像了。

 

screen capture recorder项目主页:

http://sourceforge.net/projects/screencapturer/

下载地址:

http://sourceforge.net/projects/screencapturer/files

下载完后,一路“Next”即可安装完毕。注意,需要Java运行环境(Java Runtime Environment),如果没有的话下载一个就行。

screen capture recorder本身就可以录屏,不过这里我们使用FFmpeg进行录屏。


3.1. 编码为H.264,保存为本地文件

下面的命令可以将屏幕录制后编码为H.264并保存为本地文件。

ffmpeg -f dshow -i video="screen-capture-recorder" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency MyDesktop.mkv

注:“-r 5”的意思是把帧率设置成5。         

最后得到的效果如下图。


此外,也可以录声音,声音输入可以分成两种:一种是真人说话的声音,通过话筒输入;一种是虚拟的声音,即录屏的时候电脑耳机里的声音。下面两条命令可以分别录制话筒的声音和电脑耳机里的声音。

录屏,伴随话筒输入的声音

ffmpeg -f dshow -i video="screen-capture-recorder" -f dshow -i audio="鍐呰楹﹀厠椋?(Conexant 20672SmartAudi" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec libmp3lame MyDesktop.mkv

上述命令有问题:audio那里有乱码,把乱码ANSI转UTF-8之后(乱码变成“内装麦克”)依然不行,这个问题先留着,以后有时间再解决。

PS:感觉这条命令适合做讲座之类的时候使用

 

录屏,伴随耳机输入的声音

ffmpeg -f dshow -i video="screen-capture-recorder" -f dshow -i audio="virtual-audio-capturer" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec libmp3lame MyDesktop.mkv

PS:测这条命令的时候,这在听歌,因此录制的视频中的音频就是那首歌曲。

 

3.2. 编码为H.264,发布UDP

下面的命令可以将屏幕录制后编码为H.264并封装成UDP发送到组播地址

ffmpeg -f dshow -i video="screen-capture-recorder" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f h264 udp://233.233.233.223:6666

注1:考虑到提高libx264的编码速度,添加了-preset:v ultrafast和-tune:v zerolatency两个选项。

注2:高分辨率的情况下,使用UDP可能出现丢包的情况。为了避免这种情况,可以添加–s 参数(例如-s 320x240)调小分辨率。

3.3. 编码为H.264,发布RTP

下面的命令可以将屏幕录制后编码为H.264并封装成RTP并发送到组播地址

ffmpeg -f dshow -i video="screen-capture-recorder" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f rtp rtp://233.233.233.223:6666>test.sdp

注1:考虑到提高libx264的编码速度,添加了-preset:v ultrafast和-tune:v zerolatency两个选项。

注2:结尾添加“>test.sdp”可以在发布的同时生成sdp文件。该文件可以用于该视频流的播放。如下命令即可播放:

ffplay test.sdp

3.4. 编码为H.264,发布RTMP

原理同上,不再赘述。

ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f flv rtmp://localhost/oflaDemo/livestream

注意:播放RTMP的时候,-max_delay参数会比较明显的影响延迟,将此参数值设定小一些,有利于降低延时。

ffplay -max_delay 100000 "rtmp://localhost/oflaDemo/livestream live=1"


作者:leixiaohua1020 发表于2014-8-2 0:57:27 原文链接
阅读:301 评论:1 查看评论

十一问MongoDB CTO,谈NoSQL人气王的扩展、事务及运维

$
0
0

英文原文: 11 questions for MongoDB's CTO

在“ MongoDB 成为首位 10 亿美元初创”一文中,我们曾介绍过这个千禧年的宠儿——NoSQL 领域的人气王,只通过 6 年时间就将公司市值发展到 12 亿美元,其成果相当于著名开源公司 Red Hat 20 年的发展。

总结 MongoDB 的成功之路,一大部分归功于 Web 开发者,因为作为一个文档数据库,在许多场景下它都优于 RDBMS,同时还可以获得非常高的读性能。此外,动态、灵活的模式更可以让用户在商用服务器上轻松的进行横向扩展。

然而还是有很多潜在用户抱有这样的担心——MongoDB 的成功是否建立在过度的炒作之下。同时,有些则是担心 MongoDB 还不够成熟,认为其只适合某些 Web 应用,并且在事务上存在很大的风险。为了弄清这些问题,近日,InfoWorld 的 Eric Knorr 走访了 MongoDB CTO 兼联合创始人 Eliot Horowitz。

下为采访译文

Eric:对于 MongoDB,业内通常会有这样一个说法,MongoDB 只适合初创公司,可以用它很方便的进行扩展。但是对于变化较少的企业级应用程序来说,这点似乎并不需要?

Eliot:我在与许多企业 CIO 交流的过程发现他们受许多问题困扰,其中之一就是一个项目究竟需要多少个开发者。另一个问题就是,有些想做的项目无法完成,可能是资源不足,也能使花费时间太长。

但有一点是肯定的,在使用 MongoDB 之后,这些问题出现的频率减少,他们可以更快的完成一个事情。 企业往往获益于将系统拆分为多个可以交互的小型系统,而这些更小的系统完全可以看成是“初创公司” 。

Eric:你口中的他们从事的项目类型是?

Eliot:通常情况是获得某个方面的 single view,比如用户。他们期望对大量不同的数据源进行抓取,然后清洗转换成一个易于观察的 single view。

Eric:如果这么来看的话,这似乎是 CRM 的主要应用场景?

Eliot:这和 CRM 有很多区别,主要的不同在于,如果用户拥有 72 个不同的 CRM 系统,那么将这些系统整合将难于登天。另外,还会存在风险问题,如果你拥有 20 个不同风险需求的系统,也期望采用不同的通信方式,这样的话你就需要一个可以连接不同系统的服务。

Eric:即使发展至今,NoSQL 的事务处理上仍然存在疑问,对于这一点你怎么看?

Eliot:为了更好的实现事务功能,MongoDB 加入了越来越多的特性。同时,因为 MongoDB 本质上是一个分布式系统,所以你不需要担心因为单一磁盘故障所造成的系统崩溃。

实际情况中,可能会使用两个独立的数据中心,对比只在一个物理硬盘上操作,用户将获得更强的可靠性,这些都是新型分布式环境所带来的便利。但无可否认是模式发生了整体的变化,然而只要人们真正的了解了这个环境,肯定会更加偏向于分布式系统。

对于成熟。MongoDB 已投入市场 5 年之久,当 Oracle 5 岁时,它肯定也没有现在这么成熟。数据库是个长期的工作,这个领域的产品需要更长时间能打磨,但是因为需求问题,我们已经成熟的非常快了,永远比我们想象的快。MongoDB 的企业应用流程一般是这样的,在一个用例中测试==》投入生产环境==》任务关键型应用上使用,这个是 1 到 2 两年后了。经过 5 年的发展后,我们已经看到 MongoDB 支撑着许多企业的任务关键性应用程序。

Eric:什么样的任务关键型应用?

Eliot:其中一个情景就是 user-facing 数据。在 Adobe 的中,当人们使用 Photoshop 时,所有的数据都会保存在 Adobe 中,如果服务发生故障,将会产生非常麻烦的事情。同样,在银行和风险系统中也是如此。

Eric:MongoDB 有在银行系统中投入使用?

Eliot:如果你着眼银行这个领域,许多事情变化都非常快,比如业务操作时的管理需求、业务操作方式等等。MongoDB 可以快速的适应并跟随变化,这点是其他系统做不到的。MongoDB 能进入这个领域主要就是基于这个原因,即使它不像 Oracle 那样成熟。同时,这也是开源技术背后需要公司来支撑的原因,开源道企业级应用有很大一部要走。

Eric:你好像一直在说新系统的打造,这是否意味着很少有遗留的企业系统迁至 MongoDB?

Eliot:如果遗留的系统可以工作,那么为什么要迁移?这是完全没意义的。我们看到许多新的应用程序基于 MongoDB 建立,可以说每时每刻都在发生。同时,如果遗留系统崩溃,那么通常情况下它会被重构和重新建立。但是如果遗留系统可以正常的工作,基于成本问题,相信不会有任何人做这种无意义的迁移。

因此,只有在重建时你才会看到遗留应用程序的迁移。如果你有接触这种情况,你可能就会听到工程师的抱怨:“因为不能快速演进,我们已远落后产品路线图了,因此在接下来的 6 个月内,我们必须要紧牙关完成这个迁移。”

Eric:都有哪些机构的工作者在推动 MongoDB 采用的前行?

Eliot:毫无疑问,开发者是最大的推动者。架构师因为一些架构上的问题使用 MongoDB,有些情况下运营团队使用 MongoDB 来减少运维复杂度,有些时候类似 VP 及 CIO 也期望使用它来创新。但是,我认为这些都只是基础,重点在于使用并喜欢它的人们,他们可能会对 CIO 上报:瞧,我正在使用这个产品,我认为它可以在更广泛的项目中投入使用,它肯定会运行的很好。

Eric:从运营的角度上看,我听到反馈说 MongoDB 扩展并不像宣传的那么容易,这些人的问题是出在了什么地方?你们是怎么回答的?

Eliot:我认为最大的问题在于 MongoDB 的底层系统设计是针对最大横向扩展性及许多常见的运维操作,而当下这些最常见的运维操作可能还不是最简单的。我们尽力让 MongoDB 由一堆很小的独立组件组成,让用户基于需求选择来解决扩展难问题。

那么问题就发生了,使用这种新型的集群,你可能需要管理许多小的部分。从运维的角度来说,这确实令人烦恼。这个部分会在今年搞定,将会推出一套自动化系统,只需简单的点击就可以完成工作。同时,我们还将推出管理大型分布系统的工具,彻底解放运维人员。

Eric:在 MongoDB 中,一些最常见的错误是什么?

Elito:或许也不能称之错误吧,MongoDB 面临最大的挑战就是正确的数据模型。因为 MongoDB 非常灵活,所以用户经常不考虑花时间去设计比较合适的模型,最终这将演变成搬着石头砸自己的脚。

对于传统的 RDBMS 来说,它们只提供简单的模型和选项,而基于太多的硬性规则,用户一般也不容易陷入困境,这点在 MongoDB 中就很可能发生。因此,用户需要阅读一些相关的书籍和文档,避免误区。

使用关系型数据库的思维管理 MongoDB,把 MongoDB 当做关系型数据库来使用,这样无疑会带来困境而不是好处。

Eric:什么会让你夜不能寐?

Eliot:对于我们来说,最大的挑战就是让产品运行良好,让用户喜欢 MongoDB。毫无疑问的是,对比 5 年后的产品,当下在技术方面还存在很大的差距。坦率的说,在产品的这个生命周期,我们拥有了太多的用户。

如果你对比 MongoDB 和其他数据库的同个生命周期,MongoDB 无疑拥有更多的用例,这点同样表现在企业级应用上。因此我们必须要做好迭代速度与谨慎方面的考量,平衡好速度与可靠性,因为许多应用场景都是任务关键性应用。

本文链接

NoSQL再次败北——我坚持使用SQL的原因

$
0
0

【编者按】NoSQL拥有可扩展性和超高吞吐量的能力,然而这却没有发挥实际的优势,同时它不具备关系数据库所有的智能操作,虽然具有无模式存储的优势,却无形中增加了代码的复杂度。更多的应用证明使用NoSQL如此困难,它仅能成为SQL系统的构件而不是替代品。

这是我第二次为新项目深入调研NoSQL,也是第二次决定放弃NoSQL。跟我上次发表的“为什么选择使用NoSQL如此困难”的结论一样,我们最终决定放弃NoSQL,使用传统关系型数据库。

我从上个帖子的许多评论中得出评估NoSQL的一大问题——其解决方案指向的核心是“取决于你的需求”。但尽管需求明确,仍需要花时间调研并搞清楚一个特定的NoSQL引擎是否正是你所需。有太多方面,你不可能评估所有的。更糟的是,你得费力的从engine-specific文档中解读出它是否能够实现你的目标,那些文档大多是类似选择关系型数据或者ACID的解决方案。

相比之下,如果使用关系型SQL数据库,大多数情况下,不管是哪种特定产品,你都能知道它的工作方式,不需要反复比对选择,也比较成熟稳定。选择RDBMS能大大降低做错误决定的风险。

NoSQL的吸引力在于拥有可扩展性和超高吞吐量的能力。就算其扩展性真的优于RDBMS,然而现实世界的事实是,99%的应用程序都不会变更数据模型。比如Stock Exchange,它是访问量最大的网站之一,它们的商品服务器是运行在MSSQL上的。而且很难想象NoSQL需要多么巨大的存储空间,购买一个60-core、高达6TB内存的服务器基本是不可能的。所以使用NoSQL的实际好处又是什么?

起初我认为无模式存储是NoSQL的一个优势,但我已经改变了我这个观点。至少对于关系型页面应用程序,无模式只不过是在增加代码复杂度。此外,我喜欢结构,特别是数据结构。在数据归档、文件存储、或事件日志这类数据处理中无模式是很有用的,但是对于非社交类的页面应用程序却没有任何优势。

与关系数据库比起来,文档存储会使程序的每个部分都变得更加复杂。对于那种可以将文件名作为key,文件内容作为value的平行文件存储(key-value数据库),NoSQL是很有优势的,你可以在这类文件中存储任何所需内容,读取的时候也会很方便,但这种存储很脑残。我的结论是,NoSQL在管理和优化所存储的文件时是非常复杂的,对于存储的数据内容它一无所知。关系数据库所有的智能操作NoSQL全都没有,你必须用代码来实现那些SQL自带的功能,这对大多数应用程序来说都是不合理的。

即使是建造NoSQL引擎的人也很难描述自己产品的用例,NoSQL的很多评论都在推销自己的产品,却并没有提供任何特别令人信服的理由。很少有SaaS应用程序用非关系型数据,现实情况是,RDBMS系统要比NoSQL系统多的多,一旦所有的炒作逐渐停止,NoSQL引擎的数量降到合理的范围,NoSQL将会成为这些合理应用范围内的有用工具。在未来,我认为NoSQL能够成为SQL系统的构件而不是替代品,现在我依然坚持使用SQL。

不断刷新页面的session超时控制

$
0
0
注:原创作品,转载请注明出处


解决方案:
1,在页面元素加载之前,定义js计时器,并赋值为0;
2,在页面元素加载之前,绑定body的鼠标单击事件处理函数--每次鼠标单击body都重新赋值计时
   器为0;
3,在html中body中定义一个隐藏域,值为服务器上session的最大时间。
4,在页面元素加载完毕后,启动session超时监听器:该监听器实现的功能是:
   获取页面上隐藏域的session的最大超时时间,
   比较计数器和session最大值,如果计时器的数值小于session的最大时间则,计数器+1,重新
   调用session监听器,  如果计数器的数值大于等于session的的最大时间,则调用dwr或ajax
   让后台的session失效


实例:
<html><head><script type="text/javascript">
	   //定义计时器
	   var monitorCount = 0;
	   
	   //绑定body的onclick处理函数
	   document.body.onclick = function()
	   {
		   if(typeof bodyOnClickFunction == 'function')
		   {
			   bodyOnClickFunction();
		   }
	   }
	   //body的单击处理函数
	   function bodyOnClickFunction()
	   {
		   resetTimeoutCount();
	   }
	   
	   //重置计数器为0
	   function resetTimeoutCount()
	   {
		   monitorCount = 0
	   }
	   
	   var timeOutFunction = "sessionTimeOutMonitor()";
	   function sessionTimeOutMonitor()
	   {
		   var sessionTimeCount = document.getElementById("sessionTimeCount").value / 60;
		   if(monitorCount < sessionTimeCount)
		   {
			   monitorCount++;
			   setTimeOut(sessionTimeOutMonitor,60 * 1000);
		   }
		   else
		   {
			   //调用ajax或者dwr调用使后台的session失效
		   }   
	   }
	</script></head><body onload="sessionTimeOutMonitor();"><input type="hidden" value="从服务器上获取session的最大超时数值" id="sessionTimeCount"/></body></html>


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Hibernate 调用返回值的存储过程

$
0
0
注:原创作品,转载请注明出处。

     曾一度认为oracle的函数和存储过程的区别,就是function能返回值,存储过程不能返回值。但在项目中的很多存储过程都有返回值,仔细发现得出问题的窍门。
  
     存储过程的返回值是通过参数实现的,存储过程的参数有三种类型一种是 in,一种是 out
一种是既有in也有out类型。  out的参数就是返回数值的参数。

     下面是实例
     本实例需要环境:
     1,hibernate3.0
     2,oracle 10g
     3,创建一个TBL_ADDRESS,字段有id,name,info,remark且有一条记录

java代码:
package com.supan.test;
import com.supan.dao.imp.UserDaoImp;

public class hibernate1
{
	public static void main(String[] args)
	{
		UserDaoImp udi = new UserDaoImp();
		udi.getUserNameAndInfo();
	}
}


package com.supan.dao.imp;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.supan.dao.UserDao;
public class UserDaoImp extends HibernateDaoSupport implements UserDao
{
	public void getUserNameAndInfo()
	{
		//没有spring的注入,只有人工苦逼的注册sessionFactory属性
		Configuration cof = new Configuration().configure();
		this.setSessionFactory(cof.buildSessionFactory());
		
		//定义存放结果的结果map
		final Map<String,String> result = new HashMap<String, String>();
		getHibernateTemplate().execute(new HibernateCallback<Object>()
		{
			@Override
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException
			{
				session.doWork(new Work()
				{
					@Override
					public void execute(Connection conn) throws SQLException
					{
						CallableStatement proc = null;
						try
						{
							proc = conn.prepareCall("{call PROC_GETUSER_NAME_AGE(?,?,?)}");
							//注意:这里是注册输出参数
							proc.registerOutParameter(1, java.sql.Types.VARCHAR);
							proc.registerOutParameter(2, java.sql.Types.VARCHAR);
							//注意:这里是传递输入参数
							proc.setLong(3, 1L);
							
							//执行存储过程
							proc.execute();
							
							//获取执行完的存储过程的返回值
							result.put("name", proc.getString(1));
							result.put("age", proc.getString(2));
						}
						catch(Exception e)
						{
							//logger.error("访问数据库失败");
							e.printStackTrace();
							result.put("name", null);
							result.put("age", null);
						}
						finally
						{
							if(null != proc)
							{
								proc.close();
							}
						}
					}
				});
				return null;
			}
		});
		System.out.println(result.get("name"));
		System.out.println(result.get("age"));
	}
}


下面是oracle的存储过程
/*创建存储过程,该存储过程三个参数,前两个是输出参数
最后一个是输入参数*/
create or replace procedure PROC_GETUSER_NAME_AGE(userName out varchar2,
                                                  userAge  out varchar2,
                                                  userId   in long) 
AS
  --声明该存储过程为“自治事物单元”
  PRAGMA AUTONOMOUS_TRANSACTION;
  
  --定义两个变量
  v_userName varchar2(255);
  v_userAge  varchar2(255);

begin
  select name, info into v_userName, v_userAge from TBL_ADDRESS t where t.id = userId;
  userName := v_userName;
  userAge  := v_userAge;
  
  --注意最后并没有return语句返回,返回靠的是输出参数
end;




已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




想靠写代码吃饭,这些你一定要会

$
0
0

我不是计算机系毕业的,有办法找到一个软件工程师的工作吗? 很幸运的 Medium 有一篇文章「 Top Tech Skills Required for Entry-Level Programming Jobs(初阶软件从业人员必须要学会的科技)」给了我们解答,这篇文章从美国知名求职网站 Indeed 中对于 231 份并不要求计算机系学位的工作中,整理出了你必须会的一些科技技术,让你即使不是计算机系出身,但试着学会这些技术,就可以助你成为一名初级软件工程师。

topskills

Javascript

第一名的是 Javascript ,有 42.4 % 的职缺中都要求必须会这项技能。Javascript 为什么这么厉害?最主要在于网络软件开发的盛行,Javascript 是很多现有的开发框架的基础除了用在前端的开发( Angular 、Ember 、Backbone 等),也用在后端的架构像是 Node.js 以及资料库数据的读取如 MongoDB 。Javascript 所建立起来的生态系非常的庞大也造就了大多数的工作都需要这项技能。Javascript 本身学起来并不会太困难,如果想要懂一些基本的应用那可以参考这裡。

Java

榜上的第二名是 Java ,占了 34.2% ,关于 Java 有些人认为它一个比较过时的语言也认为它即将被淘汰。但是根据 TIOBE Index 上的排行(根据程式语言的普及与受欢迎程度),Java 还是名列第二的。Java 不仅没有被淘汰,它还过得很好仍然是很多大企业爱好的后端语言。Java 之所已受到企业的重视,一个很大的原因不止是因为他本身好用,而是因为学好 Java 让你学其他程式语言相对容易,因为 Java 是种物件导向语言而且不需要处理指标( Pointer ),加上他有很好的开发环境( IDE )让初学者使用起来相对的简单,可以说是身为一个软件工程师绝对要懂得基础。以笔者的经验而言 Java 学起来比 Javascript 稍微难一些网络上关于这方面的资源也不缺乏,像是 MIT 就有提供他的课程教材免费让大家学习。

HTML

在榜上第三名是 HTML,HTML 严格说起来并不是一种程式语言它是一种标记语言( Markup Language ),简单来说就是利用标记可以达到与单纯的文字不同的效果。例如一段文字在前后加上标记就可以加上超连结。HTML 是网路开发中基础中的基础,尤其在前端的开发上了解 HTML 每一个标记的功能是必要的。 但是笔者还是要强调他本身并不是一种程式语言,如果只会 HTML 对找工作是没有任何帮助的,他只是基本中的基本。在这裡也推荐一个网站让大家可以花大约一天的时间就学会 HTML。

SQL and NoSQL

除此之外,榜上有名的是 SQL 在 31.2% 的职缺上这项技能都是必须的。根据 Asurvey 的调查有 79% 的受访者仍然是使用关係资料库也就是为什么 SQL 受到欢迎的原因。但这并不代表 NoSQL 就不重要虽然 MongoDB 在榜上只有 4.3%,但是它主要被用在需要快速存取资料的产品上,像是 Craigslist 还有 Expedia。

NoSQL 与 SQL 究竟孰好孰坏,在资料库的社群上一直是一个广受争论的议题,绝大多数的任认为 NoSQL 是未来的趋势,但是以目前而言 SQL 仍然是占了上风。

Javascript Frameworks

建立在 Javascript 上的开发框架总共有三个 Angular.js 、Ember.js 以及 Backbone.js,这类的框架可以让你很简便而且快速的处理前端的开发。然而这三种开发框架由于性质相近,因此也有很多的谁才是最好的争论。目前是由 Google 所开发的 Angular.js 站在榜上第一。当然这三种开发框架都有自己的好处,像是 Angular.js 比较适合大型的开发,Backbone.js 比较简单易学,Backbone.js 让合作开发更简单1。

Node.js

在原文中也特别提到了 Node.js 原因是在于其逐渐增加的使用者,以及大型公司像是 Linkedin 以及 Wal-mart 的使用。

结语

文章中提到了很多种的程式语言与科技技术,或许有些读者会认为把上面所说的每一项都摸一遍就可以找到一份软件工程师的工作。关于这样做有没有效果不敢保证,但是如果对于其中特定的几项技能能够有深刻的了解,对于找到一份软件工程师的工作有绝对的帮助。当然以笔者在国外求学以及找工作的经验而言,要找到一份软件工程师的工作所需要的绝对不止这些,这部分就留在下次与读者分享。 

  1. Angular, Backbone, Ember: Which is best for your build?

本文链接

WebKit是新时代的IE6,微软修改移动版IE11

$
0
0
微软IE团队调查了500家最流行网站在不同移动浏览器中的行为,发现很多网站会将移动版IE11浏览器识别为桌面版本,并特别为基于WebKit的浏览器优化。基于WebKit引擎的浏览器如Mobile Safari统治了移动浏览器市场,这些浏览器包含了许多非标准化的特性,此类的优化会给不支持这些特性的浏览器带来问题。类似的事情十年前发生过,当时的微软是受益者而不是今天的受害者,它的IE6垄断了桌面浏览器市场,网站为IE6的非标准特性优化后在其它浏览器上显示的效果会非常糟糕。为了改善IE11移动版的表现,微软修改了移动版IE11的User Agent 字符串,加入了 -webkit前缀。IE11将支持部分非标准的webkit特性(上图是IE11更新前后的百度首页)。






Android短信蠕虫XXshenqi分析

$
0
0

0×00

今天从余弦大大微博上看到了这款Android短信蠕虫的信息,于是自己下载了一款研究,看到网上很多报告的md5值不同,不知道是否是变种。接下来就分析一下,这款病毒究竟做了些什么。

0×01

病毒分为两个部分,一个是XXshenqi.apk,另一个是com.android.Trogoogle.apk。后者存在于前者解压文件下的assets目录中。首先,分析XXshenqi.apk,安装至虚拟机上运行。

0.  首先,在初始安装后,用户点击app,会出现安装另外一个apk的信息:

12

 

  1. 入口Activity是WelcomeActivity,在这个Activity中,首先获取手机中所有的联系人信息,然后群发一条短信,内容如下:
[联系人姓名] + “看这个,” +”http://cdn.yyupload.com/down/4279193/XXshenqi.apk”

骗取本机联系人点击恶意链接下载病毒进行传播。

发送成功后,执行:

SmsManager.getDefault().sendTextMessage(“18670259904″, null, “XXshenqi 群发链接OK”, null, null);

即向这个号码反馈执行信息。

  1. 开启一个MainActivity

在这个Activity中,安装一个com.example.com.android.trogoogle的文件,这个文件存在于xxshenqi.apk解压后文件的assets目录下。

安装木马后,会自动隐藏图标。

同时弹出一个登录框,只不过这个是个幌子,怎么输入都会出现密码错误或者账户不存在,将用户导入到一个注册页面:RegisterActivity中

3.RegisterActivity真的是注册页面吗?

3 (1)

 

在这个页面中,会让用户输入很多信息,比如身份证号以及姓名。

4

 

如果点击了注册按钮那么很不幸,你的信息就会发送到黑客的手机上。

至此,xxshenqi.apk的任务基本是完成了,即使用户卸载也无所谓,因为木马已经在刚开始就被释放了。

0×02

5

 

1.在入口Activity—–MainActivity中木马执行了隐藏图标的功能,随后打开了一个ListenMessageService的服务,在后台运行。

2.ListenMessageService这个服务里面,首先注册一个短信数据库的观察者,检测短信数据库的变化,一旦用户的短信数据库发生变化(收到信息或者更新信息),那么观察者就会执行回调函数执行,首先判断是否是命令短信,命令短信是用来向木马发送命令的。如果是平常的短信,就将截获的短信全部发往黑客手中,值得注意的是木马还判断短信是否是淘宝发送的短信,如果是的话就单独处理。

并且木马中还有发送伪造短信给用户的功能,如果是这种短信,木马就不会截获或者发送给黑客。

这里还存在的恶意行为是读取用户收件发件箱短信,以及手机中的联系人。

当截获完短信之后,木马就又开启了一个MySendEmailService服务。

3.MySendEmailService这个服务就是给黑客发送电子邮件。

这上面都是一些用到的发送信息包括主机,端口,账户名,密码(有密码哦~)。。。

4.定义广播接收者处理接收的命令BroadcastRecvMessage

BroadcastRecvMessage这个类继承了系统的BroadcastReceiver组件,一旦接收到短信,就会触发代码,并判断指令所要求木马执行的具体功能。

readmessage 发送邮件命令

sendmessage发送短信命令

test测试命令

makemessage伪造短信命令

sendlink发送连接的命令

当然,这里也做了具体处理,就是判断是否是普通短信和淘宝网购信息,如果是淘宝信息就加上一个Flag发送,简直碉堡。

0×03

至此,这款病毒究竟做了些啥就基本清楚了,所做的操作也是一般短信木马的常有功能,包括截获短信并发送,发送恶意链接进行传播(冒充联系人发送,更有迷惑性),支持接收指令,以及发送恶意伪造的短信给用户(冒充联系人发送,更有迷惑性)等行为,还是比较歹毒的。

Android系统具有优美的开放性的同时,同样也遭受着各种病毒的侵袭。面对这些病毒,最好的办法还是不要去一些不正规的应用市场下载,并且注意安装时认清权限,如果有可以的权限如:读取系统日志,那么就不要轻易下载。

0×04

此篇分析是小编急着赶出来的,如果有错误的地方,还请指正,共同进步!

————————————以上内容来自91Ri团队小编:隐形人,以下内容为此款蠕虫作者简要资料——————————

该蠕虫作者为 中南大学的一名李同学编写,QQ号为:1377365** 在该蠕虫爆发前QQ密码为:lishuli** 目前身在湖南,电话号码为181636573**

曾用密码:entershi** shiftct**

在用密码:略

在初中时曾就读于深圳展华实验学校,根据小编收集到的信息这名编写蠕虫的作者Android技术并不算强,但竟然能弄的包括小编身边的一些小伙伴也中招,真是令人有点蛋疼。

小编虽然已经收集到了该作者详细身份信息,住址,照片,并且进入了获得了该名作者相关账户权限。但看在他还是一名90后,且能在今天这个七夕的的份上却调皮捣蛋,跟小编一样是一个…….(省略N个字),小编就不贴出更多信息。

本文重在对该蠕虫的具体分析,文章并不是非常的详细,不保证有错误,欢迎大家留言交流。祝有妹子的同学七夕快乐,没妹子的同学撸站快乐~(话说有妹子的同学能看到你这篇文章吗!!!)

Android短信蠕虫XXshenqi分析,首发于 极客范 - GeekFan.net

优秀的轻量级邮件客户端

$
0
0
Alison Xue 写道 "电子邮件仍然是杀手级的信息通信技术,虽然协作式通信工具日益普及,但电子邮件流量并没有出现下降的迹象。在众多的电子邮件客户端中,有的提供了丰富的功能,有的只提供最基本的功能。Thunderbird就是前者的一个例子,它向初学者和专业用户提供了丰富的功能,可高度定制,但硬件需求略高。如果你寻找运行在有限系统资源的轻量级邮件客户端替代,可以尝试Geary、Sylpheed和Claws Mail等。"






iOS平台用户比Android用户活跃7倍

$
0
0

2

iOS平台用户比Android用户活跃7倍

【TechWeb报道】8月4日消息,据国外媒报道,美国市场研究公司Net Applications最新报告显示,Android平台的使用量份额达到44.62%,首次超过苹果iOS的44.19%。虽然Android在这一数字上稍稍领先,但考虑到Android的智能手机出货量份额,可以得出Android用户的平均手机浏览网页的时间仅为iOS用户的七分之一。

Net Applications公司的数据是以4万个网站为基础,通过对这些网站的移动设备流量检测得出。两个平台在使用量份额上差距甚微,然而Android的智能手机市场出货量份额高达85%,而iOS仅为11.9%。所以,可以看出iOS用户比Android用户的花在手机浏览网页上的时间多得多,几乎是后者的7倍。

这个结果不难解释。Android系统存在于众多廉价手机市场,这些用户期望获得廉价、功能够用的手机,他们大多不是科技狂人或是互联网达人。而iOS设备本身就是高端机型,这些用户会更加关注互联网以及科技信息,所以他们会花更多时间在这些事情上。

虽然多数移动广告网络都允许营销人员定位所有的移动平台,但在能够针对某个平台展开定向推广时,都应当仔细考虑这一活跃度数据。虽然这一调查可能存在一定的误差,但依然可以反映整体趋势:iOS用户比Android用户更加活跃。

这一研究使得智能手机市场出货量数据的意义进一步缩水。因为,并非所有用户都可以带来同样的价值,所以手机销量给应用、广告和游戏带来的贡献也各不相同。换句话说,瞄准最大的平台未必能带来最好的回报。(露天)

集成Diagram Viewer跟踪流程

$
0
0

首先这是一篇迟来的教程,因为从5.12版本(目前最新版本为5.15.1)开始就已经提供了Diagram Viewer这个流程图跟踪组件,不管如何总归有人需要用到,所以我觉得还是要和大家分享一下。

1. 前言

目前被大家所采用的流程图跟踪有两种方式:

  • 一种是由引擎后台提供图片,可以把当前节点标记用红色
  • 一种是比较灵活的方式,先用引擎接口获取流程图(原图),然后再通过解析引擎的Activity对象逐个解析(主要是判断哪个是当前节点),最后把这些对象组成一个集合转换成JSON格式的数据输出给前端,用Javascript和Css技术实现流程的跟踪

这两种方式在kft-activiti-demo中都有演示,这里就不介绍了,参考流程跟踪部门代码即可。

2. Diagram Viewer简介

Diagram Viewer是官方在5.12版本中添加的新组件,以 Raphaël为基础库,用REST(参考:《 如何使用Activiti Rest模块》)方式获取JSON数据生成流程图并把流程的处理过程用不同的颜色加以标注,最终的效果如下图所示。

在应用中使用时也很方便,把这个组件的源码复制到项目中再配置一个REST拦截器,最后拼接一个URL即可;举个例子:

http://demo.kafeitu.me/kft-activiti-demo/diagram-viewer/index.html?processDefinitionId=leave-jpa:1:22&processInstanceId=27

这个URL中有两个参数:

  • processDefinitionId: 流程定义ID
  • processInstanceId: 流程实例ID

3. 集成Diagram Viewer

3.1 创建REST路由类

REST路由类源码在官方的Activiti Explorer里面有提供,代码如下:

package org.activiti.explorer.rest;

import org.activiti.rest.common.api.DefaultResource;
import org.activiti.rest.common.application.ActivitiRestApplication;
import org.activiti.rest.common.filter.JsonpFilter;
import org.activiti.rest.diagram.application.DiagramServicesInit;
import org.activiti.rest.editor.application.ModelerServicesInit;
import org.restlet.Restlet;
import org.restlet.routing.Router;

public class ExplorerRestApplication extends ActivitiRestApplication {
  
  public ExplorerRestApplication() {
    super();
  }
  /**
   * Creates a root Restlet that will receive all incoming calls.
   */
  @Override
  public synchronized Restlet createInboundRoot() {
    Router router = new Router(getContext());
    router.attachDefault(DefaultResource.class);
    ModelerServicesInit.attachResources(router);
    DiagramServicesInit.attachResources(router);
    JsonpFilter jsonpFilter = new JsonpFilter(getContext());
    jsonpFilter.setNext(router);
    return jsonpFilter;
  }

}

把这个路由配置到 web.xml中:


    ExplorerRestletServlet
    org.restlet.ext.servlet.ServerServlet
    
        
        org.restlet.application
        org.activiti.explorer.rest.ExplorerRestApplication
    



    ExplorerRestletServlet
    /service/*

3.2 复制Diagram Viewer组件

在官方提供的Zip文件(可以从 www.activiti.org/download.html下载)中有一个 wars目录,用压缩工具解压 activiti-explorer.war文件,目录结构如下图:

diagram-viewer复制到项目的 webapp目录(或者是WebRoot目录)下,在项目中需要跟踪的地方拼接访问 diagram-viewer/index.html的URL即可,别忘记了刚刚介绍的两个重要参数。

http://demo.kafeitu.me/kft-activiti-demo/diagram-viewer/index.html?processDefinitionId=leave-jpa:1:22&processInstanceId=27

URL中有两个参数:

  • processDefinitionId: 流程定义ID
  • processInstanceId: 流程实例ID

这是一个独立的页面,你可以直接打开它或者把它嵌入在一个对话框里面(kft-activiti-demo就是用的嵌入方式)。

转载自:

玩游戏有助于提高儿童适应能力

$
0
0
英国牛津大学的一项研究显示,每天玩一会儿电脑游戏可能会对少年儿童的心理发育有好处。研究人员发现,每天花不到一个小时玩电脑游戏的孩子比从不玩电脑游戏的孩子适应能力更强。但是,每天玩电脑游戏超过三个小时的孩子对生活的满意度比较低。这一结论是研究人员分析对英国5千名10到15岁少年的调研数据得出的。在全部受访儿童中,75%的人说他们每天都玩电脑游戏。研究人员认为,玩电脑游戏会使孩子们有一套共同的语言,“如果一个孩子不能加入相关的谈话,他可能就会被隔离在群体之外。”







为Hadoop集群选择合适的硬件配置

$
0
0
随着Apache Hadoop的起步,云客户的增多面临的首要问题就是如何为他们新的的Hadoop集群选择合适的硬件。 尽管Hadoop被设计为运行在行业标准的硬件上,提出一个理想的集群配置不想提供硬件规格列表那么简单。 选择硬件,为给定的负载在性能和经济性提供最佳平衡是需要测试和验证其有效性。(比如,IO密集型工作负载的用户将会为每个核心主轴投资更多)。 在这个博客帖子中,你将会学到一些工作负载评估的原则和它在硬件选择中起着至关重要的作用。在这个过程中,你也将学到Hadoop管理员应该考虑到各种因素。 结合存储和计算 过去的十年,IT组织已经标准化了刀片服务器和存储区域网(SAN)来满足联网和处理密集型的工作负载。尽管这个模型对于一些方面的标准程序是有相当意义 的,比如网站服务器,程序服务器,小型结构化数据库,数据移动等,但随着数据数量和用户数的增长,对于基础设施的要求也已经改变。网站服务器现在有了缓存 层;数据库需要本地硬盘支持大规模地并行;数据迁移量也超过了本地可处理的数量。 大部分的团队还没有弄清楚实际工作负载需求就开始搭建他们的Hadoop集群。 硬件提供商已经生产了创新性的产品系统来应对这些需求,包括存储刀片服务器,串行SCSI交换机,外部SATA磁盘阵列和大容量的机架单元。然 而,Hadoop是基于新的实现方法,来存储和处理复杂数据,并伴随着数据迁移的减少。 相对于依赖SAN来满足大容量存储和可靠性,Hadoop在软件层次处理大数据和可靠性。 Hadoop在一簇平衡的节点间分派数据并使用同步复制来保证数据可用性和容错性。因为数据被分发到有计算能力的节点,数据的处理可以被直接发送到存储有数据的节点。由于Hadoop集群中的每一台节点都存储并处理数据,这些节点都需要配置来满足数据存储和运算的要求。    工作负载很重要吗? 在几乎所有情形下,MapReduce要么会在从硬盘或者网络读取数据时遇到瓶颈(称为IO受限的应用),要么在处理数据时遇到瓶颈(CPU受限)。排序是一个IO受限的例子,它需要很少的CPU处理(仅仅是简单的比较操作),但是需要大量的从硬盘读写数据。模式分类是一个CPU受限的例子,它对数据进行复杂的处理,用来判定本体。 下面是更多IO受限的工作负载的例子: 索引 分组 数据导入导出 数据移动和转换 下面是更多CPU受限的工作负载的例子: 聚类/分类 复杂文本挖掘 自然语言处理 特征提取 Cloudera的客户需要完全理解他们的工作负载,这样才能选择最优的Hadoop硬件,而这好像是一个鸡生蛋蛋生鸡的问题。大多数工作组在没有彻底剖 析他们的工作负载时,就已经搭建好了Hadoop集群,通常Hadoop运行的工作负载随着他们的精通程度的提高而完全不同。而且,某些工作负载可能会被 一些未预料的原因受限。例如,某些理论上是IO受限的工作负载却最终成为了CPU受限,这是可能是因为用户选择了不同的压缩算法,或者算法的不同实现改变 了MapReduce任务的约束方式。基于这些原因,当工作组还不熟悉要运行任务的类型时,深入剖析它才是构建平衡的Hadoop集群之前需要做的最合理 的工作。 接下来需要在集群上运行MapReduce基准测试任务,分析它们是如何受限的。完成这个目标最直接的方法是在运行中的工作负载中的适当位置添加监视器来 检测瓶颈。我们推荐在Hadoop集群上安装Cloudera Manager,它可以提供CPU,硬盘和网络负载的实时统计信息。(Cloudera Manager是Cloudera 标准版和企业版的一个组件,其中企业版还支持滚动升级)Cloudera Manager安装之后,Hadoop管理员就可以运行MapReduce任务并且查看Cloudera Manager的仪表盘,用来监测每台机器的工作情况。 第一步是弄清楚你的作业组已经拥有了哪些硬件 在为你的工作负载构建合适的集群之外,我们建议客户和它们的硬件提供商合作确定电力和冷却方面的预算。由于Hadoop会运行在数十台,数百台到数千台节 点上。通过使用高性能功耗比的硬件,作业组可以节省一大笔资金。硬件提供商通常都会提供监测功耗和冷却方面的工具和建议。 为你的CDH(Cloudera distribution for Hadoop) Cluster选择硬件 选择机器配置类型的第一步就是理解你的运维团队已经在管理的硬件类型。在购买新的硬件设备时,运维团队经常根据一定的观点或者强制需求来选择,并且他们倾 向于工作在自己业已熟悉的平台类型上。Hadoop不是唯一的从规模效率上获益的系统。再一次强调,作为更通用的建议,如果集群是新建立的或者你并不能准 确的预估你的极限工作负载,我们建议你选择均衡的硬件类型。 Hadoop集群有四种基本任务角色:名称节点(包括备用名称节点),工作追踪节点,任务执行节点,和数据节点。节点是执行某一特定功能的工作站。大部分你的集群内的节点需要执行两个角色的任务,作为数据节点(数据存储)和任务执行节点(数据处理)。  这是在一个平衡Hadoop集群中,为数据节点/任务追踪器提供的推荐规格: 在一个磁盘阵列中要有12到24个1~4TB硬盘 2个频率为2~2.5GHz的四核、六核或八核CPU 64~512GB的内存 有保障的千兆或万兆以太网(存储密度越大,需要的网络吞吐量越高) 名字节点角色负责协调集群上的数据存储,作业追踪器协调数据处理(备用的名字节点不应与集群中的名字节点共存,并且运行在与之相同的硬件环境上。)。 Cloudera推荐客户购买在RAID1或10配置上有足够功率和企业级磁盘数的商用机器来运行名字节点和作业追踪器。   [...]

腾讯TDW:大型Hadoop集群应用

$
0
0
PS:TDW是腾讯最大的离线数据处理平台。本文主要从需求、挑战、方案和未来计划等方面,介绍了TDW在建设单个大规模集群中采取的JobTracker分散化和NameNode高可用两个优化方案。 TDW(Tencent distributed Data Warehouse,腾讯分布式数据仓库)基于开源软件Hadoop和Hive进行构建,打破了传统数据仓库不能线性扩展、可控性差的局限,并且根据腾讯数据量大、计算复杂等特定情况进行了大量优化和改造。 TDW服务覆盖了腾讯绝大部分业务产品,单集群规模达到4400台,CPU总核数达到10万左右,存储容量达到100PB;每日作业数100多万,每日计算量4PB,作业并发数2000左右;实际存储数据量80PB,文件数和块数达到6亿多;存储利用率83%左右,CPU利用率85%左右。经过四年多的持续投入和建设,TDW已经成为腾讯最大的离线数据处理平台。 TDW的功能模块主要包括:Hive、MapReduce、HDFS、TDBank、Lhotse等,如图1所示。TDW Core主要包括存储引擎HDFS、计算引擎MapReduce、查询引擎Hive,分别提供底层的存储、计算、查询服务,并且根据公司业务产品的应用情况进行了很多深度订制。TDBank负责数据采集,旨在统一数据接入入口,提供多样的数据接入方式。Lhotse任务调度系统是整个数据仓库的总管,提供一站式任务调度与管理。 图1 TDW的功能模块 建设单个大规模集群的原因 随着业务的快速增长,TDW的节点数也在增加,对单个大规模Hadoop集群的需求也越来越强烈。TDW需要做单个大规模集群,主要是从数据共享、计算资源共享、减轻运营负担和成本等三个方面考虑。 1. 数据共享。TDW之前在多个IDC部署数十个集群,主要是根据业务分别部署,这样当一个业务需要其他业务的数据,或者需要公共数据时,就需要跨集群或者跨IDC访问数据,这样会占用IDC之间的网络带宽。为了减少跨IDC的数据传输,有时会将公共数据冗余分布到多个IDC的集群,这样又会带来存储空间浪费。 2. 计算资源共享。当一个集群的计算资源由于某些原因变得紧张时,例如需要数据补录时,这个集群的计算资源就捉襟见肘,而同时,另一个集群的计算资源可能空闲,但这两者之间没有做到互通有无。 3. 减轻运营负担和成本。十几个集群同时需要稳定运营,而且当一个集群的问题解决时,也需要解决其他集群已经出现的或者潜在的问题。一个Hadoop版本要在十几个集群逐一变更,监控系统也要在十几个集群上部署。这些都给运营带来了很大负担。此外,分散的多个小集群,资源利用率不高,机器成本较大。 建设单个大规模集群的方案及优化 面临的挑战 TDW从单集群400台规模建设成单集群4000台规模,面临的最大挑战是Hadoop架构的单点问题:计算引擎单点JobTracker负载重,使得调度效率低、集群扩展性不好;存储引擎单点NameNode没有容灾,使得重启耗时长、不支持灰度变更、具有丢失数据的风险。TDW单点瓶颈导致平台的高可用性、高效性、高扩展性三方面都有所欠缺,将无法支撑4000台规模。为了解决单点瓶颈,TDW主要进行了JobTracker分散化和NameNode高可用两方面的实施。 JobTracker分散化 1.单点JobTracker的瓶颈 TDW以前的计算引擎是传统的两层架构,单点JobTracker负责整个集群的资源管理、任务调度和任务管理,TaskTracker负责任务执行。JobTracker的三个功能模块耦合在一起,而且全部由一个Master节点负责执行,当集群并发任务数较少时,这种架构可以正常运行,但当集群并发任务数达到2000、节点数达到4000时,任务调度就会出现瓶颈,节点心跳处理迟缓,集群扩展也会遇到瓶颈。 2.JobTracker分散化方案 TDW借鉴YARN和Facebook版corona设计方案,进行了计算引擎的三层架构优化(如图2所示):将资源管理、任务调度和任务管理三个功能模块解耦;JobTracker只负责任务管理功能,而且一个JobTracker只管理一个Job;将比较轻量的资源管理功能模块剥离出来交给新的称为ClusterManager的Master负责执行;任务调度也剥离出来,交给具有资源信息的ClusterManager负责执行;对性能要求较高的任务调度模块采用更加精细的调度方式。 图2 JobTracker分散化架构 新架构下三个角色分别是:ClusterManager负责整个集群的资源管理和任务调度,JobTracker负责单个Job的管理,TaskTracker负责任务的执行。 (1)两路心跳。之前的架构下,TaskTracker向JobTracker上报心跳,JobTracker串行地处理这些心跳,心跳处理中进行节点管理、任务管理、任务调度等,心跳繁重,影响任务调度和集群扩展性。新架构下,心跳被拆分成两路心跳,分别上报任务和资源信息。 JobTracker获知任务信息通过任务上报心跳的方式。任务上报心跳是通过任务所在的TaskTracker启动一个新的独立线程向对应的JobTracker上报心跳这条途径,在同一个TaskTracker上,不同Job的任务使用不同的线程向不同的JobTracker上报心跳,途径分散,提升了心跳上报效率。 TaskTracker通过上报心跳的方式将资源信息汇报给ClusterManager。ClusterManager从TaskTracker的心跳中获取节点的资源信息:CPU数量、内存空间大小、磁盘空间大小等的总值和剩余值,根据这些信息判断节点是否还能执行更多的任务。同时,ClusterManager通过TaskTracker与其之间维系的心跳来管理节点的生死存亡。 以前繁重的一路心跳被拆分成了两路轻量的心跳,心跳间隔由40s优化成1s,集群的可扩展性得到了提升。 (2)资源概念。之前架构只有slot概念,一般根据核数来设置slot数量,对内存、磁盘空间等没有控制。新架构弱化了slot概念,加强了资源的概念。 每个资源请求包括具体的物理资源需求描述,包括内存、磁盘和CPU等。向ClusterManager进行资源申请的有三种来源类型:Map、Reduce、JobTracker,每种来源需要的具体资源量不同。在CPU资源上,调度器仍然保留slot概念,并且针对三种来源保证各自固定的资源帽。 例如,对于24核的节点,配置13个核给Map用、6个核给Reduce用、1个核给JobTracker用,则认为该节点上有1个JobTracker slot、13个Map slot、6个Reduce slot。某个Map请求的资源需要2个核,则认为需要两个Map slot,当一个节点的Map slot用完之后,即使有剩余的CPU,也不会继续分配Map予其执行了。内存空间、磁盘空间等资源没有slot概念,剩余空间大小满足需求即认为可以分配。在查找满足资源请求的节点时,会比较节点的这些剩余资源是否满足请求,而且还会优先选择负载低于集群平均值的节点。 (3)独立并发式的下推调度。之前架构下,调度器采用的是基于心跳模型的拉取调度:任务调度依赖于心跳,Map、Reduce的调度耦合在一起,而且对请求优先级采取全排序方式,时间复杂度为nlog(n),任务调度效率低下。 新架构采用独立并发式的下推调度。Map、Reduce、JobTracker三种资源请求使用三个线程进行独立调度,对请求优先级采取堆排序的方式,时间复杂度为log(n)。当有资源满足请求时,ClusterManager直接将资源下推到请求者,而不再被动地等待TaskTracker通过心跳的方式获取分配的资源。 例如,一个Job有10个Map,每个Map需要1个核、2GB内存空间、10GB磁盘空间,如果有足够的资源,Map调度线程查找到了满足这10个Map的节点列表,ClusterManager会把节点列表下推到JobTracker;如果Map调度线程第一次只查找到了满足5个Map的节点列表,ClusterManager会把这个列表下推到JobTracker,随后Map调度线程查找到了剩下5个Map的节点列表,ClusterManager再把这个列表下推到JobTracker。 以前基于心跳模型的拉取调度被优化成独立并发式的下推调度之后,平均调度处理时间由80ms优化至1ms,集群的调度效率得到了提升。 3. Job提交过程 新架构下,一次Job提交过程,需要Client和ClusterManager、TaskTracker均进行交互(如图3所示):JobClient先向ClusterManager申请启动JobTracker所需要的资源;申请到之后,JobClient在指定的TaskTracker上启动JobTracker进程,将Job提交给JobTracker;JobTracker再向ClusterManager申请Map和Reduce资源;申请到之后,JobTracker将任务启动命令提交给指定的TaskTracker。 图3 Job提交过程 4. 存在的问题及应对措施 JobTracker分散化方案给计算引擎带来高效性和高扩展性,但没有带来高可用性,单一故障点的问题在此方案中仍然存在,此时的单一故障点问题有别于以前,如下所述。 (1)ClusterManager如果发生故障,不会造成Job状态丢失而且在短时间内即可恢复。它只存储资源情况,不存储状态,ClusterManager在很短的时间内可以重启完成。重启之后,TaskTracker重新向ClusterManager汇报资源,ClusterManager从重启至完全获得集群的资源情况整个阶段可以在10秒内完成。 (2)JobTracker如果发生故障,只会影响单个Job,对其他Job不会造成影响。 [...]

12个你未必知道的CSS小知识

$
0
0

虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。

1.CSS的color属性并非只能用于文本显示

对于CSS的 color属性,相信所有Web开发人员都使用过。如果你并不是一个特别有经验的程序员,我相信你未必知道 color属性除了能用在文本显示,还可以用作其它地方。

你可以先看一下下面的演示:

HTML代码

<img alt="Example alt text" width="200" height="200"><ul><li>Example list item</li></ul><ol><li>Example list item</li></ol><hr>

CSS代码

body {
  color: yellow;
  background: #444;
  font-size: 20px;
  font-family: Arial, sans-serif;
  text-align: center;
}

ul {
  border: solid 2px;
  text-align: left;
}

ol {
  text-align: left;
}

hr {
  border-color: inherit;
}

请注意,上面的代码里只使用了一个 color属性,就是在 body元素上,设置成了 yellow。但是,你也看到了,所有这个页面上的东西都变成了黄色,包括:

  • 无法显示的图片的 alt文字
  • list元素的边框
  • 无序list元素前面的小点
  • 有序list元素前面的数字
  • 还有 hr元素

有趣的是,这个 hr元素,缺省情况下它并不从 body上继承 color的属性,但我使用 border-color: inherit强制让它继承。这是个很诡异的特征。

CSS规范里是这样说的:

这个属性声明了元素文本内容的前景色(foreground color)。除此之外,它的值还被用于其它地方间接的引用….比如,其它可以接受颜色值的属性。

我无法想象出还有什么地方的属性能用“前景色”来描述,如果你知道,请在评论里告诉我。

2.CSS里的visibility属性有个collapse属性值:collapse

对于CSS里的 visibility属性,相信你用过不下几百次。大多时候,你会把它的值设置成 visible(这是所有页面元素的缺省值),或者是 hidden。后者相当于 display: none,但仍然占用页面空间。

其实 visibility可以有第三种值,就是 collapse。当一个元素的 visibility属性被设置成 collapse值后,对于一般的元素,它的表现跟 hidden是一样的。但例外的是,如果这个元素是table相关的元素,例如table行,table group,table列,table column group,它的表现却跟 display: none一样,也就是说,它们占用的空间也会释放。

但遗憾的是,各种浏览器对 collapse值的处理方式不一样。看一下下面的演示:

HTML代码

<table cellspacing="0" class="table"><tr><th>Fruits</th><th>Vegetables</th><th>Rocks</th></tr><tr><td>Apple</td><td>Celery</td><td>Granite</td></tr><tr><td>Orange</td><td>Cabbage</td><td>Flint</td></tr></table><p><button>collapse行1</button></p><p><button>hide行1</button></p><p><button>重置</button></p>

CSS代码

body {
  text-align: center;
  padding-top: 20px;
  font-family: Arial, sans-serif;
}

table {
  border-collapse: separate;
  border-spacing: 5px;
  border: solid 1px black;
  width: 500px;
  margin: 0 auto;
}

th, td {
  text-align: center;
  border: solid 1px black;
  padding: 10px;
}

.vc {
  visibility: collapse;
}

.vh {
  visibility: hidden;
}

button {
  margin-top: 5px;
}

Javascript代码

var btns = document.getElementsByTagName('button'),
    rows = document.getElementsByTagName('tr');

btns[0].addEventListener('click', function () {
  rows[1].className = 'vc';
}, false);

btns[1].addEventListener('click', function () {
  rows[1].className = 'vh';
}, false);

btns[2].addEventListener('click', function () {
  rows[1].className = '';
}, false);

演示

CSS-Tricks的Almanac建议说不要使用这个值,因为浏览器的不统一。

据我的观察:

  • 在谷歌浏览器里,使用 collapse值和使用 hidden值没有什么区别。 (See this bug report and comments)
  • 在火狐浏览器、Opera和IE11里,使用 collapse值的效果就如它的字面意思:table的行会消失,它的下面一行会补充它的位置。

说实话,估计这个值很少人会使用它,但你要知道确实可以使用这样的一个值,如果以前不知道它,那么,现在,在有些罕见的地方,你也许就会变得聪明一点了。

3.CSS的background简写方式里新增了新的属性值

在CSS2.1里, background属性的简写方式包含五种属性值 – background-color, background-image, background-repeat, background-attachment, and background-position。从CSS3开始,又增加了3个新的属性值,加起来一共8个。下面是按顺序分别代表的意思:

background: [background-color] [background-image] [background-repeat]
            [background-attachment] [background-position] / [ background-size]
            [background-origin] [background-clip];

注意里面的反斜杠,它更 font和border-radius里简写方式使用的反斜杠的用法相似。反斜杠可以在支持这种写法的浏览器里在 position后面接着写 background-size

除此之外,你开可以增加另外两个描述它的属性值: background-originbackground-clip.

它的语法用起来像下面这个样子:

.example {
  background: aquamarine url(img.png)
              no-repeat
              scroll
              center center / 50%
              content-box content-box;
}

你可以用下面的演示检测你的浏览器是否支持这种写法:

关于浏览器的支持情况,大概所有的现代浏览器都支持这些新属性值,但对于一些非常老旧的浏览器(IE6/7/8),最好在代码里提供一些万一不支持的补救机制。

4.CSS的clip属性只在绝对定位的元素上才会生效

之前说到了 background-clip,你可能会想到 clip属性。它的用法是下面这个样子:

.example {
    clip: rect(110px, 160px, 170px, 60px);
}

它的作用是按指定的尺寸、规定的大小裁剪元素。很多简单,但唯一你需要注意的事情是, clip只会在绝对定位的元素上生效。所有,你必须这样做:

.example {
    position: absolute;
    clip: rect(110px, 160px, 170px, 60px);
}

在下面的演示中,你可以看到当元素在绝对定位/相对定位的切换中表现出来的效果:

但是,你也可以将元素的 position设置成 position: fixed,因为,根据 css官方规范fixed的元素也属于‘absolutely positioned’元素。

5.元素竖向的百分比设定是相对于容器的宽度,而不是高度

这是一个很让人困惑的CSS特征,我之前也谈到过它。我们大家都知道,当按百分比设定一个元素的宽度时,它是相对于父容器的宽度计算的,但是,对于一些表示竖向距离的属性,例如 padding-top, padding-bottom, margin-top, margin-bottom等,当按百分比设定它们时,依据的也是父容器的宽度,而不是高度。

下面是一个实例演示,你可以调整容器的宽度,但你会发现,黄块块的 padding-bottom的距离也会随之宽度而变大或变小。

HTML代码

<div class="wrapper" id="w"><div class="box" id="b"></div></div><input type="range" min="120" max="400" value="400" class="range" id="r"><output>宽度是: <span id="op">400px</span></output><output>黄块块的Padding bottom是:<br><span id="op2">10%</span></output>

CSS代码

body {
  font-family: Arial, sans-serif;
  padding-top: 30px;
  text-align: center;
}

.wrapper {
  width: 400px;
  margin: 0 auto;
  border: solid 1px black;
}

.box {
  width: 100px;
  height: 100px;
  background: gold;
  margin-left: auto;
  margin-right: auto;
  padding-top: 10%;
  padding-bottom: 10%;
  margin-bottom: 5%;
}

.range {
  display: block;
  margin: 20px auto;
}

output {
  text-align: center;
  display: block;
  font-weight: bold;
  padding-bottom: 20px;
}

output span {
  font-weight: normal;
}

实例演示

上面的代码中,我们对内部子元素声明了3个竖向的距离,都是百分比形式。当移动滑块时,我们的js代码只需修改了容器的宽度。但是,这个这三个属性高度都跟随着变化,可以看出,它们的百分比计算是基于容器的宽度,而不是高度的。

6.border属性比你想象的要复杂

我们很多人都用过这样的写法:

.example {
  border: solid 1px black;
}

这里的 border属性的用法实际上是一种简写的形式,它分别设置了 border-style, border-width, 和 border-color— 用一句代码表示它们三个。

但不要忘记, border-style, border-width, 和 border-color也都有自己的简写形式。所以, border-width可以写成这样:

.example {
  border-width: 2px 5px 1px 0;
}

这样,四个边的宽度被一次设定。 border-colorborder-style属性也可以这样做。下面的这个实例演示就是用的这种写法:

HTML代码

<div class="box">

CSS代码

body {
  font-family: Arial, sans-serif;
}

.box {
  width: 150px;
  height: 150px;
  margin: 20px auto;
  border-color: peachpuff chartreuse thistle firebrick;
  border-style: solid dashed dotted double;
  border-width: 10px 3px 1px 8px;
}

p {
  text-align: center;
}

 

演示

其实,这些每个属性还可以继续细化,被拆分成 border-left-style, border-top-width, border-bottom-color….

但是,你无法用 border的简写分别对四个边设置不同的值,只能分开来设置。所以, border是一个简写里还有简写的属性。

7.text-decoration属性变成了属性简写

我相信有些小知识会让你大吃一惊。

跟着最新的CSS规范, text-decoration现在的写法是这样的:

a {
  text-decoration: overline aqua wavy;
}

text-decoration属性现在需要用三种属性值来表示了: text-decoration-line, text-decoration-color, and text-decoration-style.

但不幸的是,目前只有火狐浏览器实现了对这些新属性的支持。

你可以用火狐浏览器试一试下面的演示:

HTML代码

<a href="#" id="a">IT'S LIKE WATER, PEOPLE

(You should see a wavy line on top. Currently works only in Firefox)

CSS代码

body {
  padding: 30px;
  text-align: center;
  font-family: Arial, sans-serif;
}

a, a:visited {
  color: blue;
  background: aqua;
  -moz-text-decoration-color: aqua;
  -moz-text-decoration-line: overline;
  -moz-text-decoration-style: wavy;
  text-decoration-color: aqua;
  text-decoration-line: overline;
  text-decoration-style: wavy;
}

演示

在这演示中,我们没有使用简写形式,而是分开描述每个属性。这是可以更好的保证浏览器的向后兼容,使那些目前不支持这种写法的浏览器也不受影响。

8.border-width属性可以使用预定义常量值

也许对与你来说这并不是一个什么新鲜信息。除了可以使用标准宽度值(例如5px或1em)外, border-width属性可以接受预定义的常量值: medium, thin, 和 thick

事实上,如果你不给 border-width属性赋值,那它的缺省值是“medium”。下面的演示就是用了预定义常量值:

HTML代码

<div class="example">

CSS代码

body {
  font-family: Arial, sans-serif;
  text-align: center;
}

.example {
  width: 100px;
  height: 100px;
  margin: 20px auto;
  background: aqua;
  border: solid thick red;
}

演示

在浏览器使用这些预定义常量值时,CSS规范里并没有规定都应该是什么宽度,但从我的观察看,它们的值分别是 1px, 3px, 和 5px.

9.为什么没有人使用border-image

之前我曾经写过一篇关于CSS的 border-image属性的文章。现在几乎所有的现代浏览器都支持这个属性——除了IE10及以下IE版本。

看起来这是一个非常漂亮的CSS功能,它可以让你用图片修饰元素的边框。下面是一个实例演示,你可以拖拽调整里面的方块的大小,看看有什么边框图案的变化。

HTML代码

<div class="bi"><p><上面的方块使用了图片描边。在这个方块的右下角,有一个可以调整这个方块大小的小三角,点住它,拖动它调整方块大小,看看有什么效果。.</strong></p><p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p></div>

CSS代码

body {
  font-family: Arial, sans-serif;
  text-align: center;
}

.bi {
    border: 45px solid transparent;
    -webkit-border-image: url(http://www.webhek.com/wordpress/wp-content/uploads/2014/07/bg-pawprints-all.jpg) 45 round;
    -moz-border-image: url(http://www.webhek.com/wordpress/wp-content/uploads/2014/07/bg-pawprints-all.jpg) 45 round;
    border-image: url(http://www.webhek.com/wordpress/wp-content/uploads/2014/07/bg-pawprints-all.jpg) 45 round;
    font-family: Arial, Helvetica, sans-serif;
    color: #222;
    width: 500px;
    margin: 30px auto 30px auto;
    overflow: hidden;
    resize: both;
}

演示

但不幸的是,这么好的一个功能,却没有看到多少人使用它,也许是我的眼界太窄。如果你在哪看到过有人使用 border-image属性,或你在真正项目中使用了它,请留言告诉我。

10.你知道table里的empty-cells属性吗?

css里的 empty-cells属性是所有浏览器都支持的,甚至包括IE8,它的用法是下面这个样子:

table {
  empty-cells: hide;
}

估计你从语义上已经猜出它的作用了。它是为HTML table服务的。它会告诉浏览器,当一个table单元格里没有东西时,就隐藏它。下面的演示里,你可以点击里面按钮,它会切换 empty-cells的属性值,看看table的显示有什么变化。

HTML代码

<table cellspacing="0" id="table"><tr><th>Fruits</th><th>Vegetables</th><th>Rocks</th></tr><tr><td></td><td>Celery</td><td>Granite</td></tr><tr><td>Orange</td><td></td><td>Flint</td></tr></table><button id="b" data-ec="hide">切换EMPTY-CELLS</button>

CSS代码

body {
  text-align: center;
  padding-top: 20px;
  font-family: Arial, sans-serif;
}

table {
  border: solid 1px black;
  border-collapse: separate;
  border-spacing: 5px;
  width: 500px;
  margin: 0 auto;
  empty-cells: hide;
  background: lightblue;
}

th, td {
  text-align: center;
  border: solid 1px black;
  padding: 10px;
}

button {
  margin-top: 20px;
}

js代码

var b = document.getElementById('b'),
    t = document.getElementById('table');

b.onclick = function () {
  if (this.getAttribute('data-ec') === 'hide') {
    t.style.emptyCells = 'show';
    this.setAttribute('data-ec', 'show');
  } else {
    t.style.emptyCells = 'hide';
    this.setAttribute('data-ec', 'hide');
  }
};

演示

在上面的演示中,我为能让单元格的边框显示出来,在单元格的边框间添加了空隙。有时候这个属性不会有任何视觉效果,因为你必须让你里面有可见的东西。

11.font-style的oblique属性值

对与css的 font-style属性,我估计大家每次见到的都是使用“normal”或 “italic”两个属性值。但事实上,你还可以让它赋值为“oblique”。请看下面的演示:

HTML代码

<h1>Oblique Text</h1><h1>Italic Text</h1>

CSS代码

h1 {
  font-weight: normal;
  font-family: Georgia, serif;
  font-style: oblique;
  text-align: center;
  font-size: 50px;
}

h1:nth-child(2) {
  font-style: italic;
}

p {
  font-family: Arial, sans-serif;
  text-align: center;
}

演示

这是什么意思?为什么“oblique”和斜体”italic”的效果是一样的?

CSS规范中是这样描述“oblique”的:

“…让一种字体标识为斜体(oblique),如果没有这种格式,就使用italic字体。”

这里描述所用的“oblique”和“italic”都是倾斜的意思。“oblique”在 维基百科里的解释就是一种排版术语,就是一种倾斜的文字,但不是斜体。

因为“oblique”对于 font-style来说是一种合法的属性值,它可和italic进行互换,除非真有一种字体只提供了oblique体而没有提供斜体。

但我似乎从来没有听说过哪种字形提供过oblique字体,也许我错了。研究发现,一种字库好像不能同时提供斜体和oblique两种字体,因为oblique基本上是一种模仿的斜体,而不是真正的斜体。

所以,如果我没有猜错的话,如果一种字库里没有提供斜体字,那当使用CSS的 font-style: italic时,浏览器实际上是按 font-style: oblique显示的。

12.word-wrap和overflow-wrap是等效的

word-wrap并不是一个很常用的CSS属性,但在特定的环境中确实非常有用的。我们经常使用的一个例子是让页面中显示一个长url时换行,而不是撑破页面,下面是一个例子。

HTML代码

<p class="p" id="p">supercalifragilisticexpialidocious</p><button id="b" data-ww="break-word">TOGGLE word-wrap</button>

CSS代码

body {
  font-family: Arial, sans-serif;
  text-align: center;
}

.p {
  font-size: 24px;
  text-align: center;
  width: 200px;
  margin: 20px auto;
  border: solid 1px black;
  min-height: 60px;
  word-wrap: break-word;
}

button {
  display: block;
  margin: 0 auto;
}

JS代码

var p = document.getElementById('p'),
    b = document.getElementById('b');

b.onclick = function () {
  if (this.getAttribute('data-ww') === 'break-word') {
    p.style.wordWrap = 'normal';
    this.setAttribute('data-ww', 'normal');
  } else {
    p.style.wordWrap = 'break-word';
    this.setAttribute('data-ww', 'break-word');
  }
};

演示

因为这个属性最初是由微软发明的,所以,所有的浏览器都支持这个属性。

尽管有所有的浏览器都支持,但W3C决定要用 overflow-wrap替换 word-wrap,我想可能是他们认为 word-wrap用词不当。 overflow-wrapword-wrap具有相同的属性值,但现在, word-wrap被当作 overflow-wrap的备选写法。

虽然已经有不少的浏览器支持 overflow-wrap这种写法,但看起来没必要使用 overflow-wrap来让老的浏览器不支持。所有的浏览器都会继续支持 word-wrap这种写法。

这其中有多少是以前不知道的?

不知道你从这篇博客里学到了多少知识?我希望它对你有些用处。非常有经验的Web程序员可能会知道其中的大部分,但未必全部。而如果你是新手,想必收益颇丰。

 

常用公共DNS服务器地址

$
0
0

  DNS,全称Domain Name System,即域名解析系统,帮助用户在互联网上寻找路径,它在互联网的作用是把域名转换成为网络可以识别的IP地址。目前国内电信运营商通过使用DNS劫持和DNS污染的方法,干扰用户正常上网,使得用户无法访问众多国外常用服务,因此今天我介绍一些国内外的DNS服务器地址,供大家选择使用。

   国外DNS服务器地址

  Google Public DNS (8.8.8.8, 8.8.4.4)

  OpenDNS (208.67.222.222, 208.67.220.220)

  OpenDNS Family (208.67.222.123, 208.67.220.123)

  V2EX DNS (199.91.73.222;178.79.131.110)

  Comodo Secure (8.26.56.26, 8.20.247.20)

  UltraDNS (156.154.70.1, 156.154.71.1)

  Norton ConnectSafe (199.85.126.10, 199.85.127.10)

   国内DNS服务器地址

  OneDNS  (112.124.47.27)

  OpenerDNS(42.120.21.30)

  aliDNS (223.5.5.5, 223.6.6.6)

  114DNS (114.114.114.114, 114.114.115.115)

  114DNS安全版 (114.114.114.119, 114.114.115.119)

  114DNS家庭版 (114.114.114.110, 114.114.115.110)

评论《常用公共DNS服务器地址》的内容...

相关文章:


微博: 新浪微博 - 微信公众号:williamlonginfo
月光博客投稿信箱:williamlong.info(at)gmail.com
Created by William Long www.williamlong.info

7.3 使用JMS实现请求/应答程序

$
0
0

7.3 Implementing request/reply with JMS

7.3 使用JMS实现请求/应答程序

 

As described in earlier chapters, messaging is all about the decoupling of senders from

receivers. Messages are sent by one process to a broker, and messages are received from

a broker by a different process in an asynchronous manner. One style of system architecture

that can be implemented using JMS is known as request/reply. From a high level,

a request/reply scenario involves an application that sends a message (the request)

and expects to receive a message in return (the reply). Traditionally, such a system

design was implemented using a client-server architecture, with the server and the client

communicating in a synchronous manner across a network transport (TCP, UDP,

and so on). This style of architecture certainly has scalability limitations, and it’s difficult

to distribute it further. That’s where messaging enters the picture—to provide the

ability to design a system that can easily scale much further via a messaging-based

request/reply design. Some of the most scalable systems in the world are implemented

using asynchronous processing like that being demonstrated in this example.

 

通过前面几章我们了解到,消息是用来为其发送者和接收者解耦的.消息通过一个进程发送给代理,然后

代理在另外一个进程异步的接收消息.一种可以利用JMS来实现的系统架构被称为请求/应答.概括的说,

一个请求/应答场景包括一个发送消息(请求)并期望接收消息返回值(应答)的应用程序.通常,这样的系统

被设计成CS架构,服务端和客户端通过网络传输协议(TCP,UDP等等)同步的进行通信.这种架构方式在可

扩展方面具有明显的限制,很难获得长远发展.消息系统正是为此而生--通过基于消息的请求/应答设计

模式能够设计出易于扩展的系统.正如例子中展示的那样,世界上可扩展性最好的系统都是使通过

异步处理方式实现的.

 

The diagram shown in figure 7.2 depicts an overview of the request/reply paradigm.

Note that the client consists of both a producer and a consumer, and the

worker also consists of both a producer and a consumer. These two entities are both

explained next.

 

图7.2 是请求/应答系统的示例.注意,客户端包含消息生产者(producer)和消息消费者(consumer),并且

工作者(worker)也包含消息生产者(producer)和消息消费者(consumer).后面将解释客户端和工作者(worker).

 

First, the producer creates a request in the form of a JMS message and sets a couple

of important properties on the message—the correlation ID (set via the 

message property) and the reply destination (set via the JMSReplyTo

message property). The correlation ID is important, as it allows requests to be correlated

with replies if there are multiple outstanding requests. The reply destination is

where the reply is expected to be delivered (usually a temporary JMS destination since

it’s much more resource friendly). The client then configures a consumer to listen on

the reply destination.

 

首先,消息生产者创建一个以JMS消息格式封装的请求并在消息中设置一些重要的属性,包括correlation ID

(通过消息的JMSCorrelationID属性设置)和reply destination(响应发送目的地,通过JMSReplyTo属性设置).

correlation ID属性非常重要,因为在请求数量非常多时需要使用这个属性来关联请求和应答.reply destination

属性指定应答发往的目的地(通常是一个临时的JMS目的地,因为reply destination比较消耗资源).接下来,客户端配置

一个消息消费者监听响应消息目的地(reply destination).

 

Second, a worker receives the request, processes it, and sends a reply message

using the destination named in the JMSReplyTo property of the request message. The

reply message must also set JMSCorrelationID using the correlation ID from the original request.

When the client receives this reply message, it can then properly associateit with the original 

request.

 

其次,一个工作者(woker)接收到请求,并处理请求,然后发送一个响应消息到请求消息的JMSReplyTo属性指定的目的中.

响应消息必须用原始请求消息correlation ID的属性值来设置JMSCorrelationID属性,当客户端收到响应消息后,

可以通过correlation ID关联到初始的请求.

 

Now comes the interesting part—to demonstrate how this architecture can be

highly scalable. Imagine that a single worker isn’t enough to handle the load of

incoming requests. No problem: just add additional workers to handle the load.

 

现在,感兴趣的问题是:这种结构如何实现高可扩展性.想象一个场景:单一的工作者无法处理

大量并发的请求负载时怎么办?当然没问题:可以添加工作者来平衡负载.

 

Those workers can even be distributed across multiple hosts—this is the most important

aspect of scaling this design. Because the workers aren’t contending for the same

resources on the same host, the only limit is the maximum throughput of messages

through the broker, which is much higher than you can achieve with any classic clientserver

setup. Furthermore, ActiveMQ can be scaled both vertically and horizontally, as

discussed in part 4. Let’s now take a look at a simple implementation of request/reply.

 

这些工作者甚至分布到自不同的主机,这也是这种可扩展性设计中最重要的部分.因为工作者并不是在

争夺相同主机上的资源,所以唯一的限制是代理中消息的最大吞吐量,它比使用普通的客户端服务器架构

能达到的最大吞吐量要大得多.并且,ActiveMQ可以进行水平和垂直扩展,正如在本书第4部分中讨论的那样.

下面让我们看看请求/应答程序的基本实现.

 

7.3.1 Implementing the server and the worker

7.3.1 实现服务和工作者(worker)

 

The first piece of the system on which to focus is the message broker. Get the broker

up and running so that it’s ready for connections when both sides are started up. An

embedded broker will be used for this example because it’s easy to demonstrate. The

second piece of the system to get running is the worker. The worker is composed of a

message listener that consumes the message and sends a response. Even though this is

a simple implementation, it’ll provide you enough information to use it with your systems.

So take a look at the server implementation.

首先,需要关注的是系统中使用的消息代理.先要启动代理,以便两边程序都启动时可以连接到代理.

为方便说明本例中使用一个嵌入式代理.其次,需要启动系统中的工作者(worker).工作者有消息监听

器组成,用来接收处理消息和发送消息响应.尽管如此,这个例子也只是一个简易的实现,但它将为你提供

足够的信息.下面看一下服务的实现.

 

Listing 7.14 Create a broker, a consumer, and a producer for the request/reply example

代码清单7.14 在请求/响应实例创建中一个代理,消费者以及生产者

 

...

public void start() throws Exception 

{

  createBroker();

  setupConsumer();

}

 

private void createBroker() throws Exception 

{

  broker = new BrokerService();

  broker.setPersistent(false);

  broker.setUseJmx(false);

  broker.addConnector(brokerUrl);

  broker.start();

}

 

private void setupConsumer() throws JMSException 

{

  ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);

  Connection connection;

  connection = connectionFactory.createConnection();

  connection.start();

  session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

  Destination adminQueue = session.createQueue(requestQueue);

  producer = session.createProducer(null);

  producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

  consumer = session.createConsumer(adminQueue);

  consumer.setMessageListener(this);

}

 

public void stop() throws Exception 

{

  producer.close();

  consumer.close();

  session.close();

  broker.stop();

}

...

 

As you can see, the start() method calls one method to create and start an embedded

broker, and another method to create and start up the worker. The createBroker() method 

uses the BrokerService class to create an embedded broker. ThesetupConsumer() method 

creates all the necessary JMS objects for receiving and sending messages including 

a connection, a session, a destination, a consumer, and a producer.

 

从代码中可以看到,start()方法调用一个方法创建并启动一个嵌入式代理,另外一个方法用于启动工作者.

createBroker()方法使用BrokerService类来创建爱你一个嵌入式代理.ThesetupConsumer()方法通过创建

JMS所需的所有对象来发送和接收消息,这些JMS对象包括:一个连接,一个session,一个消息目的地,一个消息

消费者和一个生产者.

 

The producer is created without a default destination, because it’ll send

messages to destinations that are specified in each message’s JMSReplyTo property.

Taking a closer look at the listener, note how it handles the consumption of each

request as shown next.

 

创建消息生产者的时候没有设置默认的消息目的地,因为该生产者会将消息发送到每个消息的

JMSReplyTo属性所指定的目的地中.下面再详细看下请求/响应中的监听者,看看它是如何处理

每个请求的:

 

Listing 7.15 The message listener for the request/reply example

代码清单7.15 请求/响应实例中的消息监听者

 

...

public void onMessage(Message message) 

{

  try 

  {

    TextMessage response = this.session.createTextMessage();

    if (message instanceof TextMessage) {

    TextMessage txtMsg = (TextMessage) message;

    String messageText = txtMsg.getText();

    response.setText(handleRequest(messageText));

  }

  

  response.setJMSCorrelationID(message.getJMSCorrelationID());

  producer.send(message.getJMSReplyTo(), response);

  

  } 

  catch (JMSException e) 

  {

    e.printStackTrace();

  }

}

 

public String handleRequest(String messageText) 

{

  return "Response to '" + messageText + "'";

}

...

 

The listener creates a new message, assigns the appropriate correlation ID, and sends

a message to the reply-to queue. Simple stuff, but still important. Although this message

listener isn’t earth shattering in its implementation, it demonstrates the basic

steps necessary to complete the task of the worker. Any amount of extra processing or

database access could be added to the listener in your systems depending on the

requirements.

 

消息监听器创建一个新消息,并设置合适的correlation ID,然后将消息发送到响应消息队列.

很简单但是很重要.尽管在这个消息监听器的实现中没做什么惊天动地的事情,但是它展示了

工作者完成器任务的必要的基本步骤.根据需求,,可以在监听器中添加其他任意额外的操作

或者数据库访问操作.

 

Starting the server is rather obvious: create an instance of it and call the start()

method. All of the server functionality is housed in the main method, as shown in the

following listing.

启动服务很简单:创建一个server实例并调用start()方法.main方法容纳了server的的所有功能,如

下面的代码清单所示:

 

Listing 7.16 Starting the server for the request-reply example

...

public static void main(String[] args) throws Exception 

{

  Server server = new Server();

  server.start();

  System.out.println();

  System.out.println("Press any key to stop the server");

  System.out.println();

  System.in.read();

  server.stop();

}

...

 

Once the server is started and the worker is running, everything is ready to accept

requests from the client.

 

一旦server启动完成,worker就正常运行了,这样所有准备接收客户端请求的工作已经就绪.

 

7.3.2 Implementing the client

7.3.2 实现客户端

 

The job of the client is to initiate requests to the broker. This is where the whole

request/reply process begins, and is typically triggered by one of your business processes.

This process could be to accept an order, fulfill an order, integrate various business

systems, or buy or sell a financial position. Whatever the case may be, request/reply

begins by sending a message.

 

客户端要做到额工作是初始化发送到代理的请求.这是整个请求/应答过程的起点,并且通常在一个

业务逻辑处理过程中触发.这个过程可能是接受订单,履行订单,整合各类业务系统,财务状况中

的买入卖出等.不管是什么情况,请求/响应过程从发送一个消息开始.

 

Sending a message to the broker requires the standard connection, session, destination,

and producer which are all created in the client by the start() method. This

is all shown in the following listing.

 

发送一个消息到代理需要标准的连接(connection),session,消息目的地(destination)以及消息

生产者(producer),它们都是在client的start()方法中创建的.下面的的代码清单中提供了完整的

示例:

 

Listing 7.17 Methods for starting and stopping the request/reply client

代码清单7.17 启动和停止响应/应答系统客户端的方法

 

...

public void start() throws JMSException 

{

  ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);

  connection = connectionFactory.createConnection();

  connection.start();

  session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

  Destination adminQueue = session.createQueue(requestQueue);

  producer = session.createProducer(adminQueue);

  producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

  tempDest = session.createTemporaryQueue();

  consumer = session.createConsumer(tempDest);

  consumer.setMessageListener(this);

}

 

public void stop() throws JMSException 

{

  producer.close();

  consumer.close();

  session.close();

  connection.close();

}

...

The producer sends a message to the request queue and then the consumer listens on

the newly created temporary queue. Now it’s time to implement an actual logic for the

client, as shown next.

 

消息生产者发送也给消息到请求队列中,然后消息消费者监听新创建的临时队列.下面的代码中

展示了实现客户端的真正逻辑:

 

Listing 7.18 Implementation of logic for request/reply client

代码清单7.18 实现客户端请求/应答逻辑

 

...

public void request(String request) throws JMSException 

{

  System.out.println("Requesting: " + request);

  TextMessage txtMessage = session.createTextMessage();

  txtMessage.setText(request);

  txtMessage.setJMSReplyTo(tempDest);

  String correlationId = UUID.randomUUID().toString();

  txtMessage.setJMSCorrelationID(correlationId);

  this.producer.send(txtMessage);

}

 

public void onMessage(Message message) 

{

  try 

  {

    System.out.println("Received response for: " + ((TextMessage) message).getText());

  } 

  catch (JMSException e) 

  {

    e.printStackTrace();

  }

}

...

 

The request() method shown in listing 7.18 creates a message with the request content,

sets the JMSReplyTo property to the temporary queue, and sets the correlation

ID—these three items are important. Although the correlation ID in this case uses a

random UUID, just about any ID generator can be used. Now we’re ready to send a

request.

 

代码清单7.18中所示的request()方法使用请求内容创建一个消息并设置JMSReplyTo属性值,接着发送

这个消息到临时队列,最后设置correlation ID 属性值.上述3个步骤很重要.在这个例子中,是使用

一个随机的UUID值来设置correlation ID的,也还可以使用其他任何ID生成器来生成这个ID.

接下就可以发送一个请求了.

 

Just like starting the server was a simple main method, the same is true of the client

as shown in the next listing.

启动客户端也可以像启动sever一样,简单的使用一个main方法即可,下面是代码清单:

 

Listing 7.19 Starting the request/reply client

代码清单7.19 启动请求/应答系统客户端

 

...

public static void main(String[] args) throws Exception 

{

  Client client = new Client();

  client.start();

  int i = 0;

  while (i++ < 10) 

  {

    client.request("REQUEST-" + i);

  }

  Thread.sleep(3000); //wait for replies

  client.stop();

}

...

 

As explained earlier, this is a simple implementation. So upon starting up the client,

10 requests are sent to the broker. Now it’s time to actually run the example.

 

如前文所述,这个是一个简单的请求/应答系统的实现.因此,启动客户端以后,会发送10个请求到代理.

下面让我们真正的运行一下这个实例.

 

7.3.3 Running the request/reply example

7.3.3 运行请求/应答实例程序

 

Running the example requires two terminals: one for the server and one for the client.

The server needs to be started first. The server is implemented in a class named

Server and the client is implemented in a class named Client. Because each of these

classes is initiated via a main method, it’s easy to start each one. The following listing

demonstrates starting up the server class.

 

运行这个实例程序需要两个终端(译注:两个dos窗口或者两个Linux命令窗口):一个用于运行server,另一个

用于client,必须先运行server.sever通过Server类来实现,client通过Client类实现.因为这两个类都是

通过main方法初始化的,所以运行它们很容易.启动这两个类的代码如下所示:

 

Listing 7.20 Start up the server for the request/reply example

 

$ mvn exec:java -Dexec.mainClass=org.apache.activemq.book.ch7.sync.Server

 

...

INFO | Using Persistence Adapter: MemoryPersistenceAdapter

INFO | ActiveMQ 5.4.1 JMS Message Broker (localhost) is starting

INFO | For help or more information please see:http://activemq.apache.org/

INFO | Listening for connections at: tcp://dejan-bosanacs-macbook-pro.local:61616

INFO | Connector tcp://dejan-bosanacs-macbook-pro.local:61616 Started

INFO | ActiveMQ JMS Message Broker (localhost, ID:dejanb-57522-1271170284460-0:0) started

Press any key to stop the server

INFO | ActiveMQ Message Broker(localhost, ID:dejanb-57522-1271170284460-0:0) is shutting down

INFO | Connector tcp://dejan-bosanacs-macbook-pro.local:61616 Stopped

INFO | ActiveMQ JMS Message Broker (localhost, ID:dejanb-57522-1271170284460-0:0) stopped

...

 

When the server is started up, then it’s time to start up the client and begin sending

requests. The following listing shows how to start up the client.

 

server启动后,即可启动client一边发送请求.启动client代码如下面代码清单所示:

 

Listing 7.21 Start up the client for the request/reply example

代码清单7.21 启动请求/响应实例客户端(client)

 

$ mvn exec:java -Dexec.mainClass=org.apache.activemq.book.ch7.sync.Client

...

Requesting: REQUEST-1

Requesting: REQUEST-2

Requesting: REQUEST-3

Requesting: REQUEST-4

Requesting: REQUEST-5

Requesting: REQUEST-6

Requesting: REQUEST-7

Requesting: REQUEST-8

Requesting: REQUEST-9

Requesting: REQUEST-10

Received response for: Response to 'REQUEST-1'

Received response for: Response to 'REQUEST-2'

Received response for: Response to 'REQUEST-3'

Received response for: Response to 'REQUEST-4'

Received response for: Response to 'REQUEST-5'

Received response for: Response to 'REQUEST-6'

Received response for: Response to 'REQUEST-7'

Received response for: Response to 'REQUEST-8'

Received response for: Response to 'REQUEST-9'

Received response for: Response to 'REQUEST-10'

...

 

Note that when the client is started, 10 requests are sent to initiate the request/reply

process and 10 replies are received back from the worker. Although it’s not glorious,

the power in this simple request/reply example will become evident when you apply it

to your own business processes.

 

注意到当client启动后,发送了10个请求用于激活请求/响应进程,然后收到了来自worker的响应.

尽管这个例子算不上很辉煌,但是日后必将称为你在其他业务中实现请求/响应系统的参考.

 

Using the request/reply pattern, envision that there are thousands of requests

entering the broker every second from many clients, all distributed across many hosts.

 

使用请求/应答模式,代理将每秒钟收到的来自无数的客户端的成千上万个请求全部分发到不同的

主机中处理.

 

In a production system, more than just a single broker instance would be used for the

purposes of redundancy, failover, and load balancing. These brokers would also be

distributed across many hosts. The only way to handle this many requests would be to

use many workers. Producers can always send messages much faster than a consumer

can receive and process them, so lots of workers would be needed, all of them spread

out across many hosts as well.

 

在生产系统中,会使用更多的代理实例用于备份,失效转移以及负载均衡.这些代理也会被分布于很多

的主机上.处理如此多请求的唯一方法是使用多工作者(worker).因为消息发送者发送消息的速度

可能比消息消费者接收并处理消息的速度快的多,所以就需要大量的工作者(worker),这些工作者

同样也分布于大量的主机上.

 

The advantage of using many workers is that each one

can go up and down at will, and the overall system itself isn’t affected. The producers

and workers would continue to process messages, and even if one of them crashed, it

wouldn’t affect the system. This is exactly how many large-scale systems can handle

such a tremendous load—through the use of asynchronous messaging like that demonstrated

by the request/reply pattern.

 

使用多工作者的好处是任何的工作者都可以根据需要启用或者停用,而整个系统不会收到影响.消息生产者

和工作者会正常处理消息,即使她们当中的一些已经崩溃了,也不会影响系统运行.这正是那些大型系统可以

处理海量负载的原因--使用前文介绍过的基于请求/应答模式的异步消息系统.

 

The JMS API can be tedious, as it requires you to write a lot of code for initializing

all the necessary JMS objects such as connections, sessions, producers, consumers, and

so forth. This is where the Spring Framework provides a lot of benefit. It helps you to

remove such boilerplate code by supplying a more cogent API and by simplifying the

overall configuration.

 

JMS的API可以说是繁琐的,因为它要求开发者书写大量的初始化代码用于初始化必要的JMS对象,包括

connection, session, producer, consumer等等.使用Spring框架通过提供可靠的API来帮助开发者

移除(类似于JMS对象初始化)的哪些固定的代码,以便简化整个配置过程.这正式使用Spring框架带来

的好处.



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>