nodejs :應(yīng)用服務(wù)器黔牵。
nginx :負(fù)載均衡反向代理。
redis cluster : 非主從的集群方案。
背景:
每當(dāng)我們的應(yīng)用服務(wù)大刊,在網(wǎng)絡(luò)上有許許多多的用戶時(shí)穷缤。我們首先考慮到的是敌蜂,我們的服務(wù)器能否承載同一時(shí)間高吞吐量的處理,我們的應(yīng)用服務(wù)器架構(gòu)應(yīng)該如何去搭建津肛。本文就是作者自己對(duì)于高性能web服務(wù)器的部分見(jiàn)解章喉。
一、數(shù)據(jù)。
針對(duì)于用戶使用數(shù)據(jù)而言秸脱,我們應(yīng)該會(huì)對(duì)數(shù)據(jù)進(jìn)行分級(jí)落包。例如簡(jiǎn)單的兩級(jí),用戶會(huì)經(jīng)常訪問(wèn)的(例如自己的id撞反,地理信息)妥色;用戶不會(huì)經(jīng)常訪問(wèn)的(例如歷史訂單)。因此我們?cè)O(shè)計(jì)系統(tǒng)時(shí)就應(yīng)該去考慮消除冗余遏片,讓經(jīng)常使用的數(shù)據(jù)有更多的訪問(wèn)速度資源嘹害,不會(huì)經(jīng)常使用的數(shù)據(jù)盡可能少的速度資源。最通俗的做法就是吮便,經(jīng)常訪問(wèn)的放在內(nèi)存中笔呀,不經(jīng)常訪問(wèn)的放入持久層,當(dāng)然髓需,經(jīng)常訪問(wèn)的也應(yīng)該在持久層中存根许师,不過(guò)用戶訪問(wèn)的時(shí)候會(huì)先去訪問(wèn)內(nèi)存中的數(shù)據(jù),看是否是自己需要的僚匆。
傳統(tǒng)的java 處理辦法是在service中加個(gè)變量微渠,缺點(diǎn):
1.服務(wù)器掛掉后,登錄狀態(tài)和會(huì)話數(shù)據(jù)丟失咧擂,體驗(yàn)差逞盆。
2.java虛擬機(jī)自身缺陷,存儲(chǔ)空間有限松申,需要進(jìn)行jvm優(yōu)化云芦。
3.丟失后不可恢復(fù),風(fēng)險(xiǎn)大贸桶。
面對(duì)傳統(tǒng)的缺陷應(yīng)運(yùn)而生的 redis 主從處理方案出現(xiàn)了舅逸,它支持雙機(jī)熱備,1臺(tái)處理皇筛,1臺(tái)備份琉历,在一定程度上可以防止數(shù)據(jù)丟失,對(duì)的你沒(méi)看錯(cuò)水醋,是一定程度上旗笔。為什么,分析下离例,在高并發(fā)環(huán)境中换团,一次吞吐量很大悉稠,主redis 正在處理新數(shù)據(jù)的時(shí)候掛掉了宫蛆,從redis能一定保證把最新的數(shù)據(jù)備份下來(lái)了么?很顯然不可能,所以一定會(huì)丟失耀盗。而且一臺(tái)redis如果和n臺(tái)web服務(wù)器交互想虎,壓力是很大的,掛掉的概率叛拷,嘖嘖嘖舌厨。當(dāng)然后來(lái)出現(xiàn)了keepalived 做負(fù)載均衡的,一定程度上減少了服務(wù)器的壓力忿薇,但是這種方案裙椭,始終是下策(為什么是下策,如果兩臺(tái)服務(wù)器都有對(duì)方?jīng)]有的新數(shù)據(jù)署浩,突然掛掉了揉燃。。筋栋。再或者承載keepalived的這太機(jī)器出了問(wèn)題炊汤。。弊攘。抢腐。)。風(fēng)險(xiǎn)還是太大了襟交。
于是出現(xiàn)了redis cluster --集群迈倍,集群首先考慮的就是數(shù)據(jù)最小丟失,因此婿着,采用的是數(shù)據(jù)分片技術(shù)授瘦,每臺(tái)redis上存儲(chǔ)的都是不同的數(shù)據(jù),redis越多竟宋,數(shù)據(jù)分離的越細(xì)提完,一次丟失內(nèi)容更少,沒(méi)有代理層丘侠,性能穩(wěn)定徒欣。XXXX等等一大堆優(yōu)勢(shì),就不說(shuō)了蜗字。
二打肝、服務(wù)器集群只做數(shù)據(jù)處理,不做狀態(tài)維持挪捕。
數(shù)據(jù)處理是很消耗計(jì)算機(jī)資源的粗梭,而狀態(tài)維持相對(duì)較少。因此级零,大多高性能服務(wù)器都做狀態(tài)和數(shù)據(jù)分離断医。nginx就是用來(lái)做會(huì)話狀態(tài)維持,如圖,整個(gè)系統(tǒng)中只有一個(gè)nginx鉴嗤。而會(huì)話狀態(tài)中的數(shù)據(jù)斩启,存儲(chǔ)在redis集群中。
這里有個(gè)梗醉锅,如之前所說(shuō)redis集群中兔簇,數(shù)據(jù)是以分片的形式存儲(chǔ)在幾臺(tái)redis中,如果我需要的數(shù)據(jù)所在的redis正好掛掉了硬耍,怎么辦垄琐。這就需要架構(gòu)師對(duì)應(yīng)用層編碼進(jìn)行規(guī)范,如何規(guī)范经柴,需要專門的人員進(jìn)行編寫這種redis數(shù)據(jù)此虑,數(shù)據(jù)提取策略是,如果這個(gè)數(shù)據(jù)從redis中獲取不到口锭,則直接從數(shù)據(jù)庫(kù)中再次讀取朦前,再存儲(chǔ)進(jìn)入redis。
例如session對(duì)象內(nèi)存儲(chǔ)的數(shù)據(jù)鹃操,存儲(chǔ)了一個(gè)user(點(diǎn)到為止韭寸,看不懂這個(gè)的,還需要多寫寫代碼)荆隘。
廢話不說(shuō)了恩伺,上nginx配置代碼(nginx如何配置安裝,百度有大巴高手寫過(guò)椰拒,小弟也是抄抄晶渠,不敢多寫):
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 集群服務(wù)器配置
upstream sxt.com{
# weight 表示權(quán)重,其實(shí)就是被訪問(wèn)的概率燃观。例如3002是3000的6倍是3001的3倍褒脯。
server 127.0.0.1:3000 weight=1;
server 127.0.0.1:3001 weight=2;
server 127.0.0.1:3002 weight=6;
}
server {
listen 80;
server_name localhost;
location / {
# 配置代理
proxy_pass http://sxt.com;
proxy_redirect default;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
作者只對(duì)跟負(fù)載均衡,反向代理相關(guān)的地方進(jìn)行了注釋缆毁。
這樣配置之后番川,當(dāng)訪問(wèn)nginx所在機(jī)器(例如192.168.1.200)的80端口時(shí)(http://192.168.1.200)
啟動(dòng)阿婆主的 nodejs服務(wù)器和 nginx服務(wù)器 并訪問(wèn):
得到如上圖中的結(jié)果:
發(fā)現(xiàn)---一共3次請(qǐng)求,3臺(tái)nodejs服務(wù)器接受到的請(qǐng)求數(shù)目不相同脊框。
一共請(qǐng)求了一個(gè)html 一個(gè)css文件和一個(gè)js文件颁督。
這就是weight (權(quán)重)起到的作用。此時(shí)浇雹,面向客戶的負(fù)載均衡和反向代理沉御,已經(jīng)部署完畢。由3臺(tái)服務(wù)器共同承擔(dān)一個(gè)定向地址的請(qǐng)求昭灵。
那么問(wèn)題來(lái)了吠裆,如何知道這個(gè)用戶是否登錄過(guò)了呢聂儒?這個(gè)問(wèn)題就是會(huì)話狀態(tài)維持,會(huì)話狀態(tài)實(shí)際上硫痰,是由session-cookies 來(lái)綁定維持的,做過(guò)移動(dòng)的webView的同學(xué)應(yīng)該都知道這個(gè)窜护,所以效斑,前端nginx和后端任意一臺(tái)nodejs服務(wù)器都不用關(guān)心會(huì)話,因?yàn)檫@個(gè)數(shù)據(jù)是存儲(chǔ)在redis集群上的柱徙,nodejs直接去集群上獲取比對(duì)session-cookies數(shù)據(jù)缓屠,相同則登陸過(guò),不同則沒(méi)有登陸過(guò)护侮,放心session-cookies是不會(huì)變的敌完,因?yàn)橛脩羰俏ㄒ慌cnginx進(jìn)行交互的,只要nodejs或者如果java服務(wù)器程序猿羊初,不去自己手動(dòng)獲取session對(duì)象滨溉,就能保證會(huì)話一致。
搞定了nginx 和 nodejs應(yīng)用服務(wù)器之后长赞,接下來(lái)就是nodejs服務(wù)器獲取redis集群的部分了晦攒。
配置redis集群的方式,本文就不詳述了得哆,百度好多meet分片的文章脯颜。其實(shí)就是修改conf中幾個(gè)參數(shù),再手動(dòng)修改分片文件進(jìn)行分片就可以贩据。當(dāng)然也可以命令分片栋操。
筆者在自己的虛擬機(jī)里,配置并啟動(dòng)了三臺(tái)redis饱亮,構(gòu)建成了一個(gè)cluster集群矾芙。
分別是:
192.168.1.200:7000
192.168.1.200:7001
192.168.1.200:7002
接下來(lái)就是需要注意的地方:
cluster集群與主從的redis有很大出入的地方:
1,java代碼中:
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
/***
* 測(cè)試redis
* @author sxt
*
*/
public class TestJedis {
/**
* @param args
*/
public static void main(String[] args) {
// 集群地址
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("192.168.1.200", 7000));
jedisClusterNodes.add(new HostAndPort("192.168.1.200", 7001));
jedisClusterNodes.add(new HostAndPort("192.168.1.200", 7002));
// 實(shí)例化集群
JedisCluster jedis = new JedisCluster(jedisClusterNodes);
// 向集群中綁定值
jedis.set("name","shixiaotian");
// 從集群中獲取值
String result = jedis.get("name");
// 輸出結(jié)果
System.out.println(result);
}
}
如上圖代碼中所示近上,官方給出的最簡(jiǎn)單的獲取方式中蠕啄,cluster并沒(méi)有讓我們指定從哪臺(tái)redis中獲取數(shù)據(jù)。為什么戈锻?
redis cluster 采用的是數(shù)據(jù)分片技術(shù)歼跟,并沒(méi)有熱備份。每個(gè)數(shù)據(jù)都是存在固定的位置的格遭,因此你訪問(wèn)任何一臺(tái)機(jī)器哈街,都會(huì)給你重新定向到存儲(chǔ)你需要數(shù)據(jù)的那臺(tái)redis上,所以不存在并發(fā)問(wèn)題拒迅,因?yàn)樯兀銠M豎都得到這臺(tái)機(jī)器上來(lái)她倘。這與之前接觸到的主從完全不一樣,這里講究的是分片后的最小丟失作箍,主從講究的是不丟失(想法總是很好的)硬梁。
因此,你完全不需要對(duì)cluster做負(fù)載均衡胞得,集群自己處理(很贊荧止,這才是合格的產(chǎn)品,屏蔽讓別人覺(jué)得麻煩的東西阶剑,不需要自己去掛個(gè)代理了)跃巡。
nodejs中讀取redis集群的方式也要進(jìn)行相應(yīng)的改變,為了區(qū)分兩種的編碼方式牧愁,下面都給出:
1.主從讀取方式
//redis 鏈接
var redis = require('redis');
var client = redis.createClient('7000', '192.168.1.200');
// redis 鏈接錯(cuò)誤
client.on("error", function(error) {
console.log(error);
});
// 向特定的redis機(jī)器上綁定數(shù)據(jù)
cluster.set('foo', 'bar');
// 從特定redis上獲取數(shù)據(jù)
cluster.get('foo', function (err, res) {
console.log(res);
});
2.cluster集群獲取方式:
// 注意素邪,不一樣的模塊
var Redis = require('ioredis');
// 不一樣的創(chuàng)建方式,多臺(tái)獲取猪半,出來(lái)就是集群
var cluster = new Redis.Cluster(
[{
port: 7000,
host: '192.168.1.200'
}, {
port: 7001,
host: '192.168.1.200'
}, {
port: 7002,
host: '192.168.1.200'
}]
);
// 設(shè)置數(shù)據(jù)相同
cluster.set('foo', 'bar');
// 獲取數(shù)據(jù)相同
cluster.get('foo', function (err, res) {
console.log(res);
});
我們這里使用的是第二種方式兔朦,因?yàn)槭莄luster集群。
接下來(lái)磨确,編寫應(yīng)用程序烘绽,所有使用到的session 數(shù)據(jù),全部使用cluster中獲取到的俐填,或者存入的安接,即可。
來(lái)源:http://blog.csdn.net/a7178077/article/details/50752413