月度归档:2018年07月

Jsp EL

1、什么是EL

        EL是JSP表达式语言,全称是ExpressionLanguage,使用EL的目的是简化在JSP中访问变量的方式,简单静态HTML与Java代码的耦合。

2、JSP EL 表达式用于以下情形

静态文本
标准标签和自定义标签
安装支持Servlet2.4/JSP2.0的Container

3、基本语法格式

${ EL Expression}

示例:
${ “Helloworld” }  //输出字符串常量
${ str }  //输出字符串变量str的值
${ 3 + 2 }  //输出3+2的结果
${ user.name} //输出user对象的name属性
${user[“name”] }  //同上
${ sessionScope[“user”].name } //同上
${user.name}
访问对象user的getName()方法以得到name成员的值。
${list[1]}
访问list对象的第二项。
${map[“key”]}
访问map指定键的值。

4、操作符

算术操作符(+,-,*,/,%)
逻辑操作符(&&,||,!或and,or,not)
XML操作符
lt  <
le  <=
gt   >
ge  >=
比较操作符(>,>=,<,<=,==,!==)—可以自动转换数据类型
空操作符(empty)//当值为null时返回true

EL的算术运算符和Java中的运算符的大致相同,优先级也相同。

注意:'+' 运算符不会连接字符串了,他只用于加法运算。

EL关系运算符有以下六个运算符

关系运算符      说明                范例             结果
= = 或 eq   |    等于   |${ 5 = = 5 } 或 ${ 5 eq 5 } | true
!= 或 ne    |   不等于 |${ 5 != 5 } 或 ${ 5 ne 5 } | false
< 或 lt     |   小于    |${ 3 < 5 }或 ${ 3 lt 5 }    | true
> 或 gt     |   大于    |${ 3 > 5 }或 ${ 3 gt 5 }    | false
<= 或 le    | 小于等于 |${ 3 <= 5 }或 ${ 3 le 5 }   | true
>= 或 ge    | 大于等于 |${ 3 >= 5 }或 ${ 3 ge 5 }   | false

5、集合访问

数组访问

${}   //如request.getAttribute(“name”);

List访问
Map访问

6、隐式对象

JSP 表达式语言定义了一组隐式对象,其中许多对象在 JSP scriplet 和表达式中可用:

允许对以下对象进行简易访问:

术语 定义

param

将请求参数名称映射到单个字符串参数值(通过调用 ServletRequest.getParameter (String name) 获得)。getParameter (String) 方法返回带有特定名称的参数。表达式 $(param.name) 相当于 request.getParameter (name)。

paramValues

将请求参数名称映射到一个数值数组(通过调用 ServletRequest.getParameter (String name) 获得)。它与 param 隐式对象非常类似,但它检索一个字符串数组而不是单个值。表达式 ${paramvalues.name) 相当于 request.getParamterValues(name)。

header

将请求头名称映射到单个字符串头值(通过调用 ServletRequest.getHeader(String name) 获得)。表达式 ${header.name} 相当于 request.getHeader(name)。

headerValues

将请求头名称映射到一个数值数组(通过调用 ServletRequest.getHeaders(String) 获得)。它与头隐式对象非常类似。表达式 ${headerValues.name} 相当于 request.getHeaderValues(name)。

cookie 将 cookie 名称映射到单个 cookie 对象。向服务器发出的客户端请求可以获得一个或多个 cookie。表达式 ${cookie.name.value} 返回带有特定名称的第一个 cookie 值。如果请求包含多个同名的 cookie,则应该使用 ${headerValues.name} 表达式。
initParam 将上下文初始化参数名称映射到单个值(通过调用 ServletContext.getInitparameter(String name) 获得)。

除了上述两种类型的隐式对象之外,还有些对象允许访问多种范围的变量,如 Web 上下文、会话、请求、页面:

术语 定义

pageScope

将页面范围的变量名称映射到其值。例如,EL 表达式可以使用 ${pageScope.objectName} 访问一个 JSP 中页面范围的对象,还可以使用 ${pageScope.objectName.attributeName} 访问对象的属性。

requestScope

将请求范围的变量名称映射到其值。该对象允许访问请求对象的属性。例如,EL 表达式可以使用 ${requestScope.objectName} 访问一个 JSP 请求范围的对象,还可以使用 ${requestScope.objectName.attributeName} 访问对象的属性。

sessionScope

将会话范围的变量名称映射到其值。该对象允许访问会话对象的属性。例如:

$sessionScope.name}

applicationScope

将应用程序范围的变量名称映射到其值。该隐式对象允许访问应用程序范围的对象

 特别说明:

cookie对象

所谓的cookie是一个小小的文本文件,它是以key、value的方式将SessionTracking的内容记录在这个文本文件内,这个文本文件通常存在于浏览器的暂存区内。JSTL并没有提供设定cookie的动作,因为这个动作通常都 是后端开发者必须去做的事情,而不是交给前端的开发者。如果我们在cookie中设定一个名称为userCountry的值,那么可以使 用${cookie.userCountry}来取得它。

header和headerValues(请求报头对象)
header储存用户浏览器和服务端用来沟通的数据,当用户要求服务端的网页时,会送出一个记载要求信息的标头文件,例如:用户浏览器的版本、用户计算机所设定的区域等其他相关数据。如果要取得用户浏览器的版本,即${header["User-Agent"]}。另外在很少机会下,有可能同一标头名称拥有不同的值,此时必须改为使用headerValues来取得这些值。
注意:因为User-Agent中包含“-”这个特殊字符,所以必须使用“[]”,而不能写成${header.User-Agent}。
initParam
就像其他属性一样,我们可以自行设定web应用的环境参数(Context),当我们想取得这些参数时,可以使用initParam隐含对象去取得它,例如:当我们在web.xml中设定如下:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<context-param>
<param-name>userid</param-name>
<param-value>mike</param-value>
</context-param>
</web-app>
那么我们就可以直接使用 ${initParam.userid}来取得名称为userid,其值为mike的参数。下面是之前的做法:String userid =(String)application.getInitParameter("userid");

pageContext对象

我们可以使用 ${pageContext}来取得其他有关用户要求或页面的详细信息。下面列出了几个比较常用的部分。
Expression 说 明
${pageContext.request} |取得请求对象
${pageContext.session} |取得session对象
${pageContext.request.queryString} |取得请求的参数字符串
${pageContext.request.requestURL} |取得请求的URL,但不包括请求之参数字符串
${pageContext.request.contextPath} |服务的web application的名称
${pageContext.request.method} |取得HTTP的方法(GET、POST)
${pageContext.request.protocol} |取得使用的协议(HTTP/1.1、HTTP/1.0)
${pageContext.request.remoteUser} |取得用户名称
${pageContext.request.remoteAddr } |取得用户的IP地址
${pageContext.session.new} |判断session是否为新的,所谓新的session,表示刚由 server产生而client尚未使用
${pageContext.session.id} |取得session的ID
${pageContext.servletContext.serverInfo}|取得主机端的服务信息

7、特别强调

1、注意当表达式根据名称引用这些对象之一时,返回的是相应的对象而不是相应的属性。例如:即使现有的 pageContext 属性包含某些其他值,${pageContext} 也返回 PageContext 对象。

2、 注意 <%@ pageisELIgnored="true" %> 表示是否禁用EL语言,TRUE表示禁止.FALSE表示不禁止.JSP2.0中默认的启用EL语言。

..

JSP & tomcat

设置jsp默认pageEncoding;windows->preference->myeclipse->files and editors->jsp->encoding  utf-8
设置jsp为编辑模式,没有预览界面:https://zhidao.baidu.com/question/581118605.html?qq-pf-to=pcqq.group
tomcat:多线程,每一个客户都会创建一个新的线程,线程之间互不影响。单例模式
单例模式:
饿汉模式:
       优点:确保一个类被调用的时候只创建一个对象,节省时间和内存
       缺点:只要加载到这个类,对象就会被创建,会造成资源浪费
懒汉模式:
       优点:确保一个类被调用的时候只创建一个对象,只有在调用的时候才生出对象,节省时间和内存
       缺点:实现比较复杂
1.Tomcat:免费的开源的轻量级的服务器,中小型企业常用
    bin:  startup.bat 和startup.sh(linux)   shutdown.bat和shutdown.sh
    conf:  server.xml  服务器配置文件 和 web.xml  项目配置
    lib: 常用类库
    logs: 运行日志文件
    webapps:放置运行的项目
    work:放置jsp编译后的class文件
2.Tomcat的lib目录,存放的jar不仅能被tomcat访问,还能被在tomcat上发布的web应用访问。
   JavaWeb应用的lib目录下的jar只能被当前web应用访问。
3.打包web应用: jar  cvf e:\TestWeb.war *.*
   解包web应用:jar xvf e:\TestWeb.war *.*
4.Cookie:位于请求头或者响应头
   a.浏览器第一次访问服务器,没有cookie,在服务器第一次应答时会对浏览器发送一个cookie.
      Cookie mcookie=new Cookie("username","tom");
      res.addCookie(mcookie);
   b.setMaxAge(0) 指定浏览器删除cookie
      setMaxAge(-1) 指定浏览器不保存cookie
  c.setPath(path)和setDomain(domain)  控制cookie的读写范围
      cookie.setPath("/");    //整个tomcat服务器的应用都能读到
      cookie.setPath("/app/");  //只有app应用能读取到
      cookie.setDomain(" .cat.com");  //只有域名为 .cat.com的应用能访问到。
7.不同web应用通讯:Tomcat中<Context=false>表示该应用无法获得其他web应用的ServletContext对象
8.避免并发:为保证Servlet能同时相应多个客户的请求,通过为每个请求分配一个工作线程。或者同步代码
9.JSP指令:
   1.一个完整的JSP页面是由普通的模板元素(html)、注释元素、指令元素、 脚本元素  、动作元素构成。
   2.指令标记、JSP动作标记统称为  jsp标记   。
   3.JSP页面的程序片中可以插入 html 标记。

4.当JSP页面的一个客户线程在执行  synchronized 方法时,其他客户必须等待。

jsp在执行过程中经过(  翻译)阶段,由web容器将之转换成java源代码

jsp在执行过程中经过(编译)阶段,会将java源码转换成class文件( b  )

   特点:
   1.Servlet:优点:业务逻辑处理很方便,写java代码方便
                     缺点:页面表现麻烦
   2.jsp:优点:页面表现方便                        //动态网页技术,动态生成网页数据,而不是动态效果的网页
             缺点:业务逻辑处理麻烦,
      两者通常结合使用。
  为什么说jsp是Servlet?
   因为jsp页面被访问时会被tomcat编译生成对应的java文件,java文件对应的类继承org.apache.jasper.runtime.HttpJspBase类
   而HttpJspBase又继承于HttpServlet.
  Jsp的执行过程:
     1.客户端发送以 .jsp结尾的请求(url)
     2.服务器接收请求后,会进行拦截(tomcat的web.xml),调用JspServlet类,处理jsp文件,生成对应的java文件和class文件(work文件夹下)
     3.tomcat会调用生成好的class文件生成html代码,返回给客户端
  web.xml关于jsp文件配置
   <%@ 指令名 属性="值"  %>   常见指令:page  include  taglib   编译指令,通知Servlet引擎处理消息
    errorPage="error.jsp"          //只能处理运行时错误,不能处理编译时的错误
    web.xml错误处理:
       <error-page>
     <error-code>404</error-code>
     <location>/error404.jsp</location>

</error-page>

   <% page method="doPost"  %>
   <%@ include file="文件绝对URL或相对URL" %>   静态包含,目标可是HTML或JSP,但不能是Servlet
      静态导入:1.把多个页面合成一个页面(编译成一个页面),不能有相同的变量。
                          2.执行效率相对较高
                          3.通常用户没有java代码页面
                          4.耦合性相对较高
   <jsp:include page="目标的绝对URL或相对URL"/>  动态包含  HTML、JSP(目标响应结果会被加到源组件)或Servlet都可以
   无论是静态还是动态包含,源组件和目标组件都共享请求范围内的共享数据。
     动态导入:1.分别生成各自的java文件和class文件,互不影响,可以有相同的变量
                         2.执行效率相对较低
                         3.通常用在java代码较多的页面
                         4.耦合性相对较低
  不能同一个页面,两次forward,类似servlet
10.声明:
   <% ! declar;declar;...  %>
  <%! int a=10,b,c; String d="ww"; %>   声明全局变量:不建议使用,因为Servlet中不建议声明全局变量
11.代码段:
   <% %>
  <% a++; %>
12.表达式:
  <%=a++%>
13.注释: <%--aaaa--%>        直接不会被编译,其他的注释写在jsp中会被编译(如html注释  <!--aa--->)
14.隐含对象:
    四大作用域
     request:
     session:
     application:最大作用域,全局共享信息
     pageContext:作用域最小,只作用于当前的页面;但可以通过pageContext获取其他八个作用域
     out:向客户端发送数据的对象(如html页面代码)
     response:  jsp中不推荐使用
     config:getInitParameter()和getInitParamterValues()   获取servlet的配置信息,但不建议在jsp用
     page:
     exception:使用<% page isErrorPage='true'%>
   路径问题:
     服务器根路径:http://localhost:8080/      D:\MainSoftWare\tomcat7\apache-tomcat-7.0.72\webapps
     项目根路径:http://localhost:8080/Demo/       D:\MainSoftWare\tomcat7\apache-tomcat-7.0.72\webapps\Demo
     String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    <base href="<%=basePath%>">           //base:默认在本页面所有的超链接和路径前面加项目的根路径
    <a href="index.jsp">path</a>              //http://localhost:8080/Demo/index.jsp
      req.getRequestDispather("/").forward(req,resp);      //请求转发   "/"  代表项目根路径  http://localhost:8080/Demo/
     resp.sendRedirect("/");        //重定向      "/" 代表服务器根路径   http://localhost:8080/
    <url-pattern>/index.jsp</url-pattern>  //  "/"代表项目的根路径
    1.相对路径:不能改变文件的相对位置
     2.绝对路径:不能修改项目的名称(推荐使用绝对路径)
15.请求转发    动作指令:jsp:forward、jsp:param等只是运行时的动作
   <jsp:forward  page="目标的绝对URL或相对URL"/>  请求转发之后的代码不会继续执行
   特点:执行forward时,用户请求的地址没有发生改变,但是请求的参数、属性不丢失
16.异常页:
     <%@ page errorPage="errorpage.jsp" %>  应先声明为异常页<% pageisErrorPage="true" %>
17.预编译:
    http://localhost:8080/app/hello.jsp?jsp_precompile=true
18.JNDI(负责将对象和名字绑定,对象工厂负责生产出对象):
   JDBC2.0开始提供javax.sql.DataSource,负责建立与数据库的链接,在程序中访问数据时
   不必编写连接数据库的代码,可以直接从数据源中获取数据库连接。
    Context:服务端:bind(String str,Object obj);      将对象与一个名字绑定
                            lookup(String name);      返回指定名字绑定的对象
   在META-INF下配置context.xml:
   <Context reloadable="true">
  <Resource name="jdbc/bookdb" auth="Container" type="javax.sql.DataSource"
          maxActive="100" maxIdle="30" maxWait="10000"
          username="root"  password="123456"
          driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/bookdb?autoReconnect=true"/>

</Context>

   并且在web.xml中配置:
   <web-app>
    <resource-ref>
          <description>MYSQLDB Connection</description>
          <res-ref-name>jdbc/bookdb</res-ref-name>
          <res-type>javax.sql.DataSource</res-type>
          <res-auth>Container</res-auth>
    </resource-ref>

</web-app>

   public BaseDao() throws Exception{
        //引入JNDI对象与名字绑定
        Context ctx=new InitialContext();
        if(null==ctx)
            throw new Exception("No Exception");
        ds=(DataSource) ctx.lookup("java:comp/env/jdbc/bookdb");

}

19.session会话:
  invalidate();   销毁当前的会话,Servlet容器释放HttpSession对象占用的资源
  cookie机制通常用于保存客户的状态信息,设置最大存活时间后,会保存到浏览器的硬盘,所以要求cookie的属性值必须是可序列化的
  Servlet容器创建新的HttpSession:
  a.一个浏览器进程第一次访问Web应用中支持会话中的任一个网页。
  b.当浏览器与web应用的一次会话已经被销毁后,浏览器进程再次访问web应用中的支持会话的任一个网页。
  JSP默认支持会话,HttpServlet默认不支持会话。
 response.encodeURL(url)   会在浏览器不支持会话的情况,使用URL支持会话。
 response.sendRedirect(response.encodeRedirectURL("mail.jsp"));  重定向时用
 Cookie:
  Cookie cookie=new Cookie("name","kkk");
  res.addCookie(cookie);
 cookie支持中文
 发送前先编码:
      java.net.URLEncoder.encode("书","utf-8");
  接收端解码:
     java.net.URLDecoder.decode(cookies[i].getValue(),"utf-8");
20.Tomcat会话管理:
   a.StandardManager:标准会话管理器。
   b.PersistentManager:更多会话管理功能。
   会话存储:
   FileStore:将HttpSession对象保存在一个文件中
   JDBCStore:将HttpSession对象保存在数据库的一张表中
21.会话监听:
 HttpSessionListener和HttpSessionAttributeListener,必须在web.xml中通过<listener>向Servlet容器注册。
23.EL表达式:
    优点:1.不用导包 2.简洁 3.如果参数没有传过来,则什么都不显示
    User里存Address对象
    EL表达式:$+{ }
       表达式获取参数信息
     <li><%=request.getParameter("uname") %></li>
     <li><%=request.getParameterValues("fav")[0] %></li>
     <li><%=request.getAttribute("realname") %></li>
     <li><%=((List<String>)request.getAttribute("list")).get(2) %></li>
     <li><%=((Map<String,String>)request.getAttribute("map")).get("CC") %> </li>
      <li><%=((User)request.getAttribute("user")).getAddress().getTown() %> </li>
     <li><%=((List<User>)request.getAttribute("userList")).get(2).getAddress().getTown() %> </li>
     <li><%=((Map<String,User>)request.getAttribute("userMap")).get("user3").getAddress().getTown() %> </li>
      EL获取请求参数信息
     <li>${param.uname }</li>
     <li>${paramValues.fav[0] }</li>             getParamter则需要param获取
     <li>${realname }</li>                              getAttribute直接获取属性
     <li>${list[2] }</li>
     <li>${map.CC }</li>
     <li>${user.address.town }</li>               address必须与User类中的address大小写一样
     <li>${userList[2].address.town }</li>
     <li>${userMap.user3.address.town }</li>
24.EL取值范围:
     1.el表达式取值默认从小到大取值(pageContext->request->session->application)
     2.pageScope: requestScope:sessionScope: applicationScope:
      导入fmt.tld文件
25. .和[]    访问一个bean属性或者Map entry        . 取值方便,[]功能强大
      ${user.name }    ${user["name"]}      ${user[name]}
      empty:用来对一个空变量值进行判断:null、一个空String、空Map、没有条目的Collection集合
      ${ empty name}
      +  作为算术符,加操作(但没有字符串相连的操作)
      ${1/0}   Infinity    ${ 1+"a"}  不能显示,结果为NaN      ${ 1+"1" }    2
26.JSTL: 替代在jsp中写java代码
       主要是get/set/remove    导入 <%@ taglib uri="http://java.sun.com/jsp/jstl/core  " prefix="s"%>
     1.<c:out value="${ name}"  default="zhansgan"> </c:out>
     2.<c:set var="name" value="kkk"  scope="session"></c:set>       //设置参数    ${ sessionScope.name }
     3.<c:remove var="name" >                ${sessionScope.name  }     //移除name就找不到值,默认移除全部
     4.<c:if test="${sex==1}">男</c:if>
     5.<c:choose>
            <c:when test="${sex==1}">男</c:when>
            <c:otherwise>其他</c:otherwise>
        </c:choose>
     6.<c:foreach  var="aa"  begin="1"  end="9"   step="3">           //类似java中的for
           ${aa}                                                                                          //每隔3个输出一次
        </c:foreach>
        <c:foreach  items="${list}" var="aa"  varStatus="vars" >
              ${aa}--${vars.first}--${vars.last}--${vars.index}                      //判断是否是list的第一个元素;index代表下标
        </c:foreach>
       <c:foreach items="${userList}"  var="aa">
              ${aa.name}--${aa.address.city}                 address存放于user里的另一个类
       </c:foreach>
Session:本质是一个哈希表,哈希表的key就是传递给浏览器的名为jessionid的Cookie值。
当一个值要保存到Session中,须有以下步骤:
(jessionid的Cookie值在浏览器关闭时会自动删除;或者将其MaxAge的值设为-1,也能达到浏览器关闭时自动删除目的)
   a.获取jsessionid的值,没有就调用request.getSession()生成
   b.得到session对象之后,通过setAttribute()往哈希表中写数据
   c.同时还可以通过getAttribute获取该属性值。
fail to load the jni shared library    由于jdk版本与eclipse位数版本不一致
Tomcat7路径:D:\MainSoftWare\tomcat7\apache-tomcat-7.0.72\webapps\ROOT
taglib definition not consistent with specification version  问题:http://www.ithao123.cn/content-37861.html
23.自定义标签: 继承SimpleTagSupport
    TLD:标签库定义
  taglib是根元素,一般包含三个元素:
      tlib-version:标签库实现的版本
      short-name:标签库短名
      uri:指定该标签库的唯一标识
  tag:包含下列子元素:
      name:标签的名称,jsp通过此名称来使用该标签
      tag-class:指定标签由哪个处理类来处理。
        body-content:指定标签体内容
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibrary</short-name>
    <uri>/jsp2-example-taglib.tld</uri>     //tld文件路径
    <tag>
       <name>MyFirstTLD</name>
       <tag-class>com.lzx.MyFirstTag</tag-class>
       <body-content>empty</body-content>
    </tag>
</taglib>
引用tld: <%@ taglib uri="/jsp2-example-taglib.tld" prefix="first" %>
                 <first:MyFirstTLD/>
24.定义带属性的标签:
   带属性需增加:<attribute>
                <name>ddd</name>
                <required>true</required>
                <fragment>true</fragment>
               </attribute>
来源:
https://blog.csdn.net/q2857864700/article/details/56676319

Paxos

Paxos算法在分布式领域具有非常重要的地位。但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难。

网上有很多讲解Paxos算法的文章,但是质量参差不齐。看了很多关于Paxos的资料后发现,学习Paxos最好的资料是论文《Paxos Made Simple》,其次是中、英文版维基百科对Paxos的介绍。本文试图带大家一步步揭开Paxos神秘的面纱。

Paxos是什么

Paxos算法是基于消息传递且具有高度容错特性一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。

Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品

虽然Mike Burrows说得有点夸张,但是至少说明了Paxos算法的地位。然而,Paxos算法也因为晦涩难懂而臭名昭著。本文的目的就是带领大家深入浅出理解Paxos算法,不仅理解它的执行流程,还要理解算法的推导过程,作者是怎么一步步想到最终的方案的。只有理解了推导过程,才能深刻掌握该算法的精髓。而且理解推导过程对于我们的思维也是非常有帮助的,可能会给我们带来一些解决问题的思路,对我们有所启发。

问题产生的背景

在常见的分布式系统中,总会发生诸如机器宕机网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。

注:这里某个数据的值并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令(command)。。。根据应用场景不同,某个数据的值有不同的含义。

相关概念

在Paxos算法中,有三种角色:

  • Proposer
  • Acceptor
  • Learners

在具体的实现中,一个进程可能同时充当多种角色。比如一个进程可能既是Proposer又是Acceptor又是Learner

还有一个很重要的概念叫提案(Proposal)。最终要达成一致的value就在提案里。

注:

  • 暂且认为『提案=value』,即提案只包含value。在我们接下来的推导过程中会发现如果提案只包含value,会有问题,于是我们再对提案重新设计
  • 暂且认为『Proposer可以直接提出提案』。在我们接下来的推导过程中会发现如果Proposer直接提出提案会有问题,需要增加一个学习提案的过程。

Proposer可以提出(propose)提案;Acceptor可以接受(accept)提案;如果某个提案被选定(chosen),那么该提案里的value就被选定了。

回到刚刚说的『对某个数据的值达成一致』,指的是Proposer、Acceptor、Learner都认为同一个value被选定(chosen)。那么,Proposer、Acceptor、Learner分别在什么情况下才能认为某个value被选定呢?

  • Proposer:只要Proposer发的提案被Acceptor接受(刚开始先认为只需要一个Acceptor接受即可,在推导过程中会发现需要半数以上的Acceptor同意才行),Proposer就认为该提案里的value被选定了。
  • Acceptor:只要Acceptor接受了某个提案,Acceptor就任务该提案里的value被选定了。
  • Learner:Acceptor告诉Learner哪个value被选定,Learner就认为那个value被选定。

问题描述

假设有一组可以提出(propose)value(value在提案Proposal里)的进程集合。一个一致性算法需要保证提出的这么多value中,只有一个value被选定(chosen)。如果没有value被提出,就不应该有value被选定。如果一个value被选定,那么所有进程都应该能学习(learn)到这个被选定的value。对于一致性算法,安全性(safaty)要求如下:

  • 只有被提出的value才能被选定。
  • 只有一个value被选定,并且
  • 如果某个进程认为某个value被选定了,那么这个value必须是真的被选定的那个。

我们不去精确地定义其活性(liveness)要求。我们的目标是保证最终有一个提出的value被选定。当一个value被选定后,进程最终也能学习到这个value。

Paxos的目标:保证最终有一个value会被选定,当value被选定后,进程最终也能获取到被选定的value。

假设不同角色之间可以通过发送消息来进行通信,那么:

  • 每个角色以任意的速度执行,可能因出错而停止,也可能会重启。一个value被选定后,所有的角色可能失败然后重启,除非那些失败后重启的角色能记录某些信息,否则等他们重启后无法确定被选定的值。
  • 消息在传递过程中可能出现任意时长的延迟,可能会重复,也可能丢失。但是消息不会被损坏,即消息内容不会被篡改(拜占庭将军问题)。

推导过程

最简单的方案——只有一个Acceptor

假设只有一个Acceptor(可以有多个Proposer),只要Acceptor接受它收到的第一个提案,则该提案被选定,该提案里的value就是被选定的value。这样就保证只有一个value会被选定。

但是,如果这个唯一的Acceptor宕机了,那么整个系统就无法工作了!

因此,必须要有多个Acceptor

多个Acceptor

多个Acceptor的情况如下图。那么,如何保证在多个Proposer和多个Acceptor的情况下选定一个value呢?

下面开始寻找解决方案。

如果我们希望即使只有一个Proposer提出了一个value,该value也最终被选定。

那么,就得到下面的约束:

P1:一个Acceptor必须接受它收到的第一个提案。

但是,这又会引出另一个问题:如果每个Proposer分别提出不同的value,发给不同的Acceptor。根据P1,Acceptor分别接受自己收到的value,就导致不同的value被选定。出现了不一致。如下图:

刚刚是因为『一个提案只要被一个Acceptor接受,则该提案的value就被选定了』才导致了出现上面不一致的问题。因此,我们需要加一个规定:

规定:一个提案被选定需要被半数以上的Acceptor接受

这个规定又暗示了:『一个Acceptor必须能够接受不止一个提案!』不然可能导致最终没有value被选定。比如上图的情况。v1、v2、v3都没有被选定,因为它们都只被一个Acceptor的接受。

最开始讲的『提案=value』已经不能满足需求了,于是重新设计提案,给每个提案加上一个提案编号,表示提案被提出的顺序。令『提案=提案编号+value』。

虽然允许多个提案被选定,但必须保证所有被选定的提案都具有相同的value值。否则又会出现不一致。

于是有了下面的约束:

P2:如果某个value为v的提案被选定了,那么每个编号更高的被选定提案的value必须也是v。

一个提案只有被Acceptor接受才可能被选定,因此我们可以把P2约束改写成对Acceptor接受的提案的约束P2a。

P2a:如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v。

只要满足了P2a,就能满足P2。

但是,考虑如下的情况:假设总的有5个Acceptor。Proposer2提出[M1,V1]的提案,Acceptor2~5(半数以上)均接受了该提案,于是对于Acceptor2~5和Proposer2来讲,它们都认为V1被选定。Acceptor1刚刚从宕机状态恢复过来(之前Acceptor1没有收到过任何提案),此时Proposer1向Acceptor1发送了[M2,V2]的提案(V2≠V1且M2>M1),对于Acceptor1来讲,这是它收到的第一个提案。根据P1(一个Acceptor必须接受它收到的第一个提案。),Acceptor1必须接受该提案!同时Acceptor1认为V2被选定。这就出现了两个问题:

  1. Acceptor1认为V2被选定,Acceptor2~5和Proposer2认为V1被选定。出现了不一致。
  2. V1被选定了,但是编号更高的被Acceptor1接受的提案[M2,V2]的value为V2,且V2≠V1。这就跟P2a(如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v)矛盾了。

所以我们要对P2a约束进行强化!

P2a是对Acceptor接受的提案约束,但其实提案是Proposer提出来的,所有我们可以对Proposer提出的提案进行约束。得到P2b:

P2b:如果某个value为v的提案被选定了,那么之后任何Proposer提出的编号更高的提案的value必须也是v。

由P2b可以推出P2a进而推出P2。

那么,如何确保在某个value为v的提案被选定后,Proposer提出的编号更高的提案的value都是v呢?

只要满足P2c即可:

P2c:对于任意的N和V,如果提案[N, V]被提出,那么存在一个半数以上的Acceptor组成的集合S,满足以下两个条件中的任意一个:

  • S中每个Acceptor都没有接受过编号小于N的提案。
  • S中Acceptor接受过的最大编号的提案的value为V。

Proposer生成提案

为了满足P2b,这里有个比较重要的思想:Proposer生成提案之前,应该先去『学习』已经被选定或者可能被选定的value,然后以该value作为自己提出的提案的value。如果没有value被选定,Proposer才可以自己决定value的值。这样才能达成一致。这个学习的阶段是通过一个『Prepare请求』实现的。

于是我们得到了如下的提案生成算法

  1. Proposer选择一个新的提案编号N,然后向某个Acceptor集合(半数以上)发送请求,要求该集合中的每个Acceptor做出如下响应(response)。

(a) 向Proposer承诺保证不再接受任何编号小于N的提案

(b) 如果Acceptor已经接受过提案,那么就向Proposer响应已经接受过的编号小于N的最大编号的提案

我们将该请求称为编号为NPrepare请求

  1. 如果Proposer收到了半数以上的Acceptor的响应,那么它就可以生成编号为N,Value为V的提案[N,V]。这里的V是所有的响应中编号最大的提案的Value。如果所有的响应中都没有提案,那 么此时V就可以由Proposer自己选择
    生成提案后,Proposer将该提案发送给半数以上的Acceptor集合,并期望这些Acceptor能接受该提案。我们称该请求为Accept请求。(注意:此时接受Accept请求的Acceptor集合不一定是之前响应Prepare请求的Acceptor集合)

Acceptor接受提案

Acceptor可以忽略任何请求(包括Prepare请求和Accept请求)而不用担心破坏算法的安全性。因此,我们这里要讨论的是什么时候Acceptor可以响应一个请求。

我们对Acceptor接受提案给出如下约束:

P1a:一个Acceptor只要尚未响应过任何编号大于NPrepare请求,那么他就可以接受这个编号为N的提案

如果Acceptor收到一个编号为N的Prepare请求,在此之前它已经响应过编号大于N的Prepare请求。根据P1a,该Acceptor不可能接受编号为N的提案。因此,该Acceptor可以忽略编号为N的Prepare请求。当然,也可以回复一个error,让Proposer尽早知道自己的提案不会被接受。

因此,一个Acceptor只需记住:1. 已接受的编号最大的提案 2. 已响应的请求的最大编号。

Paxos算法描述

经过上面的推导,我们总结下Paxos算法的流程。

Paxos算法分为两个阶段。具体如下:

  • 阶段一:

(a) Proposer选择一个提案编号N,然后向半数以上的Acceptor发送编号为N的Prepare请求

(b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的提案

  • 阶段二:

(a) 如果Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应,那么它就会发送一个针对[N,V]提案Accept请求半数以上的Acceptor。注意:V就是收到的响应编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定

(b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor没有对编号大于NPrepare请求做出过响应,它就接受该提案

Learner学习被选定的value

Learner学习(获取)被选定的value有如下三种方案:

如何保证Paxos算法的活性

通过选取主Proposer,就可以保证Paxos算法的活性。至此,我们得到一个既能保证安全性,又能保证活性分布式一致性算法——Paxos算法

参考资料

  • 论文《Paxos Made Simple》
  • 论文《The Part-Time Parliament》
  • 英文版维基百科的Paxos
  • 中文版维基百科的Paxos
  • 书籍《从Paxos到ZooKeeper》

 

原文:

https://www.cnblogs.com/linbingdong/p/6253479.html

 

... END ...

 

Raft算法

过去, Paxos一直是分布式协议的标准,但是Paxos难于理解,更难以实现,Google的分布式锁系统Chubby作为Paxos实现曾经遭遇到很多坑。

来自Stanford的新的分布式协议研究称为Raft,它是一个为真实世界应用建立的协议,主要注重协议的落地性和可理解性。

在了解Raft之前,我们先了解Consensus一致性这个概念,它是指多个服务器在状态达成一致,但是在一个分布式系统中,因为各种意外可能,有的服务器可能会崩溃或变得不可靠,它就不能和其他服务器达成一致状态。这样就需要一种Consensus协议,一致性协议是为了确保容错性,也就是即使系统中有一两个服务器当机,也不会影响其处理过程。

为了以容错方式达成一致,我们不可能要求所有服务器100%都达成一致状态,只要超过半数的大多数服务器达成一致就可以了,假设有N台服务器,N/2 +1 就超过半数,代表大多数了。

Paxos和Raft都是为了实现Consensus一致性这个目标,这个过程如同选举一样,参选者需要说服大多数选民(服务器)投票给他,一旦选定后就跟随其操作。Paxos和Raft的区别在于选举的具体过程不同。

在Raft中,任何时候一个服务器可以扮演下面角色之一:

  1. Leader: 处理所有客户端交互,日志复制等,一般一次只有一个Leader.
  2. Follower: 类似选民,完全被动
  3. Candidate候选人: 类似Proposer律师,可以被选为一个新的领导人。

Raft阶段分为两个,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。下面用图示展示这个过程:

1. 任何一个服务器都可以成为一个候选者Candidate,它向其他服务器Follower发出要求选举自己的请求:

2. 其他服务器同意了,发出OK。

注意如果在这个过程中,有一个Follower当机,没有收到请求选举的要求,因此候选者可以自己选自己,只要达到N/2 + 1 的大多数票,候选人还是可以成为Leader的。

3. 这样这个候选者就成为了Leader领导人,它可以向选民也就是Follower们发出指令,比如进行日志复制。

4. 以后通过心跳进行日志复制的通知

5. 如果一旦这个Leader当机崩溃了,那么Follower中有一个成为候选者,发出邀票选举。

6. Follower同意后,其成为Leader,继续承担日志复制等指导工作:

 

值得注意的是,整个选举过程是有一个时间限制的,如下图:

Splite Vote是因为如果同时有两个候选人向大家邀票,这时通过类似加时赛来解决,两个候选者在一段timeout比如300ms互相不服气的等待以后,因为双方得到的票数是一样的,一半对一半,那么在300ms以后,再由这两个候选者发出邀票,这时同时的概率大大降低,那么首先发出邀票的的候选者得到了大多数同意,成为领导者Leader,而另外一个候选者后来发出邀票时,那些Follower选民已经投票给第一个候选者,不能再投票给它,它就成为落选者了,最后这个落选者也成为普通Follower一员了。

 

日志复制

下面以日志复制为例子说明Raft算法,假设Leader领导人已经选出,这时客户端发出增加一个日志的要求,比如日志是"sally":

2. Leader要求Followe遵从他的指令,都将这个新的日志内容追加到他们各自日志中:

3.大多数follower服务器将日志写入磁盘文件后,确认追加成功,发出Commited Ok:

4. 在下一个心跳heartbeat中,Leader会通知所有Follwer更新commited 项目。

对于每个新的日志记录,重复上述过程。

如果在这一过程中,发生了网络分区或者网络通信故障,使得Leader不能访问大多数Follwers了,那么Leader只能正常更新它能访问的那些Follower服务器,而大多数的服务器Follower因为没有了Leader,他们重新选举一个候选者作为Leader,然后这个Leader作为代表于外界打交道,如果外界要求其添加新的日志,这个新的Leader就按上述步骤通知大多数Followers,如果这时网络故障修复了,那么原先的Leader就变成Follower,在失联阶段这个老Leader的任何更新都不能算commit,都回滚,接受新的Leader的新的更新。

总结:目前几乎所有语言都已经有支持Raft算法的库包,具体可参考:raftconsensus.github.io

 

原文:

https://www.jdon.com/artichect/raft.html

 

... END ...

 

ByteBuffer

概述

ByteBuffer是NIO里用得最多的Buffer,它包含两个实现方式:HeapByteBuffer是基于Java堆的实现,而DirectByteBuffer则使用了unsafe的API进行了堆外的实现。这里只说HeapByteBuffer。

使用

ByteBuffer最核心的方法是put(byte)get()。分别是往ByteBuffer里写一个字节,和读一个字节。

值得注意的是,ByteBuffer的读写模式是分开的,正常的应用场景是:往ByteBuffer里写一些数据,然后flip(),然后再读出来。

这里插两个Channel方面的对象,以便更好的理解Buffer。

ReadableByteChannel是一个从Channel中读取数据,并保存到ByteBuffer的接口,它包含一个方法:

public intread(ByteBuffer dst) throwsIOException;

WritableByteChannel则是从ByteBuffer中读取数据,并输出到Channel的接口:

public intwrite(ByteBuffer src) throwsIOException;

那么,一个ByteBuffer的使用过程是这样的:

1. byteBuffer = ByteBuffer.allocate(N);    //创建

2. readableByteChannel.read(byteBuffer);   //读取数据,写入byteBuffer

3. byteBuffer.flip();              //变读为写

4. writableByteChannel.write(byteBuffer);   //读取byteBuffer,写入数据

看到这里,一般都不太明白flip()干了什么事,先从ByteBuffer结构说起:

 

ByteBuffer的创建和读写

 

1. ByteBuffer定义了4个static方法来做创建工作:

ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。

2. ByteBuffer定义了一系列get和put操作来从中读写byte数据,如下面几个:
byte get()
ByteBuffer get(byte [] dst)
byte get(int index)
ByteBuffer put(byte b)
ByteBuffer put(byte [] src)
ByteBuffer put(int index, byte b)
这些操作可分为绝对定位和相对定为两种,相对定位的读写操作依靠position来定位Buffer中的位置,并在操
作完成后会更新position的值。在其它类型的buffer中,也定义了相同的函数来读写数据,唯一不同的就是一
些参数和返回值的类型。

3. 除了读写byte类型数据的函数,ByteBuffer的一个特别之处是它还定义了读写其它primitive数据的方法,如:

int getInt()             //从ByteBuffer中读出一个int值。
ByteBuffer putInt(int value)  // 写入一个int值到ByteBuffer中。

3.1 字节序

读写其它类型的数据牵涉到字节序问题,ByteBuffer会按其字节序(大字节序或小字节序)写入或读出一个其它
类型的数据(int,long…)。字节序可以用order方法来取得和设置:
ByteOrder order() //返回ByteBuffer的字节序。
ByteBuffer order(ByteOrder bo)   // 设置ByteBuffer的字节序。

3.2 ByteOrder
用来表示ByteBuffer字节序的类,可将其看成java中的enum类型。主要定义了下面几个static方法和属性:
ByteOrder BIG_ENDIAN       代表大字节序的ByteOrder。
ByteOrder LITTLE_ENDIAN 代表小字节序的ByteOrder。
ByteOrder nativeOrder()       返回当前硬件平台的字节序。

 

4. ByteBuffer另一个特别的地方是可以在它的基础上得到其它类型的buffer。如:
CharBuffer asCharBuffer()
为当前的ByteBuffer创建一个CharBuffer的视图。在该视图buffer中的读写操作会按照ByteBuffer的字节
序作用到ByteBuffer中的数据上。

用这类方法创建出来的buffer会从ByteBuffer的position位置开始到limit位置结束,可以看作是这段数据
的视图。视图buffer的readOnly属性和direct属性与ByteBuffer的一致,而且也只有通过这种方法,才可
以得到其他数据类型的direct buffer。

ByteBuffer内部字段

byte[] buff

buff即内部用于缓存的数组。

position

当前读取的位置。

读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,
buffer会更新下标的值。

mark

为某一读过的位置做标记,便于某些时候回退到该位置。

一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设
置为mark的值。mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。

capacity

初始化时候的容量。

这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。

limit

在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,
limit代表buffer中有效数据的长度。

读写的上限,limit<=capacity。

 

这些属性总是满足以下条件:
0 <= mark <= position <= limit <= capacity

limit和position的值除了通过limit()和position()函数来设置,也可以通过下面这些函数来改变:

Buffer clear()
把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

Buffer flip()
把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。

Buffer rewind()
把position设为0,limit不变,一般在把数据重写入Buffer前调用。

compact()

该方法的作用是将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position  = limit -position,limit = capacity

但如果position 与limit 之间没有数据的话发,就不会进行复制  详细参考:java nio Buffer 中 compact的作用

mark()与reset()方法

  通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

1.buffer.mark();

2.//call buffer.get() a couple of times, e.g. during parsing.

3.buffer.reset(); //set position back to mark

equals()与compareTo()方法

  可以使用equals()和compareTo()方法两个Buffer。

  equals()

  当满足下列条件时,表示两个Buffer相等:

  1. 有相同的类型(byte、char、int等)。
  2. Buffer中剩余的byte、char等的个数相等。
  3. Buffer中所有剩余的byte、char等都相同。

如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。

  compareTo()方法

  compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:

    1. 第一个不相等的元素小于另一个Buffer中对应的元素 。
    2. 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

Buffer对象有可能是只读的,这时,任何对该对象的写操作都会触发一个ReadOnlyBufferException。
isReadOnly()方法可以用来判断一个Buffer是否只读。

 

图解

put

写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。

flip

写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。

get

从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。

clear

将position置为0,并不清除buffer内容。

mark相关的方法主要是mark()(标记)和reset()(回到标记)

 

.

原文: https://www.cnblogs.com/ruber/p/6857159.html

 

 

[netty] ByteBuf

netty 中的 ByteBuf 默认是使用 big-endian
一、创建
1、池化创建 ByteBufAllocator
获取ByteBufAllocator
Channel channel = ...;
ByteBufAllocator allocator = channel.alloc();
//1....ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc();
//2
ByteBufAllocator中创建byteBuf的方法
名称
描述
buffer() buffer(int) buffer(int, int)
Return a ByteBuf with heap-based or direct data storage.
heapBuffer() heapBuffer(int) heapBuffer(int, int)
Return a ByteBuf with heap-based storage.
directBuffer() directBuffer(int) directBuffer(int, int)
Return a ByteBuf with direct storage.
compositeBuffer() compositeBuffer(int) heapCompositeBuffer() heapCompositeBuffer(int) directCompositeBuffer()directCompositeBuffer(int)
Return a CompositeByteBuf that can be expanded by adding heapbased or direct buffers.
ioBuffer()
Return a ByteBuf that will be used for I/O operations on a socket.
2、Unpooled (非池化)缓存
当未引用 ByteBufAllocator 时,上面的方法无法访问到 ByteBuf。对于这个用例 Netty 提供一个实用工具类称为 Unpooled,,它提供了静态辅助方法来创建非池化的 ByteBuf 实例。表5.9列出了最重要的方法
Table 5.9 Unpooled helper class
名称
描述
buffer() buffer(int) buffer(int, int)
Returns an unpooled ByteBuf with heap-based storage
directBuffer() directBuffer(int) directBuffer(int, int)
Returns an unpooled ByteBuf with direct storage
wrappedBuffer()
Returns a ByteBuf, which wraps the given data.
copiedBuffer()
Returns a ByteBuf, which copies the given data
在 非联网项目,该 Unpooled 类也使得它更容易使用的 ByteBuf API,获得一个高性能的可扩展缓冲 API,
3、ByteBufUtil创建 (ByteBufUtil中有很多操作buf的API)
二、读 get/read get不会改变读索引,read会改变读索引
getBoolean(int)
返回当前索引的 Boolean 值
getByte(int) getUnsignedByte(int)
返回当前索引的(无符号)字节
getMedium(int) getUnsignedMedium(int)
返回当前索引的 (无符号) 24-bit 中间值
getInt(int) getUnsignedInt(int)
返回当前索引的(无符号) 整型
getLong(int) getUnsignedLong(int)
返回当前索引的 (无符号) Long 型
getShort(int) getUnsignedShort(int)
返回当前索引的 (无符号) Short 型
getBytes(int, ...)
字节
方法名称
描述
readBoolean()
 返回当前索引的Boolean值,读索引加一
readByte()
readUnsignedByte()
返回当前索引的(无符号)字节,读索引加一
readMedium()
readUnsignedMedium()
返回当前索引的 (无符号) 24-bit 中间值,读索引加3
readInt()
readUnsignedInt()
 返回当前索引的(无符号) 整型,读索引加4
readLong()
readUnsignedLong()
 返回当前索引的 (无符号) Long 型,读索引加8
readShort()
readUnsignedShort()
返回当前索引的 (无符号) Short 型,读索引加2
readBytes(int,int, ...)
、放回当前位置到length
得一个字节数组,读索引加length
三、写操作 set/write
方法名称
描述
setBoolean(int, boolean)
在指定的索引位置设置 Boolean 值
setByte(int, int)
在指定的索引位置设置 byte 值
setMedium(int, int)
在指定的索引位置设置 24-bit 中间 值
setInt(int, int)
在指定的索引位置设置 int 值
setLong(int, long)
在指定的索引位置设置 long 值
setShort(int, int)
在指定的索引位置设置 short 值
方法名称
描述
writeBoolean(boolean)
在指定的索引位置设置 Boolean 值,写索引加一
writeByte(int)
在指定的索引位置设置 byte 值,写索引加一
writeMedium(int)
在指定的索引位置设置 24-bit 中间 值,写索引加3
writeInt(int)
在指定的索引位置设置 int 值,写索引加4
writeLong(long)
在指定的索引位置设置 long 值,写索引加8
writeShort(int)
在指定的索引位置设置 short 值,写索引加2
writeBytes(int,...)
 在当前索引写入一个Byte数组,写索引加数组长度
四、索引管理
markReaderIndex(),
 markWriterIndex()
标记读(写)索引
resetReaderIndex()
resetWriterIndex()
读(写)索引回到mark标记的索引值
readerIndex(int)
 writerIndex(int)
将读(写)索引设置到指定位置
clear()
可以同时设置 readerIndex 和 writerIndex 为 0。这不会清除内存中的内容
五、查找
forEachByte(ByteBufProcessor.FIND_NUL)
查找byte,返回byte的索引
六、副本
duplicate()
slice()
slice(int, int)
readOnly(),
order(ByteOrder)
所有这些都返回一个新的 ByteBuf 实例包括它自己的 reader, writer 和标记索引。然而,内部数据存储共享就像在一个 NIO 的 ByteBuffer
 copy()
 copy(int, int)
返回的 ByteBuf 有数据的独立副本。
七、其他
方法名称
描述
isReadable()
返回是否有字节可读
isWritable()
返回是否可以写
readableBytes()
返回可以读的字节长度
writablesBytes()
返回可以写的字节场地
capacity()
返回byteBuf的容量
maxCapacity()
返回byteBuf可以有的最大容量
hasArray()
如果byteBuf可以直接返回一个数组就返回true
(heap buf才会为true)
array()
hasArray返回true,该方法就会返回一个数组
.

ASP.NET Core 性能对比评测(ASP.NET,Python,Java,NodeJS)

测试结果的汇总统计:

编号 对比方 系统环境 宿主环境 测试结果(QPS)
1 ASP.NET Core vs ASP.NET Core Windows Kestrel vs IIS 45.6k vs 15.2k
2 ASP.NET Core vs ASP.NET Windows IIS vs IIS 15.2k vs 18.2k
3 ASP.NET Core vs ASP.NET Windows Kestrel vs IIS 45.6k vs 18.2k
4 ASP.NET Core vs Python Django Linux Kestrel vs uwsgi 26.7k vs 1.57k
5 ASP.NET Core vs Java Servlet Linux Kestrel vs Tomcat 26.7k vs 18.3k
6-1 ASP.NET Core vs NodeJS Express Linux Kestrel vs self host 26.7k vs 15.6k
6-2 ASP.NET Core vs NodeJS Koa Linux Kestrel vs self host 26.7k vs 17.5k

作为微软的下一代 ASP.NET 框架,ASP.NET Core没有让我们失望,通过本次测试,我们大概对ASP.NET Core的性能心里有底了。一个圈子的良好发展需要社区的共同参与,也希望大家共同为.NET Core社区贡献自己的力量,同时也希望看到本篇文章的CTOs们以后在平台和框架选择的过程中考虑一下ASP.NET Core,因为她真的很优秀。

 

 

 

原文:https://www.cnblogs.com/savorboard/archive/2016/10/17/dotnet-benchmarks.html

 

 

 

 

 

 

Raft 更易理解的分布式一致性算法

一致性问题可以算是分布式领域的一个圣殿级问题了,关于它的研究可以回溯到几十年前。

拜占庭将军问题

Leslie Lamport 在三十多年前发表的论文《拜占庭将军问题》(参考[1])。

拜占庭位于如今的土耳其的伊斯坦布尔,是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信差传消息。在战争的时候,拜占庭军队内所有将军必需达成 一致的共识,决定是否有赢的机会才去攻打敌人的阵营。但是,在军队内有可能存有叛徒和敌军的间谍,左右将军们的决定又扰乱整体军队的秩序,在进行共识时,结果并不代表大多数人的意见。这时候,在已知有成员不可靠的情况下,其余忠诚的将军在不受叛徒或间谍的影响下如何达成一致的协议,拜占庭问题就此形成。拜占庭假设是对现实世界的模型化,由于硬件错误、网络拥塞或断开以及遭到恶意攻击,计算机和网络可能出现不可预料的行为。

Lamport 一直研究这类问题,发表了一系列论文。但综合总结一下就是回答下面三个问题:

  1. 类似拜占庭将军这样的分布式一致性问题是否有解?
  2. 如果有解的话需要满足什么样的条件?
  3. 在特定前提条件的基础上,提出一种解法。

前两个问题 Lamport 在论文《拜占庭将军问题》已经回答,而第三个问题在后来的论文 《The Part-Time Parliament》中提出了一种算法并命名为 Paxos。这篇论文使用了大量的数学证明,而我基本就看不懂了(数学符号都认不全-。-;),考虑到大家理解起来都比较困难,后来 Lamport 又写了另外一篇论文 《Paxos Made Simple》完全放弃了所有数学符号的证明,使用纯英文的逻辑推导。我勉强逐字看了一遍,然后感觉若有所悟,但你问我搞懂了吗,我的标准应该还是没懂。对我来说理解一个算法有个明确的标准,就是真的懂了会在头脑里能将算法映射为代码,而看完后面一篇论文仅仅是若有所悟还达不到能映射为代码的清晰度。

虽然 Lamport 认为 Paxos 很 simple,但也许只是针对他的头脑而言。事实是大家理解起来都还是很困难,所以 Raft 就是建立在希望得到一个更易于理解的 Paxos 算法的替代品。把可理解性作为算法的主要目标之一,从论文题目就可看出来《In Search of an Understandable Consensus Algorithm》。

在进入正题前,我想起一个旧故事可以很直观的感受对一个问题不同的思维视角在可理解性上的差异。

不同视角的可理解性

依稀记得大约在二十年前,我还在读初中时在一本可能大概叫《数学中的发散思维》(不能很清晰记得书名了)的书中看到这么一个有趣的问题。

甲乙两人轮流在一张圆桌上平放黑白围棋子,每次放一子,棋子不许重叠,谁先没有地方放就输。
请问怎样放才能赢?

这个问题有两层意思,第一,有没有一种放法保证必赢?第二,如果有怎么证明?这里先停顿下,思考十秒钟。

上面的图回答了这个问题,就是先行者必胜,这里使用了三种不同的思维方式。

  1. 假如桌子只有一个围棋子那么大。
  2. 假如桌子无限大,先行者先占住圆心,由于圆是对称图形,所以只要对手还能找到位置放,你总能在对称的另一面找到位置放。
  3. 一个圆中可画单数个直径相等且互切的小圆。

三种不同的思维方式在可理解性难度上逐渐加深。第一种是极简化思维,但数学上是不严谨的。第二种是极限思维,和第一种结合起来就是数学归纳法了,在数学上是严谨的。第三种是形象思维,使用了几何学概念,但对于没有几何学基础知识的人就很难理解了。

Raft 协议的易理解性描述

虽然 Raft 的论文比 Paxos 简单版论文还容易读了,但论文依然发散的比较多,相对冗长。读完后掩卷沉思觉得还是整理一下才会更牢靠,变成真正属于自己的。这里我就借助前面黑白棋落子里第一种极简思维来描述和概念验证下 Raft 协议的工作方式。

在一个由 Raft 协议组织的集群中有三类角色:

  1. Leader(领袖)
  2. Follower(群众)
  3. Candidate(候选人)

就像一个民主社会,领袖由民众投票选出。刚开始没有领袖,所有集群中的参与者都是群众,那么首先开启一轮大选,在大选期间所有群众都能参与竞选,这时所有群众的角色就变成了候选人,民主投票选出领袖后就开始了这届领袖的任期,然后选举结束,所有除领袖的候选人又变回群众角色服从领袖领导。这里提到一个概念「任期」,用术语 Term 表达。关于 Raft 协议的核心概念和术语就这么多而且和现实民主制度非常匹配,所以很容易理解。三类角色的变迁图如下,结合后面的选举过程来看很容易理解。

Leader 选举过程

在极简的思维下,一个最小的 Raft 民主集群需要三个参与者(如下图:A、B、C),这样才可能投出多数票。初始状态 ABC 都是 Follower,然后发起选举这时有三种可能情形发生。下图中前二种都能选出 Leader,第三种则表明本轮投票无效(Split Votes),每方都投给了自己,结果没有任何一方获得多数票。之后每个参与方随机休息一阵(Election Timeout)重新发起投票直到一方获得多数票。这里的关键就是随机 timeout,最先从 timeout 中恢复发起投票的一方向还在 timeout 中的另外两方请求投票,这时它们就只能投给对方了,很快达成一致。

选出 Leader 后,Leader 通过定期向所有 Follower 发送心跳信息维持其统治。若 Follower 一段时间未收到 Leader 的心跳则认为 Leader 可能已经挂了再次发起选主过程。

Leader 节点对一致性的影响

Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。当 Client 向集群 Leader 节点提交数据后,Leader 节点接收到的数据处于未提交状态(Uncommitted),接着 Leader 节点会并发向所有 Follower 节点复制数据并等待接收响应,确保至少集群中超过半数节点已接收到数据后再向 Client 确认数据已接收。一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed),Leader 节点再向 Follower 节点发通知告知该数据状态已提交。

在这个过程中,主节点可能在任意阶段挂掉,看下 Raft 协议如何针对不同阶段保障数据一致性的。

1. 数据到达 Leader 节点前

这个阶段 Leader 挂掉不影响一致性,不多说。

2. 数据到达 Leader 节点,但未复制到 Follower 节点

这个阶段 Leader 挂掉,数据属于未提交状态,Client 不会收到 Ack 会认为超时失败可安全发起重试。Follower 节点上没有该数据,重新选主后 Client 重试重新提交可成功。原来的 Leader 节点恢复后作为 Follower 加入集群重新从当前任期的新 Leader 处同步数据,强制保持和 Leader 数据一致。

3. 数据到达 Leader 节点,成功复制到 Follower 所有节点,但还未向 Leader 响应接收

这个阶段 Leader 挂掉,虽然数据在 Follower 节点处于未提交状态(Uncommitted)但保持一致,重新选出 Leader 后可完成数据提交,此时 Client 由于不知到底提交成功没有,可重试提交。针对这种情况 Raft 要求 RPC 请求实现幂等性,也就是要实现内部去重机制。

4. 数据到达 Leader 节点,成功复制到 Follower 部分节点,但还未向 Leader 响应接收

这个阶段 Leader 挂掉,数据在 Follower 节点处于未提交状态(Uncommitted)且不一致,Raft 协议要求投票只能投给拥有最新数据的节点。所以拥有最新数据的节点会被选为 Leader 再强制同步数据到 Follower,数据不会丢失并最终一致。

5. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在 Leader 处于已提交状态,但在 Follower 处于未提交状态

这个阶段 Leader 挂掉,重新选出新 Leader 后的处理流程和阶段 3 一样。

6. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在所有节点都处于已提交状态,但还未响应 Client

这个阶段 Leader 挂掉,Cluster 内部数据其实已经是一致的,Client 重复重试基于幂等策略对一致性无影响。

7. 网络分区导致的脑裂情况,出现双 Leader

网络分区将原先的 Leader 节点和 Follower 节点分隔开,Follower 收不到 Leader 的心跳将发起选举产生新的 Leader。这时就产生了双 Leader,原先的 Leader 独自在一个区,向它提交数据不可能复制到多数节点所以永远提交不成功。向新的 Leader 提交数据可以提交成功,网络恢复后旧的 Leader 发现集群中有更新任期(Term)的新 Leader 则自动降级为 Follower 并从新 Leader 处同步数据达成集群数据一致。

综上穷举分析了最小集群(3 节点)面临的所有情况,可以看出 Raft 协议都能很好的应对一致性问题,并且很容易理解。

总结

就引用 Raft 论文最后的一节的综述来总结本文吧。

算法以正确性、高效性、简洁性作为主要设计目标。
虽然这些都是很有价值的目标,但这些目标都不会达成直到开发者写出一个可用的实现。
所以我们相信可理解性同样重要。

深以为然,想想 Paxos 算法是 Leslie Lamport 在 1990 年就公开发表在了自己的网站上,想想我们是什么时候才听说的?什么时候才有一个可用的实现?而 Raft 算法是 2013 年发表的,大家在参考[5]上面可以看到有多少个不同语言开源的实现库了,这就是可理解性的重要性。

参考

[1]. LESLIE LAMPORT, ROBERT SHOSTAK, MARSHALL PEASE. The Byzantine General Problem. 1982
[2]. Leslie Lamport. The Part-Time Parliament. 1998
[3]. Leslie Lamport. Paxos Made Simple. 2001
[4]. Diego Ongaro and John Ousterhout. Raft Paper. 2013
[5]. Raft Website. The Raft Consensus Algorithm
[6]. Raft Demo. Raft Animate Demo

 

文章来源:

https://www.cnblogs.com/mindwind/p/5231986.html

 

 

 

分布式事务与一致性算法 paxos & raft & zab

..

1.CAP原理

  • 要想数据高可用,就得写多份数据
  • 写多分数据就会导致数据一致性问题
  • 数据一致性问题会引起性能问题

2.一致性模型

  1. 弱一致性
  2. 最终一致性(一段时间达到一致性)
  3. 强一致
  • 1、2 异步冗余;3是同步冗余

3. 扩展服务的方案

  • 数据分区: uid % 16
  • 数据镜像:让多有的服务器都有相同的数据,提供相当的服务(冗余存储,一般3份为好)

4.两种方案的事务问题

  1. A向B汇钱,两个用户不在一个服务器上
  2. 镜像:在不同的服务器上对同一数据的写操作如何保证一致性。

5. 解决一致性事务问题的技术

1. Master -Slave

  • 读写请求由Master负责
  • 写请求写到Master后,由Master同步到Slave上
    • 由Master push or Slave pull
    • 通常是由Slave 周期性来pull,所以是最终一致性

问题: 若在 pull 周期内(不是期间?),master挂掉,那么会导致这个时间片内的数据丢失

  • 若不想让数据丢掉,Slave 只能成为 ReadOnly方式等Master恢复
  • 若容忍数据丢失,可以让 Slave代替Master工作

如何保证强一致性?

  • Master 写操作,写完成功后,再写 Slave,两者成功后返回成功。若 Slave失败,两种方法
    1. 标记 Slave 不可用报错,并继续服务(等恢复后,再同步Master的数据,多个Slave少了一个而已)
    2. 回滚自己并返回失败

2. Master-Master

  • 数据同步一般是通过 Master 间的异步完成,所以是最终一致
  • 好处: 一台Master挂掉,另外一台照样可以提供读写服务。当数据没有被赋值到别的Master上时,数据会丢失。
  • 对同一数据的处理问题:Dynamo的Vector Clock的设计(记录数据的版本号和修改者),当数据发生冲突时,要开发者自己来处理

3.两阶段提交 Two Phase Commit _ 2PC

  1. 第一阶段:针对准备工作
    • 协调者问所有节点是否可以执行提交
    • 参与者开始事务,执行准备工作:锁定资源(获取锁操作)
    • 参与者响应协调者,如果事务的准备工作成功,则回应"可以提交",否则,拒绝提交
  2. 第二阶段:
    • 若都响应可以提交,则协调者项多有参与者发送正式提交的命令(更新值),参与者完成正式提交,释放资源,回应完成。协调者收到所有节点的完成响应后结束这个全局事务.。若参与者回应拒绝提交,则协调者向所有的参与者发送回滚操作,并释放资源,当收到全部节点的回滚回应后,取消全局事务
  3. 存在的问题:若一个没提交,就会进行回滚
    • 第一阶段:若消息的传递未接收到,则需要协调者作超时处理,要么当做失败,要么重载
    • 第二阶段:若参与者的回应超时,要么重试,要么把那个参与者即为问题节点,提出整个集群
    • 在第二阶段中,参与者未收到协调者的指示(也许协调者挂掉),则所有参与者会进入“不知所措” 的状态(但是已经锁定了资源),所以引入了三段提交

4. 三段提交:把二段提交的第一阶段 break 成了两段

  1. 询问
  2. 锁定资源(获取锁)
  3. 提交
  • 核心理念:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁定
  • 好处:当发生了失败或超时时,三段提交可以继续把状态变为Commit 状态,而二段提交则不知所措?

5. Raxos 算法(少数服从多数)

  • 解决的问题:在一个可能发生异常的分布式系统中如何就某个值达成一致,让整个集群的节点对某个值的变更达成一致
  • 任何一个节点都可以提出要修改某个数据的提案,是否通过这个提案取决于这个集群中是否有超过半数的节点同意(所以节点数总是单数)—— 版本标记。虽然一致性,但是只能对一个操作进行操作啊??
  • 当一个Server接收到比当前版本号小的提案时,则拒绝。当收到比当前大的版本号的提案时,则锁定资源,进行修改,返回OK. 也就是说收到超过一半的最大版本的提案才算成功。

核心思想

  1. 在抢占式访问权的基础上引入多个acceptor,也就是说当一个版本号更大的提案可以剥夺版本号已经获取的锁。
  2. 后者认同前者的原则:
    • 在肯定旧epoch 无法生成确定性取值时,新的 epoch 会提交自己的valu
    • 一旦 旧epoch形成确定性取值,新的 epoch肯定可以获取到此取值,并且会认同此取值,不会被破坏。

步骤

  1. P1 请求Acceptor的 #1,Acceptor 这时并没有其他线程获取到锁,所以把锁交给 P1,并返回这时 #1 的值为null
  2. 然后 P1 向 第一个 Acceptor 提交 #1 的值,Acceptor 接受并返回 OK
  3. 这个时候,P2向Acceptor请求#1上的锁,因为版本号更大,所以直接抢占了 P1 的锁。这时 Acceptor 返回了 OK并且返回了 #1 的值
  4. 这时 P1 P向 后面两个 Acceptor 提交 #1 的值,但是由于中间的那个Acceptor 版本号已经更改为 2 了,所以拒绝P1。第三个 Acceptor 接受了,并且返回了 OK
  5. 由于后者认同前者的原则,这时 P1 已经形成确定性取值了 V1 了,这时新的 P2 会认同此取值,而不是提交自己的取值。所以,P2会选择最新的那个取值 也就是V1 进行提交。这时Acceptor 返回 OK

6.ZAB 协议 ( Zookeeper Atomic Broadcast) 原子广播协议:保证了发给各副本的消息顺序相同

定义:原子广播协议 ZAB 是一致性协议,Zookeeper 把其作为数据一致性的算法。ZAB 是在 Paxos 算法基础上进行扩展而来的。Zookeeper 使用单一主进程 Leader用于处理客户端所有事务请求,采用 ZAB 协议将服务器状态以事务形式广播到所有 Follower 上,由于事务间可能存在着依赖关系,ZAB协议保证 Leader 广播的变更序列被顺序的处理,一个状态被处理那么它所依赖的状态也已经提前被处理

核心思想:保证任意时刻只有一个节点是Leader,所有更新事务由Leader发起去更新所有副本 Follower,更新时用的是 两段提交协议,只要多数节点 prepare 成功,就通知他们commit。各个follower 要按当初 leader 让他们 prepare 的顺序来 apply 事务

协议状态

  1. Looking:系统刚启动时 或者 Leader 崩溃后正处于选举状态
  2. Following:Follower 节点所处的状态,Follower与 Leader处于数据同步状态
  3. Leading:Leader 所处状态,当前集群中有一个 Leader 为主进程
  • ZooKeeper启动时所有节点初始状态为Looking,这时集群会尝试选举出一个Leader节点,选举出的Leader节点切换为Leading状态;当节点发现集群中已经选举出Leader则该节点会切换到Following状态,然后和Leader节点保持同步;当Follower节点与Leader失去联系时Follower节点则会切换到Looking状态,开始新一轮选举;在ZooKeeper的整个生命周期中每个节点都会在Looking、Following、Leading状态间不断转换。
  • 选举出Leader节点后 ZAB 进入原子广播阶段,这时Leader为和自己同步每个节点 Follower 创建一个操作序列,一个时期一个 Follower 只能和一个Leader保持同步

阶段

  1. Election: 在 Looking状态中选举出 Leader节点,Leader的LastZXID总是最新的(只有lastZXID的节点才有资格成为Leade,这种情况下选举出来的Leader总有最新的事务日志)。在选举的过程中会对每个Follower节点的ZXID进行对比只有highestZXID的Follower才可能当选Leader
    • 每个Follower都向其他节点发送选自身为Leader的Vote投票请求,等待回复;
    • Follower接受到的Vote如果比自身的大(ZXID更新)时则投票,并更新自身的Vote,否则拒绝投票;
    • 每个Follower中维护着一个投票记录表,当某个节点收到过半的投票时,结束投票并把该Follower选为Leader,投票结束;
  2. Discovery:Follower 节点向准 Leader推送 FollwerInfo,该信息包含了上一周期的epoch,接受准 Leader 的 NEWLEADER 指令
  3. Sync:将 Follower 与 Leader的数据进行同步,由Leader发起同步指令,最终保持数据的一致性
  4. Broadcast:Leader广播 Proposal 与 Commit,Follower 接受 Proposal 与 commit。因为一个时刻只有一个Leader节点,若是更新请求,只能由Leader节点执行(若连到的是 Follower 节点,则需转发到Leader节点执行;读请求可以从Follower 上读取,若是要最新的数据,则还是需要在 Leader上读取)
    • 消息广播使用了TCP协议进行通讯所有保证了接受和发送事务的顺序性。广播消息时Leader节点为每个事务Proposal分配一个全局递增的ZXID(事务ID),每个事务Proposal都按照ZXID顺序来处理(Paxos 保证不了)
    • Leader节点为每一个Follower节点分配一个队列按事务ZXID顺序放入到队列中,且根据队列的规则FIFO来进行事务的发送。
  5. Recovery :根据Leader的事务日志对Follower 节点数据进行同步更新
    • 同步策略:
      1. SNAP :如果Follower数据太老,Leader将发送快照SNAP指令给Follower同步数据;
      2. DIFF :Leader发送从Follolwer.lastZXID到Leader.lastZXID议案的DIFF指令给Follower同步数据;
      3. TRUNC :当Follower.lastZXID比Leader.lastZXID大时,Leader发送从Leader.lastZXID到Follower.lastZXID的TRUNC指令让Follower丢弃该段数据;(当老Leader在Commit前挂掉,但是已提交到本地)
    • Follower将所有事务都同步完成后Leader会把该节点添加到可用Follower列表中;
    • Follower接收Leader的NEWLEADER指令,如果该指令中epoch比当前Follower的epoch小那么Follower转到Election阶段

7. Raft 算法

  • Raft 算法也是一种少数服从多数的算法,在任何时候一个服务器可以扮演以下角色之一:
    1. Leader:负责 Client 交互 和 log 复制,同一时刻系统中最多存在一个
    2. Follower:被动响应请求 RPC,从不主动发起请求 RPC
    3. Candidate : 由Follower 向Leader转换的中间状态
  • 在选举Leader的过程中,是有时间限制的,raft 将时间分为一个个 Term,可以认为是“逻辑时间”:
    1. 每个 Term中至多存在1个 Leader
    2. 某些 Term由于不止一个得到的票数一样,就会选举失败,不存在Leader。则会出现 Split Vote ,再由候选者发出邀票
    3. 每个 Server 本地维护 currentTerm
  • 选举过程:
    1. 自增 CurrentTerm,由Follower 转换为 Candidate,设置 votedFor 为自身,并行发起 RequestVote RPC,不断重试,直至满足下列条件之一为止:
      • 获得超过半数的Server的投票,转换为 Leader,广播 HeatBeat
      • 接收到 合法 Leader 的 AppendEnties RPC,转换为Follower
      • 选举超时,没有 Server选举成功,自增 currentTerm ,重新选举
    2. 当Candidate 在等待投票结果的过程中,可能会接收到来自其他Leader的 AppendEntries RPC ,如果该 Leader 的 Term 不小于本地的 Current Term,则认可该Leader身份的合法性,主动降级为Follower,反之,则维持 candida 身份继续等待投票结果
    3. Candidate 既没有选举成功,也没有收到其他 Leader 的 RPC (多个节点同时发起选举,最终每个 Candidate都将超时),为了减少冲突,采取随机退让策略,每个 Candidate 重启选举定时器
  • 日志更新问题:如果在日志复制过程中,发生了网络分区或者网络通信故障,使得Leader不能访问大多数Follwers了,那么Leader只能正常更新它能访问的那些Follower服务器,而大多数的服务器Follower因为没有了Leader,他们重新选举一个候选者作为Leader,然后这个Leader作为代表于外界打交道,如果外界要求其添加新的日志,这个新的Leader就按上述步骤通知大多数Followers,如果这时网络故障修复了,那么原先的Leader就变成Follower,在失联阶段这个老Leader的任何更新都不能算commit,都回滚,接受新的Leader的新的更新。
  • 流程:
    1. Client 发送command 命令给 Leader
    2. Leader追加日志项,等待 commit 更新本地状态机,最终响应 Client
    3. 若 Client超时,则不断重试,直到收到响应为止(重发 command,可能被执行多次,在被执行但是由于网络通信问题未收到响应)
      • 解决办法:Client 赋予每个 Command唯一标识,Leader在接收 command 之前首先检查本地log

9. paxos 算法与 raft 算法的差异

  1. raft强调是唯一leader的协议,此leader至高无上
  2. raft:新选举出来的leader拥有全部提交的日志,而 paxos 需要额外的流程从其他节点获取已经被提交的日志,它允许日志有空洞
  • 相同点:得到大多数的赞成,这个 entries 就会定下来,最终所有节点都会赞成

NWR模型

  • N: N个备份
  • W:要写入至少 w 份才认为成功
  • R : 至少读取 R 个备份
  • W+ R > N ——> R > N - W(未更新成功的) ,代表每次读取,都至少读取到一个最新的版本(更新成功的),从而不会读到一份旧数据
  • 问题:并非强一致性,会出现一些节点上的数据并不是最新版本,但却进行了最新的操作
  • 版本冲突问题:矢量钟 Vector Clock : 谁更新的我,我的版本号是什么(对于同一个操作者的同一操作,版本号递增)

参考资料:

http://www.tuicool.com/articles/IfQR3u3

http://blog.csdn.net/chen77716/article/details/7309915

http://www.infoq.com/cn/articles/distributed-system-transaction-processing/

http://www.jdon.com/artichect/raft.html

http://blog.csdn.net/cszhouwei/article/details/38374603

文章转自:
https://blog.csdn.net/followmyinclinations/article/details/52870418

CGI、FastCGI、PHP-FPM

基础

在整个网站架构中,Web Server(如Apache)只是内容的分发者。举个栗子,如果客户端请求的是 index.html,那么Web Server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。

如果请求的是 index.php,根据配置文件,Web Server知道这个不是静态文件,需要去找 PHP 解析器来处理,那么他会把这个请求简单处理,然后交给PHP解析器。

当Web Server收到 index.php 这个请求后,会启动对应的 CGI 程序,这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以规定CGI规定的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程,接下来再引出这些概念,就好理解多了,

  • CGI:是 Web Server 与 Web Application 之间数据交换的一种协议。
  • FastCGI:同 CGI,是一种通信协议,但比 CGI 在效率上做了一些优化。同样,SCGI 协议与 FastCGI 类似。
  • PHP-CGI:是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序。
  • PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理。

WEB 中,

  • Web Server 一般指Apache、Nginx、IIS、Lighttpd、Tomcat等服务器,
  • Web Application 一般指PHP、Java、Asp.net等应用程序。

Module方式

在了解 CGI 之前,我们先了解一下Web server 传递数据的另外一种方法:PHP Module加载方式。以 Apache 为例,在PHP Module方式中,是不是在 Apache 的配置文件 httpd.conf 中加上这样几句:

# 加入以下2句
LoadModule php5_module D:/php/php5apache2_2.dll
AddType application/x-httpd-php .php

# 修改如下内容
<IfModule dir_module>
    DirectoryIndex index.php index.html
</IfModule>

上面是 Windows 下安装php和apache环境后手动配置,在linux下源码安装大致是这样配置的:

# ./configure --with-mysql=/usr/local --with-apache=/usr/local/apache --enable-track-vars

所以,这种方式,他们的共同本质都是用 LoadModule 来加载 php5_module,就是把php作为apache的一个子模块来运行。当通过web访问php文件时,apache就会调用php5_module来解析php代码。

那么php5_module是怎么来将数据传给php解析器来解析php代码的呢?答案是通过sapi。

我们再来看一张图,详细的说说apache 与 php 与 sapi的关系:

从上面图中,我们看出了sapi就是这样的一个中间过程,SAPI提供了一个和外部通信的接口,有点类似于socket,使得PHP可以和其他应用进行交互数据(apache,nginx等)。php默认提供了很多种SAPI,常见的提供给apache和nginx的php5_module、CGI、FastCGI,给IIS的ISAPI,以及Shell的CLI。

所以,以上的apache调用php执行的过程如下:

apache -> httpd -> php5_module -> sapi -> php

好了。apache与php通过php5_module的方式就搞清楚了吧!

这种模式将php模块安装到apache中,所以每一次apache结束请求,都会产生一条进程,这个进程就完整的包括php的各种运算计算等操作。

在上图中,我们很清晰的可以看到,apache每接收一个请求,都会产生一个进程来连接php通过sapi来完成请求,可想而知,如果一旦用户过多,并发数过多,服务器就会承受不住了。

而且,把mod_php编进apache时,出问题时很难定位是php的问题还是apache的问题。

CGI

CGI(Common Gateway Interface)全称是“通用网关接口”,WEB 服务器与PHP应用进行“交谈”的一种工具,其程序须运行在网络服务器上。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php、perl、tcl等。

WEB服务器会传哪些数据给PHP解析器呢?URL、查询字符串、POST数据、HTTP header都会有。所以,CGI就是规定要传哪些数据,以什么样的格式传递给后方处理这个请求的协议。仔细想想,你在PHP代码中使用的用户从哪里来的。

也就是说,CGI就是专门用来和 web 服务器打交道的。web服务器收到用户请求,就会把请求提交给cgi程序(如php-cgi),cgi程序根据请求提交的参数作应处理(解析php),然后输出标准的html语句,返回给web服服务器,WEB服务器再返回给客户端,这就是普通cgi的工作原理。

CGI的好处就是完全独立于任何服务器,仅仅是做为中间分子。提供接口给apache和php。他们通过cgi搭线来完成数据传递。这样做的好处了尽量减少2个的关联,使他们2变得更独立。

但是CGI有个蛋疼的地方,就是每一次web请求都会有启动和退出过程,也就是最为人诟病的fork-and-execute模式,这样一在大规模并发下,就死翘翘了。

FastCGI介绍

FastCGI简单介绍

从根本上来说,FastCGI是用来提高CGI程序性能的。类似于CGI,FastCGI也可以说是一种协议

FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行,并且接受来自其它网站服务器来的请求。

FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中,并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中,并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等。

FastCGI的工作原理

FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。这种方式可以让HTTP服务器专一地处理静态请求,或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。

  1. Web Server启动时载入FastCGI进程管理器(Apache Module或IIS ISAPI等)
  2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可建多个php-cgi),并等待来自Web Server的连接。
  3. 当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。
  4. FastCGI子进程完成处理后,将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待,并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。 在CGI模式中,php-cgi在此便退出了。

FastCGI与CGI特点:

  1. 对于CGI来说,每一个Web请求PHP都必须重新解析php.ini、重新载入全部扩展,并重新初始化全部数据结构。而使用FastCGI,所有这些都只在进程启动时发生一次。一个额外的好处是,持续数据库连接(Persistent database connection)可以工作。
  2. 由于FastCGI是多进程,所以比CGI多线程消耗更多的服务器内存,php-cgi解释器每进程消耗7至25兆内存,将这个数字乘以50或100就是很大的内存数。

PHP-FPM介绍

要了解PHP-FPM,就得先说说PHP-CGI。

PHP-CGI就是PHP实现的自带的FastCGI管理器。 虽然是php官方出品,但是这丫的却一点也不给力,性能太差,而且也很麻烦不人性化,主要体现在:

  1. php-cgi变更php.ini配置后,需重启php-cgi才能让新的php-ini生效,不可以平滑重启。
  2. 直接杀死php-cgi进程,php就不能运行了。

上面2个问题,一直让很多人病垢了很久,所以很多人一直还是在用 Module 方式。 直到 2004年一个叫 Andrei Nigmatulin的屌丝发明了PHP-FPM ,这神器的出现就彻底打破了这种局面,这是一个PHP专用的 fastcgi 管理器,它很爽的克服了上面2个问题,而且,还表现在其他方面更表现强劲。

也就是说,PHP-FPM 是对于 FastCGI 协议的具体实现,他负责管理一个进程池,来处理来自Web服务器的请求。目前,PHP5.3版本之后,PHP-FPM是内置于PHP的

因为PHP-CGI只是个CGI程序,他自己本身只能解析请求,返回结果,不会进程管理。所以就出现了一些能够调度 php-cgi 进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。同样,PHP-FPM也是用于调度管理PHP解析器php-cgi的管理程序。

PHP-FPM通过生成新的子进程可以实现php.ini修改后的平滑重启。

总结

最后,我们来总结一下,这些技术经过不断的升级,可以解决什么问题(不然也不会升级嘛)。

所以,如果要搭建一个高性能的PHP WEB服务器,目前最佳的方式是Apache/Nginx + FastCGI + PHP-FPM(+PHP-CGI)方式

 

原文:https://www.awaimai.com/371.html

 

.