0%

Maven Archetype Plugin

原文:https://maven.apache.org/archetype/maven-archetype-plugin/index.html#

一、 介绍

​ 原型插件可以让用户根据已有工程创建一个原型工程(archetype)。

​ 插件需要 JDK 1.7 +

​ 插件工作原理如下图:

二、目标概述

  • archetype:generage 从原型创建Maven项目:要求用户从原型目录中选择原型,并从远程存储库中检索它。检索后,将对其进行处理以创建有效的Maven项目。
  • archetype:create-from-project

一、说明

主机环境Centos 7

二、安装步骤

1. 添加Mysql的yum仓库

a. 访问Mysql的yum仓库下载页面:https://dev.mysql.com/downloads/repo/yum/

b. 选择下载和操作系统版本对应的安装包

服务器为Centos 7, 因此本文选择 Red Hat Enterprise Linux 7 / Oracle Linux 7 (Architecture Independent), RPM Package 版本,(mysql80-community-release-el7-2.noarch.rpm)

c. 使用下面的命令进行安装:

1
shell> sudo rpm -Uvh mysql80-community-release-el7-2.noarch.rpm)

2. 选择一个发型版本

当使用Mysql yum仓库时,默认安装最新的发型版本,可以通过以下命令查看:

1
shell> yum repolist all | grep mysql

默认是mysql80-* enable状态,截图为修改过后的截图

如果要安装最新版本的的Mysql数据库,则不需要调整;如果要安装历史发型版,可以通过yum-config-manager命令修改生效的版本,可参考如下命令:

1
2
shell> sudo yum-config-manager --disable mysql80--community
shell> sudo yum-config-manager --enable mysql57-community

同使用yum-config-manager一样我们也可以通过手工修改/etc/yum.repos.d/mysql-community.repo文件,通过指定enable=0来禁用,enable=1启用来调整安装版本。

如果同事有多个版本被指定为有效,则yum会默认选择最新的版本进行安装。可通过下面命令查看将要安装的Mysql版本:

1
shell> yum repolist enabled | grep mysql

3. 安装Mysql

通过下面命令进行安装:

1
shell> sudo yum install mysql-community-server

该命令将安装Mysql服务以及,其依赖软件。

4. 启动Mysql服务

使用下面命令启动Mysql服务:

1
shell> sudo service mysqld start

对于基于EL7的系统,如Centos7,建议采用下面命令:

1
shell> sudo systemctl start mysqld.service

通过下面命令查看Mysql服务状态:

1
shell> sudo service mysqld status

对于基于EL7的系统,如Centos7,建议采用下面命令:

1
shell> sudo systemctl status mysqld.service

Mysql服务初始化过程大概经历以下过程

  • 服务初始化

  • 安全连接证书以及秘钥证书创建

  • 密码验证插件安装以及启用

  • 超级管理员‘root’@’localhost’创建,密码写在error日志中,使用下面的命令获取:

    1
    shell> sudo grep 'temporary password' /var/log/mysqld.log
  • 修改密码

    1
    2
    shell> mysql -uroot -p
    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!';

奇异值分解(singular value decomposition)是线性代数中一种重要的矩阵分解。


[TOC]

理论描述

摘自 维基百科-奇异值分解

假设M是一个 $m \times n$ 阶矩阵,其中的元素全部属于域 $k$ , 也就是实数域复数域。如此则存在一个分解使得

其中 $U$ 是 $m \times m$ 阶 酉矩阵 ;$\Sigma$ 是 $m \times n$ 阶非负实数矩阵;而 $V^$, 即 $V$ 的 共轭矩阵,是 $n \times n$ 阶 酉矩阵。这样的分解就称作M的 奇异值分解。$\Sigma$对角线上的的元素 $\Sigma_{i,j}$ 即为M的*奇异值

常见的做法是将奇异值由大到小排列。由此$\Sigma$便能有M唯一确定了。(虽然U、V仍然不能确定)。

直观的解释

  • V的列(columns)组成一套对M的正交”输入”或”分析”的基向量。这些向量是 M*M的特征向量。
  • U的列(columns)组成一套对M的正交”输出”的基向量。这些向量是MM*的特征向量。
  • $\Sigma$对角线上的元素是奇异值,可视为是在输入与输出间进行的标量的”膨胀控制”。这些是MM*以及M*M的特征值的非负平方根,并和U和V的行向量项对应。

转自 https://www.jianshu.com/p/c7e642877b0e

本文将从一个下山的场景开始,先提出梯度下降算法的基本思想,进而从数学上解释梯度下降算法的原理,最后实现一个简单的梯度下降算法的实例!

梯度下降的场景假设

梯度下降法的基本思想可以类比为一个下山的过程。假设这样一个场景:一个人被困在山上,需要从山上下来(i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就就可以利用特度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后没走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷。

我们同时可以假设这座山对陡峭的地方是无法通过肉眼立马观察出来的,而是需要一个复杂的工具来测量,同时,这个人此时正好用友测量出最陡峭方向的能力。所以,此人没走一段距离,都需要一段时间来测量所在位置最陡峭的方向,这是比较耗时的。那么为了在太阳下山之前到达山地,就要尽可能的减少测量方向的次数。这是一个两难的选择,如果测量的频繁,可以保证下山的方向是绝对正确的,但有非常耗时,如果测量的过好,又有偏离轨道的风险。所以需要找到一个合适的测量方向的频率,来确保下山方向不错误,同时又不至于耗时太多!

梯度下降

梯度下降的基本过程就和下山的场景类似。


首先,们有一个可微分的函数。这个函数就代表这一座山。我们的目标就是找到这个函数的最小值,也就是山底。治具之前的场景假设,最快的下山的方式就是找到当前位置最陡峭的方向,就能让函数值下降的最快!因此梯度的方向就是函数之变化最快的方向。

所以,我们重复利用这个方法,反复求去梯度,最后就能达到局部的最小值,这就类似于我们下山的过程。而求去梯度就确定了最陡峭的方向,也就是场景中测量方向的手段。那么为什么梯度的方向就是最陡峭的方向呢?接下来我们从微分开始讲起。

微分

看待微分的意义,可以有不同的角度,最常用的两种是:

  • 函数图像中,某点切线的斜率

  • 函数的变化率

    几个微分的例子:

上面的例子都是单变量的微分,当一个函数有多个变量的时候,就有了多变量的微分,即分别对每个变量进行求微分。

梯度

梯度时机上就是多变量微分的一般化。

下面这个例子:

我们可以看到,梯度就是分别对每个变量进行微分,然后用逗号分隔开,梯度是用<>包括起来的,说明梯度其实是一个向量。

梯度是微积分中一个很重要的概念,之前提到过梯度的意义

  • 在单变量的函数中,梯度其实就是函数的微分,代表着函数在某个给定的切线的斜率
  • 在多边量函数中,梯度是一个向量,向量有方向,梯度的方向就支出了函数在给定点上升最快的方向

这就说明了为什么我们要千方百计的求取梯度!我们需要到达山底,就需要在每一步观测到此时最陡峭的地方,梯度就恰巧告诉了我们这个方向,这正是我们所需要的。所以我们只要沿着梯度的方向一直走,就能走到局部最低点!

梯度下降算法的数学解释

上面我们花了大量篇幅介绍梯度下降算法的基本思想和场景假设,以及梯度的概念和思想。下面我们就开始从数学上解释梯度下降算法的计算过程和思想!

此公式的意义:

J是关于的一个函数,我们当前所处的位置为点,要从这个点走到的最小值点,也就是山底。首先我们先确定前进的方向,也就是梯度的反方向,然后走一段距离的步长,也就是,走完这个步长,就达到了这个点!

下面就这个公式的几个常见疑问:

  • 是什么?

    在梯度下降算法中被称为 学习率 或者 步长 ,意味着我们可以通过 来控制每一步走的距离,以保证不要步子跨的太大,而错过了最低点。同时也要保证不要走的太慢,导致太阳下山了,还没有走到山下。所以 的选择在梯度下降算法中往往很重要! 不能太大也不能太小,太大走不到最低点,太小可能迟迟走不到最低点。

  • 为什么要梯度横溢一个负号?

    梯度前加一个负号,就意味着朝着梯度相反的方向前进!我们在前文提到,梯度的方向时机就是函数在此点上升最快的方向!而我们需要朝着下降最快的方向走,自然就是负的梯度方向,所以此处需要加上负号

梯度下降算法的实例

我们已经基本了解梯度下降算法的计算过程,那么我们就来看几个梯度下降算法的小实例,首先从单变量的函数开始

单变量函数的梯度下降

我们假设一个单变量的函数:

​ 函数的微分:

​ 初始化起点为:

​ 学习率:

根据梯度下降的计算公式:

我们开始进行梯度下降的迭代计算过程:

如图,经过四次运算,也就是走了四步,基本就抵达了函数的最低点,也就是山底

多变量函数的梯度下降

我们假设有一个目标函数:

现在要通过梯度下降法计算这个函数的最小值,我们通过观察能发现最小值其实就是点。但是接下来我们会从梯度下降算法开始一步步计算到这个最小值!

我们假设初始化起点

初始化的学习率为:

函数的梯度为:

进行多次迭代:

​ …

​ …

​ …

我们发现已经基本靠近函数的最小值点:

梯度下降算法的实现(线性回归)

下面我们将用Python实现一个简单的梯度下降算法。场景是一个线性回归的例子;假设我们有一系列的点,如下图所示:

我们将用梯度下降算法来拟合出这条直线!

首先,我们需要定义一个代价函数,在这里我们选用 均方根代价函数

公式说明:

  • m是数据集中点的个数

  • 是一个常量,这样是为了求梯度的时候,二次方乘下来就和这里的 抵消了,自然就没有多余的常数系数,方便后续的计算,同时对结果不会有影响

  • 是数据中对应 的真实坐标值

  • $h$ 是我们的预测函数,根据每一个输入 $x_i$ ,根据 $\Theta$ 计算得到预测值 $y_i$ , 即:

我们可以根据代价函数看到,代价函数中的变量有练个,所以是一个多变量的梯度下降问题,秋姐出代价函数的梯度,也就是分别对两个变量进行微分

明确了代价函数和梯度,以及预测函数的形式,我们就可以写代码了。但是在这之前,需要说明一点,就是为了方便代码编写,我们会将所有的公式都转换成矩阵形式,python中计算矩阵是非常方便的,同事代码页会变的非常简洁。

为了转换为矩阵的的计算,我们观测到预测函数的形式

我们有两个变量,为了这个公式的矩阵化,我们可以给每个点x增加一维,这一维的值固定为1,这一维将乘到 上这样就方便我们统一矩阵化计算:

然后我们将代价函数和梯度转化向量成绩形式:

说明:

  • 为需要迭代求解的最优参数
  • 为已知的数据集
  • 为当前 参数下的预测值

Coding Time

首先,我们需要定义数据集合学习率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
import matplotlib.pyplot as plt

# Prepare data

# Size of the points dataset.
m = 20

X0 = np.ones((m, 1))
X1 = np.arange(1, m+1).reshape(m, 1)
X = np.hstack((X0, X1))

# Points y-coordinate
y = np.array([
3, 4, 5, 5, 2, 4, 7, 8, 11, 8, 12,
11, 13, 13, 16, 17, 18, 17, 19, 21
]).reshape(m, 1)

y = np.array([
3, 8, 5, 5, 2, 4, 7, 8, 11, 8, 12,
11, 13, 13, 16, 17, 6, 17, 19, 21
]).reshape(m, 1)

plt.scatter(X1, y, marker='o', s=50, c='g')
plt.show()
# The Learning Rate alpha.
alpha = 0.01

接下来我们定义梯度函数:

1
2
3
4
def gradient_function(theta, X, y):
'''Gradient of the function J definition.'''
diff = np.dot(X, theta) - y
return (1./m) * np.dot(np.transpose(X), diff)

最后就是算法的核心部分,梯度下降迭代计算

1
2
3
4
5
6
7
8
def gradient_descent(X, y, alpha):
'''Perform gradient descent.'''
theta = np.array([1, 1]).reshape(2, 1)
gradient = gradient_function(theta, X, y)
while not np.all(np.absolute(gradient) <= 1e-5):
theta = theta - alpha * gradient
gradient = gradient_function(theta, X, y)
return theta

当梯度小于1e-5时,说明已经进入了比较平滑的状态,类似于山谷的状态,这时候再继续迭代效果也不大了,所以这个时候可以退出循环!

进行方法调用:

1
2
3
4
5
6
7
optimal = gradient_descent(X, y, alpha)
print('optimal:', optimal)

plt.scatter(X1, y, marker='o', s=50, c='g')
plt.plot([0,20], [optimal[0,0], 20*optimal[1,0]+optimal[0,0]], c='blue')
print optimal[1,0],optimal[0,0]
plt.show()

最终可看到结果如下:

作者小结

​ 至此,我们就基本介绍完了梯度下降法的基本思想和算法流程,并且用python实现了一个简单的梯度下降算法拟合直线的案例!
最后,我们回到文章开头所提出的场景假设:
这个下山的人实际上就代表了反向传播算法,下山的路径其实就代表着算法中一直在寻找的参数Θ,山上当前点的最陡峭的方向实际上就是代价函数在这一点的梯度方向,场景中观测最陡峭方向所用的工具就是微分 。在下一次观测之前的时间就是有我们算法中的学习率α所定义的。
可以看到场景假设和梯度下降算法很好的完成了对应!

看过不少梯度下降文章,这一篇讲解的最系统直观,自己记录一下;感谢作者!

跨域资源共享CORS实战

CORS是一个W3C标准,全称“跨域资源共享”(Cross-origin resource sharing);CORS定义了一种浏览器和服务器可以交互以确定允许跨院请求是否安全的方式。CORS协议需要浏览器和服务器端同时支持。

CORS协议

W3C的CORS规范将跨域资源请求划分为两种类型,“简单请求”和“非简单请求”。

简单请求

定义

要弄清楚CORS规范将哪些类型的跨域资源请求划分为简单请求的范畴,需要额外理解几个名称的含义:

  • 简单(HTTP)方法

    CORS规范将 GETHEADPOST 这三个HTTP方法时为 简单HTTP方法

  • 简单(请求)报头(Simple Header)

    CORS规范将请求报头为 AcceptAccept-LanguageContent-Language 以及 Content-Type 为(application/x-www-form-urlencodedmultipart/form-datatext-plain )的请求报头称为 简单请求报头

  • 自定义请求报头(Author Request Header/Custom Request Header)

    请求报头包括两种,一种是通脱浏览器自动生成的报头,另外一种是由Javascript程序自动添加的报头(比如调用XMLHttpRequestsetRequestHeader方法可以添加任意报头),后者称为自定义报头

CORS规范将满足如下条件的的请求定义为简单请求:请求采用 简单HTTP方法 ,并且 自定义请求报头 为空,或者自定义报头简单请求报头

基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说就是在头信息中增加一个 Origin 字段。如:

1
2
3
4
5
6
GET /api/operator/v1/get1 HTTP/1.1
Origin: http://api.a.com
Host: api.b.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36

​ 上面的信息头中 Origin 字段用来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值决定是否同意这次请求。客户端根据返回请求判断相应头是否包含 Access-Control-Allow-Origin 字段来判断请求是否允许。这种错误无法通过状态码识别,因为HTTP回应的状态码可能是200。

1
2
3
4
Access-Control-Allow-Origin: http://api.a.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: myHeader
Content-Type: text/html; charset=utf-8

​ 上面的信息之中 Access-Control 打头的的属性是与CORS请求相关的。

  • Access-Control-Allow-Origin

    该字段是必须的。它的值要么是请求时 Origin 的值,要么是一个 *;且不支持通配符;

  • Access-Control-Allow-Credentials

    该字段可选;boolean值,表示是否允许发送Cookie,默认为false。设为True表名服务器明确许可,Cookie可以包含在请求中,一起发送给服务器。

    同时开发者也必须在AJAX请求中打开withCredentials属性,如:

    1
    2
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
  • Access-Control-Expose-Headers

    该字段可选。CORS请求是,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到6个基本字段(基本相应报文头):Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。 如果想拿到其他字段就必须在Access-Control-Expose-Headers里面指定。

非简单请求

对于不是简单请求的跨域请求,则被称为非简单请求;非简单请求的CORS请求,会在正式通信之前增加一次HTTP查询请求,称为“预检”请求。

预检请求

预检请求是一种OPTIONS类型的请求,请求服务器端接口访问权限。下面以访问接口 operator/get为例;

请求报文:

1
2
3
4
5
6
7
8
9
10
OPTIONS /api/operator/v1/get2 HTTP/1.1
Host: api.b.com
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://api.a.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

响应报文:

1
2
3
4
5
6
7
HTTP/1.1 204
Access-Control-Allow-Origin: http://api.a.com
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, OPTIONS
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With
Access-Control-Allow-Credentials: true
Date: Fri, 26 Oct 2018 10:16:31 GMT

​ 如果浏览器否定了“预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这是,浏览器就会认定服务器不同意预检请求,并因此触发一个错误。

​ 服务器相应的其他CORS相关字段:

  • Access-Control-Allow-Methods

    该字段必须;他是逗号分隔的一个字符串,表名服务器支持的所有跨域方法。注意,返回的是所有支持的方法;

  • Access-Control-Allow-Headers

    如果浏览器请求包括Access-Control-Request-Header ,则 Access-Control-Allow-Header字段是必须的。它也是逗号分隔的一个字符串;

  • Access-Control-Allow-Credentials

    与简单请求含义相同;

  • Access-Control-Max-Age

    该字段可选,用来指定背刺预检请求的有效期,单位为“秒”

基本流程

一旦服务器通过了“预检”请求,以后每次浏览器正常CORS请求,和简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。

服务器端实现CORS

WEB容器配置实现

NGINX配置实现CORS

通过Nginx反向代理,对Http请求进行拦截处理以实现服务器端跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
location / {
# 检查域名后缀
if ($http_origin ~ \.test\.com) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
add_header Access-Control-Max-Age 1728000;
}
# options请求不转给后端,直接返回204
# 第二个if会导致上面的add_header无效,这是nginx的问题,这里直接重复执行下
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
add_header Access-Control-Max-Age 1728000;
return 204;
}

# 其他请求代理到后端
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://xxx.xxx.xxx.xxx;
}
  • 优点:配置简单,实现便捷快速
  • 缺点:配置上稍显麻烦,安全验证上偏弱,不够灵活

SpringMVC 代码实现CORS跨域

Java Servlet Filter实现Cors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.my.web.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@WebFilter(urlPatterns = "/*")
@Slf4j
public class CrosFilter implements Filter{

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;

String origin = request.getHeader("Origin");
if (!StringUtils.isEmpty(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
}
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}

@Override
public void destroy() {

}
}
  • 优点:可以加入自定义逻辑,进行全局配置,也可以进行细微控制
  • 缺点:多个拦截器是需要注意拦截器的顺序

SpringMVC addCorsMappings实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.my.web.config;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.xiwei.marketing.web.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import javax.annotation.Resource;

@Component
public class WebMvcConfig extends WebMvcConfigurationSupport {

@Resource
private LoginInterceptor loginInterceptor;

@Value("${privilege.white.request.urls}")
private String whiteUrls;

@Value("${cross.origins}")
private String crossOrigins;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(Iterables.toArray(Splitter.on(',').split(whiteUrls), String.class));

}

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(Iterables.toArray(Splitter.on(',').split(crossOrigins), String.class))
.allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(86400);
}
}
  • 优点:配置优雅,简单
  • 缺点:如果程序中有使用拦截器对Cookie进行登录验证时,拦截器的殊勋会在CorsMapping之前,导致认证失败,在使用Cookie进行登录认证时采用了 Filter实现的方式。

@CrossOrigin注解

1
2
@RequestMapping
@CrossOrigin
  • 优点:可加入到每个具体上API方法上,实现简单
  • 缺点:通addCrosmappings全局配置

总结

在进行前后端分离的微服务架构中,在服务器端分别尝试了三种方法,由于需要用到浏览器Cookie,后两种在Cookie支持上没有很好的办法,最终从安全上,可配置上,考虑采用了使用拦截器的方式。

简易文件服务器设计与搭建

背景

​ 近期由于项目需要,分布式系统需要使用一个文件服务器,由于项目初期,出于成本,运维考虑,暂时排除掉DFS、TFS这种分布式文件服务器。

Nginx

​ Nginx是一个开源的WEB服务器,通过配置可以快速的实现文件服务器,所以这里考虑通过Nginx快速搭建一个文件服务器.

Nginx快速搭建文件服务器

这里假设Nginx已经安装完毕,可参考之前安装文章

  • 打开conf/nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    user  root;					# 修改用户为可以查看文件的用户
    worker_processes 1;
    pid logs/nginx.pid;

    events {
    worker_connections 1024;
    }

    http {
    include mime.types;
    default_type application/octet-stream;

    #tcp_nopush on;

    #keepalive_timeout 0;
    keepalive_timeout 65;

    #gzip on;
    server {
    listen 9999;
    server_name _; # 名称不坐限制
    location /images { # 匹配path
    root /mnt/bd/data/marketing; # 文件所在主机根目录
    autoindex on; # 开启文件索引
    autoindex_exact_size on; # 显示文件大小
    autoindex_localtime on; # 显示文件日期
    }
    }
    }

    保存退出,重启nginx

    1
    sbin/nginx -s reload

    此时可以登录浏览器查看http://myhome:9999/images查看文件列表

    注意:这里”location /images”不仅要求请求path包含/images 并且主机目录也要对应/mnt/bd/data/marketing/images否则会报404错误

    ​ 通过以上配置,可以快速完成文件服务器的搭建,但是,实际对外应用中,我们不可能将所有文件给所有用户访问,此时我们可以考虑如何为请求加上访问权限。

Nginx请求认证模块nginx-auth-request-module

Nginx模块添加

​ 克隆nginx-auth-request-module模块源码到服务器

1
git clone https://github.com/perusio/nginx-auth-request-module.git

​ 查看之前nginx的版本

1
2
3
4
5
6
7
nginx/sbin/nginx -V

nginx version: nginx/1.10.3
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --conf-path=/usr/local/nginx/conf/nginx.conf

​ 可以看到configure arguments,重新下载对应版本的nginx,解压,进入解压nginx目录执行

1
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --conf-path=/usr/local/nginx/conf/nginx.conf --add-module=path_To_nginx-auth-request-module

配置带有权限认证的文件服务器

  • 修改conf/nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    user  root;					# 修改用户为可以查看文件的用户
    worker_processes 1;
    pid logs/nginx.pid;

    events {
    worker_connections 1024;
    }

    http {
    include mime.types;
    default_type application/octet-stream;

    #tcp_nopush on;

    #keepalive_timeout 0;
    keepalive_timeout 65;

    #gzip on;
    server {
    listen 9999;
    server_name _; # 名称不坐限制
    location /images { # 匹配path
    auth_request /auth; # 添加认证请求uri ------------------------- 添加请求认证
    root /mnt/bd/data/marketing; # 文件所在主机根目录
    autoindex on; # 开启文件索引
    autoindex_exact_size on; # 显示文件大小
    autoindex_localtime on; # 显示文件日期
    }

    location /auth { # 认证URI重定向到具体认证服务 ----------------添加请求认证
    proxy_pass http://127.0.0.1:5000;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
    }
    }
    }

开发/auth认证服务

​ 略

Consul

what is consul ?

Consul是一种网络服务解决方案提供,提供了服务发现、配置、分段功能的全功能控制平台;其中每一个特性都可以单独使用,或者一起构建一个全方位的网络服务解决方案。

Consul的主要功能

  • 服务发现(Service Discorivy)
  • 健康检查(Health Checking)
  • Key-Value存储(KV-Store)
  • 服务间安全通信(Secure Service Communication)

基础架构

说明:其中每个数据中心(data center)需要有一组servers,servers用于存储数据

Consul集群搭建

集群规划

主机 功能
n1 Server
n2 Server
n3 Server
n4 Client

共四台主机,每台主机都是consul agent,其中n1、n2、n3为Cunsul集群Server,n4为普通client节点,官方建议Server节点数量为3-5个

安装准备

  • 为集群中每个节点(n1,n2,n3,n4)安装consul

    1
    2
    3
    4
    5
    wget https://releases.hashicorp.com/consul/1.2.3/consul_1.2.3_linux_amd64.zip
    unzip consul_1.2.3_linux_amd64.zip.zip
    # 解压后会发现只有一个 consul工具
    consul
    # 展示consul使用帮助
  • 集群中每个节点准备配置数据目录

    1
    2
    mkdir -p /etc/consul.d
    mkdir -p /tmp/consul

Consul Server配置启动

  • 登陆node1节点执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    consul agent -server -syslog \
    -ui \
    -client 0.0.0.0 \
    -bootstrap-expect=2 \
    -data-dir=/tmp/consul \
    -node=n1 \
    -bind=172.16.117.65 \
    -enable-script-checks=true \
    -config-dir=/etc/consul.d/conf \
    -pid-file=/etc/consul.d/consul.pid &

    参数说明

    server : 作为Consul集群server节点

    ui :该节点启动内置web-ui 默认地址 http://ip:8500/ui

    client : client绑定时访问的IP地址,配置维0.0.0.0,在外部浏览器才能访问到web ui

    bootstrap-expect : 期望启动后server的数量

    data-dir : 数据存储目录

    node : 节点名称,不指定时默认为主机名称

    bind : 绑定的IP地址,该参数最好指定,否则可能会使用系统IPV6地址

    enable-script-checks : 开启健康检查脚本

    config-dir : 配置存放目录

    pid-file : 启动后进程ID存放文件

  • 登陆node2节点执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    consul agent -server -syslog \
    -ui \
    -client 0.0.0.0 \
    -bootstrap-expect=2 \
    -data-dir=/tmp/consul \
    -node=n2 \
    -bind=172.16.117.64 \
    -enable-script-checks=true \
    -config-dir=/etc/consul.d/conf \
    -pid-file=/etc/consul.d/consul.pid
    -join 172.16.117.65 &

    参数说明:

    join :指定启动后加入65节点所在集群

  • 登陆node3节点node3配置与node2节点相同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    consul agent -server -syslog \
    -ui \
    -client 0.0.0.0 \
    -bootstrap-expect=2 \
    -data-dir=/tmp/consul \
    -node=n3 \
    -bind=172.16.117.63 \
    -enable-script-checks=true \
    -config-dir=/etc/consul.d/conf \
    -pid-file=/etc/consul.d/consul.pid
    -join 172.16.117.65 &
  • 登陆node4节点

    1
    2
    3
    4
    5
    6
    7
    8
    consul agent -client -syslog \
    -data-dir=/tmp/consul \
    -config-dir=/etc/consul.d/conf \
    -node=n4 \
    -bind=172.16.117.62 \
    -client=172.16.117.62 \
    -join=172.16.117.65 \
    -pid-file=/etc/consul.d/consul.pid &

    参数说明:

    client : 指定为client节点

集群检查

登陆任意节点

1
consul members

此时会发现成功建立起一个4个节点的集群

1
2
3
4
5
Node       Address             Status  Type    Build  Protocol  DC   Segment
n1 172.16.117.65:8301 alive server 1.2.3 2 dc1 <all>
n2 172.16.117.64:8301 alive server 1.2.3 2 dc1 <all>
n3 172.16.117.63:8301 alive server 1.2.3 2 dc1 <all>
n4 172.16.117.62:8301 alive client 1.2.3 2 dc1 <default>

总结

​ 本文还是粗略进行了Consul的探索,其中为后续Spring Cloud使用Consul作为配置中心进行的调研,发现Consul有很多丰富功能,对于集群管理、安全性、跨机房调用都有支持;其中功能还有待进一步学习。

章节概述(摘自 序-如何阅读本书)

第1-3章 scala核心语言特性

第4章 模式匹配

第5章 隐式详解

第6章 函数编程

第7章 scala对for循环的扩展

第8章 scala如何支持OOP

第9章 继续探索scala功能-使用trait

第10-13章 scala对象系统、集合库、课件行规则,详细讲解了对象模型和库类型

第14章 scala如何对java的public protected private可见性概念进行细粒度扩展

第15章 更高级的内容

第16章 高级函数式编程

第17章 并发工具

第18章 scala与大数据

第19章 scala动态调用

第20章 scala的领域特定语言

第21章 scala的工具和库

第22章 scala与java交互

第23章 应用程序设计

第24章 元编程 宏与反射

第一章:scala简介

整体介绍了,scala命令,scala与java进行对比;scala程序执行方式(scala 、sbt);第一个 hello world程序;scala的两个小程序展示了scala的lamba表达是,以及模式匹配,以及并发编程框架akka。

Scala 是 Scalable Language的缩写,意为可扩展的语言。

几个知识点:

  1. scala工程编译打包工具,执行sbt将进入一个REPL环境
  2. REPL = read eval print loop
  3. REPL中 :load src/main/scala/xxx/xxx/abc.sc 加载执行该文件
  4. scala .sc .scala 以及 scalac -Xscript scala用法
  5. “_” 通配符
  6. case类 自动生成 apply toString equals copy 等方法
  7. scala伴生类 (object and class)
  8. sbt用法 complile run run-main console 等 help 查看帮助
  9. 嵌套导入
  10. 偏函数 PartialFunction[Any,Unit]
  11. 插值字符串 s”abcd ${xxx}”
  12. scala方法名可以是符号,以及方法只接受单一参数时可以省略 “.” 和 括号语法糖
  13. 并发akka简单用法

第二章:更简洁、更强大

本章介绍了 Scala 编程的实践基础、如字面量、关键字,文件的组织与导入,整体还是承接第一章,对scala语法进行更全面的介绍。

知识点:

  1. 分号
  2. 变量的声明和初始化 var val
  3. Range的使用 to until by
  4. 偏函数 PartialFunction[Any, String…] 偏函数的“链式”连接 orElse 偏函数的 isDefineAt([Any]) 返回true false 避免抛出 MatchError
  5. 方法声明,方法默认值和参数列表,方法多参数列表的使用
  6. Future的使用 函数回调
  7. 嵌套方法定义与递归、参数作用域,为递归注解编译校验@tailrec
  8. 类型推断,以下情况需要显式声明类型:声明了可变、不可变的变量但未初始化;抽象声明;参数变量
  9. 数据类型以及 取值范围;字符串的 “”” “””
  10. 关键字、符号、元组
  11. Option、Some、None
  12. seald关键字告诉编译器,子类必须在同一个源文件中,可以防止派生,自定义子类
  13. 用户空间和命名代码,沿用java的包的方法,但是文件路径和包路径可以不一致
  14. scala导入的通配符是”_”而 java的是 “*”
  15. import可以放在任何位置,所以可以将其可见性限制在需要的作用域中,可以在导入时对类型做重命名
  16. 抽象类型和参数化类型

第三章:要点详解

本章从语法上对Scala进行了比较全面的介绍,包括操作符、符号优先级、流程控制(if while for)、trait、枚举、异常处理、日志等。

知识点:

  1. 操作符重载,scala中的操作符不是关键字,可以用来声明方法,scala的世界里java的基础类型 int、double、long等都变成了对象,因此可以拥有成员方法
  2. 语法糖 单一参数方法 可以省略掉前面的”.” 和后面的方法括号
  3. 无参方法可以灵活定义是否包含括号
  4. scala由于这些灵活的特性,特别适合用作领域特定语言(DSL),DSL目的是为了简介表达该领域的概念
  5. if、for、while、do while语法详解
  6. for推到中的guard for( x <- seq(1, 2, 3) if x > 1) yield
  7. for推倒中的Yielding yield关键字,for推倒会自动过滤NONE,生成新的集合
  8. catch 中的异常case语句以及 NonFatal

第四章 模式匹配

本章讲解了各种情况下的模式匹配,点单匹配、元组、对象、类型、正则表达式等,模式匹配是scala语言中重要的一个功能,可以极大简化代码量。 模式匹配会鼓励开发者多用getter setter暴露属性,

知识点:

  1. 除了偏函数所有的,所有的match语句必须是完全覆盖所有输入的。当输入类型为Any时,在结尾用case _ 或 case some_name作为默认语句。
  2. 类型匹配 case i:int => "int value" i
  3. case中的变量;参数中的变量def checkY(y: Int) = { for { x <- Seq(99,88,77)}{ val str = x match { case "y" …} 这里y需要加上”`”
  4. Nil 表示非空集合
  5. 序列的匹配:用模式和递归的方式进行匹配 顺序匹配 +: 逆序匹配 :+
  6. 序列匹配:unapplySeq 用法可以匹配序列前n个值 eg. case Seq(head1, head2, _*) => ...
  7. case中的guard eg. case a:Int if a > 5 : a+1
  8. zipWithIndex 时,返回的元组形式为 ((name,cost),index)
  9. 语法糖:包含两个类型参数的类型(类class),可以写成中缀表达式,如序列模式匹配中的”:+”
  10. 匹配可变参数 :name @ _ 定义可变参数 T
  11. 正则表达式匹配 定义正则表达式 val regx = '''Book: title=([^,]+),\s+author=(.+)’’’.r case regx(title, author) => s"$title $author"
  12. 对象匹配中同时想引用对象 case p @ Person("Tom”, 23, address) => …, 其中 p @ … 语法将将整个Person类的实例都赋值给了 p
  13. 泛型集合匹配 case a : Seq[Int] 错误语法 JVM字节码中不会记住一个泛型的实例;def doSeqMatch(seq : Seq[T]): String = seq match { case Nil => … \\\ case head +: _ => head match { case …} 可以进行case嵌套来判定集合中具体的类型
  14. 对于偏函数和全覆盖模式匹配(seald对象、枚举)可以没有默认匹配
  15. 变量声明也可以使用模式匹配val Person(name, age) = Person("TOM”, 12) ; val head +: tail = Seq(1, 2, 3, 4)
  16. for 循环中的模式匹配 如 for{ Some(breed) <- breeds; uppercaseBreed = breed.toUpperCase() } println(uppercaseBreed)

第五章 隐式详解

隐式implicit 是scala的一大特性,同时也是一个可能存在争议的特性。使用隐式能够减少代码,能够向现有的类中注入新的方法。另,隐式在最新版本中已经不推荐使用

知识点:

  1. 隐式参数
  2. 限定可用实例<: eg. def apply \[ R <: { def close():Unit }, T] (resource: => R)(f: R => T) = {…}
  3. 隐式证据 <:<[A, (U, T)] 或者 A <:< (U, T) eg. def toMap [T, U](implicit ev: <:<[A, (T, U)]): immutable.Map[T, U]
  4. 绕开类型擦除带来的限制 eg. def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit

附录:

尾递归:

【形式上】如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。

【本质上】递归函数自身的调用在函数最后,调起的新函数不依赖之前函数的上下文环境,因此很多编译器可以对尾函数进行优化,避免栈溢出的情况。

squid介绍

​ squid是支持HTTP,HTTPS,FTP等Web的缓存代理。它通过缓存和重复使用频繁请求的网页来降低带宽并缩短响应时间。Squid拥有广泛的访问控制,并且是一款出色的服务器加速器 它可以在大多数可用的操作系统上运行,包括Windows,并在GNU GPL下获得许可。

使用yum安装并配置squid

安装

1
2
yum install squid -y
yum install httpd-tools -y

生成密码文件

1
2
3
4
5
mkdir /etc/squid3/
# xiongneng 是用户名
htpasswd -cd /etc/squid3/passwords guest
# 提示输入密码,在这里我设的密码为 123456
# 注意密码不要超过8位

测试密码文件

1
2
3
4
5
6
7
/usr/lib64/squid/basic_ncsa_auth /etc/squid3/passwords
# 输入 用户名 密码
xiongneng 123456
# 提示OK说明成功,ERR是有问题,请检查一下之前步骤
OK

# 测试完成,crtl + c 打断

配置

1
2
3
4
5
6
7
8
9
10
11
12
vim /etc/squid/squid.conf

# 在最后添加

auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid3/passwords
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated

# 这里是端口号,可以按需修改
# http_port 3128 这样写会同时监听ipv6和ipv4的端口,推荐适应下面的配置方法。
http_port 0.0.0.0:3128

权限控制

​ squid有着丰富的权限控制,这里采用默认的略

启动服务

1
2
3
4
5
6
7
8
# 开启启动
systemctl enable squid.service
# 启动
systemctl start squid.service
# 停止
systemctl stop squid.service
# 重启
systemctl restart squid.service

代理服务器设置

​ 在其他CentOS机器上面配置各种代理方法

全局代理

vim /etc/profile,在最后加入

1
2
export http_proxy="http://username:password@proxy_ip:port"
export https_proxy="http://username:password@proxy_ip:port"

yum代理设置

​ 编辑/etc/yum.conf,在最后加入

1
2
# Proxy
proxy=http://username:password@proxy_ip:port/

wget代理设置

​ 编辑/etc/wgetrc,在最后加入

1
2
3
4
# Proxy
http_proxy=http://username:password@proxy_ip:port/
https_proxy=http://username:password@proxy_ip:port/
ftp_proxy=http://username:password@proxy_ip:port/

curl的代理设置

​ 在~/.bashrc里面增加一个别名:

1
alias curl="curl -x http://username:password@proxy_ip:port"

​ 另外一种方法是编辑~/.curlrc文件 (没有就创建一个):

1
proxy = http://username:password@proxy_ip:port

问题1

现象

​ CDH集群重启,重启 cloudera-scm-agentcloudera-scm-server h后,登陆CDH控制台重启 cloudera-management-service , 其中的 Activity Monitor、Service Monitor 、Host Monitor、Event Server、Alert Publisher 均启动失败,并且启动页面上日志显示 unknown 字样。

解决方案

​ 起初一直以为是CM服务的问题,少启动了什么服务,日志一直报 avro 服务连接不上,最终超时

​ 最终找到解决办法是 cloudera-scm-agent 重启的问题,cloudera-scm-agent 重启必须保证 supervised 进程杀死;即执行 cloudera-scm-agent stop 后需要 ps -ef |grep supervised 然后 kill -9 PID 最后启动 cloudera-scm-agent start

​ 之后再重启 zookeeper、cloudera-management-service 就OK了。

总结

​ 还需要对CDH集群更深入了解下。

问题二

现象

​ CDH集群(CM版本5.7.0),上安装spark2,出现yarn启动失败,其间会报一个 Role failed to start due to error com.cloudera.cmf.service.config.ConfigGenException: Conflicting yarn extensions of key [spark_shuffle]. 的异常,并伴随着 zookeeper carnary 检测失败;spark2一直不能安装成功。

解决方案

​ 起初一直以为是Spark2的问题因为CM版本的问题,但是仔细检查了spark2对应的CSD和parcel都是对应当前5.7版本的,网上查了些资料说是因为spark2 beta版本需要CM5.8以上版本等等;开始怀疑CM版本不行,差点想要升级CM版本;后面实在找不到对应CM 5.7安装Spark2的解决办法;后来开始注意到zookeeper的异常,想着不如先把这个问题解决调可能就好了,毕竟之前同事反馈过说启动Hbase时一直失败,报连不上zookeeper,说换了个节点就好了;后面开始着手解决zookeeper连接不上的问题,登陆到连接不上zookeeper的节点,使用zookeeperClient连接了下,发现很快报了一个close session的错误;后来了查这个问题才意识到是zookeeper连接数的问题,修改如下配置,

1
2
最大客户端连接数 
maxClientCnxns = 200

连接数由60调整为200;然后发现zookeeper报错果然没有了;

​ 紧接着重新尝试了安装Spark2服务,Spark2服务也安装成功了… ^_^

总结

​ 该集群是我们工作中用的测试集群;升级Spark2这个问题自己对CDH集群维护上增加了一些经验;再次意识到不能放过任何一个报错、警告;在自己平时开发中也好,用开源工程也好,不能放过每一个错误警告。