redis_note_06_主從複製及高可用
Node基礎
-
陣列的索引使用問題: 沒有語義化, 解決方法: 索引器
-
客戶端和服務端的互動模型: 請求 , 業務處理, 響應
-
非同步操作的兩大特點:
(1). 非同步非阻塞: 非同步操作不會阻塞後面程式碼的執行
(2). 非同步操作的執行順序不依賴非同步操作程式碼的書寫順序
什麼是nodejs
-
node.js官網: https://nodejs.org/en/
-
中文文件: http://nodejs.cn/api/
-
Node 是一個構建於 Chrome V8引擎之上的一個Javascript 執行環境, 作用是讓js擁有開發服務端的功能
-
使用事件驅動、非阻塞IO模型(非同步讀寫)使得它非常的輕量級和高效
-
Node中絕大多數API都是非同步(類似於ajax),目的是提高效能
-
npm官網https://www.npmjs.com
nodejs 安裝
win7建議安裝v10版本, https://nodejs.org/zh-cn/download/releases/
node會附帶安裝npm
在cmd中輸入node -v
可以獲取版本
服務端js和客戶端js的區別
- 客戶端JavaScript由 ECMAScript, DOM, BOM 三部分組成
- 服務端nodejs 由 ECMAScript , 核心模組 , 第三方模組 組成, 其中核心模組在安裝nodejs時已附帶安裝
- 所以在nodejs中使用dom和bom api會報錯
執行node.js程式
node.js程式實際由node.exe執行, 呼叫方法
-
直接打在node.exe, 然後寫程式
-
在終端中(如cmd, powershell)中輸入
node
, 然後寫程式 -
在終端中輸入
node 程式路徑名
-
tips: 在windows檔案管理器中直接輸入cmd, 即可在當前目錄開啟cmd
-
windows路徑切換
cd
* -
nodemon會在js檔案發生變化時重新啟動Node程式, 安裝
npm i -g nodemon
, 使用nodemon 檔案路徑
-
node.js核心模組
Node應用是由模組組成的,Node遵循了CommonJS的模組規範,來隔離每個模組的作用域,使每個模組在它自身的名稱空間中執行。
CommonJS規範的主要內容:
模組必須通過 module.exports 匯出對外的變數或介面,通過 require() 來匯入其他模組的輸出到當前模組作用域中。
CommonJS模組的特點:
(1)所有程式碼執行在當前模組作用域中,不會汙染全域性作用域
(2)模組同步載入,根據程式碼中出現的順序依次載入
(3)模組可以多次載入,但是隻會在第一次載入時執行一次,然後執行結果就被快取了,以後再載入,就直接讀取快取結果。要想讓模組再次執行,必須清除快取。
require函式用來在一個模組中引入另外一個模組。傳入一個模組名,返回一個模組匯出物件。用法:let cc = require("模組名") ,其中模組名可以用絕對路徑也可以用相對路徑,模組的字尾名.js 可以省略
所以模組使用前要先引入
fs檔案模組
先引入fs模組
const fs = require('fs')
readFile非同步讀取模組
fs.readFile(path[, options], callback)
callback
<回撥函式>err
data
<字串|二進位制>
示例:
fs.readFile('檔案路徑', (err, data)=>{
if(err){
console.log(err)
//丟擲異常,throw的作用就是讓node程式終止執行,顯示出現錯誤的行數, 方便除錯
throw err
}else{
console.log(data)
}
})
後續nodejs中使用箭頭函式, 為統一程式碼規範, err, data固定寫法
解決中文亂碼:
readFile('檔案路徑','utf-8', (err, data)=>{})
//按utf-8編碼讀取, 解決中文亂碼
writeFile非同步寫入模組
fs.writeFile(file, data[, options], callback)
- callback <回撥函式>
- err
- err
- 預設寫入會覆蓋
- 如果檔名不存在,新建立再寫入
- 如果資料夾不存在,報錯
appenFile 非同步追加模組
fs.appendFile(path, data[, options], callback)
callback <回撥函式>
- err
非同步地追加資料到檔案,如果檔案尚不存在則建立檔案
path路徑模組
node中的絕對路徑和相對路徑
-
node中的相對路徑: ./ 不是相對於當前檔案所在路徑,而是相對於執行node命令的資料夾路徑(當前被執行的檔案所在的資料夾路徑).
在服務端開發中,一般不要使用相對路徑,而使用絕對路徑
-
解決方案:在nodejs中,每一個js檔案都有兩個全域性屬性,它可以幫助我們獲取到檔案的絕對路徑
- __filename:當前js檔案絕對路徑
- __dirmame:當前js檔案所在目錄的絕對路徑
因為是js檔案的, 所以寫在js檔案呼叫時才有效, 直接在終端中執行無效
const path = require('path')
join()方法 路徑拼接
path.join([...paths])
- 使用path.join()的好處: 會自動幫我們新增/轉換分隔符(windows和unix分隔符不同)
- 當路徑拼寫錯誤的時候, 比如多了個點
./page
, 會幫我們改正
示例
path.join('/目錄1', '目錄2', '目錄3/目錄4', '目錄5')
//返回'\目錄1\目錄2\目錄3\目錄4\目錄'
let filePath = path.join(__dirname, './page/login.html')
伺服器基本概念
基本的訪問流程
- 輸入主機地址
- 指定埠(如果沒有指定, 預設是80)
- 指定需要訪問的資源路徑
- 發起請求
- 獲取伺服器返回的結果並處理
http協議
HTTP是一個客戶端終端(使用者)和伺服器端(網站)請求和應答的標準
客戶端和伺服器的通訊必須遵守某種協議,http協議就是最常見的一種
http協議: 超文字傳輸協議(HyperText Transfer Protocol), 是基於TCP/IP協議之上的應用層協議
ip地址
- 如果以本機做為伺服器,那麼本機伺服器的IP地址預設就為:127.0.0.1
埠
埠是通過埠號來標記的,埠號只有整數,範圍是從0 到65535(2^16-1)
-
開啟cmd後,用“netstat ”檢視埠狀態,用“netstat -n”命令,以數字格式顯示地址和埠資訊
-
常見的埠號
- 80:web伺服器埠
- 3306:mysql資料伺服器埠
資源url地址(介面地址)
- 資源url地址由零或多個“/”符號隔開的字串,一般用來表示主機上的一個目錄或檔案地址
- 一般情況下,我們向伺服器發起請求時,需要準確的告訴伺服器我們想要什麼,這個時候就有必要傳入一個資源url地址了
- 如http://157.122.54.189:9063/getUserList,這裡面的/getUserList就是資源url地址,這個地址對應著伺服器端的一個業務處理或者檔案地址,例如/getUserList就能夠獲取儲存在伺服器的使用者列表資料
返回資料
返回資料是指客戶端傳送請求之後,從伺服器端返回的資料
返回資料的格式:
-
text/html格式:html程式碼,瀏覽器會以html語法解析
-
text/css:樣式,瀏覽器會以css語法解析
-
application/javascript:js程式碼,瀏覽器會以js語法解析
-
application/json:json格式字串,描述從伺服器返回的資料
返回狀態碼
是用以表示網頁伺服器超文字傳輸協議響應狀態的3位數字程式碼
常見的狀態碼
- 200請求已成功,請求所希望的響應頭或資料體將隨此響應返回。出現此狀態碼是表示正常狀態
- 404:請求失敗,請求所希望得到的資源未被在伺服器上發現
- 500:伺服器遇到了一個未曾預料的狀況,導致了它無法完成對請求的處理。一般來說,這個問題都會在伺服器端的原始碼出現錯誤時出現
建立簡單的伺服器
流程:
- 匯入模組
const ** = require('**')
- 建立伺服器
const server = http.creatServer()
- 新增伺服器的埠監聽
server.listen(埠號, ()=>{})
- 響應使用者的請求, 進行事件處理
server.on('request', (req, res)=>{})
注意點:
-
req.url可以獲取當前使用者請求的url
-
res.setHeader('Content-type', 'text/html;charset=UTF-8') //res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}) //指定res的編碼格式, 兩種方法只需要一種 //html頁面不需要, 頭部已有
-
客戶端沒有指定url,那麼它預設為/
-
一切都是為了資料服務, 後臺需要什麼資料, 客戶端發什麼資料. 後臺給什麼資料, 客戶端接收什麼資料
-
code a little, test a little, 從後臺拿到資料, 先打印出來檢查, 多利用列印err或者shrow err 來找到異常
響應不同的url
// 1.引入模組
const http = require('http')
const fs = require('fs')
const path = require('path')
// 2.建立伺服器
const server = http.createServer()
// 3.新增伺服器的埠監聽
server.listen(3000, function () {
console.log('伺服器開好了: http://127.0.0.1:3000')
})
// 4.新增使用者請求的監聽,只要使用者發起了針對當前伺服器3000埠的請求,就會呼叫回撥函式進行處理, 同時給這個函式傳入兩個引數
//req: request, 客戶端傳遞個伺服器的請求資料(請求報文)
//res: response, 伺服器響應給客戶端的資料
server.on('request', function (req, res) {
// 想要根據使用者請求返回不同的頁面
// req.url:可以獲取當前使用者請求的url,如果客戶端沒有指定url,那麼它預設為/
let url = req.url
// 這裡需要注意的是,在node伺服器中使用console,會在伺服器端中進行列印,而不是在瀏覽器端列印輸出呢
console.log(url)
// 我們可以看到,使用者的不同請求,url是不一樣的,所以我們需要判斷當前的url以決定返回什麼樣的頁面
// 對於url的判斷,我們在開發的時候一般是在後臺進行約定,前端需要遵守
// 這裡我們約定 /或/index 就是要請求首頁, /login 就是要請求登陸頁
if (url == '/' || url == '/index') {
fs.readFile(path.join(__dirname, "/views/index.html"), (err, data) => {
// 細節:這裡不需要設定編碼,因為html頁面已經有預設的編碼了
res.setHeader('Content-type', 'text/html;charset=UTF-8')
//解決res輸出中文亂碼的問題
if (err) {
res.end('404')
} else {
res.end(data)
}
})
} else if (url == '/login') {
fs.readFile(__dirname + "/views/login.html", (err, data) => {
if (err) {
res.end('404')
} else {
res.end(data)
}
})
} else {
res.end('404錯誤')
}
})
跨域請求
// 設定允許跨域請求 --現在不用考慮,只需要知道有這句程式碼就可以
res.setHeader('Access-Control-Allow-Origin', '*');
響應不同的請求
- 常見的get請求
1.通過超連結發起請求
2.通過瀏覽器位址列發起請求
3.通過ajax發起請求,同時設定type為get
- 常見的post請求
1.通過表單的預設行為發起請求,同時將method設定為post
2.通過ajax發起請求,同時設定type為post
3.常見的post操作有:新增,編輯,登陸..
獲取請求方式
通過req的method屬性可以獲取當前使用者的請求方式
let method = req.method //GET POST
GET請求
const http = require('http')
const server = http.createServer()
server.listen(3003, () => {
console.log('開始監聽')
})
server.on('request', (req, res) => {
let url = req.url
let method = req.method
if (url == '/register.html' && method == "GET") {
console.log(`url: ${url}, method: ${method}`)
} else {
res.end('404')
}
})
POST請求
注意點:
- node支援大容量的引數傳遞, 它會分批接收引數, 接收引數會觸發兩個事件
- req.on('data', (chunk)=>{}) 每接收一次引數就觸發一次, 接收到的chunk是字串格式
- 收到的字串是 "鍵值對&鍵值對" 的格式, 比如name=aaa&age=13
- 等於號前面的就是表單裡面的name, 等於號後面的就是裡面的值
- req.on('end', ()=>{}) 當所有的引數接收完就會觸發end事件
JSON格式:
- 並列的資料之間用逗號(", ")分隔。
- 對映用冒號(": ")表示。
- 並列資料的集合(陣列)用方括號("[]")表示。
- 對映的集合(物件)用大括號("{}")表示。
什麼時候用陣列, 什麼時候用物件
陣列表示有序資料的集合,而物件表示無序資料的集合。如果資料的順序很重要,就用陣列,否則就用物件。
案例: 註冊功能實現
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- method:指定請求方式,由於現在我要做註冊,說明是將客戶端的資料傳遞給伺服器,所以是post請求 -->
<!-- action:當前表單提交到的目的地址,如果沒有設定,預設就是當前頁面 -->
<!-- enctype="application/x-www-form-urlencoded":設定提交資料的編碼格式,ajax中使用 -->
<form method="post" action="http://127.0.0.1:3003/register">
<!-- name屬性的作用就是生成引數傳遞時的鍵,如果沒有鍵,是不可能生成引數傳遞的 -->
<!-- -->
姓名: <input type="text" id='name' name='name'><br>
年齡: <input type="number" id="age" name='age'><br>
<!-- type="submit":submit有預設的提交行為,以後避免使用它 -->
<input type="submit" value="註冊">
</form>
</body>
</html>
// 建立一個伺服器,能夠實現兩個功能
// 1.響應使用者註冊頁面
// 2.實現使用者註冊功能
const http = require('http')
const fs = require('fs')
const path = require('path')
const querystring = require('querystring')
const server = http.createServer()
server.listen(3003, () => {
console.log('http://127.0.0.1:3003')
//和表單的提交埠對應
//<form method="post" action="http://127.0.0.1:3003/register">
})
server.on('request', (req, res) => {
// res.writeHead(200, {
// 'Content-Type': 'text/html;charset=utf-8'
// })
res.setHeader('Content-type', 'text/html;charset=UTF-8')
let url = req.url
let method = req.method
if (method == 'GET' && (url == '/register' || url == '/')) {
//返回頁面
fs.readFile(path.join(__dirname, '/views/register.html'), (err, data) => {
if (err) {
console.log(err)
throw err
} else {
console.log('響應get請求')
res.end(data)
}
})
} else if (method == 'POST' && url == '/register') {
//接收資料
console.log('響應post請求')
let str = ''
//node支援大容量的引數傳遞, 它會分批接收引數, 接收引數會觸發兩個事件
//req.on('data', (chunk)=>{}) 每接收一次引數就觸發一次, 接收到的chunk是字串格式
//req.on('end', ()=>{}) 當所有的引數接收完就會觸發end事件
req.on('data', (chunk) => {
str += chunk
})
req.on('end', () => {
console.log(str)
//收到的字串是 "鍵值對&鍵值對" 的格式, 比如name=aaa&age=13
//等於號前面的就是表單裡面的name, 等於號後面的就是裡面的值
let obj = querystring.parse(str)
//querystring.parse(字串) 這個方法可以把字串轉成物件
//把物件儲存到json檔案
//取, 加, 存
fs.readFile(path.join(__dirname, './data/users.json'), 'utf-8', (err, data) => {
if (err) {
console.log('404')
} else {
let arr = JSON.parse(data)
console.log(arr)
/*
JSON的格式:
1) 並列的資料之間用逗號(", ")分隔。
2) 對映用冒號(": ")表示。
3) 並列資料的集合(陣列)用方括號("[]")表示。
4) 對映的集合(物件)用大括號("{}")表示。
*/
/*
陣列表示有序資料的集合,而物件表示無序資料的集合。如果資料的順序很重要,就用陣列,否則就用物件。
*/
arr.push(obj)
fs.writeFile(path.join(__dirname, './data/users.json'), JSON.stringify(arr, null, ' '), (err) => {
// JSON.stringify(arr, null, ' ') json格式化, 單引號裡面是空格
if (err) {
res.end('註冊失敗')
console.log(err)
} else {
res.end('註冊成功')
}
})
}
})
})
} else {
res.end('註冊失敗')
}
})
客戶端和伺服器互動總結
-
使用者開啟瀏覽器
-
位址列輸入我們需要訪問的網站網址(
URL
),包含協議,IP,埠,資源url -
瀏覽器發起一個對這個 地址的 (
請求
) -
服務端監聽指定的
埠
的伺服器軟體接收到這個請求,進行相應的處理(處理
) -
服務端將處理完的結果返回給客戶端瀏覽器(
響應
)