Oct 11, 2018
基础
#
特点
commonJS规范
javascript书写(v8引擎)
js设计之初就可以运行在后端
v8
成熟的事件驱动模式
没有i/o库, 没有历史包袱
v8性能好
单线程
不用在意多线程状态同步(没有死锁, 没有上下文切换)
无法利用多核, 错误时应用退出,计算密集时无法调度 # child_process解决
事件驱动(event-driven), 回调
event loop
[while(true)] -> watcher -> handles
watcher产生事件后, event loop取到并执行其handle(回调函数)
event loop每一周询问多个watcher是否有事件
event loop中没有watcher时进程退出
http模块就是启动了一个watcher,所以执行后进程不结束
其它watcher有 timer, fs, udp/req, process
不同操作系统中event driven的实现:
windows: IOCP
Linux: epoll
Mac:kqueue
非阻塞io(non-blocking i/o model)
io与数据处理分离(所以必须异步)
线程池结合event-driven实现
异步io
go语言有协程(coroutine)而node.js没有,协程可以同步式编程
# 有第三方协程模块
promise(commonJs的规范, 其实现有whenJs, Q)
# 书写难度降低
eventProxy # 朴灵
async/step
commonJS
模块
var math = require('math') # 缓存优先,核心模块优先。依次找.js, .node, .json
exports.add = function(){}
二进制
Buffer
字符集编码
I/O流
进程环境
文件
套接字
单元测试
web网关
包管理
package.json
bin
lib
doc
test
实现技术
libev的windows与linux接口实现
c++扩展
事件循环 # 生产者消费者模型
执行一次称为Tick
询问观察者(文件、网络等)是否有待处理事件, 有关联回调执行回调
# 观察者先idle, 再io, 再check
通过请求对象实现,绑定回调、运行参数、执行标志
层次
javascript
v8
node
libuv
*nix/ windows # 分别编译
应用
I/O密集服务
cpu密集用c/c++扩展,用子进程
工具
#
node --v8-options | grep harmony
# 查看支持的es6特性
npm
介绍
cnpm是一个alibaba开发维护的,提供私有npm注册服务
-v # 版本
install # 安装,会执行package.json中scripts的勾子命令
-g
uninstall
config
list # 查看项目的默认设置。registry属性指向npm官方资源位置
set registry http://192.168.1.20:7001
# 设置源
test # package.json中scripts的test
o-> 搭建cnpm服务器
git clone https://github.com/cnpm/cnpmjs.org.git
cd cnpmjs.org
npm install npm -g
# 升级npm的版本
npm install
创建mysql数据库,并在config/index.js中修改mysql数据库的用户名和密码
config/index.js中注释bindingHost来对外网开放
node --harmony_generators dispatch.js
# 启动了两个端口, 7001用于npm注册服务, 7002用于web访问
o-> 使用私有库
npm install ape-algorithm --registry=http://192.168.1.20:7001
# 如果私有库中没有,cnpm会到npm中同步一个到cnpm, 再传给客户端一份
设置
~/.npmrc
registry=http://192.168.1.20:7001
# 淘宝翻墙库 https://registry.npm.taobao.org/
n
node-gyp # 编译c/c++模块
nvm
cnpm
介绍
cnpm是一个alibaba开发维护的,提供私有npm注册服务
安装
npm install cnpm
# 可以像使用npm一样使用
cnpm sync gulp
# npm 中发布的包在cnpm中有延时,可以用这个命令来手动同步
搭建cnpm服务器
git clone https://github.com/cnpm/cnpmjs.org.git
cd cnpmjs.org
npm install npm -g
# 升级npm的版本
npm install
创建mysql数据库,并在config/index.js中修改mysql数据库的用户名和密码
config/index.js中注释bindingHost来对外网开放
node --harmony_generators dispatch.js
# 启动了两个端口, 7001用于npm注册服务, 7002用于web访问
使用私有库
npm install ape-algorithm --registry=http://192.168.1.20:7001
# 如果私有库中没有,cnpm会到npm中同步一个到cnpm, 再传给客户端一份
项目设置私有库
npm config list
# 查看项目的默认设置。registry属性指向npm官方资源位置
npm config set registry http://192.168.1.20:7001
用户设置私有库
// ~/.npmrc
registry=http://192.168.1.20:7001
# 另外,淘宝翻墙库 https://registry.npm.taobao.org/
调试
o-> 代码中插入断点
debugger;
o-> 以debug模式运行
# debug模式运行时, help查看可用命令
node debug app.js
配置
#
package.json
name # 包名
description # 简介
version # 版本
keywords # 搜索关键词
maintainers # 维护者
contributors # 代码贡献者
bugs # 反馈bug的地址
licenses # 许可证
repositories # 托管代码地址
dependencies # 依赖包
homepage # 该包网站
os # 操作系统支持列表
cpu # cpu架构支持列表
engine # 支持的js引擎, 如ejs
builtin # 内建在底层系统的哪些组件上
directories # 目录说明
implements # 实现了commonJS哪些规范
scripts # 脚本命令
preinstall
install
uninstall
test
author # 包作者
bin # 全局安装时,命令安装的位置
main # require()包时入口,默认找index
devDependencies # 开发时需要的依赖
o->
{
"name": "test",
"version": "0.1.0",
"keywords": ["a", "b"], # npm search时用
"description": "A testing package",
"os": ["linux", "darwin"],
"author": "outrun<outrun@mail.com>",
"dependencies": {
"express": "^1.0.0",
"redis": ">= 0.6.7"
},
"devDependencies": {
"grunt": "^0.4.5"
},
"main": "index",
"bin": {
"test": "./bin/test.js"
},
"scripts": {
"start": "node server.js",
"test": "vows test/*.js", # "grunt test" "mocha test" "make test" "make test-all"
"preinstall": "./configure",
"install": "make && make install",
"uninstall": ""
},
"engines": {
"node": "5.0.0"
}
}
api
#
异步api
#
I/O操作api
setTimeout() setInterval()
定时器插入观察者的红黑树中, tick时迭代定时器,如果超时就形成事件
# 如果前一个tick耗时大,定时会拖后
# 比较浪费性能
process.nextTick()
回调放入队列,下tick全部执行, 位于idle观察者
setImmediate()
回调放入链表, 每tick执行一个,位于check观察者,晚于idle
宿主对象
#
global #全局对象
root # 指向自己
BLOBAL # 指向自己
setTimeout()
setInterval()
clearTimeout()
clearInterval()
process #当前进程
argv # 获得命令行参数数组
title # node
version # v0.12.2
事件
process.on("uncaughtException",function(e){
console.log("error:"+e);
});
buffer
特点
node中buffer不属于v8, 使用c++扩展编写。所以可以使用高于1.4g的内存
一个元素一字节
8kb之内为小对象,slab机制分配内存, 先申请后分配。大于8kb的创建SlowBuffer对象
pool的实现方式
字符编码类型
# 默认编码UTF-8, 一个buffer只能有一个编码
ascii
utf-8
utf-16le/ucs-2
base64
binary
hex
length
write()
# write(str, [offset], [length], [encoding])
toString()
# toString([encoding], [start], [end])
isEncoding()
# 指定编码是否支持转换
copy()
# 复制自身到另一个buffer的某位置
buf.copy(buffer, 0)
concat() #静态方法
concat(chunks, size)
# chunks中为buffer数组, size为总大小
使用
new Buffer(100)
new Buffer('a', 'UTF-8')
console #控制台
log('',obj)
console.log('[%s] listening on http://127.0.0.1:%d', app.setting.env, port)
module
Module.exports真正的接口,导出的是一个类型
exports是Module.exports的包装,导出的是Object类型的对象
promise # 0.11.x后加入的全局对象
使用
# 复制
var jadeTemplate = new Promise(function(resolve, reject) {
fs.readFile(path.join(__dirname, 'views/article.jade'), function(err, data) {
if (err) {
reject(err.message);
} else {
resolve(data.toString());
}
});
});
var localData = new Promise(function(resolve, reject) {
fs.readFile(path.join(__dirname, 'static/shuffle.json'), function(err, data) {
if (err) {
reject(err.message);
} else {
resolve(JSON.parse(data.toString()));
}
});
});
Promise
.all([jadeTemplate, localData])
.then(function(value) {
console.log(jade.compile(value[0])(value[1]));
});
内置lib
#
http
#
介绍
继承自net模块
EventEmitter实例
事件
服务端
connection
request
close
checkContinue
# 发送较大数据时,先发送Expect: 100-continue请求头,此时触发
connect
upgrade
# 要求升级连接协议时
clientError
# 客户端触发error事件时触发
客户端
response
socket
# 建立连接时触发
connect
# 响应200时触发
upgrade
continue
globalAgent # 重用http长连接,实际是个连接池,默认有5个并发
sockets
# 连接数
requests
# 处于等待状态的请求数
createServer(onRequest).listen(8888, func); # 创建服务器并启动
request
req.setEncoding("utf8")
var postData = "";
req.addListener("data", function(postDataChunk){
postData += postDataChunk;
});
req.addListener("end", function(){
route(handle, pathname, res, postData);
}); # 拼接post请求数据
req.rawBody
# post来的原生数据
req.destroy()
# 放弃请求,停止招收
response
res.setHeader('WWW-Authenticate', 'Basic realm=\"Tomcat Manager Application\"')
res.writeHead(200, {"Content-Type": "text/html"});
# 调用setHeader多次,调用writeHead后才写入
res.write("");
res.write(file,?"binary");?
res.end();
request(options, fn) # 发起客户端请求
# options中有 host, hostname, port, localAddress(使用本地的哪个网卡), socketPath(本地套接字文件路径), method, path, headers, auth(被计算成请求头的Authorization部分), agent(并发连接数,默认5)
https
#
介绍
nodejs
申请ca证书
访问端口为443
使用
express -e nodejs-https
cd nodejs-https && npm install
git --version
openssl version -a
openssl genrsa -out privatekey.pem 1024
# 生成证书文件
openssl req -new -key privatekey.pem -out certrequest.csr
# 通过私钥生成CSR证书签名
openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
# 通过私钥和证书签名生成证书文件
## 这时生成了三个文件: certificate.pem, certrequest.csr, privatekey.pem
### 分别是: 证书文件, CSR证书签名, 私钥
## 由于证书是自己创建的,没有经过第三方机构验证,用户访问时会出现警告提示
服务器
var https = require('https')
, fs = require('fs');
var options = {
key: fs.readFileSync('./privatekey.pem'),
cert: fs.readFileSync('./certificate.pem')
};
https.createServer(options, app).listen(3011, function(){
console.log('Https server listening on port: ' + 3011);
});
客户端
var options = {
hostname: 'localhost',
port: 8000,
path: '/',
method: 'GET',
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
# 设置rejectUnauthorized: false 来忽略ca验证
}
options.agent = new https.Agent(options)
var req = https.request(options, function (res) {
res.setEncoding('utf-8')
res.on('data', function (d) {
console.log(d)
})
})
req.end()
req.on('error', function(e){
console.log(e)
})
net
#
介绍
处理tcp请求
socket是EventEmitter的Stream实例
注意
默认开启Nagle, 会合并小数据成一个数据包延迟发送
socket.setNoDelay(true)关闭Nagle
并不是每次write都触发data事件, 关掉Nagle后,可能接收到多个小数据包后触发一次data
服务器事件
listening
connection
close
error
连接事件
data
end
connect
drain
# 任意一端调用write时触发
error
close
timeout
o-> 基本服务
# telnet来测试
var net = require('net')
var server = net.createServer(function (socket) {
socket.on('data', function (data) {
socket.write('a')
})
socket.on('end', function () {
console.log('disconnected.')
})
socket.write('welcome')
})
server.listen(8124, function () {
console.log('server bound')
})
o-> 基本服务2
var server = net.createServer()
server.on('connection', function (socket) {})
server.listen(8124)
o-> 监听
server.listen('/tmp/echo.sock')
# nc -U /tmp/echo.sock 来测试
o-> 客户端
var client = net.connect({port: 8124}, function () {
console.log('client connected')
client.write('a')
})
client.on('data', function (data) {
console.log(data.toString())
client.end()
})
client.on('end', function() {
console.log('disconnected.')
})
o-> 客户端
var client = net.connect({path: '/tmp/echo.sock'})
o-> 管道
var server = net.createServer(function (socket) {
socket.write('a')
socket.pipe(socket)
})
dgram
#
介绍
处理udp
socket是EventEmitter实例
o-> 服务
var dgram = require('dgram')
var server = dgram.createSocket('udp4')
server.on('message', function (msg, rinfo) {
console.log(msg + 'from' + rinfo.address + ':' +)
})
server.on('listening', function () {
var address = server.address()
console.log('listening ' + address.address + ':' + address.port)
})
server.bind(41234)
o-> 客户端
var message = new Buffer('a')
var client = dgram.createSocket('udp4')
client.send(message, 0, message.length, 41234, 'localhost', function (err, bytes) {
client.close()
})
events
#
介绍
几乎所有对象的父类
使用
var events = require('events')
, util = require('util');
function Obj(){events.EventEmitter.cal(this);}
util.inherits(Obj, events.EventEmitter);
# Obj.prototype.__proto__ = events.EventEmitter.prototype;
Obj.prototype.write = function (data) {this.emit('data', data);};
var obj = new Obj();
obj.on('data', function (data) {console.log('Received data', data);})
# obj.once
obj.write('hello');
setMaxListeners(0)
# 侦听器过多不警告
path
#
os
#
方法
totalmem
freemem
fs
#
fs.readFile("tmp/test.png", "binary", function(error, file){
});
fs.writeFile('target.png', 'binary', function(err){
})
fs.exists(filePath, function(exists){
if(exists){}
})
fs.unlink(filePath, function(err){
})
fs.renameSync(files.upload.path,?"/tmp/test.png"); # 写入文件(阻塞)
o-> 流读写
var reader = fs.createReadStream('in.txt')
# 第二个参数为设置, highWaterMark: 每次读取的size, encoding: 编码
var writer = fs.createWriteStream('out.txt')
reader.on('data', function (chunk) {
writer.write(chunk)
})
reader.on('end', function() {
writer.end()
})
var reader = fs.createReadStream('in.txt')
var writer = fs.createWriteStream('out.txt')
reader.pipe(writer)
sys
#
process
#
argv
# 启动时参数
pid
# 当前进程的pid
once()
once('SIGINT', function () {})
# ctrl + c
memoryUsage()
# 查看v8内存使用量
# 其中rrs是resident set size, 是常驻内存的部分,其他在swap或文件系统中
kill()
# process.kill(pid[, signal])
on()
# 事件触发
o->
process.on('SIGTERM', function () {
console.log('Got a SIGTERM, exiting...')
process.exit(1)
})
o->
process.on('uncaughtException', function () {
logger.error(err)
process.send({act: 'suicide'})
# 向主进程发送信号
worker.close(function () {
process.exit(1)
})
setTimeout(function () {
# 长连接断开需要时间较久, 超时自动退出
process.exit(1)
}, 5000)
})
module
#
exports
parent
stream
#
介绍
继承EventEmitter, 处理文件之类的流
tls
#
介绍
建立在tls/ssl上的加密tcp
使用openssl来构建证书和测试
o-> 服务器
var tls = require('tls')
var fs = require('fs')
var options = {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
requestCert: true,
ca: [fs.readFileSync('./keys/ca.crt')]
}
var server = tls.createServer(options, function (stream) {
console.log('server connected', stream.authorized ? 'authorized' : 'unauthorized')
stream.write('welcome!\n')
stream.setEncoding('utf8')
stream.pipe(stream)
})
server.listen(8000, function () {
console.log('server bound')
})
o-> 客户端
var options = {
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
}
var stream = tls.connect(8000, options, function () {
console.log('client connected', stream.authorized ? 'authorized' : 'unauthorized')
# 证书是否通过
process.stdin.pipe(stream)
})
stream.setEncoding('utf8')
stream.on('data', function (data) {
console.log(data)
})
stream.on('end', function () {
server.close()
})
child_process
#
介绍
可以创建新的node进程
spawn(command[, args][a options])
# command执行的命令
# args参数列表
options 环境变量对象, 包括7个属性
cwd 子进程当前工作目录
env 环境变量键值对
stdio 子进程stdio配置
customFds 子进程stdio使用的文件标示符
detached 进程组的主控制
uid 用户进程id
进程组id
var du = child.spawn('du', ['-sh', '/disk1']);
du.stdout.on('data', function(data){})
du.stderr.on('data', function(data){})
du.on('exit', function(code){})
exec('')
# 对spawn的友好封装, 增加了shell命令解析
child.exec('cat *.js | ws', function(error, stdout, stderr){})
execFile(command[, args])
# 执行可执行文件,不解析args,防止了exec参数注入的风险
child.execFile('/bin/ls', ['-l', '.'], function(err, result){})
fork()
# 同spawn, 但建立ipc(进程通信, inter-process communication)
var n = child.fork('./son.js');
n.on('message', function(){
console.log('Main listen: ', m);
});
n.send({hello: 'i am parent'});
// son.js
process.on('message', function(m){
console.log('Son listen: ', m);
});
process.send({hello: 'i am child'});
子进程对象
send()
# 发送消息和句柄,句柄可以是
## net.Socket, net.Server, net.Native(c++层面的tcp套接字或IPC管道), dgram.Socket, dgram.Native
kill()
# 向子进程发送SIGTERM信号
事件
message
error
exit
close
disconnect
o-> cpu核数worker
o-> master.js
var fork = require('child_process').fork
var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++){
fork('./worker.js')
}
o-> worker.js
var http = require('http')
http.createServer(function(req, res){...}).listen(Math.round((1+Math.random()) * 1000), '127.0.0.1')
o-> spawn
var spawn = require('child_process').spawn
free = spawn('free', ['-m'])
free.stdout.on('data', function (data) {})
free.stderr.on('data', function (data) {})
free.on('exit', function (code, signal) {})
o-> fork
# 需要至少30ms, 10M启动一个v8实例
var fork = require('child_process').fork
var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++) {
fork('./worker.js')
}
o-> 通信
# 只有子进程是node进程时才可以通信
var cp = require('child_process')
var n = cp.fork(__dirname + '/sub.js')
n.on('message', function (m) {
console.log('PARENT got message: ', m)
})
n.send({a: 1})
process.on('message', function (m) {
console.log('CHILD got message:', m)
})
process.send({b: 2})
o-> 句柄通信
# 节省了代理建立socket浪费的文件描述符
var child = require('child_process').fork('child.js')
var server = require('net').createServer()
server.on('connection', function (socket) {
socket.end('handled by parent \n')
})
server.listen(1337, function () {
child.send('server', server)
})
// child.js
process.on('message', function (m, server) {
if (m === 'server') {
server.on('connection', function (socket) {
socket.end('handled by child \n')
})
}
})
o-> 句柄负载http
# 对描述符是抢占式的
var cp = require('child_process')
var child1 = cp.fork('child.js')
var child2 = cp.fork('child.js')
var server = require('net').createServer()
server.listen(1337, function () {
child1.send('server', server)
child2.send('server', server)
server.close()
})
// child.js
var http = require('http')
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'})
res.end('handled by child, pid is ' + process.pid + '\n')
})
process.on('message', function (m, tcp) {
if (m === 'server') {
tcp.on('connection', function (socket) {
server.emit('connection', socket)
})
}
})
cluster
#
介绍
child_process和net模块的组合
cluster启动时,内部启动tcp服务器
fork()时,将tcp服务器socket文件描述符发给worker, 实现共享端口
isWorker
判断process.env是否值为NODE_UNIQUE_ID
isMaster
判断cluster.isWorker
事件
fork # fork时
online # 工作进程创建好后
listening # 工作进程调listen()后
disconnect # 主进程和工作进程IPC通道断开后
exit # 所有工作进程退出后
setup # cluster.setupMaster()执行后
o-> cpu核数worker
var cluster = require('cluster')
cluster.setupMaster({
exec: "worker.js"
})
var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++) {
cluster.fork()
}
domain
#
介绍
用于异步异常捕获
绑定方式
隐式绑定: 把domain上下文中定义的变量,自动绑定到domain对象
显式绑定: 把不是domain上下文中定义的变量,以代码的方式绑定到domain对象
members # 已加入domain对象的域定时器和事件发射器的数组
create() # 返回一个domain对象
run(fn) # 在domain上下文中执行一个函数,并隐式绑定所有事件、定时器和低级请求
add(emitter) # 显式的增加事件
remove(emitter) # 删除事件
bind(callback) # 以return为封闭callback函数
intercept(callback) # 同bind, 但返回第一个参数
enter() # 进入一个异步调用的上下文,绑定到domain
exit() # 退出当前的domain, 切换到不同的链的异步调用的上下文中,对应domain.enter()
dispose() # 释放一个domain对象,让node进程回收这部分资源
使用
var domain = require('domain');
function async_error(){
setTimeout(function(){
var r = Math.random() * 10;
console.log('random num is ' + r);
if(r > 5)
throw new Error('Error: random num ' + r + ' > 5');
}, 10);
}
var d = domain.create();
d.on('error', function(err){
console.log(err);
});
setInterval(function(){
d.run(async_err);
}, 1000);
未绑定不捕获
代码
var domain = require('domain');
var EventEmitter = require('events').EventEmitter;
var e = new EventEmitter();
var timer = setTimeout(function(){
e.emit('data');
}, 10);
function next(){
e.once('data', function(){
throw new Error('Receive data error!');
});
}
var d = domain.create();
d.on('error', function(err){
console.log(err);
});
d.run(next);
原因
timer和e两个关键对象在初始化时都没有在domain范围内。当next函数中抛出异常时, 没有处于domain的包裹中
修改
...
d.add(e);
d.add(timer);
d.run(next);
外部lib
#
url
#
parse()
pathname
url.parse(req.url)
url.parse(req.url, true) 会parse出query对象
querystring
#
parse()
querystring.parse(url.parse(req.url).query)
crypto
#
介绍
加密并生成各种散列
利用openssl库来实现,提供openssl中一系列哈希方法,包括hmac, cipher, decipher, 签名和验证等方法的封装
使用
var crypto = require('crypto');
console.log(crypto.getHashes()); # 打印支持的所有hasp算法
node-gyp
#
编译C++模块的编译工具
util
#
使用
var util = require('util');
方法
inherits(Sub, Base) # 对象间原型继承,Sub 仅继承Base原型中定义 的函数
inspect(obj) # 任意对象转换为字符串
log(string) # 带时间戳的log
format('%s:%s', 'a', 'b', 'c') // 'a:b c'
# format('%s:%s', 'a') // 'a:%s'
# format(1, 2, 3) // '1 2 3'
is系列
isArray(obj)
isRegExp(obj)
isDate(obj)
isError(obj)
isBoolean(obj)
isNull(obj)
isNullOrUndefined(obj)
isNumber(obj)
isString(obj)
isSymbol(obj)
isUndefined(obj)
isObject(obj)
isFunction(obj)
isPrimitive(obj)
# 是否基本类型
isBuffer(obj)
deprecate(foo, 'foo() is deprecated, use bar() instead');
# 标记为过时, 调用foo()时显示后面的话
zlib
#
介绍
提供压缩方法,如gzip
全局属性
#
介绍
并非挂在global下的属性,但可以直接使用
__dirname
# 在任何模块内获取当前模块文件的绝对路径
__filename
# 当前在执行的js文件路径
方案
#
异常捕获
process.on("uncaughtException",function(e){
logger.error("error:"+e);
});
process.on('unhandledRejection', function (err, p) {
console.error(err.stack)
});
Oct 11, 2018
亿级流量
#
流量接入层
#
二级域名
泛域名
A记录
dns解析
udp
向网关请求dns解析
httpDNS
用ip请求http服务, 返回域名解析的ip
因为用ip请求,适合app,不适合网页
lvs + keepalive # 多lvs时用dns负载
nginx
openresty
kong
动静分离
cdn
dns动态域名解析
cdn分发服务
源服务拉取FastDFS
CDN节点分发
WEB服务层
#
webflux
不基于重量的servlet标准
基于netty
Eureka
#
使用
@EnableEurekaServer
application.properties
eureka.client.register-with-eureka=false # 是否注册自己
eureka.client.fetch-registry=false # 是否拉取eureka
eureka.client.service-url.defaultZone=http://localhost:7900/eureka/ # 设置注册中心的URL
eureka.instance.hostname=euk1.com
spring.application.name=EurekeServer # eureka集群中各节点要同名
行为
register # 注册
renew # 通过心跳, 默认30s。三次失败删除实例
fetch registry # 拉注册的信息
cancel # 发取消请求,删除实例
time lag # 同步时间延迟
communication mechanism # 通讯机制,默认jersey和jackson
功能
唯一标识 # service id
主机名:application.name:端口
提供RestAPI, 可多终端接入
问题
一致性问题方案
Eureka间不同步,client向多个Eureka提交
Enreka间同步,Eureka强可用性弱一致性
基础
#
介绍
spring boot基础上构建,快速构建分布式系统, 全家桶
面向云环境架构(云原生) # 适合在docker和paas部署
功能
配置管理
服务发现
熔断
智能路由
微代理
控制总线
全局锁
决策竞选
分布式会话
集群状态管理
子项目
spring cloud netflix # 对netflix oss套件整合
eureka # 服务治理(注册、发现)
hystrix # 容错管理
ribbon # 软负载均衡(客户端)
feign # 基于hystrix和ribbon,服务调用组件
zuul # 网关,智能路由、访问过滤
archaius # 外部化配置
基础
spring cloud starters # 基础依赖, 高版本取消
spring cloud commons
服务
spring cloud consul # 封装consul(服务发现与配置, 与docker无缝)
spring cloud cluster # 抽象zookeeper, redis, hazelcast, consul的选举算法和通用状态模式实现接口
spring cloud cloudfoundry # 与pivotal cloudfoundry整合
spring cloud aws # 整合aws
spring cloud zookeeper # 整合zookeeper
spring cloud cli # groovy中快速创建应用
spring cloud task # 任务
配置
spring cloud config # 应用配置外部化, 推送客户端配置, 支持git存储
消息
spring cloud bus # 消息总线,传播集群状态变化来触发动作,如刷新配置
spring cloud stream # 声明式发送、接收消息
监控
spring cloud sleuth # 跟踪
安全
spring cloud security # 应用安全控制, zuul代理中OAuth2中继器
测试
spring cloud contract # 契约测试, 可用groovy和yaml定义
版本
用命名不用版本号,因为有多子项目版本,易混淆
命名用伦敦地铁站用,字母表排序
缺点
难于追查框架问题
非二进制通信协议
适合中小团队
配置
#
pom.xml
<modules>
<module>spring-cloud-common</module>
<module>spring-cloud-provider-book</module>
<module>spring-cloud-service-discovery</module>
<module>spring-cloud-api-gateway</module>
<module>spring-cloud-consumer-book</module>
<module>spring-cloud-monitor-dashboard</module>
<module>spring-cloud-aggregator</module>
<module>spring-cloud-zipkin-server</module>
<module>spring-cloud-admin-server</module>
<module>spring-cloud-config-server</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
application.yml
spring:
profiles: peer1 # bean的逻辑组
组件
#
dependencyManagement
spring-cloud-dependencies
spring cloud spring boot
#
spring cloud eureka
#
原理
生产者向eureka注册, 周期发送心跳(默认30s)
eureka服务间同步注册信息
消费者请求地址,缓存本地
eurake接收生产者心跳超时, 设置为down, 推送状态到消费者
eurake短期down过多生产者,进入自我保护不再down
组件
spring-cloud-starter-[netflix-]eureka-server
application.yml
eureka:
instance:
hostname: localhost # 实例主机名
client:
registerWithEureka: false # 当前服务不注册
fetchRegistry: false # 不获取注册信息
serviceUrl: # server地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
bootstrap.yml # 基础配置, 待拉取config
注解
@EnableEurekaServer # 修饰Application类
spring cloud ribbon
#
application.yml
ribbon:
eureka:
enabled: false # 禁止从eureka获得注册列表
spring cloud hystrix
#
# 单个服务监控
hystrix dashboard
路径
/hystrix
/hystrix.stream # 至少请求一次接口,才有数据
spring cloud turbine
#
# 多服务监控
application.yml
turbine:
aggregator:
clusterConfig: default # 此监控器名
appConfig: erp-consumer-metadb, erp-consumer # 目标服务名
clusterNameExpression: new String("default") # 名称匹配表达式
路径
/turbine.stream
spring cloud feign
#
application.yml
feign:
hystrix:
enabled: true
spring cloud zuul
#
# 默认会用ribbon负载均衡
application.yml
zuul:
prefix: /v1
routes:
hiapi:
path: /hiapi/**
serviceId: erp-consumer-metadb
# url: http://localhost:8762 # 这样写不会做负载均衡
# serviceId: hiapi-v1
## 手动url负载均衡
# ribbon:
# eureka:
# enabled: false
# hiapi-v1:
# ribbon:
# listOfServers: http://localhost:8762,http://localhost:8763
案例
过滤
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter("token");
if (accessToken == null) {
log.warn("token is empty");
//
// ctx.setSendZuulResponse(false);
// ctx.setResponseStatusCode(401);
// try {
// ctx.getResponse().getWriter().write("token is empty");
// }catch (Exception e){
//
// }
return null;
}
log.info("ok");
return null;
}
}
熔断
@Component
public class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("error, I'm the fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
spring cloud config
#
config-server
application.yml
server:
port: 9012
spring:
application:
name: erp-config-server
cloud:
config:
server:
native:
search-locations: classpath:/shared # 读取路径
profiles:
active: native # 本地读取
shared/config-client-dev.yml # 文件名为 [客户端服务名]-[profile变量]
server:
port: 9013
foo: foo version 1
地址
localhost:9012/config-client/dev # 查看分发给服务的配置
config-client
spring:
application:
name: erp-config-client
cloud:
config:
uri: http://localhost:9012
fail-fast: true
profiles:
active: dev
注解
@RefreshScope # 热更新
spring cloud bus
#
application.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: outrun
password: asdf
publisher-confirms: true
virtual-host: /
management:
security:
enabled: false
路径
POST /bus/refresh # 从新拉配置, 其它服务也触发同步
?destination=appName:*.* # 指定刷新服务名下实例
spring cloud stream
#
spring cloud sleuth
#
application.yml
zipkin:
base-url: http://localhost:9014
sleuth:
sampler:
percentage: 1.0