2.0要到3.0

到现在貌似明白了一些内容,2.0要到3.0的内容。
我现在做的内容,几乎要到30才能做出来,原来如此,时间,我需要继续坚持不懈,继续坚持,慢慢增加专业度。这个世界就我一个人。
这次写小说,再次签约,写上2017年的记录,明显感觉有所不同,编辑要求蛮高的,专业度开始上升了,不再局限于600元的全勤了,。。
生活嘛,3个职业,小说家,淘宝店店主,程序员。

微擎MVC

小小的生活继续!

结构概述

微擎分为四大模块,分别是“api关键字回复”,“微站”,“粉丝&会员”,“扩展模块”

api关键字

用于回复相关的消息

微站

用于承担微站的站点

入口

入口分为 Web端入口”、“App端入口”、“微信Api入口”

微擎MVC

此MVC 为位于source目录,每一个目录代表一个控制器,每个文件为action,系统提供do用来区分一个action的不同操作。
例如
http://pro.we7.cc/web/index.php?c=extension&a=module&do=designer

上方url中extension为控制器,model为action,ddesigner表示下方的一个具体的do

模型位于 framework/model 目录下。
摸板位于 web|app / themes / default

URL 路由和模板文件

当传入的URL请求中包含一个名为 c、a、do(可选) 的 GET 参数,它即被视为一个路由,例如:

http://we7.cc/web/index.php?c=platform&a=menu&

则会路由至 /web/source/platform/menu.ctrl.php 文件中

http://we7.cc/app/index.php?c=mc&a=home&

则会路由至 /app/source/mc/home.ctrl.php 文件中

模块URL地址路由

当传入的 c 值为 “site”, a 值为 “entry”时则是一个模块路由,例如:

http://we7.cc/web/index.php?c=site&a=entry&do=themeset&m=we7_demo

则会路由至
/addons/we7_demo/site.php 文件中的 doWebThemeset() 方法。

http://we7.cc/app/index.php?i=1&j=2&c=entry&do=list&m=we7_demo

则会路由至 /addons/we7_demo/site.php 文件中的 doMobileList() 方法。

约定及使用

GET 参数中的 c、a、do为微擎系统的路由参数,应当避免与系统参数冲突,在程序中可以使用 $controller、$action、$do来获取对应的路由三个参数

模板

在任何php代码中可以使用 template() 函数来渲染一个视图文件。例如:

<?php
/**
 * [WeEngine System] Copyright (c) 2013 WE7.CC
 */
$setting = $_W['setting'];
//将渲染web/themes/default/user/login.html文件
template('user/login');

app端与web端类似,只不过是起始目录为 /app/themes/default/xxx/yyyy.html

调用模块中的模板文件
同生成URL函数一样,微擎也同样为模块封装了单独的模板调用函数,例如:

<?php
class We7_demoModuleSite extends WeModuleSite {
    public function doMobileIndex1() {
        global $_W, $_GPC;
        $title = '支付测试';
        //将渲染模块目录下的app端的模板文件
        // addons/we7_demo/template/mobile/index1.html
        include $this->template('index1');
    }

    public function doWebManage() {
        global $_W, $_GPC;
        //将渲染模块目录下的web端的模板文件
        // addons/we7_demo/template/manage1.html
        include $this->template('manage1');
    }
}

Swagger 相关问题

小小的生活继续进行,小小这边继续找实习,生活已经逐步进入到了下一个阶段,上一个阶段的生活已经告一段落了。
这里安利一本小小每次开篇都要写的一本书,没到一个阶段都会写的书,上一个阶段写的书是拥抱大明,这一次写的书的小明的穿越史,这里安利一下链接,在创世,目前尚未签约状态中。、

http://chuangshi.qq.com/bk/ls/30825982

算是一种纪念意义吧,生活嘛,这就是生活。

找实习,工作也算是一种乐趣吧,继续进行一个新的项目的学习,Swagger

这里进行基于Swagger前后端分离的实践,这里由阿里云社区砖家为此带来。这次关于Swagger的前后端分离的砖家讲解

前言

关于前后端分离,是一个相当流行的开发方式,前端开发不需要部署后端语言的环境,后端开发也不需要前端写好的任何程序。后端只管暴露各种 API 接口供给前端进行数据的增、删、改、查,不负责生成 HTML 页面,这种方式能够减轻后端任务让后端开发更加专注。尤其是在微服务的开发框架下, 前后端分离开发的模式应用更加广泛。本篇亦是在微服务的开发框架下的实践总结。

在微服务开发框架下,前端通常被设计成一个独立的微服务。前后端仅仅通过接口来协作,前端服务最终生成一个独立的 Docker 镜像来部署。在产品的核心微服务定义完成后,我们希望前后端 Service 同时开始开发,所以这里我们利用 Swagger 创建了一个基于 Node.js 的 Mock Server 作为前后端分离开发的工具。在后端服务没有完全实现的情况下, 使用 Mock Server 作为前端开发的支持工具, 来实现前后端同时开发的目的。

什么是Swagger

Swagger 是一个简单但功能强大的 API 设计表达工具。目前几乎所有的编程语言,都能支持 Swagger。Swagger 包括库、编辑器、代码生成器等很多部分,这里我们主要用到了 Swagger Editor。Swagger 可以选择使用 JSON 或者 YAML 的语言格式来编写 API 文档。我们可以用任何文档编辑器来编写 Swagger API 文档,也可以选择使用 Swagger Editor。Swagger Editor 能够提供语法高亮、自动完成、即时预览等功能,非常强大。如图 1 展示的编辑功能,当我们修改了 API 的定义之后,在编辑器右侧就可以看到相应的 API 文档了。

我们可以使用在线版本来编辑,也可以非常简单地部署本地的 Swagger Editor,部署细节请参考https://swagger.io/tools/swagger-editor/。Swagger Editor 不仅能很便捷地生成 API 文档,它还能够自动生成 Mock Server 所需要的代码。而本文实践采用的是其他生成 Mock Server 的方法,所以此处就不对自动生成 Mock Server 代码多做介绍了。

编写 Swagger API 文档

Swagger API 文档遵循 OpenAPI 文档定义标准。文档内容使用 key:value 的格式。

当前后端共同定义好 API 接口后, 我们就可以利用 Swagger Editor 或者其他编辑器将 API 文档写出来。比如我们希望 Mock Server 的名字是 Docs API,host 为 localhost,端口 10010,所有 API 的共同前置路径为 /docs。其中一个 Rest API 定义为:

URL:  /docs/note
Request: 
Method: Get
Parameter: note
            Type: string
            In: path
            Required: true
Respond: 
    200:  Return success message
    500:  Return error message

依据上面的需求我们开始编写 API 文档,Swagger 文档大致可以分为以下四个大的部分:

  1. Swagger API 版本。目前 OpenAPI 最新已经到 3.0.1 版本。本文实践采用 2.0 版本。
  2. 基本定义。包括 Info Object,Server Object 等等,根据产品的需要添加相应的定义。
    基本定义如下
       info:
  version: "0.0.1"
  title: Docs API
host: localhost:10010
basePath: /docs 
schemes:
  - http
  - https
consumes:
  - application/json
produces:
  - application/json
  - text/html

Paths 模块。包括 Path Item Object,Operation Object 等等,每一个具体的 Rest API 请求需要一个 Path Item Object。根据产品的需要添加相应的定义。对应 API URL /docs/note, 具体

paths:
  /{filename}:
    get:
      description: Returns note special resource to the caller
      parameters:
        - name: filename
          in: path
          description: resource name
          required: true
          type: string
      responses:
        "200":
          description: Success
          schema:
            required:
             - message
            properties:
              message:
                type: string
        # responses may fall through to errors
        default:
          description: Error
          schema:
            required:
             - message
            properties:
              message:
                type: string

Definitions 模块。复杂 Object 或者公用 Object 的定义可以写在 Definitions 模块,这样可以增强 API 文档的可读性。比如我们可以把 response 中 200 和 500 的定义抽取出来,定义 2 个 definitions,然后修改 Path 对应部分的代码。具体代码参考清单 3:

definitions:
  GeneralResponse:
    required:
      - message
    properties:
      message:
        type: string
  ErrorResponse:
    required:
      - message
    properties:
      message:
        type: string
  ……

/{filename}:
    get:
      …
      responses:
        "200":
          description: Success
          schema:
            # a pointer to a definition
            $ref: "#/definitions/GeneralResponse"
        # responses may fall through to errors
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"

构建 Mock Server

前后端分离的模式下, 前后端开发人员最先做的事情是定义前后端通讯的 API 接口文档。但是在只有 API 接口文档,而后端服务没有开发完成的时候,前端如何开始开发呢?我们的做法是利用 Swagger API 文档,开发一个简单的 Mock Server 给前端应用使用。

我们使用 swagger-express-mw lib 包来创建基于 Node.js 的 Mock Server,swagger-express-mw 提共一个前端基础框架,这个框架完全可以作为前端代码的开发框架,将 controller 部分实现成真正项目的代码逻辑。因为我们的项目是重构,前端代码需在原有代码基础上做开发,重点重构后端代码。所以只用其作为 Mock Server 来测试前后端的通信,controller 部分的实现只是简单的返回 Rest API 需要的固定的数据。

除了模拟 Rest API 响应,我们还可以扩展 Mock Server 提供的功能,比如加载运行前端 JavaScript 文件的功能,响应 HTTPS 请求,提供 Proxy Server 的功能,当后端完成相应的 API 开发后,Mock Server 可以将指定 API 请求转发到真正的后端 Service 服务器上。这样就可以随时测试后端新实现的 Rest API。

下面让我们来开始创建 Mock Server,以下安装启动命令全部基于 Windows 平台。

安装Node.js

安装 Node.js。Swagger 工程是基于 Node.js 的,所以需要首先安装 Node.js。关于 Node.js 细节请参照https://nodejs.org/en/。
安装 npm。在 Windows 平台上,安装 Node.js 的同时就会安装 npm,无须单独安装。关于 npm 细节请参照https://www.npmjs.com/。
安装 Swagger。运行以下命令安装 Swagger:

npm install -g swagger

安装命令执行完成后,可以运行以下命令来确定 Swagger 是否安装成功:

swagger -V

创建工程

运行以下命令,创建 Mock Server 工程:

swagger project create YourProjectName

选择要创建的工程的架构,比如 express

D:\innovation>swagger project create SwaggerProject
? Framework? express
Project SwaggerProject created in D:\innovation\SwaggerProject
Running "npm install"...
npm
 notice created a lockfile as package-lock.json. You should commit this file.

added 223 packages in 9.305s

Success! You may start your new app by running: "swagger project start SwaggerPr
oject"

工程目录介绍

进入工程根目录,我们可以看到工程结构如图 2:

图 2. 工程目录

api 文件夹:这里又包含 controllers,helpers,mocks 和 swagger 四个文件夹。其中 controllers 用来放置响应 API 请求的 controller 文件;另外 swagger 用来放置 API 文档 swagger.yaml 文件。这两个是 Mock Server 用到的也是不可缺少的部分。
config 文件夹:这里有默认创建的 default.yaml 文件。这个文件是 Mock Server 的默认配置文件,比如这里定义 Mock Server 去哪里找 API 文件等等,仅仅利用工程做 Mock Server 的话,我们不需要改动这个配置文件。同时你也可以将自己的配置文件放在这里。
test 文件夹:这里包含 api/contollers 和 api/helpers。这里是 API 文件夹对应的测试文件。
app.js:Mock Server 的启动文件。我们可以根据自己项目的需求,在这个文件里添加代码增加 Mock Server 的功能。
工程目录如下

启动工程

命令行切换到工程根目录下,运行以下命令

swagger project start

工程正确启动后会提示我们尝试打开链接:http://127.0.0.1:10010/hello?name=Scott。这个链接是工程默认带的一个例子,我们可以在 api/swagger/swagger.yaml 文件中找到这个 API 请求的定义,以及响应这个请求的 controller。

D:\innovation\SwaggerProject>swagger project start
Starting: D:\innovation\SwaggerProject\app.js...
project started here: http://localhost:10010/
project will restart on changes.
to restart at any time, enter `rs`
try this:
curl http://127.0.0.1:10010/hello?name=Scott

编辑API文档

这里编辑API文档。
工程支持在浏览器中显示、编辑、保存、测试 api/swagger/swagger.yaml 文档。具体步骤如下:

  1. 首先启动在线编辑器。打开一个新的命令行窗口,运行命令: # swagger project Edit,运行结果如清单 6。工程默认创建的 swagger.yaml 文件会在浏览器中显示

编辑 API 文档命令

D:\innovation\SwaggerProject>swagger project edit
Starting Swagger Editor.
Opening browser to: http://127.0.0.1:49965/#/edit
Do not terminate this process or close this window until finished editing.

这里在浏览器里编辑文档,进行相关的操作。

编写 controller

当 API 请求被 Mock Server 拦截后,由 controller 决定要如何响应这个 API 请求。所以需要对应所有的 API 请求,编写响应逻辑支持前端开发。需要注意的是要把响应的入口方法导出。这里有个小技巧,我们可以在 controller 的入口将请求的 URL 输出到 console 或者日志中,这样便于在开发的过程中追踪 API 请求。

module.exports = {  viewContent };

function viewContent(req, res) {
  console.log('viewContent : url=' + req.url);
  var type = req.swagger.params.type.value
  var htmlPath = '';
  if (type === 'text'){
    htmlPath = path.join(webStaticPath, 'html','text.html');
  }
  res.sendFile(htmlPath, function (err) {
        if (err) {
            console.error(err);
        } else {
            console.log('Sent:', htmlPath);
        }
    });   
}

添加 proxy 功能

当后端代码部分 API 开发完成后,我们希望能及时对已完成代码部分进行前后端联调。这时可以给 Mock Server 添加 proxy 的功能,使 Mock Server 接到的请求都转到真正的后端服务环境上去。 这样前端就可以在开发环境中直接访问真正的后端服务,而不必担心跨域的问题。这里我们使用 http-proxy-middleware 包来实现 proxy 功能。

首先在 package.json 文件中添加 http-proxy-middleware lib 包,然后工程里运行以下命令安装 node module:

npm install
var proxy = require('http-proxy-middleware');
    /**
     * Configure proxy middleware
    */
    var targetUri = http + '://' + remoteHostName:port ;
    var routePathList = ["/docs/docsrv","/docs/api"];
    var docsProxy = proxy({
        target: targetUri,
        changeOrigin: true,
        logLevel: 'debug',
        secure: false
    }) 
    for(var i = 0; i  routePathList.length; i++){
        app.use(routePathList[i], docsProxy);
    }

应用 Mock Server

Mock Server 构建完成后,我们开始使用 Mock Server 开始前端开发。首先确定你的配置文件的设置是你需要的。比如 Mock Server 是否加载前端代码,Mock Server 的响应端口,Mock Server 是否转发请求到其他 Server。比如以下是我们开发过程中用的 Mock Server 配置文件。我们使用 Mock Server 加载前端代码,指定 Mock Server 响应端口在 10010,HTTP 协议,将以 /docs/docsrv 和 /docs/api 开始的 API 请求转发到 docsServeribm.com:80 的 Server 上。

{
  "webresource_dir": "C:/Users/IBM_ADMIN/git/docs-webresource/docs-webresource",
  "fake_data_dir": "",
  "fake_server_port": "10010",
  "version": "1.0.0",
  "web_server": "true",
  "mock": "true",
  "protocol": "http",
  "real_server": {
    "protocol": "http",  
    "host": "docsServer.ibm.com",
    "port": "80",
    "pathList": ["/docs/docsrv","/docs/api"]
  },
}

然后打开命令行运行以下命令,将 Mock Server 起动起来:

# swagger project start

因为 Mock Server 加载了前端代码,我们可以在浏览器中以 http://localhost:10010/docs/index.html 直接访问前端的入口页面。如果前端代码起在另一个 Server 上,那么需要将你将前端代码的 API 访问请求配置到 Mock Server 的上,然后访问前端的入口页面。这时已经开发好的功能应该能正常显示。如果不能正常显示,我们需要追踪出错的位置,查看对应 API 的定义调用响应各个环节是否一致,来排除 Mock Server 响应出错。比如确认代码发出的 API 请求,与 swagger.yaml 文件中定义的 API 是否一致,这决定了 API 是否能在 Mock Server 中被正确拦截;然后查看响应 API 的 controller 方法的逻辑,是否返回了 API 需要的假数据。如果 Mock Server 响应正确,那么这就是前端代码真正的 BUG,否则需要先修正 Mock Server 的错误。

好啦。这就是关于Swagger的相关的问题。

解释RPC远程调用的来龙去脉

在分布式计算,远程过程调用是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程。RPC是一种服务器-客户端模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。

本地过程调用

这里先解释本地过程调用
假设本地类有一个方法。其方法为add,这里演示一下本地过程调用。

package com.company;

public class Main {

    public static void main(String[] args) {
    // write your code here
        System.out.println(add(2,4));
    }

    public static int add(int a, int b){
        return a + b;
    }
}

在上面这个方法中,本地调用了其add方法,这里属于本地过程调用。其中第五行,就是本地调用其方法add。

远程过程调用

对于远程过程调用。其有以下几个方法,

原理解释

Client端

  1. 将这个调用映射为Call ID。这里假设用最简单的字符串当Call ID的方法
  2. 将Call ID,lvalue和rvalue序列化。可以直接将它们的值以二进制形式打包
  3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
  4. 等待服务器返回结果
  5. 如果服务器调用成功,那么就将结果反序列化,并赋给l_times_r

Server端

  1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用std::map<std::string, std::function<>>
  2. 等待请求
  3. 得到一个请求后,将其数据包反序列化,得到Call ID
  4. 通过在call_id_map中查找,得到相应的函数指针
  5. 将lvalue和rvalue反序列化后,在本地调用Multiply函数,得到结果
  6. 将结果序列化后通过网络返回给Client

如下

1)提供了一个api包,用于标出provider提供的所有接口名称;
2)提供一个provider(生产者)包,用于实现所有的接口,并可以注册到zookeeper上,统一管理;
3)提供一个consumer(消费者)包,用于调用接口,为了便于管理,消费者开启时,也需要注册上zookeeper上,具体注册方式如下:

注册到zookeeper上的配置

注册到zookeeper上的配置
1)生产者:主要包括接口实现类、启动方法类、配置文件。
下面是包结构:

配置文件中主要包括dubbo的注册中心、端口号、以及定义实现的接口描述。

启动类中,需要读取配置文件信息,然后启动:

2)消费者:主要包括启动类、配置文件。
包结构如下:

配置文件内容除了跟生产者一样的以外,若是调用远程接口,可以通过dubbo:reference进行引入,前提是被应用的接口类已被引入(具体操作后面再论);

启动类中可通过getBean方式,注入接口类,然后调用其方法:

先启动生产者,然后启动消费者,消费者输出如下:

可以看到前两条是应用的本地接口实现方法,后一条则是调用的远程接口实现。

继续!面试继续!Netty dubbo的通信方式

小小又去面试了,小小的生活继续,(^o^)/
这次小小的技术方面有Netty,以及Dubbo的通信方式,对这两个点进行继续的复习和学习。

1. Netty

Netty 通过缓冲区实现。

WebSocket

为什么需要WebSocket

聊天室之前采用的是轮询,效率相当的慢,所以这里采用WebSocket,实现长连接通信。
WebSocket于是这样的诞生。

介绍

特点如下
1. 建立在TCP连接之上。
2. 与HTTP有较好的兼容性。
3. 数据格式轻量,性能开销小,消耗小。
4. 可以发送文本,可以发送二进制数据。
5. 没有同源限制,可以进行任意的通信。
6. 协议的标识符是ws。
网址如下
ws://example.com:80/some/path

示例

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      

客户端的 API

新建实例

var ws = new WebSocket('ws://localhost:8080');
readyState属性返回实例对象的当前状态,共有四种。

CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

下面是一个实例

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}
webSocket.onopen
实例对象的onopen属性,用于指定连接成功后的回调函数。


ws.onopen = function () {
  ws.send('Hello Server!');
}
如果要指定多个回调函数,可以使用addEventListener方法。


ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});
webSocket.onclose
实例对象的onclose属性,用于指定连接关闭后的回调函数。


ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});
webSocket.onmessage
实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。

ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  // 处理数据
});
注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。


ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}
除了动态判断收到的数据类型,也可以使用binaryType属性,显式指定收到的二进制数据类型。


// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};
4.6 webSocket.send()
实例对象的send()方法用于向服务器发送数据。

发送文本的例子。


ws.send('your message');
发送 Blob 对象的例子。


var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);
发送 ArrayBuffer 对象的例子。


// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

webSocket.bufferedAmount
实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。


var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}
 webSocket.onerror
实例对象的onerror属性,用于指定报错时的回调函数。


socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});

Netty的使用

新建工程,通过meaven导入Netty的库包

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha2</version>
</dependency>

创建NettyServer

package com.jiutianbian.NettyTest;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    private int port;

    public NettyServer(int port) {
        this.port = port;
        bind();
    }

    private void bind() {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(boss, worker);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024); // 连接数
            bootstrap.option(ChannelOption.TCP_NODELAY, true); // 不延迟,消息立即发送
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // 长连接
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel)
                        throws Exception {
                    ChannelPipeline p = socketChannel.pipeline();
                    p.addLast(new NettyServerHandler());// 添加NettyServerHandler,用来处理Server端接收和处理消息的逻辑
                }
            });
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            if (channelFuture.isSuccess()) {
                System.err.println("启动Netty服务成功,端口号:" + this.port);
            }
            // 关闭连接
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            System.err.println("启动Netty服务异常,异常信息:" + e.getMessage());
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new NettyServer(10086);
    }
}

创建NettyServerHandler,用来接收和回复Client端的消息

package com.jiutianbian.NettyTest;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.io.UnsupportedEncodingException;

public class NettyServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

        ByteBuf buf = (ByteBuf) msg;

        String recieved = getMessage(buf);
        System.err.println("服务器接收到客户端消息:" + recieved);

        try {
            ctx.writeAndFlush(getSendByteBuf("你好,客户端"));
            System.err.println("服务器回复消息:你好,客户端");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /*
     * 从ByteBuf中获取信息 使用UTF-8编码返回
     */
    private String getMessage(ByteBuf buf) {

        byte[] con = new byte[buf.readableBytes()];
        buf.readBytes(con);
        try {
            return new String(con, "UTF8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    private ByteBuf getSendByteBuf(String message)
            throws UnsupportedEncodingException {

        byte[] req = message.getBytes("UTF-8");
        ByteBuf pingMessage = Unpooled.buffer();
        pingMessage.writeBytes(req);

        return pingMessage;
    }
}

启动Server端

Netty Client端
1. 新建工程,通过meaven导入Netty的库包
导入代码同上面的Server端代码

2. 创建NettyClient
新建NettyClient类

package com.jiutianbian.NettyClinetTest;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {

    /*
     * 服务器端口号
     */
    private int port;

    /*
     * 服务器IP
     */
    private String host;

    public NettyClient(int port, String host) throws InterruptedException {
        this.port = port;
        this.host = host;
        start();
    }

    private void start() throws InterruptedException {

        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {

            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.group(eventLoopGroup);
            bootstrap.remoteAddress(host, port);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel)
                        throws Exception {
                    socketChannel.pipeline().addLast(new NettyClientHandler());
                }
            });
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            if (channelFuture.isSuccess()) {
                System.err.println("连接服务器成功");
            }
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new NettyClient(10086, "localhost");
    }
}
package com.jiutianbian.NettyClinetTest;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.io.UnsupportedEncodingException;

public class NettyClientHandler extends ChannelHandlerAdapter {
    private ByteBuf firstMessage;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        byte[] data = "你好,服务器".getBytes();
        firstMessage = Unpooled.buffer();
        firstMessage.writeBytes(data);
        ctx.writeAndFlush(firstMessage);
        System.err.println("客户端发送消息:你好,服务器");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        String rev = getMessage(buf);
        System.err.println("客户端收到服务器消息:" + rev);
    }

    private String getMessage(ByteBuf buf) {
        byte[] con = new byte[buf.readableBytes()];
        buf.readBytes(con);
        try {
            return new String(con, "UTF8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

启动Client端

Server端日志输出,此时如下

Dubbo 通信

Dubbo通信,Dubbo 通信方式使用RPC的方式进行通信。

容器间通信 ip网段

容器间通信

容器之间通信是通过默认网桥的方式进行通信。
如图所示

如图所示,其中eth0为docker宿主机上的网卡,docker0为在宿主机上创建的网桥,通过创建的网桥,进行相关的连接。

Ip网段分类

ip地址分类如下

IP默认分配的子网掩码每段只有255或0
A类的默认子网掩码 255.0.0.0 一个子网最多可以容纳1677万多台电脑
B类的默认子网掩码 255.255.0.0 一个子网最多可以容纳6万台电脑
C类的默认子网掩码 255.255.255.0 一个子网最多可以容纳254台电脑
要想在同一网段,只要网络标识相同就可以了,那要怎么看网络标识呢?首先要做的是把每段的IP转换为二进制。
把子网掩码切换至二进制,我们会发现,所有的子网掩码是由一串连续的1和一串连续的0组成的(一共4段,每段8位,一共32位数)。
255.0.0.0 11111111.00000000.00000000.00000000
255.255.0.0 11111111.11111111.00000000.00000000
255.255.255.0 11111111.11111111.11111111.00000000
这是A/B/C三类默认子网掩码的二进制形式,其实,还有好多种子网掩码,只要是一串连续的1和一串连续的0就可以了(每段都是8位)。如11111111.11111111.11111000.00000000,这也是一段合法的子网掩码。子网掩码决定的是一个子网的计算机数目,计算机公式是2的m次方,其中,我们可以把m看作后面0的个数。如255.255.255.0转换成二进制,那就是11111111.11111111.11111111.00000000,后面有8个0,那m就是8,255.255.255.0这个子网掩码可以容纳2的8次方(台)电脑,也就是256台,但是有两个IP是不能用的,那就是最后一段不能为0和255,减去这两台,就是254台。

分布式锁 动态代理 Java数据结构List,Set,Map,Spring执行流程,Spring MVC组件

这里对今日的内容进行总结:

分布式锁具备的条件:

具备的条件:
1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行。
2. 高可用的获取锁与释放锁。
3. 高性能的获取锁与释放锁。
4. 具备可重入的特性。
5. 具备锁失效机制,防止死锁。
6. 具备阻塞锁特性,即没有获取到锁将会继续等待获取锁。
7. 具备非阻塞锁特性,即没有获取到锁将会直接返回获取锁失败。

spring mvc 组件

  在学习9个组件之前,我们需要先了解Handler的概念,也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler。

【1. HandlerMapping】

是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。

【2. HandlerAdapter】

    从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
    小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。

【3. HandlerExceptionResolver】

    其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

【4. ViewResolver】

    ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

【5. RequestToViewNameTranslator】

    ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。

【6. LocaleResolver】

    解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。

【7. ThemeResolver】

    用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。

【8. MultipartResolver】

    用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。

【9. FlashMapManager】

用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

动态代理应用场景。

Spring AOP 机制是动态代理的应用场景。

List,Set,Map特点

List

ArrAyList

ArrayList是Java集合框架中使用最多的一个类,是一个数组队列,线程不安全集合。

它继承于AbstractList,实现了List, RandomAccess, Cloneable, Serializable接口。
(1)ArrayList实现List,得到了List集合框架基础功能;
(2)ArrayList实现RandomAccess,获得了快速随机访问存储元素的功能,RandomAccess是一个标记接口,没有任何方法;
(3)ArrayList实现Cloneable,得到了clone()方法,可以实现克隆功能;
(4)ArrayList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。

它具有如下特点:

容量不固定,随着容量的增加而动态扩容(阈值基本不会达到)
有序集合(插入的顺序==输出的顺序)
插入的元素可以为null
增删改查效率更高(相对于LinkedList来说)
线程不安全

LinkList

LinkedList是一个双向链表,每一个节点都拥有指向前后节点的引用。相比于ArrayList来说,LinkedList的随机访问效率更低。

它继承AbstractSequentialList,实现了List, Deque, Cloneable, Serializable接口。
(1)LinkedList实现List,得到了List集合框架基础功能;
(2)LinkedList实现Deque,Deque 是一个双向队列,也就是既可以先入先出,又可以先入后出,说简单些就是既可以在头部添加元素,也可以在尾部添加元素;
(3)LinkedList实现Cloneable,得到了clone()方法,可以实现克隆功能;
(4)LinkedList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。

数据结构:(JDK1.7)

A:添加功能
boolean add(E e):向集合中添加一个元素
void add(int index, E element):在指定位置添加元素
boolean addAll(Collection<? extends E> c):向集合中添加一个集合的元素。

B:删除功能
void clear():删除集合中的所有元素
E remove(int index):根据指定索引删除元素,并把删除的元素返回
boolean remove(Object o):从集合中删除指定的元素
boolean removeAll(Collection<?> c):从集合中删除一个指定的集合元素。

C:修改功能
E set(int index, E element):把指定索引位置的元素修改为指定的值,返回修改前的值。

D:获取功能
E get(int index):获取指定位置的元素
Iterator iterator():就是用来获取集合中每一个元素。

E:判断功能
boolean isEmpty():判断集合是否为空。
boolean contains(Object o):判断集合中是否存在指定的元素。
boolean containsAll(Collection<?> c):判断集合中是否存在指定的一个集合中的元素。

F:长度功能
int size():获取集合中的元素个数

G:把集合转换成数组
Object[] toArray():把集合变成数组。

Set 集合

Set:注重独一无二的性质,该体系集合可以知道某物是否已近存在于集合中,不会存储重复的元素
用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。

对象的相等性

   引用到堆上同一个对象的两个引用是相等的。如果对两个引用调用hashCode方法,会得到相同的结果,如果对象所属的类没有覆盖Object的hashCode方法的话,hashCode会返回每个对象特有的序号(java是依据对象的内存地址计算出的此序号),所以两个不同的对象的hashCode值是不可能相等的。

如果想要让两个不同的Person对象视为相等的,就必须覆盖Object继下来的hashCode方法和equals方法,因为Object  hashCode方法返回的是该对象的内存地址,所以必须重写hashCode方法,才能保证两个不同的对象具有相同的hashCode,同时也需要两个不同对象比较equals方法会返回true

该集合中没有特有的方法,直接继承自Collection。

---| Itreable      接口 实现该接口可以使用增强for循环
                ---| Collection     描述所有集合共性的接口
                    ---| List接口     可以有重复元素的集合
                            ---| ArrayList   
                            ---|  LinkedList
                    ---| Set接口      不可以有重复元素的集合

eg:

public class Demo4 {
    public static void main(String[] args) {
        //Set 集合存和取的顺序不一致。
        Set hs = new HashSet();
        hs.add("世界军事");
        hs.add("兵器知识");
        hs.add("舰船知识");
        hs.add("汉和防务");
        System.out.println(hs);
        // [舰船知识, 世界军事, 兵器知识, 汉和防务]
        Iterator it = hs.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

HashSet

—| Itreable 接口 实现该接口可以使用增强for循环
—| Collection 描述所有集合共性的接口
—| List接口 可以有重复元素的集合
—| ArrayList
—| LinkedList
—| Set接口 不可以有重复元素的集合
—| HashSet 线程不安全,存取速度快。底层是以哈希表实现的。

元素的哈希值是通过元素的hashcode方法 来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。

哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。

TreeSet

public class Demo5 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add("ccc");
        ts.add("aaa");
        ts.add("ddd");
        ts.add("bbb");

        System.out.println(ts); // [aaa, bbb, ccc, ddd]

    }
}
---| Itreable      接口 实现该接口可以使用增强for循环
                ---| Collection     描述所有集合共性的接口
                    ---| List接口     有序,可以重复,有角标的集合
                            ---| ArrayList   
                            ---|  LinkedList
                    ---| Set接口      无序,不可以重复的集合
                            ---| HashSet  线程不安全,存取速度快。底层是以hash表实现的。
                            ---| TreeSet  红-黑树的数据结构,默认对元素进行自然排序(String)。如果在比较的时候两个对象返回值为0,那么元素重复。

红-黑树

红黑树是一种特定类型的二叉树

map

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map是一个接口,实例化Map可以采用下面的方式:

HashMap //Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
LinkedHashMap //类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
TreeMap //底层是二叉树数据结构,线程不同步,可用于给Map集合中的键进行排序。
HashTable //HashMap是Hashtable的轻量级实现,非线程安全的实现他们都实现了map接口,主要区别是HashMap键值可以为空null,效率可以高于Hashtable。
ConcurrentHashMap //ConcurrentHashMap通常只被看做并发效率更高的Map,用来替换其他线程安全的Map容器,比如Hashtable和Collections.synchronizedMap。
WeakHashMap //弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。
IdentifyHashMap //使用==代替equals()对“键”作比较的hash map
ArrayMap //ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。
SparseArray //SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间。

Map的基本操作:

Object put(Object key, Object value): 向集合中加入元素
Object remove(Object key): 删除与KEY相关的元素
void putAll(Map t): 将来自特定映像的所有元素添加给该映像
void clear():从映像中删除所有映射

HashMap的存储原理

HashMap的存储原理:(跟HashSet很像的,可以类比的理解下)
    往HashMap添加元素的时候,首先会调用键的hashCode方法得到元素 的哈希码值,然后经过运算就可以算出该
    元素在哈希表中的存储位置。 
    情况1: 如果算出的位置目前没有任何元素存储,那么该元素可以直接添加到哈希表中。

    情况2:如果算出 的位置目前已经存在其他的元素,那么还会调用该元素的equals方法与这个位置上的元素进行比较
    ,如果equals方法返回 的是false,那么该元素允许被存储,如果equals方法返回的是true,那么该元素被视为
    重复元素,不允存储。
HashMap的基本用法
package collection;

import java.util.HashMap;  
import java.util.Iterator;  
import java.util.Set;  
import java.util.Map.Entry;  

public class HashMap {  

    public static void main(String[] args) {  

        HashMap<String, String> hashMap = new HashMap<String, String>();  
        hashMap.put("cn", "中国");  
        hashMap.put("jp", "日本");  
        hashMap.put("fr", "法国");  

        System.out.println(hashMap); 
        System.out.println("****");
        System.out.println("cn:" + hashMap.get("cn"));  
        System.out.println(hashMap.containsKey("cn")); 

        System.out.println(hashMap.keySet());  
        System.out.println(hashMap.isEmpty());  

        hashMap.remove("cn");  
        System.out.println(hashMap);  
        //采用Iterator遍历HashMap  
        Iterator it = hashMap.keySet().iterator();  
        while(it.hasNext()) {  
            String key = (String)it.next();  
            System.out.println("key:" + key);  
            System.out.println("value:" + hashMap.get(key));  
        }  

        //遍历HashMap的另一个方法  
        Set<Entry<String, String>> sets = hashMap.entrySet();  
        for(Entry<String, String> entry : sets) {  
            System.out.print(entry.getKey() + ", ");  
            System.out.println(entry.getValue());  
        }  
    }  
}  

TreeMap的原理:

TreeMap   TreeMap也是基于红黑树(二叉树)数据结构实现 的, 特点:会对元素的键进行排序存储。(这个跟TreeSet一样的,可以参考TreeSet的原理)
TreeMap 要注意的事项:
    1.  往TreeMap添加元素的时候,如果元素的键具备自然顺序,那么就会按照键的自然顺序特性进行排序存储。
    2.  往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性, 那么键所属的类必须要实现Comparable接口,把键
    的比较规则定义在CompareTo方法上。

    3. 往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性,而且键所属的类也没有实现Comparable接口,那么就必须
    在创建TreeMap对象的时候传入比较器。
public class TreeMapTest {
    public static void main(String[] agrs){
        //创建TreeMap对象:
        TreeMap<String,Integer> treeMap = new TreeMap<String,Integer>();
        System.out.println("初始化后,TreeMap元素个数为:" + treeMap.size());

        //新增元素:
        treeMap.put("hello",1);
        treeMap.put("world",2);
        treeMap.put("my",3);
        treeMap.put("name",4);
        treeMap.put("is",5);
        treeMap.put("jiaboyan",6);
        treeMap.put("i",6);
        treeMap.put("am",6);
        treeMap.put("a",6);
        treeMap.put("developer",6);
        System.out.println("添加元素后,TreeMap元素个数为:" + treeMap.size());

        //遍历元素:
        Set<Map.Entry<String,Integer>> entrySet = treeMap.entrySet();
        for(Map.Entry<String,Integer> entry : entrySet){
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("TreeMap元素的key:"+key+",value:"+value);
        }

        //获取所有的key:
        Set<String> keySet = treeMap.keySet();
        for(String strKey:keySet){
            System.out.println("TreeMap集合中的key:"+strKey);
        }

        //获取所有的value:
        Collection<Integer> valueList = treeMap.values();
        for(Integer intValue:valueList){
            System.out.println("TreeMap集合中的value:" + intValue);
        }

        //获取元素:
        Integer getValue = treeMap.get("jiaboyan");//获取集合内元素key为"jiaboyan"的值
        String firstKey = treeMap.firstKey();//获取集合内第一个元素
        String lastKey =treeMap.lastKey();//获取集合内最后一个元素
        String lowerKey =treeMap.lowerKey("jiaboyan");//获取集合内的key小于"jiaboyan"的key
        String ceilingKey =treeMap.ceilingKey("jiaboyan");//获取集合内的key大于等于"jiaboyan"的key
        SortedMap<String,Integer> sortedMap =treeMap.subMap("a","my");//获取集合的key从"a"到"jiaboyan"的元素

        //删除元素:
        Integer removeValue = treeMap.remove("jiaboyan");//删除集合中key为"jiaboyan"的元素
        treeMap.clear(); //清空集合元素:

        //判断方法:
        boolean isEmpty = treeMap.isEmpty();//判断集合是否为空
        boolean isContain = treeMap.containsKey("jiaboyan");//判断集合的key中是否包含"jiaboyan"
    }
}

Spring 执行流程

  1. 发起请求到前端控制器
  2. 前端控制器请求HandlerMapping 查找Handler
  3. 处理器映射器HandlerMapping处理查找Handler
  4. 处理器映射器HandlerMapping返回一个执行器链,内包括拦截器等。
  5. 前端控制器调用处理器进行执行Handler
  6. 处理器先适配Handler得到ModelAndView
  7. 处理器适配器向前端控制器返回ModelAndView
  8. 解析ModelAndView 获取Jsp。
  9. 返回View
  10. 进行相关渲染。
  11. 返回相应结果。

Spring MVC 组件

  1. 前端控制器组件。
  2. 处理器组件
  3. 处理器映射器组件
  4. 处理器适配器组件
  5. 拦截器组件。
  6. 视图解析器组件。
  7. 视图组件。
  8. 数据转换组件。
  9. 消息转换器组件。

今日总结

这里对今日的面试进行总结。

二叉树:

二叉树的遍历分为前序、中序、后序
前序遍历:根结点 —> 左子树 —> 右子树

中序遍历:左子树—> 根结点 —> 右子树

后序遍历:左子树 —> 右子树 —> 根结点

层次遍历:只需按层次遍历即可

前序遍历:1 2 4 5 7 8 3 6

中序遍历:4 2 7 5 8 1 3 6

后序遍历:4 7 8 5 2 6 3 1

层次遍历:1 2 3 4 5 6 7 8

JVM

JVM进行相关的调优参数。

参数介绍

-XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起JVM 性能上的差异,使JVM 存在极大的不稳定性。如果此类参数设置合理将大大提高JVM 的性能及稳定性。

JVM参数示例

配置: -Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m

-XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15

解析:
-Xmx4g:堆内存最大值为4GB。
-Xms4g:初始化堆内存大小为4GB 。
-Xmn1200m:设置年轻代大小为1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss512k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:PermSize=100m:初始化永久代大小为100MB。
-XX:MaxPermSize=256m:设置持久代大小为256MB。
-XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

JVM调优经验总结

JVM调优的一般步骤为:

  第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;

  第2步:确定JVM调优量化目标;

  第3步:确定JVM调优参数(根据历史JVM参数来调整);

  第4步:调优一台服务器,对比观察调优前后的差异;

  第5步:不断的分析和调整,直到找到合适的JVM参数配置;

  第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

CAP原理

Consistency(一致性): 数据一致更新,所有数据变动都是同步的
Availability(可用性): 好的响应性能
Partition tolerance(分区耐受性): 可靠性
上面的解释可能显得太过抽象,举例来说在高可用的网站架构中,对于数据基础提出了以下的要求:

分区耐受性

保证数据可持久存储,在各种情况下都不会出现数据丢失的问题。为了实现数据的持久性,不但需要在写入的时候保证数据能够持久存储,还需要能够将数据备份一个或多个副本,存放在不同的物理设备上,防止某个存储设备发生故障时,数据不会丢失。

数据一致性

在数据有多份副本的情况下,如果网络、服务器、软件出现了故障,会导致部分副本写入失败。这就造成了多个副本之间的数据不一致,数据内容冲突。

数据可用性

多个副本分别存储于不同的物理设备的情况下,如果某个设备损坏,就需要从另一个数据存储设备上访问数据。如果这个过程不能很快完成,或者在完成的过程中需要停止终端用户访问数据,那么在切换存储设备的这段时间内,数据就是不可访问的。

原理认为

CAP原理认为,一个提供数据服务的存储系统无法同时完美的满足一致性(Consistency)、数据可用性(Availability)、分区耐受性(Partition Tolerance)这三个条件。对于三者的关系见图1.

MySQL左连接与右连接

前几次面试的时候遇到左右连接的问题,这里进行回顾。

先通过下面两个表展示一下左连接和右连接的结果

左连接与右连接

员工表:

mysql> select * from employ;
+-----------+------+------+
| name      | id   | sal  |
+-----------+------+------+
| 小王      | 1000 |    0 |
| 小李      | 1001 |   90 |
| 小华      | 1002 | NULL |
| xiaogui   | 1008 | 3800 |
| xiaolang  | 1009 | 3900 |
| xiaohu    | 1003 | 4000 |
+-----------+------+------+
6 rows in set (0.00 sec)

学生表:

mysql> select * from student;
+--------+------+------+
| name   | age  | id   |
+--------+------+------+
| 懒洋洋 |   10 | 1001 |
| 喜羊羊 |  100 | 1002 |
| 美羊羊 |   12 | NULL |
| 灰太狼 | NULL | 1004 |
+--------+------+------+
4 rows in set (0.00 sec)

左连接

mysql> select employ.sal,student.age from employ left join student on employ.id
= student.id;
+------+------+
| sal  | age  |
+------+------+
|    0 | NULL |
|   90 |   10 |
| NULL |  100 |
| 3800 | NULL |
| 3900 | NULL |
| 4000 | NULL |
+------+------+
6 rows in set (0.00 sec)

右连接

mysql> select employ.sal,student.age from employ right join student on employ.id
 = student.id;
+------+------+
| sal  | age  |
+------+------+
|   90 |   10 |
| NULL |  100 |
| NULL |   12 |
| NULL | NULL |
+------+------+
4 rows in set (0.00 sec)

通过以上两个表我们总结如下:

左连接:以左边的表(employ)为主。显示左边表列的全部数据,如果右边表没有对应的数据,则为NULL

右连接:以右边的表(student)为主。显示右边表列的全部数据,如果左边表没有对应的数据,则为NULL

笛卡儿积

两个表的信息:

员工表:

mysql> select * from employ;
+-----------+------+------+
| name      | id   | sal  |
+-----------+------+------+
| 小王      | 1000 |    0 |
| 小李      | 1001 |   90 |
| 小华      | 1002 | NULL |
| xiaogui   | 1008 | 3800 |
| xiaolang  | 1009 | 3900 |
| xiaohu    | 1003 | 4000 |
+-----------+------+------+
6 rows in set (0.00 sec)

学生表:

mysql> select * from student;
+--------+------+------+
| name   | age  | id   |
+--------+------+------+
| 懒洋洋 |   10 | 1001 |
| 喜羊羊 |  100 | 1002 |
| 美羊羊 |   12 | NULL |
| 灰太狼 | NULL | 1004 |
+--------+------+------+
4 rows in set (0.00 sec)

笛卡儿积的情况:

mysql> select * from employ, student;
+-----------+------+------+--------+------+------+
| name      | id   | sal  | name   | age  | id   |
+-----------+------+------+--------+------+------+
| 小王      | 1000 |    0 | 懒洋洋 |   10 | 1001 |
| 小王      | 1000 |    0 | 喜羊羊 |  100 | 1002 |
| 小王      | 1000 |    0 | 美羊羊 |   12 | NULL |
| 小王      | 1000 |    0 | 灰太狼 | NULL | 1004 |
| 小李      | 1001 |   90 | 懒洋洋 |   10 | 1001 |
| 小李      | 1001 |   90 | 喜羊羊 |  100 | 1002 |
| 小李      | 1001 |   90 | 美羊羊 |   12 | NULL |
| 小李      | 1001 |   90 | 灰太狼 | NULL | 1004 |
| 小华      | 1002 | NULL | 懒洋洋 |   10 | 1001 |
| 小华      | 1002 | NULL | 喜羊羊 |  100 | 1002 |
| 小华      | 1002 | NULL | 美羊羊 |   12 | NULL |
| 小华      | 1002 | NULL | 灰太狼 | NULL | 1004 |
| xiaogui   | 1008 | 3800 | 懒洋洋 |   10 | 1001 |
| xiaogui   | 1008 | 3800 | 喜羊羊 |  100 | 1002 |
| xiaogui   | 1008 | 3800 | 美羊羊 |   12 | NULL |
| xiaogui   | 1008 | 3800 | 灰太狼 | NULL | 1004 |
| xiaolang  | 1009 | 3900 | 懒洋洋 |   10 | 1001 |
| xiaolang  | 1009 | 3900 | 喜羊羊 |  100 | 1002 |
| xiaolang  | 1009 | 3900 | 美羊羊 |   12 | NULL |
| xiaolang  | 1009 | 3900 | 灰太狼 | NULL | 1004 |
| xiaohu    | 1003 | 4000 | 懒洋洋 |   10 | 1001 |
| xiaohu    | 1003 | 4000 | 喜羊羊 |  100 | 1002 |
| xiaohu    | 1003 | 4000 | 美羊羊 |   12 | NULL |
| xiaohu    | 1003 | 4000 | 灰太狼 | NULL | 1004 |
+-----------+------+------+--------+------+------+
24 rows in set (0.00 sec)
mysql> select count(*) from employ,student;
+----------+
| count(*) |
+----------+
|       24 |
+----------+
1 row in set (0.00 sec)

总结:两个表总共14数据,但我们得到了24条数据。这是什么情况?这就是笛卡儿积所产生的问题。笛卡儿积的具体执行过程看下图:


就像图中,employ表中有6项,student中有4项,6*4=24。所以左连接,右连接解决了笛卡儿积的问题。