答:進程是全部線程的集合,每個線程是進程中的一條執行路徑,線程只是一條執行路徑。javascript
答:提升程序效率css
答:繼承Thread或Runnable 接口。html
答:Runnable接口好,由於實現了接口還能夠繼續繼承。繼承Thread類不能再繼承。前端
答:主要能體現到多線程提升程序效率。java
舉例:分批發送短信、迅雷多線程下載等。node
答:當多個線程同時共享,同一個全局變量或靜態變量,作寫的操做時,可能會發生數據衝突問題,也就是線程安全問題。作讀操做是不會發生數據衝突問題。python
答:使用多線程之間同步或使用鎖(lock)。mysql
答:將可能會發生數據衝突問題(線程不安全問題),只能讓當前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其餘線程進行執行。這樣的話就能夠解決線程不安全問題。linux
答:當多個線程共享同一個資源,不會受到其餘線程的干擾。nginx
答:就是將可能會發生線程安全問題的代碼,給包括起來。只能讓當前一個線程進行執行,被包裹的代碼執行完成以後才能釋放所,讓後才能讓其餘線程進行執行。
synchronized(同一個數據){
可能會發生線程衝突問題
}
private Object mutex = new Object();// 自定義多線程同步鎖 public void sale() { synchronized (mutex) { if (trainCount > 0) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--;} } } |
在方法上修飾synchronized 稱爲同步函數
public synchronized void sale() { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--; } } |
方法上加上static關鍵字,使用synchronized 關鍵字修飾 爲靜態同步函數
靜態的同步函數使用的鎖是 該函數所屬字節碼文件對象
答:
同步代碼使用自定鎖(明鎖)
同步函數使用this鎖
注意:有些面試會這樣問:例如如今一個靜態方法和一個非靜態靜態怎麼實現同步?
答:
同步函數使用this鎖
靜態同步函數使用字節碼文件,也就是類.class
答:
同步中嵌套同步,沒法釋放鎖的資源。
解決辦法:同步中儘可能不要嵌套同步
Wait讓當前線程有運行狀態變爲等待狀態,和同步一塊兒使用
Notify 喚醒如今正在等待的狀態,和同步一塊兒使用
對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程當中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
獲取對象鎖進入運行狀態。
Lock 接口能夠嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。若是這一時刻鎖沒有被其餘線程獲取到,則成功獲取並持有鎖。
*Lock 接口能被中斷地獲取鎖 與 synchronized 不一樣,獲取到鎖的線程可以響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
Lock 接口在指定的截止時間以前獲取鎖,若是截止時間到了依舊沒法獲取鎖,則返回。
Condition的功能相似於在傳統的線程技術中的,Object.wait()和Object.notify()的功能,
代碼:
Condition condition = lock.newCondition(); res. condition.await(); 相似wait res. Condition. Signal() 相似notify Signalall notifyALL |
1. 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
2. 使用stop方法強行終止線程(這個方法不推薦使用,由於stop和suspend、resume同樣,也可能發生不可預料的結果)。
3. 使用interrupt方法中斷線程。 線程在阻塞狀態
Java中有兩種線程,一種是用戶線程,另外一種是守護線程。
當進程不存在或主線程中止,守護線程也會被中止。
使用setDaemon(true)方法設置爲守護線程
join做用是讓其餘線程變爲等待,只有當前線程執行完畢後,等待的線程纔會被釋放。
多線程有三大特性,原子性、可見性、有序性
原子性:保證數據一致性,線程安全。
可見性:對另外一個線程是否課件
有序性:線程之間執行有順序
共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。
Volatile 關鍵字的做用是變量在多個線程之間可見。
AtomicInteger原子類
ThreadLocal提升一個線程的局部變量,訪問某個線程擁有本身局部變量。
當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。
ThreadLocal的接口方法
ThreadLocal類接口很簡單,只有4個方法,咱們先來了解一下:
void set(Object value)設置當前線程的線程局部變量的值。
public Object get()該方法返回當前線程所對應的線程局部變量。
public void remove()將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。
protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
線程池是指在初始化一個多線程應用程序過程當中建立一個線程集合,而後在須要執行新的任務時重用這些線程而不是新建一個線程。線程池中線程的數量一般徹底取決於可用內存數量和應用程序的需求。然而,增長可用線程數量是可能的。線程池中的每一個線程都有被分配一個任務,一旦任務已經完成了,線程回到池子中並等待下一次分配任務。
基於如下幾個緣由在多線程應用程序中使用線程是必須的:
1. 線程池改進了一個應用程序的響應時間。因爲線程池中的線程已經準備好且等待被分配任務,應用程序能夠直接拿來使用而不用新建一個線程。
2. 線程池節省了CLR 爲每一個短生存週期任務建立一個完整的線程的開銷並能夠在任務完成後回收資源。
3. 線程池根據當前在系統中運行的進程來優化線程時間片。
4. 線程池容許咱們開啓多個任務而不用爲每一個線程設置屬性。
5. 線程池容許咱們爲正在執行的任務的程序參數傳遞一個包含狀態信息的對象引用。
6. 線程池能夠用來解決處理一個特定請求最大線程數量限制問題。
Java經過Executors(jdk1.5併發包)提供四種線程池,分別爲:
newCachedThreadPool建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。
newFixedThreadPool 建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 建立一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。
名稱 |
做用 |
Lock |
鎖 |
Executors |
線程池 |
ReentrantLock |
一個可重入的互斥鎖定 Lock,功能相似synchronized,但要強大的多。 |
Condition |
Condition的功能相似於在傳統的線程技術中的,Object.wait()和Object.notify()的功能,
|
ConcurrentHashMap |
分段HasMap |
AtomicInteger |
原子類 |
BlockingQueue
|
BlockingQueue 一般用於一個線程生產對象,而另一個線程消費這些對象的場景 |
ExecutorService
|
執行器服務 |
自旋鎖是採用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其餘線程改變時 才能進入臨界區。以下
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock() { Thread current = Thread.currentThread(); while (!sign.compareAndSet(null, current)) { } }
public void unlock() { Thread current = Thread.currentThread(); sign.compareAndSet(current, null); } } |
所謂互斥鎖, 指的是一次最多隻能有一個線程持有的鎖. 在jdk1.5以前, 咱們一般使用synchronized機制控制多個線程對共享資源Lock接口及其實現類ReentrantLock
可重入鎖,也叫作遞歸鎖,指的是同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖
.悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系 統不會修改數據)。
相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫 性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。 而樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本( Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如 果提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。
信號量(Semaphore),有時被稱爲信號燈,是在多線程環境下使用的一種設施, 它負責協調各個線程, 以保證它們可以正確、合理的使用公共資源。
ArrayList:
底層數據結構是數組,查詢快,增刪慢。線程不安全,效率高。
Vector:
底層數據結構是數組,查詢快,增刪慢。線程安全,效率低。
LinkedList:
底層數據結構是鏈表,查詢慢,增刪快。線程不安全,效率高。
* Hashtable是JDK1.0版本出現的,是線程安全的,效率低,HashMap是JDK1.2版本出現的,是線程不安全的,效率高
* Hashtable不能夠存儲null鍵和null值,HashMap能夠存儲null鍵和null值
Socket就是爲網絡服務提供的一種機制。
通信的兩端都有Sokcet
網絡通信其實就是Sokcet間的通信
數據在兩個Sokcet間經過IO傳輸。
udp: a、是面向無鏈接, 將數據及源的封裝成數據包中,不須要創建創建鏈接
b、每一個數據報的大小在限制64k內
c、因無鏈接,是不可靠協議
d、不須要創建鏈接,速度快
tcp: a、建議鏈接,造成傳輸數據的通道.
b、在鏈接中進行大數據量傳輸,以字節流方式
c 經過三次握手完成鏈接,是可靠協議
d 必須創建鏈接m效率會稍低
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石同樣。項目中合理的運用設計模式能夠完美的解決不少問題,每種模式在如今中都有相應的原理來與之對應,每個模式描述了一個在咱們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被普遍應用的緣由。本章系Java之美[從菜鳥到高手演變]系列之設計模式,咱們會以理論與實踐相結合的方式來進行本章的學習,但願廣大程序愛好者,學好設計模式,作一個優秀的軟件工程師!
整體來講設計模式分爲三大類:
建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
其實還有兩類:併發型模式和線程池模式。用一個圖片來總體描述一下:
一、開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序須要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。因此一句話歸納就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,咱們須要使用接口和抽象類,後面的具體設計中咱們會提到這點。
二、里氏代換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類能夠出現的地方,子類必定能夠出現。 LSP是繼承複用的基石,只有當衍生類能夠替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也可以在基類的基礎上增長新的行爲。里氏代換原則是對「開-閉」原則的補充。實現「開-閉」原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範。—— From Baidu 百科
三、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。
四、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。仍是一個下降類之間的耦合度的意思,從這兒咱們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。因此上文中屢次出現:下降依賴,下降耦合。
五、迪米特法則(最少知道原則)(Demeter Principle)
爲何叫最少知道原則,就是說:一個實體應當儘可能少的與其餘實體之間發生相互做用,使得系統功能模塊相對獨立。
六、合成複用原則(Composite Reuse Principle)
原則是儘可能使用合成/聚合的方式,而不是使用繼承。
單例保證一個對象JVM中只能有一個實例,常見單例 懶漢式、餓漢式
什麼是懶漢式,就是須要的纔會去實例化,線程不安全。
什麼是餓漢式,就是當class文件被加載的時候,初始化,天生線程安全。
懶漢式代碼
class SingletonTest { public static void main(String[] args) { Singleton sl1 = Singleton.getSingleton(); Singleton sl2 = Singleton.getSingleton(); System.out.println(sl1 == sl2); } }
public class Singleton { // 當須要的纔會被實例化 private static Singleton singleton;
private Singleton() {
}
synchronized public static Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } return singleton; }
}
|
餓漢式代碼
class SingletonTest1 { public static void main(String[] args) { Singleton1 sl1 = Singleton1.getSingleton(); Singleton1 sl2 = Singleton1.getSingleton(); System.out.println((sl1 == sl2)+"-"); } }
public class Singleton1 { //當class 文件被加載初始化 private static Singleton1 singleton = new Singleton1();
private Singleton1() {
}
public static Singleton1 getSingleton() { return singleton; }
} |
實現建立者和調用者分離
public interface Car { public void run(); } public class AoDi implements Car { @Override public void run() { System.out.println("奧迪...."); } } public interface Car { public void run(); }
|
public class CarFactory { static public Car createCar(String carName) { Car car = null; if (carName.equals("奧迪")) { car = new AoDi(); } else if (carName.equals("奔馳")) { car = new BenChi(); } return car;
} public static void main(String[] args) { Car car1 = CarFactory.createCar("奧迪"); Car car2 = CarFactory.createCar("奔馳"); car1.run(); car2.run(); } }
|
public interface Car {
public void run();
}
public class AoDi implements Car {
@Override public void run() { System.out.println("奧迪...."); }
}
public class BenChi implements Car {
@Override public void run() { System.out.println("奔馳...."); }
}
|
public class AoDiChiFactory { static public Car createCar() { return new AoDi(); } }
public interface BenChiFactory { static public Car createCar() { return new BenChi(); } } public class Main {
public static void main(String[] args) { Car c1 = AoDiChiFactory.createCar(); Car c2 = BenChiFactory.createCar(); c1.run(); c2.run(); }
}
|
經過代理控制對象的訪問,能夠詳細訪問某個對象的方法,在這個方法調用處理,或調用後處理。既(AOP微實現) ,AOP核心技術面向切面編程。
安全代理 能夠屏蔽真實角色
遠程代理 遠程調用代理類RMI
延遲加載 先加載輕量級代理類,真正須要在加載真實
靜態代理(靜態定義代理類)
動態代理(動態生成代理類)
Jdk自帶動態代理
Cglib 、javaassist(字節碼操做庫)
靜態代理須要本身生成代理類
public class XiaoMing implements Hose { @Override public void mai() { System.out.println("我是小明,我要買房啦!!!!haha "); } } class Proxy implements Hose { private XiaoMing xiaoMing; public Proxy(XiaoMing xiaoMing) { this.xiaoMing = xiaoMing; } public void mai() { System.out.println("我是中介 看你買房開始啦!"); xiaoMing.mai(); System.out.println("我是中介 看你買房結束啦!"); } public static void main(String[] args) { Hose proxy = new Proxy(new XiaoMing()); proxy.mai(); } } |
實現InvocationHandler 就能夠了。
public interface Hose {
/** * * @methodDesc: 功能描述:(買房代理) * @author: 餘勝軍 * @param: * @createTime:2017年8月27日 上午2:54:34 * @returnType: void * @copyright:上海每特教育科技有限公司 */ public void mai();
}
public class XiaoMing implements Hose {
@Override public void mai() { System.out.println("我是小明,我要買房啦!!!!haha "); }
} |
public class JDKProxy implements InvocationHandler { private Object tarjet;
public JDKProxy(Object tarjet) { this.tarjet = tarjet; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是房產中介.....開始監聽你買房啦!"); Object oj = method.invoke(tarjet, args); System.out.println("我是房產中介.....結束監聽你買房啦!"); return oj;
}
}
class Test222 { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); JDKProxy jdkProxy = new JDKProxy(xiaoMing); Hose hose=(Hose) Proxy.newProxyInstance(xiaoMing.getClass().getClassLoader(), xiaoMing.getClass().getInterfaces(), jdkProxy); hose.mai(); }
} |
實現
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;
public class Cglib implements MethodInterceptor {
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("我是買房中介 , 開始監聽你買房了...."); Object invokeSuper = methodProxy.invokeSuper(o, args); System.out.println("我是買房中介 , 開結束你買房了...."); return invokeSuper;
}
}
class Test22222 { public static void main(String[] args) { Cglib cglib = new Cglib(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(XiaoMing.class); enhancer.setCallback(cglib); Hose hose = (Hose) enhancer.create(); hose.mai(); } }
|
jdk動態代理是由Java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。總的來講,反射機制在生成類的過程當中比較高效,而asm在生成類以後的相關執行過程當中比較高效(能夠經過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。還有一點必須注意:jdk動態代理的應用前提,必須是目標類基於統一的接口。若是沒有上述前提,jdk動態代理不能應用。
注:asm其實就是java字節碼控制.
Jdk1.5新增新技術,註解。不少框架爲了簡化代碼,都會提供有些註解。能夠理解爲插件,是代碼級別的插件,在類的方法上寫:@XXX,就是在代碼上插入了一個插件。
註解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的做用。
註解分類:內置註解(也成爲元註解 jdk 自帶註解)、自定義註解(Spring框架)
代碼:
使用@interface 定義註解。
@Target(value = { ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotation { int beanId() default 0; String className() default ""; String[]arrays(); }
|
客戶端與服務器經常使用數據交換格式xml、json、html
移動端(安卓、IOS)通信方式採用http協議+JSON格式 走restful風格。
不少互聯網項目都採用Http協議+JSON
由於xml比較重WebService服務採用http+xml格式 銀行項目使用比較多
同窗們能夠思考下?移動端和PC端服務器是接口是怎麼設計的?
fastjson(阿里)、gson(谷歌)、jackson(SpringMVC自帶)
Dom4j、Sax、Pull
dom4j不適合大文件的解析,由於它是一會兒將文件加載到內存中,因此有可能出現內存溢出,sax是基於事件來對xml進行解析的,因此他能夠解析大文件的xml,也正是由於如此,因此dom4j能夠對xml進行靈活的增刪改查和導航,而sax沒有這麼強的靈活性,因此sax常常是用來解析大型xml文件,而要對xml文件進行一些靈活(crud)操做就用dom4j。
Xml是重量級數據交換格式,佔寬帶比較大。
JSON是輕量級交換格式,xml佔寬帶小。
全部不少互聯網公司都會使用json做爲數據交換格式
不少銀行項目,大多數仍是在使用xml。
就是正在運行,動態獲取這個類的全部信息。
-1,反編譯:.class-->.java
-2.經過反射機制訪問java對象的屬性,方法,構造方法等;
Jdbc 加載驅動-----
Spring ioc
框架
//第一種方式: Classc1 = Class.forName("Employee"); //第二種方式: //java中每一個類型都有class 屬性. Classc2 = Employee.class;
//第三種方式: //java語言中任何一個java對象都有getClass 方法 Employeee = new Employee(); Classc3 = e.getClass(); //c3是運行時類 (e的運行時類是Employee) |
Class<?> forName = Class.forName("com.itmayiedu.entity.User"); // 建立此Class 對象所表示的類的一個新實例 調用了User的無參數構造方法. Object newInstance = forName.newInstance(); |
實例化有參構造函數
Class<?> forName = Class.forName("com.itmayiedu.entity.User"); Constructor<?> constructor = forName.getConstructor(String.class, String.class); User newInstance = (User) constructor.newInstance("123", "123"); |
方法名稱 |
做用 |
getDeclaredMethods [] |
獲取該類的全部方法 |
getReturnType() |
獲取該類的返回值 |
getParameterTypes() |
獲取傳入參數 |
getDeclaredFields() |
獲取該類的全部字段 |
setAccessible |
容許訪問私有成員 |
// 獲取當前類class地址 Class<?> forName = Class.forName("com.itmayiedu.entity.User"); // 使用反射實例化對象 無參數構造函數 Object newInstance = forName.newInstance(); // 獲取當前類的 userId字段 Field declaredField = forName.getDeclaredField("userId"); // 容許操做私有成員 declaredField.setAccessible(true); // 設置值 declaredField.set(newInstance, "123"); User user = (User) newInstance; System.out.println(user.getUserId()); |
所謂虛擬機,就是一臺虛擬的機器。他是一款軟件,用來執行一系列虛擬計算指令,大致上虛擬機能夠分爲
系統虛擬機和程序虛擬機, 大名鼎鼎的Visual Box、Vmare就屬於系統虛擬機,他們徹底是對物理計算的仿真,
提供了一個能夠運行完整操做系統的軟件平臺。
程序虛擬機典型代碼就是Java虛擬機,它專門爲執行單個計算程序而計算,在Java虛擬機中執行的指令咱們成爲Java
本身碼指令。不管是系統虛擬機仍是程序虛擬機,在上面運行的軟件都被限制於虛擬機提供的資源中。
Java發展至今,出現過不少虛擬機,作初Sun使用的一款叫ClassIc的Java虛擬機,到如今引用最普遍的是HotSpot虛擬
機,除了Sum意外,還有BEA的Jrockit,目前Jrockit和HostSopt都被oralce收入旗下,大有整合的趨勢。
一、 類加載子系統:負責從文件系統或者網絡加載Class信息,加載的信息存放在一塊稱之方法區的內存空間。
二、 方法區:就是存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等。
三、 Java堆:在Java虛擬機啓動的時候創建Java堆,它是Java程序最主要的內存工做區域,幾乎全部的對象實例都存放到
Java堆中,堆空間是全部線程共享。
四、 直接內存:JavaNio庫容許Java程序直接內存,從而提升性能,一般直接內存速度會優於Java堆。讀寫頻繁的場合可能會考慮使用。
五、 每一個虛擬機線程都有一個私有棧,一個線程的Java棧在線程建立的時候被建立,Java棧保存着局部變量、方法參數、同事Java的方法調用、
返回值等。
六、 本地方法棧,最大不一樣爲本地方法棧用於本地方法調用。Java虛擬機容許Java直接調用本地方法(經過使用C語言寫)
七、 垃圾收集系統是Java的核心,也是不可少的,Java有一套本身進行垃圾清理的機制,開發人員無需手工清理,下一節課詳細講。
八、 PC(Program Couneter)寄存器也是每一個線程私有的空間, Java虛擬機會爲每一個線程建立PC寄存器,在任意時刻,
一個Java線程老是在執行一個方法,這個方法稱爲當前方法,若是當前方法不是本地方法,PC寄存器總會執行當前正在被執行的指令,
若是是本地方法,則PC寄存器值爲Underfined,寄存器存放若是當前執行環境指針、程序技術器、操做棧指針、計算的變量指針等信息。
九、 虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,通常戶先進行編譯成機器碼後執行。
堆內存用於存放由new建立的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,之後就能夠在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量至關於爲數組或者對象起的一個別名,或者代號。
根據垃圾回收機制的不一樣,Java堆有可能擁有不一樣的結構,最爲常見的就是將整個Java堆分爲
新生代和老年代。其中新聲帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。
新生代分爲den區、s0區、s1區,s0和s1也被稱爲from和to區域,他們是兩塊大小相等而且能夠互相角色的空間。
絕大多數狀況下,對象首先分配在eden區,在新生代回收後,若是對象還存活,則進入s0或s1區,以後每通過一次
新生代回收,若是對象存活則它的年齡就加1,對象達到必定的年齡後,則進入老年代。
Java棧是一塊線程私有的空間,一個棧,通常由三部分組成:局部變量表、操做數據棧和幀數據區
局部變量表:用於報錯函數的參數及局部變量
操做數棧:主要保存計算過程的中間結果,同時做爲計算過程當中的變量臨時的存儲空間。
幀數據區:除了局部變量表和操做數據棧之外,棧還須要一些數據來支持常量池的解析,這裏幀數據區保存着
訪問常量池的指針,方便計程序訪問常量池,另外當函數返回或出現異常時賣虛擬機子必須有一個異常處理表,方便發送異常
的時候找到異常的代碼,所以異常處理表也是幀數據區的一部分。
Java方法區和堆同樣,方法區是一塊全部線程共享的內存區域,他保存系統的類信息。
好比類的字段、方法、常量池等。方法區的大小決定系統能夠保存多少個類。若是系統
定義太多的類,致使方法區溢出。虛擬機一樣會拋出內存溢出的錯誤。方法區能夠理解
爲永久區。
在虛擬機運行的過程當中,若是能夠跟蹤系統的運行狀態,那麼對於問題的故障
排查會有必定的幫助,爲此,在虛擬機提供了一些跟蹤系統狀態的參數,使用
給定的參數執行Java虛擬機,就能夠在系統運行時打印相關日誌,用於分析實際
問題。咱們進行虛擬機參數配置,其實就是圍繞着堆、棧、方法區、進行配置。
你說下 你熟悉那些jvm參數調優
-XX:+PrintGC 每次觸發GC的時候打印相關日誌
-XX:+UseSerialGC 串行回收
-XX:+PrintGCDetails 更詳細的GC日誌
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-XX:SurvivorRatio 用來設置新生代中eden空間和from/to空間的比例.
含以-XX:SurvivorRatio=eden/from=den/to
總結:在實際工做中,咱們能夠直接將初始的堆大小與最大堆大小相等,
這樣的好處是能夠減小程序運行時垃圾回收次數,從而提升效率。
-XX:SurvivorRatio 用來設置新生代中eden空間和from/to空間的比例.
參數: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
/** * jvm參數設置 * * @author Administrator * */ public class JvmDemo01 {
public static void main(String[] args) throws InterruptedException { byte[] b1 = new byte[1 * 1024 * 1024]; System.out.println("分配了1m"); jvmInfo(); Thread.sleep(3000); byte[] b2 = new byte[4 * 1024 * 1024]; System.out.println("分配了4m"); Thread.sleep(3000); jvmInfo();
}
/** * 轉換爲m * * @param maxMemory * @return */ static private String toM(long maxMemory) { float num = (float) maxMemory / (1024 * 1024); DecimalFormat df = new DecimalFormat("0.00");// 格式化小數 String s = df.format(num);// 返回的是String類型 return s; }
static private void jvmInfo() { // 最大內存 long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("maxMemory:" + maxMemory + ",轉換爲M:" + toM(maxMemory)); // 當前空閒內存 long freeMemory = Runtime.getRuntime().freeMemory(); System.out.println("freeMemory:" +freeMemory+",轉換爲M:"+toM(freeMemory)); // 已經使用內存 long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("totalMemory:" +totalMemory+",轉換爲M"+toM(totalMemory)); }
}
|
-Xmn 新生代大小,通常設爲整個堆的1/3到1/4左右
-XX:SurvivorRatio 設置新生代中eden區和from/to空間的比例關係n/1
參數: -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
public class JvmDemo02 {
public static void main(String[] args) { //-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC byte [] b = null; for (int i = 0; i < 10; i++) { b =new byte[1*1024*1024]; }
}
}
|
-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
-XX:NewRatio=2
總結:不一樣的堆分佈狀況,對系統執行會產生必定的影響,在實際工做中,
應該根據系統的特色作出合理的配置,基本策略:儘量將對象預留在新生代,
減小老年代的GC次數。
除了能夠設置新生代的絕對大小(-Xmn),能夠使用(-XX:NewRatio)設置新生代和老年
代的比例:-XX:NewRatio=老年代/新生代
錯誤緣由: java.lang.OutOfMemoryError: Java heap space
解決辦法:設置堆內存大小 -Xms1m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError
public static void main(String[] args) throws InterruptedException { List<Object> list = new ArrayList<>(); Thread.sleep(3000); jvmInfo(); for (int i = 0; i < 10; i++) { System.out.println("i:"+i); Byte [] bytes=new Byte[1*1024*1024]; list.add(bytes); jvmInfo(); } System.out.println("添加成功..."); } |
錯誤緣由: java.lang.StackOverflowError
棧溢出 產生於遞歸調用,循環遍歷是不會的,可是循環方法裏面產生遞歸調用, 也會發生棧溢出。
解決辦法:設置線程最大調用深度
-Xss5m 設置最大調用深度
public class JvmDemo04 { private static int count; public static void count(){ try { count++; count(); } catch (Throwable e) { System.out.println("最大深度:"+count); e.printStackTrace(); } } public static void main(String[] args) { count(); } }
|
內存溢出 out of memory,是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;好比申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。
內存泄露 memory leak,是指程序在申請內存後,沒法釋放已申請的內存空間,一次內存泄露危害能夠忽略,但內存泄露堆積後果很嚴重,不管多少內存,早晚會被佔光。
memory leak會最終會致使out of memory!
內存溢出就是你要求分配的內存超出了系統能給你的,系統不能知足需求,因而產生溢出。
內存泄漏是指你向系統申請分配內存進行使用(new),但是使用完了之後卻不歸還(delete),結果你申請到的那塊內存你本身也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給須要的程序。一個盤子用盡各類方法只能裝4個果子,你裝了5個,結果掉倒地上不能吃了。這就是溢出!比方說棧,棧滿時再作進棧一定產生空間溢出,叫上溢,棧空時再作退棧也產生空間溢出,稱爲下溢。就是分配的內存不足以放下數據項序列,稱爲內存溢出.
以發生的方式來分類,內存泄漏能夠分爲4類:
1. 常發性內存泄漏。發生內存泄漏的代碼會被屢次執行到,每次被執行的時候都會致使一塊內存泄漏。
2. 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操做過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。因此測試環境和測試方法對檢測內存泄漏相當重要。
3. 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者因爲算法上的缺陷,致使總會有一塊僅且一塊內存發生泄漏。好比,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,因此內存泄漏只會發生一次。
4. 隱式內存泄漏。程序在運行過程當中不停的分配內存,可是直到結束的時候才釋放內存。嚴格的說這裏並無發生內存泄漏,由於最終程序釋放了全部申請的內存。可是對於一個服務器程序,須要運行幾天,幾周甚至幾個月,不及時釋放內存也可能致使最終耗盡系統的全部內存。因此,咱們稱這類內存泄漏爲隱式內存泄漏。
從用戶使用程序的角度來看,內存泄漏自己不會產生什麼危害,做爲通常的用戶,根本感受不到內存泄漏的存在。真正有危害的是內存泄漏的堆積,這會最終消耗盡系統全部的內存。從這個角度來講,一次性內存泄漏並無什麼危害,由於它不會堆積,而隱式內存泄漏危害性則很是大,由於較之於常發性和偶發性內存泄漏它更難被檢測到
在JVM啓動參數中,能夠設置跟內存、垃圾回收相關的一些參數設置,默認狀況不作任何設置JVM會工做的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能得到最佳性能。經過設置咱們但願達到一些目標:
前兩個目前是相悖的,要想GC時間小必需要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,咱們只能取其平衡。
(1)針對JVM堆的設置,通常能夠經過-Xms -Xmx限定其最小、最大值,爲了防止垃圾收集器在最小、最大之間收縮堆而產生額外的時間,咱們一般把最大、最小設置爲相同的值
(2)年輕代和年老代將根據默認的比例(1:2)分配堆內存,能夠經過調整兩者之間的比率NewRadio來調整兩者之間的大小,也能夠針對回收代,好比年輕代,經過 -XX:newSize -XX:MaxNewSize來設置其絕對大小。一樣,爲了防止年輕代的堆收縮,咱們一般會把-XX:newSize -XX:MaxNewSize設置爲一樣大小
(3)年輕代和年老代設置多大才算合理?這個我問題毫無疑問是沒有答案的,不然也就不會有調優。咱們觀察一下兩者大小變化有哪些影響
Java語言中一個顯著的特色就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候再也不須要考慮內存管理。因爲有個垃圾回收機制,Java中的對象再也不有「做用域」的概念,只有對象的引用纔有「做用域」。垃圾回收能夠有效的防止內存泄露,有效的使用空閒的內存。
ps:內存泄露是指該內存空間使用完畢以後未回收,在不涉及複雜數據結構的通常狀況下,Java 的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度,咱們有時也將其稱爲「對象遊離」。
這裏必須點出一個很重要的誤區:不可達的對象並不會立刻就會被直接回收,而是至少要通過兩次標記的過程。
第一次被標記過的對象,會檢查該對象是否重寫了finalize()方法。若是重寫了該方法,則將其放入一個F-Query隊列中,不然,直接將對象加入「即將回收」集合。在第二次標記以前,F-Query隊列中的全部對象會逐個執行finalize()方法,可是不保證該隊列中全部對象的finalize()方法都能被執行,這是由於JVM建立一個低優先級的線程去運行此隊列中的方法,極可能在沒有遍歷完以前,就已經被剝奪了運行的權利。那麼運行finalize()方法的意義何在呢?這是對象避免本身被清理的最後手段:若是在執行finalize()方法的過程當中,使得此對象從新與GC Roots引用鏈相連,則會在第二次標記過程當中將此對象從F-Query隊列中清除,避免在此次回收中被清除,恢復成了一個「正常」的對象。但顯然這種好事不能無限的發生,對於曾經執行過一次finalize()的對象來講,以後若是再被標記,則不會再執行finalize()方法,只能等待被清除的命運。
以後,GC將對F-Queue中的對象進行第二次小規模的標記,將隊列中從新與GC Roots引用鏈恢復鏈接的對象清除出「即將回收」集合。全部此集合中的內容將被回收。
public class JVMDemo05 { public static void main(String[] args) { JVMDemo05 jvmDemo05 = new JVMDemo05(); //jvmDemo05 = null; System.gc(); } protected void finalize() throws Throwable { System.out.println("gc在回收對象..."); } } |
Java技術使用finalize()方法在垃圾收集器將對象從內存中清除出去前,作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,所以全部的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者執行其餘清理工做。finalize()方法是在垃圾收集器刪除對象以前對這個對象調用的。
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器都爲0的對象就是再也不被使用的,垃圾收集器將回收該對象使用的內存。
優勢:
引用計數收集器能夠很快的執行,交織在程序運行中。對程序須要不被長時間打斷的實時環境比較有利。
缺點:
沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.並且每次加減很是浪費內存。
標記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動做,一個是標記,另外一個就是清除。
標記就是根據特定的算法(如:引用計數算法,可達性分析算法等)標出內存中哪些對象能夠回收,哪些對象還要繼續用。
標記指示回收,那就直接收掉;標記指示對象還能用,那就原地不動留下。
缺點
因此碎片這個問題還得處理,怎麼處理,看標記-整理算法。
S0和s1將可用內存按容量分紅大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象複製到另外一塊內存上去,而後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等複雜狀況,只須要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
複製算法的缺點顯而易見,可以使用的內存降爲原來一半。
複製算法用於在新生代垃圾回收
標記壓縮法在標記清除基礎之上作了優化,把存活的對象壓縮到內存一端,然後進行垃圾清理。(java中老年代使用的就是標記壓縮法)
根據內存中對象的存活週期不一樣,將內存劃分爲幾塊,java的虛擬機中通常把內存劃分爲新生代和年老代,當新建立對象時通常在新生代中分配內存空間,當新生代垃圾收集器回收幾回以後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中沒法找到足夠的連續內存時也直接在年老代中建立。
對於新生代和老年代來講,新生代回收頻率很高,可是每次回收耗時很短,而老年代回收頻率較低,可是耗時會相對較長,因此應該儘可能減小老年代的GC.
垃圾回收的任務是識別和回收垃圾對象進行內存清理,爲了讓垃圾回收器能夠更高效的執行,大部分狀況下,會要求系統進如一個停頓的狀態。停頓的目的是爲了終止全部的應用線程,只有這樣的系統纔不會有新垃圾的產生。同時停頓保證了系統狀態在某一個瞬間的一致性,也有利於更好的標記垃圾對象。所以在垃圾回收時,都會產生應用程序的停頓。
Java垃圾回收器是Java虛擬機(JVM)的三個重要模塊(另外兩個是解釋器和多線程機制)之一,爲應用程序提供內存的自動分配(Memory Allocation)、自動回收(Garbage Collect)功能,這兩個操做都發生在Java堆上(一段內存快)。某一個時點,一個對象若是有一個以上的引用(Rreference)指向它,那麼該對象就爲活着的(Live),不然死亡(Dead),視爲垃圾,可被垃圾回收器回收再利用。垃圾回收操做須要消耗CPU、線程、時間等資源,因此容易理解的是垃圾回收操做不是實時的發生(對象死亡立刻釋放),當內存消耗完或者是達到某一個指標(Threshold,使用內存佔總內存的比列,好比0.75)時,觸發垃圾回收操做。有一個對象死亡的例外,java.lang.Thread類型的對象即便沒有引用,只要線程還在運行,就不會被回收。
單線程執行回收操做,回收期間暫停全部應用線程的執行,client模式下的默認回收器,經過-XX:+UseSerialGC命令行可選項強制指定。參數能夠設置使用新生代串行和老年代串行回收器
年輕代的回收算法(Minor Collection)
把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區裏面年齡很大的升級到年老代。 回收結束以後,Eden和From區都爲空,此時把From和To的功能互換,From變To,To變From,每一輪迴收以前To都是空的。設計的選型爲複製。
年老代的回收算法(Full Collection)
年老代的回收分爲三個步驟,標記(Mark)、清除(Sweep)、合併(Compact)。標記階段把全部存活的對象標記出來,清除階段釋放全部死亡的對象,合併階段 把全部活着的對象合併到年老代的前部分,把空閒的片斷都留到後面。設計的選型爲合併,減小內存的碎片。
並行回收器在串行回收器基礎上作了改進,他能夠使用多個線程同時進行垃
圾回收,對於計算能力強的計算機而言,能夠有效的縮短垃圾回收所需的尖
際時間。
ParNew回收器是一個工做在新生代的垃圾收集器,他只是簡單的將串行回收
器多線程快他的回收策略和算法和串行回收器同樣。
使用XX:+UseParNewGC 新生代ParNew回收器,老年代則使用市行回收器
ParNew回收器工做時的線程數量能夠使用XX:ParaleiGCThreads參數指
定,通常最好和計算機的CPU至關,避免過多的栽程影響性能。
老年代ParallelOldGC回收器也是一種多線程的回收器,和新生代的
ParallelGC回收器同樣,也是一種關往吞吐量的回收器,他使用了標記壓縮
算法進行實現。
-XX:+UseParallelOldGC 進行設置
-XX:+ParallelCThread也能夠設置垃圾收集時的線程教量。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是基於「標記-清除」算法實現的,整個收集過程大體分爲4個步驟:
①.初始標記(CMS initial mark)
②.併發標記(CMS concurrenr mark)
③.從新標記(CMS remark)
④.併發清除(CMS concurrent sweep)
其中初始標記、從新標記這兩個步驟任然須要停頓其餘用戶線程。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會斷定對象是否存活。而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
因爲整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,因此總體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CMS收集器的優勢:併發收集、低停頓,可是CMS還遠遠達不到完美,器主要有三個顯著缺點:
CMS收集器對CPU資源很是敏感。在併發階段,雖然不會致使用戶線程停頓,可是會佔用CPU資源而致使引用程序變慢,總吞吐量降低。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。
CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure「,失敗後而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲「浮動垃圾」。也是因爲在垃圾收集階段用戶線程還須要運行,
即須要預留足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,須要預留一部份內存空間提供併發收集時的程序運做使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也能夠經過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以下降內存回收次數提升性能。要是CMS運行期間預留的內存沒法知足程序其餘線程須要,就會出現「Concurrent Mode Failure」失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。因此說參數-XX:CMSInitiatingOccupancyFraction設置的太高將會很容易致使「Concurrent Mode Failure」失敗,性能反而下降。
最後一個缺點,CMS是基於「標記-清除」算法實現的收集器,使用「標記-清除」算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來不少麻煩,好比說大對象,內存空間找不到連續的空間來分配不得不提早觸發一次Full GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full GC以後增長一個碎片整理過程,還可經過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC以後,跟着來一次碎片整理過程。
G1回收器(Garbage-First)實在]dk1.7中提出的垃圾回收器,從長期目標來看是爲了取代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬於分代垃圾回收器,區分新生代和老年代,依然有eden和from/to區,它並不要求整個eden區或者新生代、老年代的空間都連續,它使用了分區算法。
並行性: G1回收期間可多線程同時工做。
井發性G1擁有與應用程序交替執行能力,部分工做可與應用程序同時執行,在整個GC期間不會徹底阻塞應用程序。
分代GC:G1依然是一個分代的收集器,可是它是非兩新生代和老年代一杯政的雜尊。
空間基理,G1在國收過程當中,不會微CMS那樣在若千tacAy 要進行碎片整理。
G1
來用了有效複製對象的方式,減小空間碎片。
利得程,用於分區的緣由,G能夠貝造取都分區城進行回收,帽小了國收的格想,
提高了性能。
使用.XXX:+UseG1GC 應用G1收集器,
Mills指定最大停頓時間
使用-XX:MaxGCPausel
設置並行回收的線程數量
使用-XX:ParallelGCThreads
初始堆值和最大堆內存內存越大,吞吐量就越高。
最好使用並行收集器,由於並行手機器速度比串行吞吐量高,速度快。
設置堆內存新生代的比例和老年代的比例最好爲1:2或者1:3。
減小GC對老年代的回收。
表的設計合理化(符合3NF)
添加適當索引(index) [四種: 普通索引、主鍵索引、惟一索引unique、全文索引]
SQL語句優化
分表技術(水平分割、垂直分割)
讀寫[寫: update/delete/add]分離
存儲過程 [模塊化編程,能夠提升速度]
對mysql配置優化 [配置最大併發數my.ini, 調整緩存大小 ]
mysql服務器硬件升級
定時的去清除不須要的數據,定時進行碎片整理(MyISAM)
爲了創建冗餘較小、結構合理的數據庫,設計數據庫時必須遵循必定的規則。在關係型數據庫中這種規則就稱爲範式。範式是符合某一種設計要求的總結。要想設計一個結構合理的關係型數據庫,必須知足必定的範式。
第一範式:1NF是對屬性的原子性約束,要求屬性(列)具備原子性,不可再分解;(只要是關係型數據庫都知足1NF)
第二範式:2NF是對記錄的唯一性約束,表中的記錄是惟一的, 就知足2NF, 一般咱們設計一個主鍵來實現,主鍵不能包含業務邏輯。
第三範式:3NF是對字段冗餘性的約束,它要求字段沒有冗餘。 沒有冗餘的數據庫設計能夠作到。
可是,沒有冗餘的數據庫未必是最好的數據庫,有時爲了提升運行效率,就必須下降範式標準,適當保留冗餘數據。具體作法是: 在概念數據模型設計時遵照第三範式,下降範式標準的工做放到物理數據模型設計時考慮。下降範式就是增長字段,容許冗餘。
MySQL默認10秒內沒有響應SQL結果,則爲慢查詢
能夠去修改MySQL慢查詢默認時間
--查詢慢查詢時間 show variables like 'long_query_time'; --修改慢查詢時間 set long_query_time=1; ---可是重啓mysql以後,long_query_time依然是my.ini中的值 |
在默認狀況下,咱們的mysql不會記錄慢查詢,須要在啓動mysql時候,指定記錄慢查詢才能夠
bin\mysqld.exe --safe-mode --slow-query-log [mysql5.5 能夠在my.ini指定](安全模式啓動,數據庫將操做寫入日誌,以備恢復)
bin\mysqld.exe –log-slow-queries=d:/abc.log [低版本mysql5.0能夠在my.ini指定]
先關閉mysql,再啓動, 若是啓用了慢查詢日誌,默認把這個文件放在
my.ini 文件中記錄的位置
#Path to the database root
datadir=" C:/ProgramData/MySQL/MySQL Server 5.5/Data/"
索引用來快速地尋找那些具備特定值的記錄,全部MySQL索引都以B-樹的形式保存。若是沒有索引,執行查詢時MySQL必須從第一個記錄開始掃描整個表的全部記錄,直至找到符合要求的記錄。表裏面的記錄數量越多,這個操做的代價就越高。若是做爲搜索條件的列上已經建立了索引,MySQL無需掃描任何記錄便可迅速獲得目標記錄所在的位置。若是表有1000個記錄,經過索引查找記錄至少要比順序掃描記錄快100倍。
主鍵是一種惟一性索引,但它必須指定爲「PRIMARY KEY」。若是你曾經用過AUTO_INCREMENT類型的列,你可能已經熟悉主鍵之類的概念了。主鍵通常在建立表的時候指定,例如「CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) ); 」。可是,咱們也能夠經過修改表的方式加入主鍵,例如「ALTER TABLE tablename ADD PRIMARY KEY (列的列表); 」。每一個表只能有一個主鍵。
主鍵是一種惟一性索引,但它必須指定爲「PRIMARY KEY」。若是你曾經用過AUTO_INCREMENT類型的列,你可能已經熟悉主鍵之類的概念了。主鍵通常在建立表的時候指定,例如「CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) ); 」。可是,咱們也能夠經過修改表的方式加入主鍵,例如「ALTER TABLE tablename ADD PRIMARY KEY (列的列表); 」。每一個表只能有一個主鍵。
當一張表,把某個列設爲主鍵的時候,則該列就是主鍵索引
create table aaa
(id int unsigned primary key auto_increment ,
name varchar(32) not null default '');
這是id 列就是主鍵索引.
create table bbb (id int , name varchar(32) not null default '');
若是你建立表時,沒有指定主鍵索引,也能夠在建立表後,在添加, 指令:
實例:
alter table 表名 add primary key (列名);
刪除主鍵索引
alter table articles drop primary key;
desc 表名; 不能顯示索引名稱
show index from 表名
show keys from 表名
錯誤用法:
select * from articles where body like '%mysql%'; 錯誤用法 索引不會生效
正確用法:
select * from articles where match(title,body) against ( 'database')
說明:
mysql> select match(title,body) against ('database') from articles;(輸出的是每行和database的匹配度)
這種索引和前面的「普通索引」基本相同,但有一個區別:索引列的全部值都只能出現一次,即必須惟一。惟一性索引能夠用如下幾種方式建立:
建立索引,例如CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表);
修改表,例如ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表);
建立表的時候指定索引,例如CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) );
普通索引(由關鍵字KEY或INDEX定義的索引)的惟一任務是加快對數據的訪問速度。所以,應該只爲那些最常常出如今查詢條件(WHEREcolumn=)或排序條件(ORDERBYcolumn)中的數據列建立索引。只要有可能,就應該選擇一個數據最整齊、最緊湊的數據列(如一個整數類型的數據列)來建立索引。
create table ccc(
id int unsigned,
name varchar(32)
)
create index 索引名 on 表 (列1,列名2);
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現一般使用 B 樹及其變種 B+ 樹。
在數據以外,數據庫系統還維護着知足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就能夠在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
爲表設置索引要付出代價的:一是增長了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(由於索引也要隨之變更)。
上圖展現了一種可能的索引方式。左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也並非必定物理相鄰的)。爲了加快 Col2 的查找,能夠維護一個右邊所示的二叉查找樹,每一個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就能夠運用二叉查找在 O(log2n)的複雜度內獲取到相應數據。
建立索引能夠大大提升系統的性能。
第一,經過建立惟一性索引,能夠保證數據庫表中每一行數據的惟一性。
第二,能夠大大加快數據的檢索速度,這也是建立索引的最主要的緣由。
第三,能夠加速表和表之間的鏈接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序子句進行數據檢索時,一樣能夠顯著減小查詢中分組和排序的時間。
第五,經過使用索引,能夠在查詢的過程當中,使用優化隱藏器,提升系統的性能。
也許會有人要問:增長索引有如此多的優勢,爲何不對錶中的每個列建立一個索引呢?由於,增長索引也有許多不利的方面。
第一,建立索引和維護索引要耗費時間,這種時間隨着數據量的增長而增長。
第二,索引須要佔物理空間,除了數據表佔數據空間以外,每個索引還要佔必定的物理空間,若是要創建聚簇索引,那麼須要的空間就會更大。
第三,當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,這樣就下降了數據的維護速度。
索引是創建在數據庫表中的某些列的上面。在建立索引的時候,應該考慮在哪些列上能夠建立索引,在哪些列上不能建立索引。通常來講,應該在這些列上建立索引:在常常須要搜索的列上,能夠加快搜索的速度;在做爲主鍵的列上,強制該列的惟一性和組織表中數據的排列結構;在常常用在鏈接的列上,這些列主要是一些外鍵,能夠加快鏈接的速度;在常常須要根據範圍進行搜索的列上建立索引,由於索引已經排序,其指定的範圍是連續的;在常常須要排序的列上建立索引,由於索引已經排序,這樣查詢能夠利用索引的排序,加快排序查詢時間;在常用在 WHERE 子句中的列上面建立索引,加快條件的判斷速度。
一樣,對於有些列不該該建立索引。通常來講,不該該建立索引的的這些列具備下列特色:
第一,對於那些在查詢中不多使用或者參考的列不該該建立索引。這是由於,既然這些列不多使用到,所以有索引或者無索引,並不能提升查詢速度。相反,因爲增長了索引,反而下降了系統的維護速度和增大了空間需求。
第二,對於那些只有不多數據值的列也不該該增長索引。這是由於,因爲這些列的取值不多,例如人事表的性別列,在查詢的結果中,結果集的數據行佔了表中數據行的很大比例,即須要在表中搜索的數據行的比例很大。增長索引,並不能明顯加快檢索速度。
第三,對於那些定義爲 text, image 和 bit 數據類型的列不該該增長索引。這是由於,這些列的數據量要麼至關大,要麼取值不多。
第四,當修改性能遠遠大於檢索性能時,不該該建立索引。這是由於,修改性能和檢索性能是互相矛盾的。當增長索引時,會提升檢索性能,可是會下降修改性能。當減小索引時,會提升修改性能,下降檢索性能。所以,當修改性能遠遠大於檢索性能時,不該該建立索引。
根據數據庫的功能,能夠在數據庫設計器中建立三種索引:惟一索引、主鍵索引和彙集索引。
惟一索引
惟一索引是不容許其中任何兩行具備相同索引值的索引。
當現有數據中存在重複的鍵值時,大多數數據庫不容許將新建立的惟一索引與表一塊兒保存。數據庫還可能防止添加將在表中建立重複鍵值的新數據。例如,若是在 employee 表中職員的姓(lname)上建立了惟一索引,則任何兩個員工都不能同姓。主鍵索引數據庫表常常有一列或列組合,其值惟一標識表中的每一行。該列稱爲表的主鍵。在數據庫關係圖中爲表定義主鍵將自動建立主鍵索引,主鍵索引是惟一索引的特定類型。該索引要求主鍵中的每一個值都惟一。當在查詢中使用主鍵索引時,它還容許對數據的快速訪問。彙集索引在彙集索引中,表中行的物理順序與鍵值的邏輯(索引)順序相同。一個表只能包含一個彙集索引。
若是某索引不是彙集索引,則表中行的物理順序與鍵值的邏輯順序不匹配。與非彙集索引相比,彙集索引一般提供更快的數據訪問速度。
局部性原理與磁盤預讀
因爲存儲介質的特性,磁盤自己存取就比主存慢不少,再加上機械運動耗費,磁盤的存取速度每每是主存的幾百分分之一,所以爲了提升效率,要儘可能減小磁盤 I/O。爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這樣作的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也一般會立刻被使用。程序運行期間所須要的數據一般比較集中。
因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升 I/O 效率。
預讀的長度通常爲頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操做系統每每將主存和磁盤存儲區分割爲連續的大小相等的塊,每一個存儲塊稱爲一頁(在許多操做系統中,頁得大小一般爲 4k),主存和磁盤以頁爲單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,而後異常返回,程序繼續運行。
B-/+Tree 索引的性能分析
到這裏終於能夠分析 B-/+Tree 索引的性能了。
上文說過通常使用磁盤 I/O 次數評價索引結構的優劣。先從 B-Tree 分析,根據 B-Tree 的定義,可知檢索一次最多須要訪問 h 個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點只須要一次 I/O 就能夠徹底載入。爲了達到這個目的,在實際實現 B-Tree 還須要使用以下技巧:
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一個 node 只需一次 I/O。
B-Tree 中一次檢索最多須要 h-1 次 I/O(根節點常駐內存),漸進複雜度爲 O(h)=O(logdN)。通常實際應用中,出度 d 是很是大的數字,一般超過 100,所以 h 很是小(一般不超過 3)。
而紅黑樹這種結構,h 明顯要深的多。因爲邏輯上很近的節點(父子)物理上可能很遠,沒法利用局部性,因此紅黑樹的 I/O 漸進複雜度也爲 O(h),效率明顯比 B-Tree 差不少。
綜上所述,用 B-Tree 做爲索引結構效率是很是高的。
應該花時間學習 B-樹和 B+ 樹數據結構
=============================================================================================================
1)B 樹
B 樹中每一個節點包含了鍵值和鍵值對於的數據對象存放地址指針,因此成功搜索一個對象能夠不用到達樹的葉節點。
成功搜索包括節點內搜索和沿某一路徑的搜索,成功搜索時間取決於關鍵碼所在的層次以及節點內關鍵碼的數量。
在 B 樹中查找給定關鍵字的方法是:首先把根結點取來,在根結點所包含的關鍵字 K1,…,kj 查找給定的關鍵字(可用順序查找或二分查找法),若找到等於給定值的關鍵字,則查找成功;不然,必定能夠肯定要查的關鍵字在某個 Ki 或 Ki+1 之間,因而取 Pi 所指的下一層索引節點塊繼續查找,直到找到,或指針 Pi 爲空時查找失敗。
2)B+ 樹
B+ 樹非葉節點中存放的關鍵碼並不指示數據對象的地址指針,非也節點只是索引部分。全部的葉節點在同一層上,包含了所有關鍵碼和相應數據對象的存放地址指針,且葉節點按關鍵碼從小到大順序連接。若是實際數據對象按加入的順序存儲而不是按關鍵碼次數存儲的話,葉節點的索引必須是稠密索引,若實際數據存儲按關鍵碼次序存放的話,葉節點索引時稀疏索引。
B+ 樹有 2 個頭指針,一個是樹的根節點,一個是最小關鍵碼的葉節點。
因此 B+ 樹有兩種搜索方法:
一種是按葉節點本身拉起的鏈表順序搜索。
一種是從根節點開始搜索,和 B 樹相似,不過若是非葉節點的關鍵碼等於給定值,搜索並不中止,而是繼續沿右指針,一直查到葉節點上的關鍵碼。因此不管搜索是否成功,都將走完樹的全部層。
B+ 樹中,數據對象的插入和刪除僅在葉節點上進行。
這兩種處理索引的數據結構的不一樣之處:
a,B 樹中同一鍵值不會出現屢次,而且它有可能出如今葉結點,也有可能出如今非葉結點中。而 B+ 樹的鍵必定會出如今葉結點中,而且有可能在非葉結點中也有可能重複出現,以維持 B+ 樹的平衡。
b,由於 B 樹鍵位置不定,且在整個樹結構中只出現一次,雖然能夠節省存儲空間,但使得在插入、刪除操做複雜度明顯增長。B+ 樹相比來講是一種較好的折中。
c,B 樹的查詢效率與鍵在樹中的位置有關,最大時間複雜度與 B+ 樹相同(在葉結點的時候),最小時間複雜度爲 1(在根結點的時候)。而 B+ 樹的時候複雜度對某建成的樹是固定的。能夠掃描2的次方。
佔用磁盤空間
對DML(update、delete、insert)語句的效率影響
增刪改會對索引影響,由於索引要從新整理。
存儲引擎 |
容許的索引類型 |
myisam |
btree |
innodb |
btree |
memory/yeap |
Hash,btree |
① 查詢做爲查詢條件字段應該建立索引
② 惟一性太差的字段不適合單首創建索引,即便頻繁
Select * from emp where sex=’男’
③ 頻繁更新字段,也不要定義索引。
④ 不會出如今where語句的字段不要建立索引
總結:滿處一下條件的字段,才應該建立索引
① 確定在where條件常用
② 該字段的內容不是惟一的幾個值
③ 字段內容不是頻繁變化
建立主鍵索引
alter table 表名 add primary key (列名);
建立一個聯合索引
alter table dept add index my_ind (dname,loc); // dname 左邊的列,loc就是右邊的列
注意:
1.對於建立的多列索引,若是不是使用第一部分,則不會建立索引。
explain select * from dept where loc='aaa'\G
就不會使用到索引
2.模糊查詢在like前面有百分號開頭會失效。
3. 若是條件中有or,即便其中有條件帶索引也不會使用。換言之,就是要求使用的全部字段,都必須創建索引, 咱們建議你們儘可能避免使用or 關鍵字
4.若是列類型是字符串,那必定要在條件中將數據使用引號引用起來。不然不使用索引。(添加時,字符串必須’’), 也就是,若是列是字符串類型,就必定要用 ‘’ 把他包括起來.
5.若是mysql估計使用全表掃描要比使用索引快,則不使用索引。
show status like ‘handler_read%’;
你們能夠注意:
handler_read_key:這個值越高越好,越高表示使用索引查詢到的次數。
handler_read_rnd_next:這個值越高,說明查詢低效。
① 使用group by 分組查詢是,默認分組後,還會排序,可能會下降速度,
在group by 後面增長 order by null 就能夠防止排序.
explain select * from emp group by deptno order by null;
② 有些狀況下,能夠使用鏈接來替代子查詢。由於使用join,MySQL不須要在內存中建立臨時表。
select * from dept, emp where dept.deptno=emp.deptno; [簡單處理方式]
select * from dept left join emp on dept.deptno=emp.deptno; [左外鏈接,更ok!]
③ 對查詢進行優化,要儘可能避免全表掃描,首先應考慮在 where 及 order by 涉及的列上創建索引
應儘可能避免在 where 子句中對字段進行 null 值判斷,不然將致使引擎放棄使用索引而進行全表掃描,如:
select id from t where num is null
最好不要給數據庫留 NULL,儘量的使用 NOT NULL 填充數據庫.
備註、描述、評論之類的能夠設置爲 NULL,其餘的,最好不要使用 NULL。
不要覺得 NULL 不須要空間,好比:char(100) 型,在字段創建時,空間就固定了, 無論是否插入值(NULL 也包含在內),都是佔用 100 個字符的空間的,若是是 varchar 這樣的變長字段, null 不佔用空間。
能夠在 num 上設置默認值 0,確保表中 num 列沒有 null 值,而後這樣查詢:
select id from t where num = 0
更多mysql sql語句調優查看http://bbs.itmayiedu.com/article/1511164574773
使用的存儲引擎 myisam / innodb/ memory
myisam 存儲: 若是表對事務要求不高,同時是以查詢和添加爲主的,咱們考慮使用myisam存儲引擎. ,好比 bbs 中的 發帖表,回覆表.
INNODB 存儲: 對事務要求高,保存的數據都是重要數據,咱們建議使用INNODB,好比訂單表,帳號表.
MyISAM 和 INNODB的區別
1. 事務安全(MyISAM不支持事務,INNODB支持事務)
2. 查詢和添加速度(MyISAM批量插入速度快)
3. 支持全文索引(MyISAM支持全文索引,INNODB不支持全文索引)
4. 鎖機制(MyISAM時表鎖,innodb是行鎖)
5. 外鍵 MyISAM 不支持外鍵, INNODB支持外鍵. (在PHP開發中,一般不設置外鍵,一般是在程序中保證數據的一致)
Memory 存儲,好比咱們數據變化頻繁,不須要入庫,同時又頻繁的查詢和修改,咱們考慮使用memory, 速度極快. (若是mysql重啓的話,數據就不存在了)
若是你的數據庫的存儲引擎是myisam,請必定記住要定時進行碎片整理
舉例說明:
create table test100(id int unsigned ,name varchar(32))engine=myisam;
insert into test100 values(1,’aaaaa’);
insert into test100 values(2,’bbbb’);
insert into test100 values(3,’ccccc’);
咱們應該定義對myisam進行整理
optimize table test100;
cmd控制檯:
在環境變量中配置mysql環境變量
mysqldump –u -帳號 –密碼 數據庫 [表名1 表名2..] > 文件路徑
案例: mysqldump -u -root root test > d:\temp.sql
好比: 把temp數據庫備份到 d:\temp.bak
mysqldump -u root -proot test > f:\temp.bak
若是你但願備份是,數據庫的某幾張表
mysqldump -u root -proot test dept > f:\temp.dept.sql
如何使用備份文件恢復咱們的數據.
mysql控制檯
source d:\temp.dept.bak
把備份數據庫的指令,寫入到 bat文件, 而後經過任務管理器去定時調用 bat文件.
mytask.bat 內容是:
@echo off F:\path\mysqlanzhuang\bin\mysqldump -u root -proot test dept > f:\temp.dept.sql |
建立執行計劃任務執行腳本。
垂直拆分就是要把表按模塊劃分到不一樣數據庫表中(固然原則仍是不破壞第三範式),這種拆分在大型網站的演變過程當中是很常見的。當一個網站還在很小的時候,只有小量的人來開發和維護,各模塊和表都在一塊兒,當網站不斷豐富和壯大的時候,也會變成多個子系統來支撐,這時就有按模塊和功能把表劃分出來的需求。其實,相對於垂直切分更進一步的是服務化改造,說得簡單就是要把原來強耦合的系統拆分紅多個弱耦合的服務,經過服務間的調用來知足業務需求看,所以表拆出來後要經過服務的形式暴露出去,而不是直接調用不一樣模塊的表,淘寶在架構不斷演變過程,最重要的一環就是服務化改造,把用戶、交易、店鋪、寶貝這些核心的概念抽取成獨立的服務,也很是有利於進行局部的優化和治理,保障核心模塊的穩定性
垂直拆分用於分佈式場景。
上面談到垂直切分只是把表按模塊劃分到不一樣數據庫,但沒有解決單表大數據量的問題,而水平切分就是要把一個表按照某種規則把數據劃分到不一樣表或數據庫裏。例如像計費系統,經過按時間來劃分表就比較合適,由於系統都是處理某一時間段的數據。而像SaaS應用,經過按用戶維度來劃分數據比較合適,由於用戶與用戶之間的隔離的,通常不存在處理多個用戶數據的狀況,簡單的按user_id範圍來水平切分
通俗理解:水平拆分行,行數據拆分到不一樣表中, 垂直拆分列,表數據拆分到不一樣表中
思路:在大型電商系統中,天天的會員人數不斷的增長。達到必定瓶頸後如何優化查詢。
可能你們會想到索引,萬一用戶量達到上億級別,如何進行優化呢?
使用水平分割拆分數據庫表。
使用水平分割拆分表,具體根據業務需求,有的按照註冊時間、取摸、帳號規則、年份等。
首先我建立三張表 user0 / user1 /user2 , 而後我再建立 uuid表,該表的做用就是提供自增的id。
create table user0( id int unsigned primary key , name varchar(32) not null default '', pwd varchar(32) not null default '') engine=myisam charset utf8;
create table user1( id int unsigned primary key , name varchar(32) not null default '', pwd varchar(32) not null default '') engine=myisam charset utf8;
create table user2( id int unsigned primary key , name varchar(32) not null default '', pwd varchar(32) not null default '') engine=myisam charset utf8;
create table uuid( id int unsigned primary key auto_increment)engine=myisam charset utf8;
|
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> |
@Service public class UserService {
@Autowired private JdbcTemplate jdbcTemplate;
public String regit(String name, String pwd) { // 1.先獲取到 自定增加ID String idInsertSQL = "INSERT INTO uuid VALUES (NULL);"; jdbcTemplate.update(idInsertSQL); Long insertId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class); // 2.判斷存儲表名稱 String tableName = "user" + insertId % 3; // 3.註冊數據 String insertUserSql = "INSERT INTO " + tableName + " VALUES ('" + insertId + "','" + name + "','" + pwd + "');"; System.out.println("insertUserSql:" + insertUserSql); jdbcTemplate.update(insertUserSql); return "success"; }
public String get(Long id) { String tableName = "user" + id % 3; String sql = "select name from " + tableName + " where id="+id; System.out.println("SQL:" + sql); String name = jdbcTemplate.queryForObject(sql, String.class); return name; }
} |
@RestController public class UserController { @Autowired private UserService userService;
@RequestMapping("/regit") public String regit(String name, String pwd) { return userService.regit(name, pwd); }
@RequestMapping("/get") public String get(Long id) { String name = userService.get(id); return name; }
} |
在數據庫集羣架構中,讓主庫負責處理事務性查詢,而從庫只負責處理select查詢,讓二者分工明確達到提升數據庫總體讀寫性能。固然,主數據庫另一個功能就是負責將事務性查詢致使的數據變動同步到從庫中,也就是寫操做。
1)分攤服務器壓力,提升機器的系統處理效率
讀寫分離適用於讀遠比寫的場景,若是有一臺服務器,當select不少時,update和delete會被這些select訪問中的數據堵塞,等待select結束,併發性能並不高,而主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖爭用;
假如咱們有1主3從,不考慮上述1中提到的從庫單方面設置,假設如今1分鐘內有10條寫入,150條讀取。那麼,1主3從至關於共計40條寫入,而讀取總數沒變,所以平均下來每臺服務器承擔了10條寫入和50條讀取(主庫不承擔讀取操做)。所以,雖然寫入沒變,可是讀取大大分攤了,提升了系統性能。另外,當讀取被分攤後,又間接提升了寫入的性能。因此,整體性能提升了,說白了就是拿機器和帶寬換性能;
2)增長冗餘,提升服務可用性,當一臺數據庫服務器宕機後能夠調整另一臺從庫以最快速度恢復服務
依賴於二進制日誌,binary-log.
二進制日誌中記錄引發數據庫發生改變的語句
Insert 、delete、update、create table
Scale Out是指Application能夠在水平方向上擴展。通常對數據中心的應用而言,Scale out指的是當添加更多的機器時,應用仍然能夠很好的利用這些機器的資源來提高本身的效率從而達到很好的擴展性。
Scale Up是指Application能夠在垂直方向上擴展。通常對單臺機器而言,Scale Up值得是當某個計算節點(機器)添加更多的CPU Cores,存儲設備,使用更大的內存時,應用能夠很充分的利用這些資源來提高本身的效率從而達到很好的擴展性。
數據如何不被丟失
備份
讀寫分離
數據庫負載均衡
高可用
兩臺windows操做系統 ip分別爲: 172.27.185.1(主)、172.27.185.2(從)
GRANT REPLICATION SLAVE ON *.* TO 'root'@'172.27.185.2' IDENTIFIED BY 'root';
server-id=200 log-bin=mysql-bin relay-log=relay-bin relay-log-index=relay-bin-index |
重啓mysql服務
server-id = 210 replicate-do-db =itmayiedu #須要同步數據庫 |
重啓mysql服務
stop slave; change master to master_host='172.27.185.1',master_user='root',master_password='root'; start slave; show slave status; |
是一個開源的分佈式數據庫系統,可是由於數據庫通常都有本身的數據庫引擎,而Mycat並無屬於本身的獨有數據庫引擎,全部嚴格意義上說並不能算是一個完整的數據庫系統,只能說是一個在應用和數據庫之間起橋樑做用的中間件。
在Mycat中間件出現以前,MySQL主從複製集羣,若是要實現讀寫分離,通常是在程序段實現,這樣就帶來了一個問題,即數據段和程序的耦合度過高,若是數據庫的地址發生了改變,那麼個人程序也要進行相應的修改,若是數據庫不當心掛掉了,則同時也意味着程序的不可用,而對於不少應用來講,並不能接受;
引入Mycat中間件能很好地對程序和數據庫進行解耦,這樣,程序只需關注數據庫中間件的地址,而無需知曉底層數據庫是如何提供服務的,大量的通用數據聚合、事務、數據源切換等工做都由中間件來處理;
Mycat中間件的原理是對數據進行分片處理,從原有的一個庫,被切分爲多個分片數據庫,全部的分片數據庫集羣構成完成的數據庫存儲,有點相似磁盤陣列中的RAID0.
C/S (Client - Server 客戶端-服務器端)
典型應用:QQ軟件 ,飛秋,紅蜘蛛。
特色:
1)必須下載特定的客戶端程序。
2)服務器端升級,客戶端升級。
B/S (Broswer -Server 瀏覽器端- 服務器端)
典型應用: 騰訊官方(www.qq.com) 163新聞網站, 螞蟻課堂官網(俗稱:網站)
特色:
1)不須要安裝特定的客戶端(只須要安裝瀏覽器便可!!)
2)服務器端升級,瀏覽器不須要升級!!!!
javaweb的程序就是b/s軟件結構!!!
一、在瀏覽器中輸入www.qq.com域名,操做系統會先檢查本身本地的hosts文件是否有這個網址映射關係,若是有,就先調用這個IP地址映射,完成域名解析。
二、若是hosts裏沒有這個域名的映射,則查找本地DNS解析器緩存,是否有這個網址映射關係,若是有,直接返回,完成域名解析。
外網映射工具的做用,主要將本地服務映射到外網。
應用場景:支付回調、微信開發、對接第三方接口等。
映射工具Ngrok、花生殼等。
靜態資源: 當用戶屢次訪問這個資源,資源的源代碼永遠不會改變的資源。
動態資源:當用戶屢次訪問這個資源,資源的源代碼可能會發送改變。
構造方法: 建立servlet對象的時候調用。
默認狀況下,第一次訪問servlet的時候建立servlet對象 只調用1次。證實servlet對象在tomcat是單實例的。
init方法: 建立完servlet對象的時候調用。只調用1次。
service方法: 每次發出請求時調用。調用n次。
destroy方法: 銷燬servlet對象的時候調用。中止服務器或者從新部署web應用時銷燬servlet對象。只調用1次。
由於Servlet是經過Java反射機制,讀取web.xml配置中的servlet-class 完整路徑,進行反射默認執行無參構造函數,因此只要servlet類執行無參構造函數永遠只執行一遍,則Servlet是單例的。
注意: servlet對象在tomcat服務器是單實例多線程的。
由於servlet是多線程的,因此當多個servlet的線程同時訪問了servlet的共享數據,如成員變量,可能會引起線程安全問題。
解決辦法:
1)把使用到共享數據的代碼塊進行同步(使用synchronized關鍵字進行同步)
2)建議在servlet類中儘可能不要使用成員變量。若是確實要使用成員,必須同步。並且儘可能縮小同步代碼塊的範圍。(哪裏使用到了成員變量,就同步哪裏!!),以免由於同步而致使併發效率下降。
1)轉發
a)地址欄不會改變
b)轉發只能轉發到當前web應用內的資源
c)能夠在轉發過程當中,能夠把數據保存到request域對象中
2)重定向
a)地址欄會改變,變成重定向到地址。
b)重定向能夠跳轉到當前web應用,或其餘web應用,甚至是外部域名網站。
c)不能再重定向的過程,把數據保存到request中。
重定向會發送兩次請求,瀏覽器認識狀態碼爲302,會再次向服務器發送一次請求,獲取請求頭的中location的value值進行重定向。
Cookie會話數據保存在瀏覽器客戶端
服務器建立Cookie,將Cookie內容以響應頭方式發送給客戶端存放在本地,當下次發送請求時.會將Cookie信息以請求方式發送給服務器端
注意:Cookie信息不能誇瀏覽器訪問
Session會話保存與服務器端
服務器建立Session,Session內容存放服務器端上,以響應頭方式將SessionId發送給客戶端保存,當下次發送請求時,會將SessionID 以請求頭方式發送給服務器端
注意: 瀏覽器關閉,只是清除了Sessionid,並無清除Session
1)服務器建立cookie對象,把會話數據存儲到cookie對象中。
new Cookie("name","value");
2) 服務器發送cookie信息到瀏覽器
response.addCookie(cookie);
舉例: set-cookie: name=eric (隱藏發送了一個set-cookie名稱的響應頭)
3)瀏覽器獲得服務器發送的cookie,而後保存在瀏覽器端。
4)瀏覽器在下次訪問服務器時,會帶着cookie信息
舉例: cookie: name=eric (隱藏帶着一個叫cookie名稱的請求頭)
5)服務器接收到瀏覽器帶來的cookie信息
request.getCookies();
購物車、顯示用戶上次訪問的時間
1)第一次訪問建立session對象,給session對象分配一個惟一的ID,叫JSESSIONID
new HttpSession();
2)把JSESSIONID做爲Cookie的值發送給瀏覽器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3)第二次訪問的時候,瀏覽器帶着JSESSIONID的cookie訪問服務器
4)服務器獲得JSESSIONID,在服務器的內存中搜索是否存放對應編號的session對象。
if(找到){
return map.get(sessionID);
}
Map<String,HttpSession>]
<"s001", s1>
<"s001,"s2>
5)若是找到對應編號的session對象,直接返回該對象
6)若是找不到對應編號的session對象,建立新的session對象,繼續走1的流程
結論:經過JSESSION的cookie值在服務器找session對象!!!!!
token其實就是一個令牌,具備隨機性,相似於sessionId。
在對接一些第三方平臺的時候,爲了可以保證數據安全性,一般會使用一些令牌進行交互
例如: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
token生成規則,只要保證token生成一個不重複的惟一字符串便可。
使用jdk自帶的uuid生成規則。
UUID含義是通用惟一識別碼 (Universally Unique Identifier),這是一個軟件建構的標準,也是被開源軟件基金會 (Open Software Foundation, OSF)
的組織應用在分佈式計算環境 (Distributed Computing Environment, DCE) 領域的一部分。
UUID 的目的,是讓分佈式系統中的全部元素,都能有惟一的辨識資訊,而不須要透過中央控制端來作辨識資訊的指定。如此一來,每一個人均可以創建不與其它人衝突的 UUID。
在這樣的狀況下,就不需考慮數據庫創建時的名稱重複問題。目前最普遍應用的 UUID,便是微軟的 Microsoft's Globally Unique Identifiers (GUIDs),而其餘重要的應用,
則有 Linux ext2/ext3 檔案系統、LUKS 加密分割區、GNOME、KDE、Mac OS X 等等
UUID保證對在同一時空中的全部機器都是惟一的。一般平臺會提供生成的API。按照開放軟件基金會(OSF)制定的標準計算,用到了以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字
UUID由如下幾部分的組合:
(1)當前日期和時間,UUID的第一個部分與時間有關,若是你在生成一個UUID以後,過幾秒又生成一個UUID,則第一個部分不一樣,其他相同。
(2)時鐘序列。
(3)全局惟一的IEEE機器識別號,若是有網卡,從網卡MAC地址得到,沒有網卡以其餘方式得到。
UUID的惟一缺陷在於生成的結果串會比較長。關於UUID這個標準使用最廣泛的是微軟的GUID(Globals Unique Identifiers)。在ColdFusion中能夠用CreateUUID()函數很簡單地生成UUID,
其格式爲:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每一個 x 是 0-9 或 a-f 範圍內的一個十六進制的數字。而標準的UUID格式爲:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12);
UUID.randomUUID().toString() |
Filter 是Servlet技術中的過濾器,主要作攔截請求做用,通常用於防護XSS攻擊、權限、登陸判斷等。
http協議: 對瀏覽器客戶端 和 服務器端 之間數據傳輸的格式規範
請求行
請求頭
請求內容
響應行
響應頭
響應內容
雖說 HTTPS 有很大的優點,但其相對來講,仍是存在不足之處的:
(1)HTTPS 協議握手階段比較費時,會使頁面的加載時間延長近 50%,增長 10% 到 20% 的耗電;
(2)HTTPS 鏈接緩存不如 HTTP 高效,會增長數據開銷和功耗,甚至已有的安全措施也會所以而受到影響;
(3)SSL 證書須要錢,功能越強大的證書費用越高,我的網站、小網站沒有必要通常不會用。
(4)SSL 證書一般須要綁定 IP,不能在同一 IP 上綁定多個域名,IPv4 資源不可能支撐這個消耗。
(5)HTTPS 協議的加密範圍也比較有限,在黑客攻擊、拒絕服務攻擊、服務器劫持等方面幾乎起不到什麼做用。最關鍵的,SSL 證書的信用鏈體系並不安全,特別是在某些國家能夠控制 CA 根證書的狀況下,中間人攻擊同樣可行。
常見的請求方式: GET 、 POST、 HEAD、 TRACE、 PUT、 CONNECT 、DELETE
經常使用的請求方式: GET 和 POST
表單提交:
<form action="提交地址" method="GET/POST">
<form>
GET vs POST 區別
1)GET方式提交
a)地址欄(URI)會跟上參數數據。以?開頭,多個參數之間以&分割。
GET /day09/testMethod.html?name=eric&password=123456 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,en-us;q=0.8,zh;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://localhost:8080/day09/testMethod.html Connection: keep-alive |
b)GET提交參數數據有限制,不超過1KB。
c)GET方式不適合提交敏感密碼。
d)注意: 瀏覽器直接訪問的請求,默認提交方式是GET方式
2)POST方式提交
a)參數不會跟着URI後面。參數而是跟在請求的實體內容中。沒有?開頭,多個參數之間以&分割。
POST /day09/testMethod.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,en-us;q=0.8,zh;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://localhost:8080/day09/testMethod.html Connection: keep-alive
name=eric&password=123456 |
b)POST提交的參數數據沒有限制。
c)POST方式提交敏感數據。
3.2 請求頭
Accept: text/html,image/* -- 瀏覽器接受的數據類型 Accept-Charset: ISO-8859-1 -- 瀏覽器接受的編碼格式 Accept-Encoding: gzip,compress --瀏覽器接受的數據壓縮格式 Accept-Language: en-us,zh- --瀏覽器接受的語言 Host: www.it315.org:80 --(必須的)當前請求訪問的目標地址(主機:端口) If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT --瀏覽器最後的緩存時間 Referer: http://www.it315.org/index.jsp -- 當前請求來自於哪裏 User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) --瀏覽器類型 Cookie:name=eric -- 瀏覽器保存的cookie信息 Connection: close/Keep-Alive -- 瀏覽器跟服務器鏈接狀態。close: 鏈接關閉 keep-alive:保存鏈接。 Date: Tue, 11 Jul 2000 18:23:51 GMT -- 請求發出的時間 |
Postmen(谷歌插件)、RestClient
httpclient、HttpURLConnection
$.ajax({ type : 'post', dataType : "text", url : "http://a.a.com/a/FromUserServlet", data : "userName=餘勝軍&userAge=19", success : function(msg) { alert(msg); } }); |
Spring框架,能夠解決對象建立以及對象之間依賴關係的一種框架。
且能夠和其餘框架一塊兒使用;Spring與Struts, Spring與hibernate
(起到整合(粘合)做用的一個框架)
Spring提供了一站式解決方案:
1) Spring Core spring的核心功能: IOC容器, 解決對象建立及依賴關係
2) Spring Web Spring對web模塊的支持。
-à 能夠與struts整合,讓struts的action建立交給spring
-à spring mvc模式
3) Spring DAO Spring 對jdbc操做的支持 【JdbcTemplate模板工具類】
4) Spring ORM spring對orm的支持:
à 既能夠與hibernate整合,【session】
à 也能夠使用spring的對hibernate操做的封裝
5)Spring AOP 切面編程
6)SpringEE spring 對javaEE其餘模塊的支持
會報錯。
SpringIOC就是把每一個bean與bean之間的關係交給第三方容器進行管理,這個容器就是Spring。
什麼是AOP
Aop, aspect object programming 面向切面編程
功能: 讓關注點代碼與業務代碼分離!
關注點,
重複代碼就叫作關注點;
切面,
關注點造成的類,就叫切面(類)!
面向切面編程,就是指 對不少功能都有的重複的代碼抽取,再在運行的時候網業務方法上動態植入「切面類代碼」。
切入點,
執行目標對象方法,動態植入切面代碼。
能夠經過切入點表達式,指定攔截哪些類的哪些方法; 給指定的類在運行的時候植入切面類代碼。
應用場景:事物、日誌、權限控制
步驟:
1) 先引入aop相關jar文件 (aspectj aop優秀組件)
spring-aop-3.2.5.RELEASE.jar 【spring3.2源碼】
aopalliance.jar 【spring2.5源碼/lib/aopalliance】
aspectjweaver.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
注意: 用到spring2.5版本的jar文件,若是用jdk1.7可能會有問題。
須要升級aspectj組件,即便用aspectj-1.8.2版本中提供jar文件提供。
2) bean.xml中引入aop名稱空間
3) 開啓aop註解
4) 使用註解
@Aspect 指定一個類爲切面類
@Pointcut("execution(* com.itmayiedu.service.UserService.add(..))") 指定切入點表達式
@Before("pointCut_()") 前置通知: 目標方法以前執行
@After("pointCut_()") 後置通知:目標方法以後執行(始終執行)
@AfterReturning("pointCut_()") 返回後通知: 執行方法結束前執行(異常不執行)
@AfterThrowing("pointCut_()") 異常通知: 出現異常時候執行
@Around("pointCut_()") 環繞通知: 環繞目標方法執行
@Component @Aspect public class Aop { @Before("execution(* com.itmayiedu.service.UserService.add(..))") public void begin() { System.out.println("前置通知"); }
@After("execution(* com.itmayiedu.service.UserService.add(..))") public void commit() { System.out.println("後置通知"); }
@AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))") public void afterReturning() { System.out.println("運行通知"); }
@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))") public void afterThrowing() { System.out.println("異常通知"); }
@Around("execution(* com.itmayiedu.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("我是環繞通知-前"); proceedingJoinPoint.proceed(); System.out.println("我是環繞通知-後"); }
}
|
Xml實現aop編程:
1) 引入jar文件 【aop 相關jar, 4個】
2) 引入aop名稱空間
3)aop 配置
* 配置切面類 (重複執行代碼造成的類)
* aop配置
攔截哪些方法 / 攔截到方法後應用通知代碼
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao 實例 --> <bean id="userDao" class="com.itmayiedu.UserDao"></bean> <bean id="orderDao" class="com.itmayiedu.OrderDao"></bean>
<!-- 切面類 --> <bean id="aop" class="com.itmayiedu.Aop"></bean>
<!-- Aop配置 --> <aop:config> <!-- 定義一個切入點表達式: 攔截哪些方法 --> <aop:pointcut expression="execution(* com.itmayiedu.*.*(..))" id="pt"/> <!-- 切面 --> <aop:aspect ref="aop"> <!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pt"/> <!-- 前置通知: 在目標方法調用前執行 --> <aop:before method="begin" pointcut-ref="pt"/> <!-- 後置通知: --> <aop:after method="after" pointcut-ref="pt"/> <!-- 返回後通知 --> <aop:after-returning method="afterReturning" pointcut-ref="pt"/> <!-- 異常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect> </aop:config> </beans> |
Spring默認是單例
當一個bean的 做用域設置爲singleton, 那麼Spring IOC容器中只會存在一個共享的bean實例,而且全部對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。換言之,當把 一個bean定義設置爲singleton做用域時,Spring IOC容器只會建立該bean定義的惟一實例。這個單一實例會被存儲到單例緩存(singleton cache)中,而且全部針對該bean的後續請求和引用都 將返回被緩存的對象實例,這裏要注意的是singleton做用域和GOF設計模式中的單例是徹底不一樣的,單例設計模式表示一個ClassLoader中 只有一個class存在,而這裏的singleton則表示一個容器對應一個bean,也就是說當一個bean被標識爲singleton時 候,spring的IOC容器中只會存在一個該bean。
prototype做用域部署的bean,每一次請求(將其注入到另外一個bean中,或者以程序的方式調用容器的 getBean()方法)都會產生一個新的bean實例,至關與一個new的操做,對於prototype做用域的bean,有一點很是重要,那就是Spring不能對一個prototype bean的整個生命週期負責,容器在初始化、配置、裝飾或者是裝配完一個prototype實例後,將它交給客戶端,隨後就對該prototype實例漠不關心了。無論何種做用域,容器都會調用全部對象的初始化生命週期回調方法,而對prototype而言,任何配置好的析構生命週期回調方法都將不會被調用。 清除prototype做用域的對象並釋聽任何prototype bean所持有的昂貴資源,都是客戶端代碼的職責。(讓Spring容器釋放被singleton做用域bean佔用資源的一種可行方式是,經過使用 bean的後置處理器,該處理器持有要被清除的bean的引用。)
request表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效,配置實例:
request、session、global session使用的時候首先要在初始化web的web.xml中作以下配置:
session做用域表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP session內有效
建立對象, 有幾種方式:
1) 調用無參數構造器
2) 帶參數構造器
3) 工廠建立對象
工廠類,靜態方法建立對象
工廠類,非靜態方法建立對象
Spring中,如何給對象的屬性賦值? 【DI, 依賴注入】
1) 經過構造函數
2) 經過set方法給屬性注入值
3) p名稱空間
4) 註解
原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。
一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態,也就是說一個事務執行以前和執行以後都必須處於一致性狀態。
拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。
即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。
關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。
持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。
例如咱們在使用JDBC操做數據庫時,在提交事務方法後,提示用戶事務操做完成,當咱們程序執行完成直到看到提示後,就能夠認定事務以及正確提交,即便這時候數據庫出現了問題,也必需要將咱們的事務徹底執行完成,不然就會形成咱們看到提示事務處理完畢,可是數據庫由於故障而沒有執行事務的重大錯誤。
編程式事務控制
本身手動控制事務,就叫作編程式事務控制。
Jdbc代碼:
Conn.setAutoCommite(false); // 設置手動控制事務
Hibernate代碼:
Session.beginTransaction(); // 開啓一個事務
【細粒度的事務控制: 能夠對指定的方法、指定的方法的某幾行添加事務控制】
(比較靈活,但開發起來比較繁瑣: 每次都要開啓、提交、回滾.)
聲明式事務控制
Spring提供了對事務的管理, 這個就叫聲明式事務管理。
Spring聲明事物有xml方式和註解方式
Spring提供了對事務控制的實現。用戶若是想用Spring的聲明式事務管理,只須要在配置文件中配置便可; 不想使用時直接移除配置。這個實現了對事務控制的最大程度的解耦。
Spring聲明式事務管理,核心實現就是基於Aop。
【粗粒度的事務控制: 只能給整個方法應用事務,不能夠對方法的某幾行應用事務。】
(由於aop攔截的是方法。)
Spring聲明式事務管理器類:
Jdbc技術:DataSourceTransactionManager
Hibernate技術:HibernateTransactionManager
Spring中事務的定義:
Propagation(key屬性肯定代理應該給哪一個方法增長事務行爲。這樣的屬性最重要的部份是傳播行爲。)有如下選項可供使用:
1、 用戶發送請求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到請求調用HandlerMapping處理器映射器。
3、 處理器映射器找到具體的處理器(能夠根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(若是有則生成)一併返回給DispatcherServlet。
4、 DispatcherServlet調用HandlerAdapter處理器適配器。
5、 HandlerAdapter通過適配調用具體的處理器(Controller,也叫後端控制器)。
6、 Controller執行完成返回ModelAndView。
7、 HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。
8、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
9、 ViewReslover解析後返回具體View。
10、DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。
11、 DispatcherServlet響應用戶。
在您第1次接觸和學習Spring框架的時候,是否由於其繁雜的配置而退卻了?在你第n次使用Spring框架的時候,是否以爲一堆反覆黏貼的配置有一些厭煩?那麼您就不妨來試試使用Spring Boot來讓你更易上手,更簡單快捷地構建Spring應用!
Spring Boot讓咱們的Spring應用變的更輕量化。好比:你能夠僅僅依靠一個Java類來運行一個Spring引用。你也能夠打包你的應用爲jar並經過使用java -jar來運行你的Spring Web應用。
Spring Boot的主要優勢:
爲全部Spring開發者更快的入門
開箱即用,提供各類默認配置來簡化項目配置
內嵌式容器簡化Web項目
沒有冗餘代碼生成和XML配置的要求
本章主要目標完成Spring Boot基礎項目的構建,而且實現一個簡單的Http請求處理,經過這個例子對Spring Boot有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。
爲全部Spring開發者更快的入門
開箱即用,提供各類默認配置來簡化項目配置
內嵌式容器簡化Web項目
沒有冗餘代碼生成和XML配置的要求
本章主要目標完成Spring Boot基礎項目的構建,而且實現一個簡單的Http請求處理,經過這個例子對Spring Boot有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。
在上加上RestController 表示修飾該Controller全部的方法返回JSON格式,直接能夠編寫
Restful接口
註解:做用在於讓 Spring Boot 根據應用所聲明的依賴來對 Spring 框架進行自動配置
這個註解告訴Spring Boot根據添加的jar依賴猜想你想如何配置Spring。因爲spring-boot-starter-web添加了Tomcat和Spring MVC,因此auto-configuration將假定你正在開發一個web應用並相應地對Spring進行設置。
標識爲啓動類---Springboot默認端口號爲8080
SpringBoot啓動方式:2種
@RestController、 @EnableAutoConfiguration
@ComponentScan(basePackages="com.itmayiedu.controller")、 @EnableAutoConfiguration
在咱們開發Web應用的時候,須要引用大量的js、css、圖片等靜態資源。
默認配置
Spring Boot默認提供靜態資源目錄位置需置於classpath下,目錄名需符合以下規則:舉例:咱們能夠在src/main/resources/目錄下建立static,在該位置放置一個圖片文件。啓動程序後,嘗試訪問http://localhost:8080/D.jpg 如能顯示圖片,配置成功。
@ControllerAdvice 是 controller 的一個輔助類,最經常使用的就是做爲全局異常處理的切面類@ControllerAdvice 能夠指定掃描範圍。@ControllerAdvice 約定了幾種可行的返回值,若是是直接返回 model 類的話,須要使用@ResponseBody 進行 json 轉換
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Map<String, Object> exceptionHandler() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("errorCode", "101");
map.put("errorMsg", "系統錯誤!");
return map;
}
}
經過 @RestController來處理請求,因此返回的內容爲json對象
Thymeleaf、FreeMarker、Velocity、Groovy、Mustache
Spring Boot建議使用這些模板引擎,避免使用JSP,若必定要使用JSP將沒法實現
、Spring Boot的多種特性,具體可見後文:支持JSP的配置 。當你使用上述模板引擎中的任何一個,它們默認的模板配置路徑爲:src/main/resources/templates。
使用Freemarker模板引擎渲染web視圖
使用JSP渲染Web視圖
maven多引入spring-boot-starter-jdbc,mysql-connector-java
maven 引入mybatis-spring-boot-starter
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Mapper代碼
public interface UserMapper {
@Select("SELECT * FROM USERS WHERE NAME = #{name}")
User findByName(@Param("name") String name);
}
啓動
@ComponentScan(basePackages = "com.itmayiedu")
@MapperScan(basePackages = "com.itmayiedu.mapper")
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
maven引入 spring-boot-starter-data-jpa
springboot整合多數據源
springboot整合事物管理
springboot默認集成事物,只主要在方法上加上 @Transactional便可
SpringBoot分佈式事物管理
使用springboot+jta+atomikos 分佈式事物管理
maven引入spring-boot-starter-jta-atomikos
使用log4j記錄日誌
新建log4j配置文件
使用AOP統一處理Web請求日誌
maven引入spring-boot-starter-aop
@Aspect
@Component
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("execution(public * com.itmayiedu.controller..*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到請求,記錄請求內容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
Enumeration<String> enu = request.getParameterNames();
while (enu.hasMoreElements()) {
String name = (String) enu.nextElement();
logger.info("name:{},value:{}", name, request.getParameter(name));
}
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求,返回內容
logger.info("RESPONSE : " + ret);
}
}
註解配置與EhCache使用
maven引入spring-boot-starter-cache
新建ehcache.xml 文件,採用默認配置
代碼使用:
@CacheConfig(cacheNames = "baseCache")
public interface UserMapper {
@Select("select * from users where name=#{name}")
@Cacheable
UserEntity findName(@Param("name") String name);
}
清除緩存
@Autowired
private CacheManager cacheManager;
@RequestMapping("/remoKey")
public void remoKey() {
cacheManager.getCache("baseCache").clear();
}
使用Redis作集中式緩存
在Spring Boot的主類中加入 @EnableScheduling註解,啓用定時任務的配置
@Component
public class ScheduledTasks {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
System.out.println("如今時間:" + dateFormat.format(new Date()));
}
}
啓動加上 @EnableAsync ,須要執行異步方法上加入 @Async
自定義參數
@Value("${name}")
private String name;
@ResponseBody
@RequestMapping("/getValue")
public String getValue() {
return name;
}
spring.profiles.active=pre
application-dev.properties:開發環境
application-test.properties:測試環境
application-prod.properties:生產環境
server.port=8888
server.context-path=/itmayiedu
建立application.yml
server:
port: 8090
context-path: /itmayiedu
在使用 yml 來配置的時候不要保留對應的 applicaiont-dev.properties,application-proc.properties… 等
properties 配置文件,不然,SpringBoot 會優先去 properties 配置文件中查找。
使用mvn package 打包
使用java –jar 包名
若是報錯沒有主清單,在pom文件中新增
Mybatis是輕量級封裝,Hibernate是重量級封裝
Mybatis 以SQL語句獲得對象,hibernate是以對象獲得SQL語句
String resource = "mybatis.xml";
// 讀取配置文件
Reader reader = Resources.getResourceAsReader(resource);
// 獲取會話工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession openSession = sqlSessionFactory.openSession();
// 查詢
String sql = "com.itmayiedu.mapper.UserMapper.delUser";
int reuslt = openSession.delete(sql,1);
優先使用 #{}。由於 ${} 會致使 sql 注入的問題
產生緣由
網絡延時、從新刷新、點擊瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交
使用javascript 解決
既然存在上述所說的表單重複提交問題,那麼咱們就要想辦法解決,比較經常使用的方法是採用JavaScript來防止表單重複提交,具體作法以下:
修改form.jsp頁面,添加以下的JavaScript代碼來防止表單重複提交
代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表單</title> <script type="text/javascript"> var isFlag = false; //表單是否已經提交標識,默認爲false
function submitFlag() {
if (isFlag == false) { isFlag = true; return true; } else { return false; }
} </script> </head>
<body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return submitFlag()"> 用戶名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html> |
除了用這種方式以外,常常見的另外一種方式就是表單提交以後,將提交按鈕設置爲不可用,讓用戶沒有機會點擊第二次提交按鈕,代碼以下:
function dosubmit(){ //獲取表單提交按鈕 var btnSubmit = document.getElementById("submit"); //將表單提交按鈕設置爲不可用,這樣就能夠避免用戶再次點擊提交按鈕 btnSubmit.disabled= "disabled"; //返回true讓表單能夠正常提交 return true; } |
對於【場景二】和【場景三】致使表單重複提交的問題,既然客戶端沒法解決,那麼就在服務器端解決,在服務器端解決就須要用到session了。
具體的作法:在服務器端生成一個惟一的隨機標識號,專業術語稱爲Token(令牌),同時在當前用戶的Session域中保存這個Token。而後將Token發送到客戶端的Form表單中,在Form表單中使用隱藏域來存儲這個Token,表單提交的時候連同這個Token一塊兒提交到服務器端,而後在服務器端判斷客戶端提交上來的Token與服務器端生成的Token是否一致,若是不一致,那就是重複提交了,此時服務器端就能夠不處理重複提交的表單。若是相同則處理表單提交,處理完後清除當前用戶的Session域中存儲的標識號。
在下列狀況下,服務器程序將拒絕處理用戶提交的表單請求:
存儲Session域中的Token(令牌)與表單提交的Token(令牌)不一樣。
當前用戶的Session中不存在Token(令牌)。
用戶提交的表單數據中沒有Token(令牌)。
轉發代碼:
@WebServlet("/ForwardServlet") public class ForwardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getSession().setAttribute("sesionToken", TokenUtils.getToken()); req.getRequestDispatcher("form.jsp").forward(req, resp); } }
|
轉發頁面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表單</title>
</head>
<body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return dosubmit()"> <input type="hidden" name="token" value="${sesionToken}"> 用戶名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html> |
後端Java代碼:
@WebServlet("/DoFormServlet") public class DoFormServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); boolean flag = isFlag(req); if (!flag) { resp.getWriter().write("已經提交..."); System.out.println("數據已經提交了.."); return; } String userName = req.getParameter("userName"); try { Thread.sleep(300); } catch (Exception e) { // TODO: handle exception } System.out.println("往數據庫插入數據...." + userName); resp.getWriter().write("success"); }
public boolean isFlag(HttpServletRequest request) { HttpSession session = request.getSession(); String sesionToken = (String) session.getAttribute("sesionToken"); String token = request.getParameter("token"); if (!(token.equals(sesionToken))) { return false; } session.removeAttribute("sesionToken"); return true; } }
|
XSS攻擊使用Javascript腳本注入進行攻擊
解決辦法:就是將請求可能會發送的特殊字符、javascript標籤轉換爲html代碼執行
例如在表單中注入: <script>location.href='http://www.itmayiedu.com'</script>
注意:谷歌瀏覽器 已經防止了XSS攻擊,爲了演示效果,最好使用火狐瀏覽器
實例:
演示:
代碼: fromToXss.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="XssDemo" method="post"> <input type="text" name="userName"> <input type="submit"> </form> </body> </html> |
代碼: XssDemo
import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@WebServlet("/XssDemo") public class XssDemo extends HttpServlet {
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); req.setAttribute("userName", userName); req.getRequestDispatcher("showUserName.jsp").forward(req, resp); }
}
|
代碼: showUserName.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title>
</head> <body>userName:${userName}
</body> </html> |
7.1 解決方案
使用Fileter過濾器過濾器注入標籤
FilterDemo
import java.io.IOException; import java.util.Map;
import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
/** * 使用Filter 打印參數 * * @author Administrator * */
public class FilterDemo implements Filter { public FilterDemo() { System.out.println("FilterDemo 構造函數被執行..."); }
/** * 銷燬 */ public void destroy() { System.out.println("destroy"); }
public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) throws IOException, ServletException { System.out.println("doFilter"); HttpServletRequest request = (HttpServletRequest) paramServletRequest; XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(request); paramFilterChain.doFilter(xssRequestWrapper, paramServletResponse);
} /** * 初始化 */ public void init(FilterConfig paramFilterConfig) throws ServletException { System.out.println("init"); } }
|
XssAndSqlHttpServletRequestWrapper
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils;
/** * 防止XSS攻擊 */ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest request; public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) { super(request); this.request = request; } @Override public String getParameter(String name) { String value = request.getParameter(name); System.out.println("name:" + name + "," + value); if (!StringUtils.isEmpty(value)) { // 轉換Html value = StringEscapeUtils.escapeHtml4(value); } return value; } } |
跨域緣由產生:在當前域名請求網站中,默認不容許經過ajax請求發送其餘域名。
XMLHttpRequest cannot load 跨域問題解決辦法
後臺response添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持全部網站
JSONP的優缺點:
JSONP只支持get請求不支持psot請求
什麼是SQL語句注入
使用nginx轉發。
配置:
server { listen 80; server_name www.itmayiedu.com; location /A { proxy_pass http://a.a.com:81/A; index index.html index.htm; } location /B { proxy_pass http://b.b.com:81/B; index index.html index.htm; } } |
相關圖:
內部服務器使用HttpClient技術進行轉發
Sql語句若是是經過拼接方式執行的話,傳入參數 ‘ or 1=1 會發生語句成立,致使數據錯誤。
應該使用PreparedStatement 先編譯 在執行 經過?號穿參數的方式進行執行。
設置Nginx、Nginx Plus的鏈接請求在一個真實用戶請求的合理範圍內。好比,若是你以爲一個正經常使用戶每兩秒能夠請求一次登陸頁面,你就能夠設置Nginx每兩秒鐘接收一個客戶端IP的請求(大約等同於每分鐘30個請求)。
limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m; server { ... location /login.html { limit_req zone=one; ... } }
|
`limit_req_zone`命令設置了一個叫one的共享內存區來存儲請求狀態的特定鍵值,在上面的例子中是客戶端IP($binary_remote_addr)。location塊中的`limit_req`經過引用one共享內存區來實現限制訪問/login.html的目的。
設置Nginx、Nginx Plus的鏈接數在一個真實用戶請求的合理範圍內。好比,你能夠設置每一個客戶端IP鏈接/store不能夠超過10個。
NoSQL 是 Not Only SQL 的縮寫,意即"不只僅是SQL"的意思,泛指非關係型的數據庫。強調Key-Value Stores和文檔數據庫的優勢,而不是單純的反對RDBMS。
NoSQL產品是傳統關係型數據庫的功能閹割版本,經過減小用不到或不多用的功能,來大幅度提升產品性能
NoSQL產品 redis、mongodb Membase、HBase
Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。
Redis 與其餘 key - value 緩存產品有如下三個特色:
Redis支持數據的持久化,能夠將內存中的數據保存在磁盤中,重啓的時候能夠再次加載進行使用。
Redis不只僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
Redis支持數據的備份,即master-slave模式的數據備份。
主要可以體現 解決數據庫的訪問壓力。
例如:短信驗證碼時間有效期、session共享解決方案
性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
豐富的數據類型 – Redis支持二進制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 數據類型操做。
原子 – Redis的全部操做都是原子性的,同時Redis還支持對幾個操做全並後的原子性執行。
豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過時等等特性。
Redis有着更爲複雜的數據結構而且提供對他們的原子性操做,這是一個不一樣於其餘數據庫的進化路徑。Redis的數據類型都是基於基本數據結構的同時對程序員透明,無需進行額外的抽象。
Redis運行在內存中可是能夠持久化到磁盤,因此在對不一樣數據集進行高速讀寫時須要權衡內存,由於數據量不能大於硬件內存。在內存數據庫方面的另外一個優勢是,相比在磁盤上相同的複雜的數據結構,在內存中操做起來很是簡單,這樣Redis能夠作不少內部複雜性很強的事情。同時,在磁盤格式方面他們是緊湊的以追加的方式產生的,由於他們並不須要進行隨機訪問。
redis 127.0.0.1:6379> SET mykey "redis" OK redis 127.0.0.1:6379> GET mykey "redis" |
在上面的例子中,SET和GET是redis中的命令,而mykey是鍵的名稱。
Redis字符串命令用於管理Redis中的字符串值。如下是使用Redis字符串命令的語法。
redis 127.0.0.1:6379> COMMAND KEY_NAME
Shell
示例
redis 127.0.0.1:6379> SET mykey "redis"
OK
redis 127.0.0.1:6379> GET mykey
"redis"
Shell
在上面的例子中,SET和GET是redis中的命令,而mykey是鍵的名稱。
Redis字符串命令
下表列出了一些用於在Redis中管理字符串的基本命令。
編號 |
命令 |
描述說明 |
1 |
此命令設置指定鍵的值。 |
|
2 |
獲取指定鍵的值。 |
|
3 |
獲取存儲在鍵上的字符串的子字符串。 |
|
4 |
設置鍵的字符串值並返回其舊值。 |
|
5 |
返回在鍵處存儲的字符串值中偏移處的位值。 |
|
6 |
獲取全部給定鍵的值 |
|
7 |
存儲在鍵上的字符串值中設置或清除偏移處的位 |
|
8 |
使用鍵和到期時間來設置值 |
|
9 |
設置鍵的值,僅當鍵不存在時 |
|
10 |
在指定偏移處開始的鍵處覆蓋字符串的一部分 |
|
11 |
獲取存儲在鍵中的值的長度 |
|
12 |
爲多個鍵分別設置它們的值 |
|
13 |
爲多個鍵分別設置它們的值,僅當鍵不存在時 |
|
14 |
設置鍵的值和到期時間(以毫秒爲單位) |
|
15 |
將鍵的整數值增長1 |
|
16 |
將鍵的整數值按給定的數值增長 |
|
17 |
將鍵的浮點值按給定的數值增長 |
|
18 |
將鍵的整數值減1 |
|
19 |
按給定數值減小鍵的整數值 |
|
20 |
將指定值附加到鍵 |
Redis列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多能夠包含 232 - 1 個元素 (4294967295, 每一個列表超過40億個元素)。
redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql
(integer) 3
redis 127.0.0.1:6379> LRANGE runoobkey 0 10
1) "mysql"
2) "mongodb"
3) "redis"
Redis 列表命令
下表列出了列表相關的基本命令:
序號 |
命令及描述 |
1 |
BLPOP key1 [key2 ] timeout |
2 |
BRPOP key1 [key2 ] timeout |
3 |
BRPOPLPUSH source destination timeout |
4 |
LINDEX key index |
5 |
LINSERT key BEFORE|AFTER pivot value |
6 |
LLEN key |
7 |
LPOP key |
8 |
LPUSH key value1 [value2] |
9 |
LPUSHX key value |
10 |
LRANGE key start stop |
11 |
LREM key count value |
12 |
LSET key index value |
13 |
LTRIM key start stop |
14 |
RPOP key |
15 |
RPOPLPUSH source destination |
16 |
RPUSH key value1 [value2] |
17 |
RPUSHX key value |
Redis的Set是string類型的無序集合。集合成員是惟一的,這就意味着集合中不能出現重複的數據。
Redis 中 集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
實例
redis 127.0.0.1:6379> SADD runoobkey redis
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mongodb
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 0
redis 127.0.0.1:6379> SMEMBERS runoobkey
1) "mysql"
2) "mongodb"
3) "redis"
在以上實例中咱們經過 SADD 命令向名爲 runoobkey 的集合插入的三個元素。
Redis 集合命令
下表列出了 Redis 集合基本命令:
序號 |
命令及描述 |
1 |
SADD key member1 [member2] |
2 |
SCARD key |
3 |
SDIFF key1 [key2] |
4 |
SDIFFSTORE destination key1 [key2] |
5 |
SINTER key1 [key2] |
6 |
SINTERSTORE destination key1 [key2] |
7 |
SISMEMBER key member |
8 |
SMEMBERS key |
9 |
SMOVE source destination member |
10 |
SPOP key |
11 |
SRANDMEMBER key [count] |
12 |
SREM key member1 [member2] |
13 |
SUNION key1 [key2] |
14 |
SUNIONSTORE destination key1 [key2] |
15 |
Redis 有序集合和集合同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
有序集合的成員是惟一的,但分數(score)卻能夠重複。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。 集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
實例
redis 127.0.0.1:6379> ZADD runoobkey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD runoobkey 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES
1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"
在以上實例中咱們經過命令 ZADD 向 redis 的有序集合中添加了三個值並關聯上分數。
Redis 有序集合命令
下表列出了 redis 有序集合的基本命令:
Redis hash 是一個string類型的field和value的映射表,hash特別適合用於存儲對象。
Redis 中每一個 hash 能夠存儲 232 - 1 鍵值對(40多億)。
實例
127.0.0.1:6379> HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes 20 visitors 23000
OK
127.0.0.1:6379> HGETALL runoobkey
1) "name"
2) "redis tutorial"
3) "description"
4) "redis basic commands for caching"
5) "likes"
6) "20"
7) "visitors"
8) "23000"
hset key mapHey MapValue
在以上實例中,咱們設置了 redis 的一些描述信息(name, description, likes, visitors) 到哈希表的 runoobkey 中。
Redis hash 命令
下表列出了 redis hash 基本的相關命令:
序號 |
命令及描述 |
1 |
HDEL key field2 [field2] |
2 |
HEXISTS key field |
3 |
HGET key field |
4 |
HGETALL key |
5 |
HINCRBY key field increment |
6 |
HINCRBYFLOAT key field increment |
7 |
HKEYS key |
8 |
HLEN key |
9 |
HMGET key field1 [field2] |
10 |
HMSET key field1 value1 [field2 value2 ] |
11 |
HSET key field value |
12 |
HSETNX key field value |
13 |
HVALS key |
14 |
HSCAN key cursor [MATCH pattern] [COUNT count] |
概述
1、redis的複製功能是支持多個數據庫之間的數據同步。一類是主數據庫(master)一類是從數據庫(slave),主數據庫能夠進行讀寫操做,當發生寫操做的時候自動將數據同步到從數據庫,而從數據庫通常是隻讀的,並接收主數據庫同步過來的數據,一個主數據庫能夠有多個從數據庫,而一個從數據庫只能有一個主數據庫。
2、經過redis的複製功能能夠很好的實現數據庫的讀寫分離,提升服務器的負載能力。主數據庫主要進行寫操做,而從數據庫負責讀操做。
主從複製過程
主從複製過程:見下圖
過程:
1:當一個從數據庫啓動時,會向主數據庫發送sync命令,
2:主數據庫接收到sync命令後會開始在後臺保存快照(執行rdb操做),並將保存期間接收到的命令緩存起來
3:當快照完成後,redis會將快照文件和全部緩存的命令發送給從數據庫。
4:從數據庫收到後,會載入快照文件並執行收到的緩存的命令。
修改redis.conf
修改從redis中的 redis.conf文件
slaveof 192.168.33.130 6379
masterauth 123456--- 主redis服務器配置了密碼,則須要配置
Redis的哨兵(sentinel) 系統用於管理多個 Redis 服務器,該系統執行如下三個任務:
· 監控(Monitoring): 哨兵(sentinel) 會不斷地檢查你的Master和Slave是否運做正常。
· 提醒(Notification):當被監控的某個 Redis出現問題時, 哨兵(sentinel) 能夠經過 API 向管理員或者其餘應用程序發送通知。
· 自動故障遷移(Automatic failover):當一個Master不能正常工做時,哨兵(sentinel) 會開始一次自動故障遷移操做,它會將失效Master的其中一個Slave升級爲新的Master, 並讓失效Master的其餘Slave改成複製新的Master; 當客戶端試圖鏈接失效的Master時,集羣也會向客戶端返回新Master的地址,使得集羣能夠使用Master代替失效Master。
哨兵(sentinel) 是一個分佈式系統,你能夠在一個架構中運行多個哨兵(sentinel) 進程,這些進程使用流言協議(gossipprotocols)來接收關於Master是否下線的信息,並使用投票協議(agreement protocols)來決定是否執行自動故障遷移,以及選擇哪一個Slave做爲新的Master.
每一個哨兵(sentinel) 會向其它哨兵(sentinel)、master、slave定時發送消息,以確認對方是否」活」着,若是發現對方在指定時間(可配置)內未迴應,則暫時認爲對方已掛(所謂的」主觀認爲宕機」 Subjective Down,簡稱sdown).
若「哨兵羣」中的多數sentinel,都報告某一master沒響應,系統才認爲該master"完全死亡"(即:客觀上的真正down機,Objective Down,簡稱odown),經過必定的vote算法,從剩下的slave節點中,選一臺提高爲master,而後自動修改相關配置.
雖然哨兵(sentinel) 釋出爲一個單獨的可執行文件 redis-sentinel ,但實際上它只是一個運行在特殊模式下的 Redis 服務器,你能夠在啓動一個普通 Redis 服務器時經過給定 --sentinel 選項來啓動哨兵(sentinel).
哨兵(sentinel) 的一些設計思路和zookeeper很是相似
單個哨兵(sentinel)
10.2 哨兵模式修改配置
實現步驟:
1.拷貝到etc目錄
cp sentinel.conf /usr/local/redis/etc
2.修改sentinel.conf配置文件
sentinel monitor mymast 192.168.110.133 6379 1 #主節點 名稱 IP 端口號 選舉次數
3. 修改心跳檢測 5000毫秒
sentinel down-after-milliseconds mymaster 5000
4.sentinel parallel-syncs mymaster 2 --- 作多多少合格節點
5. 啓動哨兵模式
./redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
6. 中止哨兵模式
Redis 事務能夠一次執行多個命令, 而且帶有如下兩個重要的保證:
事務是一個單獨的隔離操做:事務中的全部命令都會序列化、按順序地執行。事務在執行的過程當中,不會被其餘客戶端發送來的命令請求所打斷。
事務是一個原子操做:事務中的命令要麼所有被執行,要麼所有都不執行。
一個事務從開始到執行會經歷如下三個階段:
開始事務。
命令入隊。
執行事務。
如下是一個事務的例子, 它先以 MULTI 開始一個事務, 而後將多個命令入隊到事務中, 最後由 EXEC 命令觸發事務, 一併執行事務中的全部命令:
redis 127.0.0.1:6379> MULTI OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days" QUEUED
redis 127.0.0.1:6379> GET book-name QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series" QUEUED
redis 127.0.0.1:6379> SMEMBERS tag QUEUED
redis 127.0.0.1:6379> EXEC 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Mastering Series" 2) "C++" 3) "Programming"
|
什麼是Redis持久化,就是將內存數據保存到硬盤。
Redis 持久化存儲 (AOF 與 RDB 兩種模式)
RDB 是在某個時間點將數據寫入一個臨時文件,持久化結束後,用這個臨時文件替換上次持久化的文件,達到數據恢復。
優勢:使用單獨子進程來進行持久化,主進程不會進行任何 IO 操做,保證了 redis 的高性能
缺點:RDB 是間隔一段時間進行持久化,若是持久化之間 redis 發生故障,會發生數據丟失。因此這種方式更適合數據要求不嚴謹的時候
這裏說的這個執行數據寫入到臨時文件的時間點是能夠經過配置來本身肯定的,經過配置redis 在 n 秒內若是超過 m 個 key 被修改這執行一次 RDB 操做。這個操做就相似於在這個時間點來保存一次 Redis 的全部數據,一次快照數據。全部這個持久化方法也一般叫作 snapshots。
RDB 默認開啓,redis.conf 中的具體配置參數以下;
#dbfilename:持久化數據存儲在本地的文件 dbfilename dump.rdb #dir:持久化數據存儲在本地的路徑,若是是在/redis/redis-3.0.6/src下啓動的redis-cli,則數據會存儲在當前src目錄下 dir ./ ##snapshot觸發的時機,save ##以下爲900秒後,至少有一個變動操做,纔會snapshot ##對於此值的設置,須要謹慎,評估系統的變動操做密集程度 ##能夠經過「save 「」」來關閉snapshot功能 #save時間,如下分別表示更改了1個key時間隔900s進行持久化存儲;更改了10個key300s進行存儲;更改10000個key60s進行存儲。 save 900 1 save 300 10 save 60 10000 ##當snapshot時出現錯誤沒法繼續時,是否阻塞客戶端「變動操做」,「錯誤」可能由於磁盤已滿/磁盤故障/OS級別異常等 stop-writes-on-bgsave-error yes ##是否啓用rdb文件壓縮,默認爲「yes」,壓縮每每意味着「額外的cpu消耗」,同時也意味這較小的文件尺寸以及較短的網絡傳輸時間 rdbcompression yes |
Append-only file,將「操做 + 數據」以格式化指令的方式追加到操做日誌文件的尾部,在 append 操做返回後(已經寫入到文件或者即將寫入),才進行實際的數據變動,「日誌文件」保存了歷史全部的操做過程;當 server 須要數據恢復時,能夠直接 replay 此日誌文件,便可還原全部的操做過程。AOF 相對可靠,它和 mysql 中 bin.log、apache.log、zookeeper 中 txn-log 簡直殊途同歸。AOF 文件內容是字符串,很是容易閱讀和解析。
優勢:能夠保持更高的數據完整性,若是設置追加 file 的時間是 1s,若是 redis 發生故障,最多會丟失 1s 的數據;且若是日誌寫入不完整支持 redis-check-aof 來進行日誌修復;AOF 文件沒被 rewrite 以前(文件過大時會對命令進行合併重寫),能夠刪除其中的某些命令(好比誤操做的 flushall)。
缺點:AOF 文件比 RDB 文件大,且恢復速度慢。
咱們能夠簡單的認爲 AOF 就是日誌文件,此文件只會記錄「變動操做」(例如:set/del 等),若是 server 中持續的大量變動操做,將會致使 AOF 文件很是的龐大,意味着 server 失效後,數據恢復的過程將會很長;事實上,一條數據通過屢次變動,將會產生多條 AOF 記錄,其實只要保存當前的狀態,歷史的操做記錄是能夠拋棄的;由於 AOF 持久化模式還伴生了「AOF rewrite」。
AOF 的特性決定了它相對比較安全,若是你指望數據更少的丟失,那麼能夠採用 AOF 模式。若是 AOF 文件正在被寫入時忽然 server 失效,有可能致使文件的最後一次記錄是不完整,你能夠經過手工或者程序的方式去檢測並修正不完整的記錄,以便經過 aof 文件恢復可以正常;同時須要提醒,若是你的 redis 持久化手段中有 aof,那麼在 server 故障失效後再次啓動前,須要檢測 aof 文件的完整性。
AOF 默認關閉,開啓方法,修改配置文件 reds.conf:appendonly yes
##此選項爲aof功能的開關,默認爲「no」,能夠經過「yes」來開啓aof功能 ##只有在「yes」下,aof重寫/文件同步等特性纔會生效 appendonly yes
##指定aof文件名稱 appendfilename appendonly.aof
##指定aof操做中文件同步策略,有三個合法值:always everysec no,默認爲everysec appendfsync everysec ##在aof-rewrite期間,appendfsync是否暫緩文件同步,"no"表示「不暫緩」,「yes」表示「暫緩」,默認爲「no」 no-appendfsync-on-rewrite no
##aof文件rewrite觸發的最小文件尺寸(mb,gb),只有大於此aof文件大於此尺寸是纔會觸發rewrite,默認「64mb」,建議「512mb」 auto-aof-rewrite-min-size 64mb
##相對於「上一次」rewrite,本次rewrite觸發時aof文件應該增加的百分比。 ##每一次rewrite以後,redis都會記錄下此時「新aof」文件的大小(例如A),那麼當aof文件增加到A*(1 + p)以後 ##觸發下一次rewrite,每一次aof記錄的添加,都會檢測當前aof文件的尺寸。 auto-aof-rewrite-percentage 100
|
AOF 是文件操做,對於變動操做比較密集的 server,那麼必將形成磁盤 IO 的負荷加劇;此外 linux 對文件操做採起了「延遲寫入」手段,即並不是每次 write 操做都會觸發實際磁盤操做,而是進入了 buffer 中,當 buffer 數據達到閥值時觸發實際寫入(也有其餘時機),這是 linux 對文件系統的優化,可是這卻有可能帶來隱患,若是 buffer 沒有刷新到磁盤,此時物理機器失效(好比斷電),那麼有可能致使最後一條或者多條 aof 記錄的丟失。經過上述配置文件,能夠得知 redis 提供了 3 中 aof 記錄同步選項:
always:每一條 aof 記錄都當即同步到文件,這是最安全的方式,也覺得更多的磁盤操做和阻塞延遲,是 IO 開支較大。
everysec:每秒同步一次,性能和安全都比較中庸的方式,也是 redis 推薦的方式。若是遇到物理服務器故障,有可能致使最近一秒內 aof 記錄丟失(可能爲部分丟失)。
no:redis 並不直接調用文件同步,而是交給操做系統來處理,操做系統能夠根據 buffer 填充狀況 / 通道空閒時間等擇機觸發同步;這是一種普通的文件操做方式。性能較好,在物理服務器故障時,數據丟失量會因 OS 配置有關。
其實,咱們能夠選擇的太少,everysec 是最佳的選擇。若是你很是在乎每一個數據都極其可靠,建議你選擇一款「關係性數據庫」吧。
AOF 文件會不斷增大,它的大小直接影響「故障恢復」的時間, 並且 AOF 文件中歷史操做是能夠丟棄的。AOF rewrite 操做就是「壓縮」AOF 文件的過程,固然 redis 並無採用「基於原 aof 文件」來重寫的方式,而是採起了相似 snapshot 的方式:基於 copy-on-write,全量遍歷內存中數據,而後逐個序列到 aof 文件中。所以 AOF rewrite 可以正確反應當前內存數據的狀態,這正是咱們所須要的;*rewrite 過程當中,對於新的變動操做將仍然被寫入到原 AOF 文件中,同時這些新的變動操做也會被 redis 收集起來(buffer,copy-on-write 方式下,最極端的多是全部的 key 都在此期間被修改,將會耗費 2 倍內存),當內存數據被所有寫入到新的 aof 文件以後,收集的新的變動操做也將會一併追加到新的 aof 文件中,此後將會重命名新的 aof 文件爲 appendonly.aof, 此後全部的操做都將被寫入新的 aof 文件。若是在 rewrite 過程當中,出現故障,將不會影響原 AOF 文件的正常工做,只有當 rewrite 完成以後纔會切換文件,由於 rewrite 過程是比較可靠的。*
觸發 rewrite 的時機能夠經過配置文件來聲明,同時 redis 中能夠經過 bgrewriteaof 指使人工干預。
redis-cli -h ip -p port bgrewriteaof
由於 rewrite 操做 /aof 記錄同步 /snapshot 都消耗磁盤 IO,redis 採起了「schedule」策略:不管是「人工干預」仍是系統觸發,snapshot 和 rewrite 須要逐個被執行。
AOF rewrite 過程並不阻塞客戶端請求。系統會開啓一個子進程來完成。
OF 和 RDB 各有優缺點,這是有它們各自的特色所決定:
1) AOF 更加安全,能夠將數據更加及時的同步到文件中,可是 AOF 須要較多的磁盤 IO 開支,AOF 文件尺寸較大,文件內容恢復數度相對較慢。
*2) snapshot,安全性較差,它是「正常時期」數據備份以及 master-slave 數據同步的最佳手段,文件尺寸較小,恢復數度較快。
能夠經過配置文件來指定它們中的一種,或者同時使用它們(不建議同時使用),或者所有禁用,在架構良好的環境中,master 一般使用 AOF,slave 使用 snapshot,主要緣由是 master 須要首先確保數據完整性,它做爲數據備份的第一選擇;slave 提供只讀服務(目前 slave 只能提供讀取服務),它的主要目的就是快速響應客戶端 read 請求;可是若是你的 redis 運行在網絡穩定性差 / 物理環境糟糕狀況下,建議你 master 和 slave 均採起 AOF,這個在 master 和 slave 角色切換時,能夠減小「人工數據備份」/「人工引導數據恢復」的時間成本;若是你的環境一切很是良好,且服務須要接收密集性的 write 操做,那麼建議 master 採起 snapshot,而 slave 採用 AOF。
Redis 發佈訂閱(pub/sub)是一種消息通訊模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
Redis 客戶端能夠訂閱任意數量的頻道。
下圖展現了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關係:
當有新消息經過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被髮送給訂閱它的三個客戶端:
如下實例演示了發佈訂閱是如何工做的。在咱們實例中咱們建立了訂閱頻道名爲 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
如今,咱們先從新開啓個 redis 客戶端,而後在同一個頻道 redisChat 發佈兩次消息,訂閱者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique"
(integer) 1
redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com"
(integer) 1
# 訂閱者的客戶端會顯示以下消息
1) "message"
2) "redisChat"
3) "Redis is a great caching technique"
1) "message"
2) "redisChat"
3) "Learn redis by runoob.com"
Redis 發佈訂閱命令
下表列出了 redis 發佈訂閱經常使用命令:
序號 |
命令及描述 |
1 |
PSUBSCRIBE pattern [pattern ...] |
2 |
PUBSUB subcommand [argument [argument ...]] |
3 |
PUBLISH channel message |
4 |
PUNSUBSCRIBE [pattern [pattern ...]] |
5 |
SUBSCRIBE channel [channel ...] |
6 |
UNSUBSCRIBE [channel [channel ...]] |
架構細節:
(1)全部的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.
(2)節點的fail是經過集羣中超過半數的節點檢測失效時才生效.
(3)客戶端與redis節點直連,不須要中間proxy層.客戶端不須要鏈接集羣全部節點,鏈接集羣中任何一個可用節點便可
(4)redis-cluster把全部的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value。Redis 集羣中內置了 16384 個哈希槽,當須要在 Redis 集羣中放置一個 key-value 時,redis 先對 key 使用 crc16 算法算出一個結果,而後把結果對 16384 求餘數,這樣每一個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大體均等的將哈希槽映射到不一樣的節點
redis-cluster投票:容錯-集羣中全部master參與投票,若是半數以上master節點與其中一個 master節點通訊超過(cluster-node-timeout),認爲該master節點掛掉.
集羣管理工具(redis-trib.rb)是使用ruby腳本語言編寫的安裝ruby和redis接口
搭建集羣
建立集羣
./redis-trib.rb create --replicas 1 192.168.242.137:7001 192.168.242.137:7002
192.168.242.137:7003 192.168.242.137:7004 192.168.242.137:7005 192.168.242.137:7006
鏈接集羣./redis-cli -h 192.168.242.137 -p 7001 –c
查看集羣信息:cluster info
jedis鏈接集羣
redis.clients.jedis.JedisPoolConfig
redis.clients.jedis.JedisCluster
經過前面的複製過程咱們瞭解到,主庫接收到SYNC的命令時會執行RDB過程,即便在配置文件中禁用RDB持久化也會生成,那麼若是主庫所在的服務器磁盤IO性能較差,那麼這個複製過程就會出現瓶頸,慶幸的是,Redis在2.8.18版本開始實現了無磁盤複製功能
Redis在與從數據庫進行復制初始化時將不會將快照存儲到磁盤,而是直接經過網絡發送給從數據庫,避免了IO性能差問題。開啓無磁盤複製:repl-diskless-sync yes
nginx是一款高性能的http 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器。由俄羅斯的程序設計師Igor Sysoev所開發,官方測試nginx可以支支撐5萬併發連接,而且cpu、內存等資源消耗卻很是低,運行很是穩定,因此如今不少知名的公司都在使用nginx。
一、http服務器。Nginx是一個http服務能夠獨立提供http服務。能夠作網頁靜態服務器。
二、虛擬主機。能夠實如今一臺服務器虛擬出多個網站。例如我的網站使用的虛擬主機。
三、反向代理,負載均衡。當網站的訪問量達到必定程度後,單臺服務器不能知足用戶的請求時,須要用多臺服務器集羣能夠使用nginx作反向代理。而且多臺服務器能夠平均分擔負載,不會由於某臺服務器負載高宕機而某臺服務器閒置的狀況。
佔內存小,能夠實現高併發鏈接、處理響應快。
能夠實現http服務器、虛擬主機、反向代理、負載均衡。
nginx配置簡單
能夠不暴露真實服務器IP地址
nginx的配置由特定的標識符(指令符)分爲多個不一樣的模塊。
指令符分爲簡單指令和塊指令。
conf文件中,全部不屬於塊指令的簡單指令都屬於main上下文的,http塊指令屬於main上下文,server塊指令http上下文。
Web server很重要一部分工做就是提供靜態頁面的訪問,例如images, html page。nginx能夠經過不一樣的配置,根據request請求,從本地的目錄提供不一樣的文件返回給客戶端。
打開安裝目錄下的nginx.conf文件,默認配置文件已經在http指令塊中建立了一個空的server塊,在nginx-1.8.0中的http塊中已經建立了一個默認的server塊。內容以下:
server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } |
反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。
啓動一個Tomcat 127.0.0.1:8080
使用nginx反向代理 8080.itmayiedu.com 直接跳轉到127.0.0.1:8080
127.0.0.1 8080.itmayiedu.com 127.0.0.1 b8081.itmayiedu.com |
配置信息:
server { listen 80; server_name 8080.itmayiedu.com; location / { proxy_pass http://127.0.0.1:8080; index index.html index.htm; } } server { listen 80; server_name b8081.itmayiedu.com; location / { proxy_pass http://127.0.0.1:8081; index index.html index.htm; } } |
負載均衡 創建在現有網絡結構之上,它提供了一種廉價有效透明的方法擴展網絡設備和服務器的帶寬、增長吞吐量、增強網絡數據處理能力、提升網絡的靈活性和可用性。
負載均衡,英文名稱爲Load Balance,其意思就是分攤到多個操做單元上進行執行,例如Web服務器、FTP服務器、企業關鍵應用服務器和其它關鍵任務服務器等,從而共同完成工做任務。
一、輪詢(默認) |
upstream backserver { server 127.0.0.1:8080; server 127.0.0.1:8081; }
server { listen 80; server_name www.itmayiedu.com; location / { proxy_pass http://backserver; index index.html index.htm; } } |
server { listen 80; server_name www.itmayiedu.com; location / { proxy_pass http://backserver; index index.html index.htm; proxy_connect_timeout 1; proxy_send_timeout 1; proxy_read_timeout 1; } } |
LVS、Ngnix、Tengine(taobao 開發的 Nginx 升級版)、HAProxy(高可用、負載均衡)、Keepalived(故障轉移,備機,linux 環境下的組件)
配置:
server { listen 80; server_name www.itmayiedu.com; location /A { proxy_pass http://a.a.com:81/A; index index.html index.htm; } location /B { proxy_pass http://b.b.com:81/B; index index.html index.htm; } } |
location ~ .*\.(jpg|jpeg|JPG|png|gif|icon)$ { valid_referers blocked http://www.itmayiedu.com www.itmayiedu.com; if ($invalid_referer) { return 403; } } |
設置Nginx、Nginx Plus的鏈接請求在一個真實用戶請求的合理範圍內。好比,若是你以爲一個正經常使用戶每兩秒能夠請求一次登陸頁面,你就能夠設置Nginx每兩秒鐘接收一個客戶端IP的請求(大約等同於每分鐘30個請求)。
limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m; server { ... location /login.html { limit_req zone=one; ... } }
|
`limit_req_zone`命令設置了一個叫one的共享內存區來存儲請求狀態的特定鍵值,在上面的例子中是客戶端IP($binary_remote_addr)。location塊中的`limit_req`經過引用one共享內存區來實現限制訪問/login.html的目的。
設置Nginx、Nginx Plus的鏈接數在一個真實用戶請求的合理範圍內。好比,你能夠設置每一個客戶端IP鏈接/store不能夠超過10個。
Keepalived是一個免費開源的,用C編寫的相似於layer3, 4 & 7交換機制軟件,具有咱們平時說的第3層、第4層和第7層交換機的功能。主要提供loadbalancing(負載均衡)和 high-availability(高可用)功能,負載均衡實現須要依賴Linux的虛擬服務內核模塊(ipvs),而高可用是經過VRRP協議實現多臺機器之間的故障轉移服務。
上圖是Keepalived的功能體系結構,大體分兩層:用戶空間(user space)和內核空間(kernel space)。
內核空間:主要包括IPVS(IP虛擬服務器,用於實現網絡服務的負載均衡)和NETLINK(提供高級路由及其餘相關的網絡功能)兩個部份。
用戶空間:
Keepalived的全部功能是配置keepalived.conf文件來實現的。
用Nginx 作的負載均衡能夠添加ip_hash這個配置,
用haproxy作的負載均衡能夠用 balance source這個配置。
從而使同一個ip的請求發到同一臺服務器。
缺點:安全性差、http請求都須要帶參數增長了帶寬消耗
使用spring-session框架,底層實現原理是重寫httpsession
<!--spring boot 與redis應用基本環境配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> <!--spring session 與redis應用基本環境配置,須要開啓redis後才能夠使用,否則啓動Spring boot會報錯 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> |
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
//這個類用配置redis服務器的鏈接 //maxInactiveIntervalInSeconds爲SpringSession的過時時間(單位:秒) @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) public class SessionConfig {
// 冒號後的值爲沒有配置文件時,制動裝載的默認值 @Value("${redis.hostname:localhost}") String HostName; @Value("${redis.port:6379}") int Port;
@Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connection = new JedisConnectionFactory(); connection.setPort(Port); connection.setHostName(HostName); return connection; } } |
//初始化Session配置 public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{ public SessionInitializer() { super(SessionConfig.class); } } |
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class SessionController {
@Value("${server.port}") private String PORT;
public static void main(String[] args) { SpringApplication.run(SessionController.class, args); }
@RequestMapping("/index") public String index() { return "index:" + PORT; }
/** * * @methodDesc: 功能描述:(往session存放值) * @author: 餘勝軍 * @param: @param * httpSession * @param: @param * sessionKey * @param: @param * sessionValue * @param: @return * @createTime:2017年10月8日 下午3:55:26 * @returnType:@param httpSession * @returnType:@param sessionKey * @returnType:@param sessionValue * @returnType:@return String * @copyright:上海每特教育科技有限公司 * @QQ:644064779 */ @RequestMapping("/setSession") public String setSession(HttpServletRequest request, String sessionKey, String sessionValue) { HttpSession session = request.getSession(true); session.setAttribute(sessionKey, sessionValue); return "success,port:" + PORT; }
/** * * @methodDesc: 功能描述:(從Session獲取值) * @author: 餘勝軍 * @param: @param * httpSession * @param: @param * sessionKey * @param: @return * @createTime:2017年10月8日 下午3:55:47 * @returnType:@param httpSession * @returnType:@param sessionKey * @returnType:@return String * @copyright:上海每特教育科技有限公司 * @QQ:644064779 */ @RequestMapping("/getSession") public String getSession(HttpServletRequest request, String sessionKey) { HttpSession session =null; try { session = request.getSession(false); } catch (Exception e) { e.printStackTrace(); } String value=null; if(session!=null){ value = (String) session.getAttribute(sessionKey); } return "sessionValue:" + value + ",port:" + PORT; }
} |
業務數據庫 -》 數據水平分割(分區分表分庫)、讀寫分離、SQL優化
業務應用 -》 邏輯代碼優化(算法優化)、公共數據緩存
應用服務器 -》 反向靜態代理、配置優化、負載均衡(apache分發,多tomcat實例)
系統環境 -》 JVM調優
頁面優化 -》 減小頁面鏈接數、頁面尺寸瘦身
1、動態資源和靜態資源分離;
2、CDN;
3、負載均衡;
4、分佈式緩存;
5、數據庫讀寫分離或數據切分(垂直或水平);
6、服務分佈式部署。
在客戶端與服務器進行通信時.客戶端調用後,必須等待服務對象完成處理返回結果才能繼續執行。
客戶與服務器對象的生命週期緊密耦合,客戶進程和服務對象進程都都必須正常運行;若是因爲服務對象崩潰或者網絡故障致使用戶的請求不可達,客戶會受到異常
點對點通訊: 客戶的一次調用只發送給某個單獨的目標對象。
(畫圖演示)
面向消息的中間件(MessageOrlented MiddlewareMOM)較好的解決了以上問
題。發送者將消息發送給消息服務器,消息服務器將消感存放在若千隊列中,在合適
的時候再將消息轉發給接收者。
這種模式下,發送和接收是異步的,發送者無需等
待; 兩者的生命週期未必相同: 發送消息的時候接收者不必定運行,接收消息的時候
發送者也不必定運行;一對多通訊: 對於一個消息能夠有多個接收者。
JMS是java的消息服務,JMS的客戶端之間能夠經過JMS服務進行異步的消息傳輸。
○ Point-to-Point(P2P) --- 點對點 ○ Publish/Subscribe(Pub/Sub)--- 發佈訂閱 |
即點對點和發佈訂閱模型
P2P
若是你但願發送的每一個消息都應該被成功處理的話,那麼你須要P2P模式。
應用場景
A用戶與B用戶發送消息
Pub/Sub模式圖
涉及到的概念
主題(Topic)
發佈者(Publisher)
訂閱者(Subscriber)
客戶端將消息發送到主題。多個發佈者將消息發送到Topic,系統將這些消息傳遞給多個訂閱者。
Pub/Sub的特色
每一個消息能夠有多個消費者
發佈者和訂閱者之間有時間上的依賴性。針對某個主題(Topic)的訂閱者,它必須建立一個訂閱者以後,才能消費發佈者的消息,並且爲了消費消息,訂閱者必須保持運行的狀態。
爲了緩和這樣嚴格的時間相關性,JMS容許訂閱者建立一個可持久化的訂閱。這樣,即便訂閱者沒有被激活(運行),它也能接收到發佈者的消息。
若是你但願發送的消息能夠不被作任何處理、或者被一個消息者處理、或者能夠被多個消費者處理的話,那麼能夠採用Pub/Sub模型
消息的消費
在JMS中,消息的產生和消息是異步的。對於消費來講,JMS的消息者能夠經過兩種方式來消費消息。
○ 同步
訂閱者或接收者調用receive方法來接收消息,receive方法在可以接收到消息以前(或超時以前)將一直阻塞
○ 異步
訂閱者或接收者能夠註冊爲一個消息監聽器。當消息到達以後,系統自動調用監聽器的onMessage方法。
應用場景:
用戶註冊、訂單修改庫存、日誌存儲
畫圖演示
是使用Erlang編寫的一個開源的消息隊列,自己支持不少的協議:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它變的很是重量級,更適合於企業級的開發。同時實現了一個經紀人(Broker)構架,這意味着消息在發送給客戶端時先在中心隊列排隊。對路由(Routing),負載均衡(Load balance)或者數據持久化都有很好的支持。
是一個Key-Value的NoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它自己支持MQ功能,因此徹底能夠當作一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操做,各執行100萬次,每10萬次記錄一次執行時間。測試數據分爲128Bytes、512Bytes、1K和10K四個不一樣大小的數據。實驗代表:入隊時,當數據比較小時Redis的性能要高於RabbitMQ,而若是數據大小超過了10K,Redis則慢的沒法忍受;出隊時,不管數據大小,Redis都表現出很是好的性能,而RabbitMQ的出隊性能則遠低於Redis。
|
入隊 |
出隊 |
||||||
|
128B |
512B |
1K |
10K |
128B |
512B |
1K |
10K |
Redis |
16088 |
15961 |
17094 |
25 |
15955 |
20449 |
18098 |
9355 |
RabbitMQ |
10627 |
9916 |
9370 |
2366 |
3219 |
3174 |
2982 |
1588 |
號稱最快的消息隊列系統,尤爲針對大吞吐量的需求場景。ZMQ可以實現RabbitMQ不擅長的高級/複雜的隊列,可是開發人員須要本身組合多種技術框架,技術上的複雜度是對這MQ可以應用成功的挑戰。ZeroMQ具備一個獨特的非中間件的模式,你不須要安裝和運行一個消息服務器或中間件,由於你的應用程序將扮演了這個服務角色。你只須要簡單的引用ZeroMQ程序庫,能夠使用NuGet安裝,而後你就能夠愉快的在應用程序之間發送消息了。可是ZeroMQ僅提供非持久性的隊列,也就是說若是down機,數據將會丟失。其中,Twitter的Storm中使用ZeroMQ做爲數據流的傳輸。
是Apache下的一個子項目。 相似於ZeroMQ,它可以以代理人和點對點的技術實現隊列。同時相似於RabbitMQ,它少許代碼就能夠高效地實現高級應用場景。RabbitMQ、ZeroMQ、ActiveMQ均支持經常使用的多種語言客戶端 C++、Java、.Net,、Python、 Php、 Ruby等。
Kafka是Apache下的一個子項目,是一個高性能跨語言分佈式Publish/Subscribe消息隊列系統,而Jafka是在Kafka之上孵化而來的,即Kafka的一個升級版。具備如下特性:快速持久化,能夠在O(1)的系統開銷下進行消息持久化;高吞吐,在一臺普通的服務器上既能夠達到10W/s的吞吐速率;徹底的分佈式系統,Broker、Producer、Consumer都原生自動支持分佈式,自動實現複雜均衡;支持Hadoop數據並行加載,對於像Hadoop的同樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。Kafka經過Hadoop的並行加載機制來統一了在線和離線的消息處理,這一點也是本課題所研究系統所看重的。Apache Kafka相對於ActiveMQ是一個很是輕量級的消息系統,除了性能很是好以外,仍是一個工做良好的分佈式系統。
其餘一些隊列列表HornetQ、Apache Qpid、Sparrow、Starling、Kestrel、Beanstalkd、Amazon SQS就再也不一一分析。
在分佈式環境中,MQ通信產生網絡延遲,重試補償中,會形成MQ重複消費。
解決辦法:
① 使用日誌+msg-id保存報文信息,做爲去重和冪等的依據。
② 消費端代碼拋出異常,不須要重試補償,使用日誌記錄報文,下次發版本解決。
Stomp、XMPP
Stomp協議,英文全名Streaming Text Orientated Message Protocol,中文名稱爲 ‘流文本定向消息協議’。是一種以純文本爲載體的協議(以文本爲載體的意思是它的消息格式規範中沒有相似XMPP協議那樣的xml格式要求,你能夠將它看做‘半結構化數據’)。目前Stomp協議有兩個版本:V1.1和V1.2。
一個標準的Stomp協議包括如下部分:命令/信息關鍵字、頭信息、文本內容。以下圖所示:
如下爲一段簡單的協議信息示例:
CONNECT
accept-version:1.2
someparam1:value1
someparam2:value2
上面的示例中,咱們使用了Stomp協議的CONNECT命令,它的意思爲鏈接到Stomp代理端,而且攜帶了要求代理端的版本信息和兩個自定義的K-V信息(請注意’^@’符號,STOMP協議中用它來表示NULL)。
XMPP基於XML,用於IM系統的開發。國內比較流行的XMPP服務器叫作Openfire,它使用MINA做爲下層的網絡IO框架(不是MINA2是MINA1);國外用的比較多的XMPP服務器叫作Tigase,它的官網號稱單節點能夠支撐50萬用戶在線,集羣能夠支持100萬用戶在線:(http://projects.tigase.org/)
RPC 的全稱是 Remote Procedure Call 是一種進程間通訊方式。
它容許程序調用另外一個地址空間(一般是共享網絡的另外一臺機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節。即不管是調用本地接口/服務的仍是遠程的接口/服務,本質上編寫的調用代碼基本相同。
好比兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數或者方法,因爲不在一個內存空間,不能直接調用,這時候須要經過就能夠應用RPC框架的實現來解決
SOA是一種面向服務架構,是將相同業務邏輯抽取出來組成單獨服務。
SOAP是WebService面向服務協議, 採用xml,由於比較中,如今不是特別流行。
微服務架構師一種架構模式,它提倡將單一應用程序劃分紅一組小的服務,服務之間互相
協調、互相配合沒用戶提供最終價值。每一個服務運行在其獨立的進程中,服務與服務間採用輕量級的同窗機制互相溝通(通暢採用Http+restful API),每一個服務都圍繞着具體業務進行構建,而且可以被獨立的部署到生成環境、類生存環境等。另外,應儘可能避免同一的、集中式服務管理機制。
SOA實現 |
微服務架構實現 |
企業級,自頂向下開展實施 |
團隊級,自定向上開展實施 |
服務由多個子系統組成 |
一個系統被拆分紅多個服務 |
集成式服務(esb、ws、soap) |
集成方式簡單(http、rest、json) |
SpringCloud、Dubbo、Dubbox、Hessian、HttpClient、thrift等。
SpringCloud 爲開發人員提供了快速構建分佈式系統的一些工具,包括配置管理、服務發現(Eureka)、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分佈式會話等等。它運行環境簡單,能夠在開發人員的電腦上跑。
另外說明spring cloud是基於Springboot的,因此須要開發中對Springboot有必定的瞭解。SpringCloud使用Eureka做爲註冊中心,使用rest+ribbon或者feign,斷路器Hystrix、zuul接口網關等。
服務提供者與消費關係 服務提供者:提供服務被人調用 消費者:調用被人服務
服務的註冊與發現(Eureka ) 咱們須要用的的組件上Spring Cloud Netflix的Eureka ,eureka是一個服務註冊和發現模塊
服務註冊
建立eurekaserver 項目
引入maven依賴spring-cloud-starter-eureka-server
配置application.yml
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
啓動EurekaServer
服務提供者
服務消費者(rest+ribbon-負載均衡)
在微服務架構中,業務都會被拆分紅一個獨立的服務,服務與服務的通信是基於http restful的。Spring cloud有兩種服務調用方式,一種是ribbon+restTemplate,另外一種是feign
ribbon:ribbon是一個負載均衡客戶端,能夠很好的控制http和tcp的一些行爲。Feign默認集成了ribbon。
spring-cloud-starter-ribbon
架構
一個服務註冊中心,eureka server,端口爲8761
service-hi工程跑了兩個實例,端口分別爲8762,8763,分別向服務註冊中心註冊
sercvice-ribbon端口爲8764,向服務註冊中心註冊
當sercvice-ribbon經過restTemplate調用service-hi的hi接口時,由於用ribbon進行了負載均衡,會輪流的調用service-hi:8762和8763 兩個端口的hi接口;
@LoadBalanced註解代表這個restRemplate開啓負載均衡的功能
服務消費者(Feign-負載均衡)
Feign是一個聲明式的僞Http客戶端,它使得寫Http客戶端變得更簡單。使用Feign,只須要建立一個接口並註解。 它具備可插拔的註解特性,可以使用Feign 註解和JAX-RS註解。Feign支持可插拔的編碼器和解碼器。
Feign默認集成了Ribbon,並和Eureka結合,默認實現了負載均衡的效果。
Feign 採用的是基於接口的註解:Feign 整合了ribbon
spring-cloud-starter-feign
定義接口 @FeignClient(value = "service-hi")
Hystrix斷路器spring-cloud-starter-hystrix --- @HystrixCommand(fallbackMethod = "hiError") 改造HelloService類,在hiService方法上加上 @HystrixCommand註解。該註解對該方法建立了熔斷器的功能,並指定了fallbackMethod熔斷方法,熔斷方法直接返回了一個字符串,字符串爲」hi,」+name+」,sorry,error!」
Feign是自帶斷路器,配置文件中feign.hystrix.enabled=true,接口上 @FeignClient(value = "service-hi",fallback=SchedualServiceHiHystric.class)
Hystrix Dashboard (斷路器:Hystrix 儀表盤)
zuul(nginx)
使用Zuul構建API Gateway
在Spring Cloud微服務系統中,一種常見的負載均衡方式是,客戶端的請求首先通過負載均衡(zuul、Ngnix),再到達服務網關(zuul集羣),而後再到具體的服務。
Zuul是Netflix的基於JVM的路由器和服務器端負載均衡器--Zuul的規則引擎容許規則和過濾器基本上用任何JVM語言編寫,內置支持Java和Groovy。
spring-cloud-starter-zuul
在其入口applicaton類加上註解 @EnableZuulProxy,開啓zuul的功能
application.yml配置
zuul:
routes:
api-a:
path: /api-a/**
service-id: service-ribbon
api-b:
path: /api-b/**
service-id: service-feign
zuul不只只是路由,而且還能過濾extends ZuulFilter(過濾器)
@Override
public String filterType() {
return "pre";
}
重寫此方法,filterType,返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型pre、routing、post、error
filterOrder:過濾的順序。oshouldFilter:這裏能夠寫邏輯判斷,是否要過濾,本文true,永遠過濾。
run:過濾器的具體邏輯。可用很複雜,包括查sql,nosql去判斷該請求到底有沒有權限訪問。
spring cloud config
在Spring Cloud中,有分佈式配置中心組件spring cloud config ,它支持配置服務放在配置服務的內存中(即本地),也支持放在遠程Git倉庫中。在spring cloud config 組件中,分兩個角色,一是config server,二是config client。
maven:spring-cloud-config-server
構建config-server
Application類加上 @EnableConfigServer
application.properties文件配置如下
spring.application.name=config-server
server.port=8888
spring.cloud.config.server.git.uri=https://gitee.com/itmayi/itmayiedu2.git
spring.cloud.config.server.git.searchPaths=/itmayi/itmayiedu2.git
spring.cloud.config.label=master
spring.cloud.config.server.git.username=
spring.cloud.config.server.git.password=
構建config-client
maven:spring-cloud-starter-config
配置文件bootstrap.properties
spring.application.name=config-client
spring.cloud.config.label=master
spring.cloud.config.profile=dev
spring.cloud.config.uri= http://localhost:8888/
server.port=8881
高可用的分佈式配置中心(Spring Cloud Config) 配置中心如何從遠程git讀取配置文件,當服務實例不少時,都從配置中心讀取文件,這時能夠考慮將配置中心作成一個微服務,將其集羣化,從而達到高可用
Duubbo是一個RPC遠程調用框架, 分佈式服務治理框架
什麼是Dubbo服務治理?
服務與服務之間會有不少個Url、依賴關係、負載均衡、容錯、自動註冊服務。
默認用的dubbo協議、Http、RMI、Hessian
分爲四大模塊
生產者、消費者、註冊中心、監控中心
生產者:提供服務
消費者: 調用服務
註冊中心:註冊信息(redis、zk)
監控中心:調用次數、關係依賴等。
首先生產者將服務註冊到註冊中心(zk),使用zk持久節點進行存儲,消費訂閱zk節點,一旦有節點變動,zk經過事件通知傳遞給消費者,消費能夠調用生產者服務。
服務與服務之間進行調用,都會在監控中心中,存儲一個記錄。
Dubox使用http協議+rest風格傳入json或者xml格式進行遠程調用。Dubbo使用Dubbo協議。
Dubbo-admin管理平臺搭建
Dubbo集羣、負載均衡、容錯:Dubbo-admin管理平臺在其Dubbo-admin管理平臺上操做便可
1.透明化的遠程方法調用,就像調用本地方法同樣調用遠程方法
2.軟負載均衡及容錯機制,可在內網替代F5等硬件負鞭均衡器,下降成本,減小單點。
3.服務自動註冊與發現,再也不須要寫死服務提供方地址,註冊中心基於樓口名查詢服務提供者的IP地址,而且可以平滑添加或刪除服務提供者
4.Dubbo採用全Spring 配置方式,功明化接入應用,對應用沒有任何API侵只需用Spring加載Dubbo的配置便可,
Dubbo基FSpring的Schema擴入,展進行加載。
dubbo與springcloud均可以實現RPC遠程調用。
dubbo與springcloud均可以使用分佈式、微服務場景下。
dubbo有比較強的背景,在國內有必定影響力。
dubbo使用zk或redis做爲做爲註冊中心
springcloud使用eureka做爲註冊中心
dubbo支持多種協議,默認使用dubbo協議。
Springcloud只能支持http協議。
Springcloud是一套完整的微服務解決方案。
Dubbo目前已經中止更新,SpringCloud更新速度快。
Zookeeper是一個工具,能夠實現集羣中的分佈式協調服務。
所謂的分佈式協調服務,就是在集羣的節點中進行可靠的消息傳遞,來協調集羣的工做。
Zookeeper之因此可以實現分佈式協調服務,靠的就是它可以保證分佈式數據一致性。
所謂的分佈式數據一致性,指的就是能夠在集羣中保證數據傳遞的一致性。
Zookeeper可以提供的分佈式協調服務包括:數據發佈訂閱、負載均衡、命名服務、分佈式協調/通知、集羣管理、分佈式鎖、分佈式隊列等功能
Zookeeper工做在集羣中,對集羣提供分佈式協調服務,它提供的分佈式協調服務具備以下的特色:
順序一致性
從同一個客戶端發起的事務請求,最終將會嚴格按照其發起順序被應用到zookeeper中
原子性
全部事物請求的處理結果在整個集羣中全部機器上的應用狀況是一致的,即,要麼整個集羣中全部機器都成功應用了某一事務,要麼都沒有應用,必定不會出現集羣中部分機器應用了改事務,另一部分沒有應用的狀況。
單一視圖
不管客戶端鏈接的是哪一個zookeeper服務器,其看到的服務端數據模型都是一致的。
可靠性
一旦服務端成功的應用了一個事務,並完成對客戶端的響應,那麼該事務所引發的服務端狀態變動將會一直保留下來,除非有另外一個事務又對其進行了改變。
實時性
zookeeper並非一種強一致性,只能保證順序一致性和最終一致性,只能稱爲達到了僞實時性。
zookeeper中能夠保存數據,正是利用zookeeper能夠保存數據這一特色,咱們的集羣經過在zookeeper裏存取數據來進行消息的傳遞。
zookeeper中保存數據的結構很是相似於文件系統。都是由節點組成的樹形結構。不一樣的是文件系統是由文件夾和文件來組成的樹,而zookeeper中是由ZNODE來組成的樹。
每個ZNODE裏均可以存放一段數據,ZNODE下還能夠掛載零個或多個子ZNODE節點,從而組成一個樹形結構。
數據發佈訂閱
負載均衡
命名服務
分佈式協調
集羣管理
配置管理
分佈式隊列
分佈式鎖
1、Zookeeper:一個leader,多個follower組成的集羣
2、全局數據一致:每一個server保存一份相同的數據副本,client不管鏈接到哪一個server,數據都是一致的
3、分佈式讀寫,更新請求轉發,由leader實施
4、更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行
5、數據更新原子性,一次數據更新要麼成功,要麼失敗
6、實時性,在必定時間範圍內,client能讀到最新數據
Zookeeper數據結構
層次化的目錄結構
每一個節點在zookeeper中叫作znode,而且其有一個惟一的路徑標識
節點Znode能夠包含數據和子節點(可是EPHEMERAL類型的節點不能有子節Znode有兩種類型:短暫(ephemeral)、EPHEMERAL_SEQUENTIAL、持久(persistent)、PERSISTENT_SEQUENTIAL
在分佈式系統中,順序號能夠被用於爲全部的事件進行全局排序,這樣客戶端能夠經過順序號推斷事件的順序 myid (內容爲服務器標識 : 0)
zkServer.sh start stop status……
Zookeeper客戶端
Java操做Zookeeper
依賴:org.apache.zookeeper
建立節點的類型: CreateMode,提供四種首點象型
PERSISTENT(持久節點)
PERSISTENT SEQUENTIAL(持久順序節點)
EPHEMERAL(臨時節點)
EPHEMERAL SEQUENTAL(臨時順序節點)
Zookeeper客戶端鏈接
Watcher
在ZooKeeper中,接口類Watcher用於表示一個標準的事件處理器,其定義了事件通知相關的邏輯,包含KeeperState和EventType兩個枚舉類,分別表明了通知狀態和事件類型,同時定義了事件的回調方法:process(WatchedEvent event)
Zookeeper實戰分佈式鎖
線程進程資源競爭
線程進程同步的方式和機制
臨界區
經過對多線程的串行化來訪問公共資源或一段代碼
synchronized 修飾的java方法 僅用於線程同步
互斥量
採用互斥對象機制。只有擁有互斥對象的線程纔有訪問公共資源競爭的問題源的權限synchronized 修飾的代碼塊java.util.concurrent.locks.Lock分佈式鎖的主要實現機制
分佈式鎖實現的技術
基於數據實現分佈式鎖( 性能較差,容易出現單點故障)
基於緩存實現分佈式鎖( 鎖沒有失效事件,容易死鎖)
基於Zookeeper實現分佈式鎖(建立臨時節點)
簡單的理解就是:分佈式鎖是一個在不少環境中很是有用的原語,它是不一樣的系統或是同一個系統的不一樣主機之間互斥操做共享資源的有效方法。
分佈式鎖使用zk,在zk上建立一個臨時節點,使用臨時節點做爲鎖,由於節點不容許重複。
若是能建立節點成功,生成訂單號,若是建立節點失敗,就等待。臨時節點zk關閉,釋放鎖,其餘節點就能夠從新生成訂單號。
通常的鎖只能針對單機下同一進程的多個線程,或單機的多個進程。多機狀況下,對同一個資源訪問,須要對每臺機器的訪問進程或線程加鎖,這即是分佈式鎖。分佈式鎖能夠利用多機的共享緩存(例如redis)實現。redis的命令文檔[1],實現及分析參考文檔[2]。
利用redis的get、setnx、getset、del四個命令能夠實現基於redis的分佈式鎖:
get key:表示從redis中讀取一個key的value,若是key沒有對應的value,返回nil,若是存儲的值不是字符串類型,返回錯誤
setnx key value:表示往redis中存儲一個鍵值對,但只有當key不存在時才成功,返回1;不然失敗,返回0,不改變key的value
getset key:將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。當舊值不存在時返回nil,當舊值不爲字符串類型,返回錯誤
del key:表示刪除key,當key不存在時不作操做,返回刪除的key數量
關於加鎖思考,循環中:
0、setnx的value是當前機器時間+預估運算時間做爲鎖的失效時間。這是爲了防止得到鎖的線程掛掉而沒法釋放鎖而致使死鎖。
0.1、返回1,證實已經得到鎖,返回啦
0.2、返回0,得到鎖失敗,須要檢查鎖超時時間
1、get 獲取到鎖,利用失效時間判斷鎖是否失效。
1.1、取鎖超時時間的時刻可能鎖被刪除釋放,此時並無拿到鎖,應該從新循環加鎖邏輯。
2、取鎖超時時間成功
2.1、鎖沒有超時,休眠一下,從新循環加鎖
2.2、鎖超時,但此時不能直接釋放鎖刪除。由於此時可能多個線程都讀到該鎖超時,若是直接刪除鎖,全部線程均可能刪除上一個刪除鎖又新上的鎖,會有多個線程進入臨界區,產生競爭狀態。
3、此時採用樂觀鎖的思想,用getset再次獲取鎖的舊超時時間。
3.1、若是此時得到鎖舊超時時間成功
3.1.1、等於上一次得到的鎖超時時間,證實兩次操做過程當中沒有別人動過這個鎖,此時已經得到鎖
3.1.2、不等於上一次得到的鎖超時時間,說明有人先動過鎖,獲取鎖失敗。雖然修改了別人的過時時間,但由於衝突的線程相差時間極短,因此修改後的過時時間並沒有大礙。此處依賴全部機器的時間一致。
3.2、若是此時得到鎖舊超時時間失敗,證實當前線程是第一個在鎖失效後又加上鎖的線程,因此也得到鎖
4、其餘狀況都沒有得到鎖,循環setnx吧
關於解鎖的思考:
在鎖的時候,若是鎖住了,回傳超時時間,做爲解鎖時候的憑證,解鎖時傳入鎖的鍵值和憑證。我思考的解鎖時候有兩種寫法:
1、解鎖前get一下鍵值的value,判斷是否是和本身的憑證同樣。但這樣存在一些問題:
get時返回nil的可能,此時表示有別的線程拿到鎖並用完釋放
get返回非nil,可是不等於自身憑證。因爲有getset那一步,當兩個競爭線程都在這個過程當中時,存在持有鎖的線程憑證不等於value,而是value是稍慢那一步線程設置的value。
2、解鎖前用憑證判斷鎖是否已經超時,若是沒有超時,直接刪除;若是超時,等着鎖自動過時就好,省得誤刪別人的鎖。但這種寫法一樣存在問題,因爲線程調度的不肯定性,判斷到刪除之間可能過去好久,並非絕對意義上的正確解鎖。
public class RedisLock {
private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
//顯然jedis還須要本身配置來初始化 private Jedis jedis = new Jedis();
//默認鎖住15秒,盡力規避鎖時間過短致使的錯誤釋放 private static final long DEFAULT_LOCK_TIME = 15 * 1000;
//嘗試鎖住一個lock,設置嘗試鎖住的次數和超時時間(毫秒),默認最短15秒 //成功時返回這把鎖的key,解鎖時須要憑藉鎖的lock和key //失敗時返回空字符串 public String lock(String lock, int retryCount, long timeout) { Preconditions.checkArgument(retryCount > 0 && timeout > 0, "retry count <= 0 or timeout <= 0 !"); Preconditions.checkArgument(retryCount < Integer.MAX_VALUE && timeout < Long.MAX_VALUE - DEFAULT_LOCK_TIME, "retry count is too big or timeout is too big!"); String $lock = Preconditions.checkNotNull(lock) + "_redis_lock"; long $timeout = timeout + DEFAULT_LOCK_TIME; String ret = null; //重試必定次數,仍是拿不到,就放棄 try { long i, status; for (i = 0, status = 0; status == 0 && i < retryCount; ++i) { //嘗試加鎖,並設置超時時間爲當前機器時間+超時時間 if ((status = jedis.setnx($lock, ret = Long.toString(System.currentTimeMillis() + $timeout))) == 0) { //獲取鎖失敗,查看鎖是否超時 String time = jedis.get($lock); //在加鎖和檢查之間,鎖被刪除了,嘗試從新加鎖 if (time == null) { continue; } //鎖的超時時間戳小於當前時間,證實鎖已經超時 if (Long.parseLong(time) < System.currentTimeMillis()) { String oldTime = jedis.getSet($lock, Long.toString(System.currentTimeMillis() + $timeout)); if (oldTime == null || oldTime.equals(time)) { //拿到鎖了,跳出循環 break; } } try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException e) { logger.error("lock key:{} sleep failed!", lock); } } } if (i == retryCount && status == 0) { logger.info("lock key:{} failed!", lock); return ""; } //給鎖加上過時時間 jedis.pexpire($lock, $timeout); logger.info("lock key:{} succsee!", lock); return ret; } catch (Exception e) { logger.error("redis lock key:{} failed! cached exception: ", lock, e); return ""; } }
//釋放lock的鎖,須要傳入lock和key //盡力確保刪除屬於本身的鎖,可是不保證作獲得 public void releaseLock(String lock, String key) { String $lock = Preconditions.checkNotNull(lock) + "_redis_lock"; Preconditions.checkNotNull(key); try { long timeout = Long.parseLong(key); //鎖尚未超時,鎖還屬於本身能夠直接刪除 //但因爲線程運行的不肯定性,其實不能徹底保證刪除時鎖還屬於本身 //真正執行刪除操做時,距離上語句判斷可能過了好久 if (timeout <= System.currentTimeMillis()) { jedis.del($lock); logger.info("release lock:{} with key:{} success!", lock, key); } else { logger.info("lock:{} with key:{} timeout! wait to expire", lock, key); } } catch (Exception e) { logger.error("redis release {} with key:{} failed! cached exception: ", lock, key, e); } } } |
鎖沒有失效事件,容易死鎖
非阻塞式
不可重入
實現相對簡單
可靠性高
性能較好
public class Demo01 { static long count = 0; public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); count++; System.out.println(count); } catch (Exception e) { // TODO: handle exception } } } }; Thread thread = new Thread(runnable); thread.start(); } } |
/** * 使用TimerTask類實現定時任務 */ public class Demo02 { static long count = 0;
public static void main(String[] args) { TimerTask timerTask = new TimerTask() {
@Override public void run() { count++; System.out.println(count); } }; Timer timer = new Timer(); // 天數 long delay = 0; // 秒數 long period = 1000; timer.scheduleAtFixedRate(timerTask, delay, period); }
}
|
使用ScheduledExecutorService是從Java
JavaSE5的java.util.concurrent裏,作爲併發工具類被引進的,這是最理想的定時任務實現方式。
public class Demo003 { public static void main(String[] args) { Runnable runnable = new Runnable() { public void run() { // task to run goes here System.out.println("Hello !!"); } }; ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); // 第二個參數爲首次執行的延時時間,第三個參數爲定時執行的間隔時間 service.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS); } } |
<dependencies> <!-- quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.1</version> </dependency> </dependencies> |
public class MyJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("quartz MyJob date:" + new Date().getTime()); }
}
|
//1.建立Scheduler的工廠 SchedulerFactory sf = new StdSchedulerFactory(); //2.從工廠中獲取調度器實例 Scheduler scheduler = sf.getScheduler();
//3.建立JobDetail JobDetail jb = JobBuilder.newJob(MyJob.class) .withDescription("this is a ram job") //job的描述 .withIdentity("ramJob", "ramGroup") //job 的name和group .build();
//任務運行的時間,SimpleSchedle類型觸發器有效 long time= System.currentTimeMillis() + 3*1000L; //3秒後啓動任務 Date statTime = new Date(time);
//4.建立Trigger //使用SimpleScheduleBuilder或者CronScheduleBuilder Trigger t = TriggerBuilder.newTrigger() .withDescription("") .withIdentity("ramTrigger", "ramTriggerGroup") //.withSchedule(SimpleScheduleBuilder.simpleSchedule()) .startAt(statTime) //默認當前時間啓動 .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //兩秒執行一次 .build();
//5.註冊任務和定時器 scheduler.scheduleJob(jb, t);
//6.啓動 調度器 scheduler.start(); |
分佈式集羣的狀況下,怎麼保證定時任務不被重複執行
①使用zookeeper實現分佈式鎖 缺點(須要建立臨時節點、和事件通知不易於擴展)
②使用配置文件作一個開關 缺點發布後,須要重啓
③數據庫惟一約束,缺點效率低
④使用分佈式任務調度平臺
XXLJOB
使用全局事物兩段提交協議,遵循XA協議規範,使用開源框架jta+automatic 。
什麼是兩段提交協議:在第一階段,全部參與全局事物的節點都開始準備,告訴事物管理器
它們準備好了,在是第二階段,事物管理器告訴資源執行ROLLBACK仍是Commit,只要任何一方爲ROLLBACK ,則直接回滾。
參考案例:springboot集成automatic+jta
這種實現方式的思路,實際上是源於ebay,後來經過支付寶等公司的佈道,在業內普遍使用。其基本的設計思想是將遠程分佈式事務拆分紅一系列的本地事務。若是不考慮性能及設計優雅,藉助關係型數據庫中的表便可實現。
舉個經典的跨行轉帳的例子來描述。
第一步,僞代碼以下,扣款1W,經過本地事務保證了憑證消息插入到消息表中。
第二步,通知對方銀行帳戶上加1W了。那問題來了,如何通知到對方呢?
一般採用兩種方式:
1. 採用時效性高的MQ,由對方訂閱消息並監聽,有消息時自動觸發事件
2. 採用定時輪詢掃描的方式,去檢查消息表的數據。
兩種方式其實各有利弊,僅僅依靠MQ,可能會出現通知失敗的問題。而過於頻繁的定時輪詢,效率也不是最佳的(90%是無用功)。因此,咱們通常會把兩種方式結合起來使用。
解決了通知的問題,又有新的問題了。萬一這消息有重複被消費,往用戶賬號上多加了錢,那豈不是後果很嚴重?
仔細思考,其實咱們能夠消息消費方,也經過一個「消費狀態表」來記錄消費狀態。在執行「加款」操做以前,檢測下該消息(提供標識)是否已經消費過,消費完成後,經過本地事務控制來更新這個「消費狀態表」。這樣子就避免重複消費的問題。
總結:上訴的方式是一種很是經典的實現,基本避免了分佈式事務,實現了「最終一致性」。可是,關係型數據庫的吞吐量和性能方面存在瓶頸,頻繁的讀寫消息會給數據庫形成壓力。因此,在真正的高併發場景下,該方案也會有瓶頸和限制的。
一般狀況下,在使用非事務消息支持的MQ產品時,咱們很難將業務操做與對MQ的操做放在一個本地事務域中管理。通俗點描述,仍是以上述提到的「跨行轉帳」爲例,咱們很難保證在扣款完成以後對MQ投遞消息的操做就必定能成功。這樣一致性彷佛很難保證。
先從消息生產者這端來分析,請看僞代碼:
根據上述代碼及註釋,咱們來分析下可能的狀況:
1. 操做數據庫成功,向MQ中投遞消息也成功,皆大歡喜
2. 操做數據庫失敗,不會向MQ中投遞消息了
3. 操做數據庫成功,可是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操做將被回滾
從上面分析的幾種狀況來看,貌似問題都不大的。那麼咱們來分析下消費者端面臨的問題:
1. 消息出列後,消費者對應的業務操做要執行成功。若是業務執行失敗,消息不能失效或者丟失。須要保證消息與業務操做一致
2. 儘可能避免消息重複消費。若是重複消費,也不能所以影響業務結果
如何保證消息與業務操做一致,不丟失?
主流的MQ產品都具備持久化消息的功能。若是消費者宕機或者消費失敗,均可以執行重試機制的(有些MQ能夠自定義重試次數)。
如何避免消息被重複消費形成的問題?
1. 保證消費者調用業務的服務接口的冪等性
2. 經過消費日誌或者相似狀態表來記錄消費狀態,便於判斷(建議在業務上自行實現,而不依賴MQ產品提供該特性)
總結:這種方式比較常見,性能和吞吐量是優於使用關係型數據庫消息表的方案。若是MQ自身和業務都具備高可用性,理論上是能夠知足大部分的業務場景的。不過在沒有充分測試的狀況下,不建議在交易業務中直接使用。
作過支付寶交易接口的同窗都知道,咱們通常會在支付寶的回調頁面和接口裏,解密參數,而後調用系統中更新交易狀態相關的服務,將訂單更新爲付款成功。同時,只有當咱們回調頁面中輸出了success字樣或者標識業務處理成功相應狀態碼時,支付寶纔會中止回調請求。不然,支付寶會每間隔一段時間後,再向客戶方發起回調請求,直到輸出成功標識爲止。
其實這就是一個很典型的補償例子,跟一些MQ重試補償機制很相似。
通常成熟的系統中,對於級別較高的服務和接口,總體的可用性一般都會很高。若是有些業務因爲瞬時的網絡故障或調用超時等問題,那麼這種重試機制實際上是很是有效的。
固然,考慮個比較極端的場景,假如系統自身有bug或者程序邏輯有問題,那麼重試1W次那也是無濟於事的。那豈不是就發生了「明明已經付款,卻顯示未付款不發貨」相似的悲劇?
其實爲了交易系統更可靠,咱們通常會在相似交易這種高級別的服務代碼中,加入詳細日誌記錄的,一旦系統內部引起相似致命異常,會有郵件通知。同時,後臺會有定時任務掃描和分析此類日誌,檢查出這種特殊的狀況,會嘗試經過程序來補償並郵件通知相關人員。
在某些特殊的狀況下,還會有「人工補償」的,這也是最後一道屏障。
兩階段提交協議(two phase commit protocol,2PC)能夠保證數據的強一致性,許多分佈式關係型數據管理系統採用此協議來完成分佈式事務。它是協調全部分佈式原子事務參與者,並決定提交或取消(回滾)的分佈式算法。同時也是解決一致性問題的一致性算法。該算法可以解決不少的臨時性系統故障(包括進程、網絡節點、通訊等故障),被普遍地使用。可是,它並不可以經過配置來解決全部的故障,在某些狀況下它還須要人爲的參與才能解決問題。參與者爲了可以從故障中恢復,它們都使用日誌來記錄協議的狀態,雖然使用日誌下降了性能可是節點可以從故障中恢復。
在兩階段提交協議中,系統通常包含兩類機器(或節點):
①協調者coordinator,一般一個系統中只有一個;
②事務參與者 participants,cohorts或workers,通常包含多個;
在數據存儲系統中能夠理解爲數據副本的個數,協議中假設:
①每一個節點都會記錄寫前日誌並持久性存儲,即便節點發生故障日誌也不會丟失;
②節點不會發生永久性故障並且任意兩個節點均可以互相通訊;
當事務的最後一步完成以後,協調器執行協議,參與者根據本地事務,可以成功完成回覆贊成提交事務或者回滾事務。
顧名思義,兩階段提交協議由兩個階段組成。在正常的執行下,這兩個階段的執行過程以下所述:
(1)階段1:請求階段(commit-request phase,或稱表決階段,voting phase)
在請求階段,協調者將通知事務參與者準備提交或取消事務,而後進入表決過程。在表決過程當中,參與者將告知協調者本身的決策:贊成(事務參與者本地做業執行成功)或取消(本地做業執行故障)。
(2)階段2:提交階段(commit phase)
在該階段,協調者將基於第一個階段的投票結果進行決策:提交或取消。當且僅當全部的參與者贊成提交事務協調者才通知全部的參與者提交事務,不然協調者將通知全部的參與者取消事務。參與者在接收到協調者發來的消息後將執行響應的操做。
注意 兩階段提交協議與兩階段鎖協議不一樣,兩階段鎖協議爲一致性控制協議。
(3)該協議的執行過程能夠經過下圖來描述:
(a)成功 (b)失敗
兩階段提交協議最大的劣勢是其經過阻塞完成的協議,在節點等待消息的時候處於阻塞狀態,節點中其餘進程則須要等待阻塞進程釋放資源才能使用。若是協調器發生了故障,那麼參與者將沒法完成事務則一直等待下去。如下狀況可能會致使節點發生永久阻塞:
(1)若是參與者發送贊成提交消息給協調者,進程將阻塞直至收到協調器的提交或回滾的消息。若是協調器發生永久故障,參與者將一直等待,這裏能夠採用備份的協調器,全部參與者將回復發給備份協調器,由它承擔協調器的功能。
(2)若是協調器發送「請求提交」消息給參與者,它將被阻塞直到全部參與者回覆了,若是某個參與者發生永久故障,那麼協調器也不會一直阻塞,由於協調器在某一時間內還未收到某參與者的消息,那麼它將通知其餘參與者回滾事務。
同時兩階段提交協議沒有容錯機制,一個節點發生故障整個事務都要回滾,代價比較大。
下面咱們經過一個例子來講明兩階段提交協議的工做過程:
A組織B、C和D三我的去爬長城:若是全部人都贊成去爬長城,那麼活動將舉行;若是有一人不一樣意去爬長城,那麼活動將取消。用2PC算法解決該問題的過程以下:
首先A將成爲該活動的協調者,B、C和D將成爲該活動的參與者。
(1)階段1:
①A發郵件給B、C和D,提出下週三去登山,問是否贊成。那麼此時A須要等待B、C和D的郵件。
②B、C和D分別查看本身的日程安排表。B、C發現本身在當日沒有活動安排,則發郵件告訴A它們贊成下週三去爬長城。因爲某種緣由, D白天沒有查看郵 件。那麼此時A、B和C均須要等待。到晚上的時候,D發現了A的郵件,而後查看日程安排,發現週三當天已經有別的安排,那麼D回覆A說活動取消吧。
(2)階段2:
①此時A收到了全部活動參與者的郵件,而且A發現D下週三不能去登山。那麼A將發郵件通知B、C和D,下週三爬長城活動取消。
②此時B、C回覆A「太惋惜了」,D回覆A「很差意思」。至此該事務終止。
經過該例子能夠發現,2PC協議存在明顯的問題。假如D一直不能回覆郵件,那麼A、B和C將不得不處於一直等待的狀態。而且B和C所持有的資源,即下週三不能安排其它活動,一直不能釋放。其它等待該資源釋放的活動也將不得不處於等待狀態。
基於此,後來有人提出了三階段提交協議,在其中引入超時的機制,將階段1分解爲兩個階段:在超時發生之前,系統處於不肯定階段;在超市發生之後,系統則轉入肯定階段。
2PC協議包含協調者和參與者,而且兩者都有發生問題的可能性。假如協調者發生問題,咱們能夠選出另外一個協調者來提交事務。例如,班長組織活動,若是班長生病了,咱們能夠請副班長來組織。若是協調者出問題,那麼事務將不會取消。例如,班級活動但願每一個人都能去,假若有一位同窗不能去了,那麼直接取消活動便可。或者,若是大多數人去的話那麼活動如期舉行(2PC變種)。爲了可以更好地解決實際的問題,2PC協議存在不少的變種,例如:樹形2PC協議 (或稱遞歸2PC協議)、動態2階段提交協議(D2PC)等。
產生:第三方支付網關,重試機制形成冪等性
判斷支付結果標識 注意:回調接口中,若是調用耗時代碼,使用mq異步推送
Token、對稱加密 base64 加簽、rsa
使用對帳(第三方支付交易接口),去進行查詢。
對帳(第三方交易平臺交易結果)
聯合登陸步驟:
螞蟻課堂生成受權鏈接,跳轉到騰訊企業
選擇受權QQ用戶,受權成功後,就會跳轉到原地址
受權鏈接:
回調地址 :受權成功後,跳轉到回調地址
跳轉到回調地址:傳一些參數
跳轉到回調地址:
傳一個受權code有效期 10分鐘 受權code使用完畢以後,直接刪除,不能重複使用
受權碼的做用:使用受權碼換取aess_token,使用aess_token換取openid
openid做用: 惟一用戶主鍵(受權系統會員主鍵,不代碼騰訊userid)
openid和咱們用戶表中存放一個openid進行關聯
使用openid調用騰訊會員接口查詢QQ信息
本地回調
跨域緣由產生:在當前域名請求網站中,默認不容許經過ajax請求發送其餘域名。
XMLHttpRequest cannot load 跨域問題解決辦法
後臺response添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持全部網站
JSONP的優缺點:
JSONP只支持get請求不支持psot請求
什麼是SQL語句注入
使用nginx轉發。
配置:
server { listen 80; server_name www.itmayiedu.com; location /A { proxy_pass http://a.a.com:81/A; index index.html index.htm; } location /B { proxy_pass http://b.b.com:81/B; index index.html index.htm; } } |
相關圖:
內部服務器使用HttpClient技術進行轉發
例如A調用B接口,B接口沒有及時反應,怎麼進行補償?
日誌記錄,任務調度定時補償,自動重試機制。
①使用zookeeper實現分佈式鎖 缺點(須要建立臨時節點、和事件通知不易於擴展)
②使用配置文件作一個開關 缺點發布後,須要重啓
③數據庫惟一約束,缺點效率低
④使用分佈式任務調度平臺
XXLJOB
解決辦法:
① 使用日誌+msg-id保存報文信息,做爲去重和冪等的依據。
② 消費端代碼拋出異常,不須要重試補償,使用日誌記錄報文,下次發版本解決。
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、受權、密碼和會話管理。使用Shiro的易於理解的API,您能夠快速、輕鬆地得到任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
subject:主體,能夠是用戶也能夠是程序,主體要訪問系統,系統須要對主體進行認證、受權。
securityManager:安全管理器,主體進行認證和受權都 是經過securityManager進行。它包含下面的認證器和受權器。
authenticator:認證器,主體進行認證最終經過authenticator進行的。
authorizer:受權器,主體進行受權最終經過authorizer進行的。
sessionManager:web應用中通常是用web容器對session進行管理,shiro也提供一套session管理的方式。能夠實現單點登陸。
SessionDao: 經過SessionDao管理session數據,針對個性化的session數據存儲須要使用sessionDao。
cache Manager:緩存管理器,主要對session和受權數據進行緩存,好比將受權數據經過cacheManager進行緩存管理,和ehcache整合對緩存數據進行管理。
realm:域,領域,至關於數據源,經過realm存取認證、受權相關數據。(它的主要目的是與數據庫打交道,查詢數據庫中的認證的信息(好比用戶名和密碼),查詢受權的信息(好比權限的code等,因此這裏能夠理解爲調用數據庫查詢一系列的信息,通常狀況下在項目中採用自定義的realm,由於不一樣的業務需求不同))
1、經過ini配置文件建立securityManager
2、調用subject.login方法主體提交認證,提交的token
3、securityManager進行認證,securityManager最終由ModularRealmAuthenticator進行認證。
4、ModularRealmAuthenticator調用IniRealm(給realm傳入token) 去ini配置文件中查詢用戶信息
5、IniRealm根據輸入的token(UsernamePasswordToken,即這裏的token是用戶從頁面輸入的信息)從 shiro-first.ini查詢用戶信息(這裏是測試階段,後面都是查詢的數據庫,注入service,調用dao),根據帳號查詢用戶信息(帳號和密碼)若是查詢到用戶信息,就給ModularRealmAuthenticator返回用戶信息(帳號和密碼)若是查詢不到,就給ModularRealmAuthenticator返回null
6、ModularRealmAuthenticator接收IniRealm返回Authentication認證信息若是返回的認證信息是null,ModularRealmAuthenticator拋出異常(org.apache.shiro.authc.UnknownAccountException) 若是返回的認證信息不是null(說明inirealm找到了用戶),對IniRealm返回用戶密碼 (在ini文件中存在)和 token中的密碼 進行對比,若是不一致拋出異常(org.apache.shiro.authc.IncorrectCredentialsException)
小結:
ModularRealmAuthenticator做用進行認證,須要調用realm查詢用戶信息(在數據庫中存在用戶信息)ModularRealmAuthenticator進行密碼對比(認證過程)。
realm:須要根據token中的身份信息去查詢數據庫(入門程序使用ini配置文件),若是查到用戶返回認證信息,若是查詢不到返回null。
md5+salt(這個鹽通常是隨機鹽,即開發人員給定義隨機的字符串或者數字便可)+散列次數,這裏的md5是原始的md5的加密了一次的密碼+隨機鹽,而後對這個新的密碼password=(md5+salt),進行散列:如何進行散列呢:就是屢次md5加密md5(md5(md5(md5(password)))),這是4次散列,每次密碼的破解的難度都加大。
原理:
1、對subject進行受權,調用方法isPermitted("permission串")
2、SecurityManager執行受權,經過ModularRealmAuthorizer執行受權
3、ModularRealmAuthorizer執行realm(自定義的CustomRealm)從數據庫查詢權限數據調用realm的受權方法:doGetAuthorizationInfo
4、realm從數據庫查詢權限數據,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer調用PermissionResolver進行權限串比對
6、若是比對後,isPermitted中"permission串"在realm查詢到權限數據中,說明用戶訪問permission串有權限,不然 沒有權限,拋出異常。
shiro的受權方式有三種:
(1)—— 編程式:經過寫if/else 受權代碼塊完成:(這種比較少用,通常在項目中採用後兩種)
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(「admin」)) {
//有權限
} else {
//無權限
}
(2)—— 註解式:經過在執行的Java方法上放置相應的註解完成:
@RequiresRoles("admin")
public void hello() {
//有權限
}
(3)—— JSP/GSP 標籤:在JSP/GSP 頁面經過相應的標籤完成:
在jsp頁面導入shiro的標籤既能夠使用shiro的標籤來進行權限的判斷:
Jsp頁面添加:
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
Hessian 是一個基於 binary-RPC 實現的遠程通信 library。使用二進制傳輸數據。
Hessian一般經過Web應用來提供服務,經過接口暴露。
Servlet和Spring的DispatcherServlet均可以把請求轉發給Hessian服務。
由如下兩種方式提供,分別爲:
com.caucho.hessian.server.HessianServlet
org.springframework.web.servlet.DispatcherServlet。
使用hessian提供的方式建立代理對象調用服務
HessianProxyFactory factory = new HessianProxyFactory();
HelloService proxy = (HelloService)
factory.create(HelloService.class,
"http://localhost:8080/hessian_server/hessian"
);
Object users = proxy.findAllUser();
System.out.println(users);
Lucene是Apache的一個全文檢索引擎工具包,經過lucene可讓程序員快速開發一個全文檢索功能。
Lucene和搜索引擎不是一回事-Lucene是一個工具包,它不能獨立運行,不能單獨對外提供服務。
搜索引擎能夠獨立運行對外提供搜索服務。
全文檢索首先對要搜索的文檔進行分詞,而後造成索引,經過查詢索引來查詢文檔。
全文檢索就是先建立索引,而後根據索引來進行搜索的過程,就叫全文檢索。
好比:字典,
字典的偏旁部首頁,就相似於luence的索引---索引域
字典的具體內容,就相似於luence的文檔內容-文檔域
全文檢索的流程:索引流程、搜索流程
索引流程:採集數據—》文檔處理存儲到索引庫中
搜索流程:輸入查詢條件—》經過lucene的查詢器查詢索引—》從索引庫中取出結—》視圖渲染
Lucene自己不能進行視圖渲染。
全文檢索搜索的內容的格式是多種多樣的,好比:視頻、mp3、圖片、文檔等等。
對於這種格式不一樣的數據,須要先將他們採集到本地,而後統一封裝到lucene的文檔對象中,也就是說須要將存儲的內容進行統一才能對它進行查詢。
對於互聯網中的數據,使用爬蟲工具(http工具)將網頁爬取到本地
對於數據庫中的數據,使用jdbc程序進行數據採集
對於文件系統的數據,使用io流採集
由於目前搜索引擎主要搜索數據的來源是互聯網,搜索引擎使用一種爬蟲程序抓取網頁( 經過http抓取html網頁信息)
如下是一些爬蟲項目-Solr-Nutch-jsoup-heritrix
文檔域存儲的信息就是採集到的信息,經過Document對象來存儲,具體說是經過Document對象中field域來存儲數據。
好比:數據庫中一條記錄會存儲一個一個Document對象,數據庫中一列會存儲成Document中一個field域。
文檔域中,Document對象之間是沒有關係的。並且每一個Document中的field域也不必定同樣。
索引域主要是爲了搜索使用的。索引域內容是通過lucene分詞以後存儲的。
倒排索引結構是根據內容(詞語)找文檔,倒排索引結構也叫反向索引結構,包括索引和文檔兩部分,
索引即詞彙表,它是在索引中匹配搜索關鍵字,因爲索引內容量有限而且採用固定優化算法搜索速度很快,
找到了索引中的詞彙,詞彙與文檔關聯,從而最終找到了文檔。
IndexWriter是索引過程的核心組件,經過IndexWriter能夠建立新索引、更新索引、刪除索引操做。 IndexWriter須要經過Directory對索引進行存儲操做。
Directory描述了索引的存儲位置,底層封裝了I/O操做,負責對索引進行存儲。
它是一個抽象類,它的子類經常使用的包括FSDirectory(在文件系統存儲索引)、RAMDirectory(在內存存儲索引)。
Lucene中分詞主要分爲兩個步驟:分詞、過濾
分詞:將field域中的內容一個個的分詞。
過濾:將分好的詞進行過濾,好比去掉標點符號、大寫轉小寫、詞的型還原
(複數轉單數、過去式轉成如今式)、停用詞過濾
停用詞:單獨應用沒有特殊意義的詞。好比的、啊、等,英文中的this is a the等等。
同一個域中相同的語彙單元(Token)對應同一個Term(詞),
它記錄了語匯單元的內容及所在域的域名等,還包括來該token出現的頻率及位置。
l不一樣的域中拆分出來的相同的單詞對應不一樣的term。
l相同的域中拆分出來的相同的單詞對應相同的term。
查詢對象Query-索引搜索對象IndexSearcher-結果集TopDocs
索引讀取對象IndexReader
索引目錄流對象Directory
索引庫
Field的屬性
Ø 是否分詞(Tokenized)
是:對該field存儲的內容進行分詞,分詞的目的,就是爲了索引。
否:不須要對field存儲的內容進行分詞,不分詞,不表明不索引,而是將整個內容進行索引。
是否索引(Indexed)
是否存儲(Stored)
Field的經常使用類型
字符串-StringField
Long型-LongField
重載方法支持多種類型-StoredField
字符串-流-TextField
建立查詢對象的方式
Ø 經過Query子類來建立查詢對象
Query子類經常使用的有:TermQuery、NumericRangeQuery、BooleanQuery
不能輸入lucene的查詢語法,不須要指定分詞器
Ø
經過QueryParser來建立查詢對象(經常使用)
QueryParser、MultiFieldQueryParser
能夠輸入lucene的查詢語法、能夠指定分詞器
域名+「:」+搜索的關鍵字
域名+「:」+[最小值 TO 最大值]
組合條件查詢--and--or--not
條件1 AND 條件2
條件1 OR 條件2
條件1 NOT 條件2
Lucene搜索結果可經過TopDocs遍歷,TopDocs類提供了少許的屬性
totalHits--匹配搜索條件的總記錄數
scoreDocs--頂部匹配記錄
Tf(詞在同一個文檔中出現的頻率)越高,說明詞的權重越高
Df(詞在多個文檔中出現的頻率)越高,說明詞的權重越低
建立索引時設置boost值
搜索時設置boost值
Lucene自帶的中文分詞器--CJKAnalyzer--StandardAnalyzer
一般使用IK-analyzer中文分詞器,使用luke來查詢中文分詞效果
Solr是apache的頂級開源項目,它是使用java開發 ,基於lucene的全文檢索服務器。
Solr比lucene提供了更多的查詢語句,並且它可擴展、可配置,同時它對lucene的性能進行了優化。
索引流程:solr客戶端(瀏覽器、java程序)能夠向solr服務端發送POST請求,
請求內容是包含Field等信息的一個xml文檔,經過該文檔,solr實現對索引的維護(增刪改)
搜索流程:solr客戶端(瀏覽器、java程序)能夠向solr服務端發送GET請求,solr服務器返回一個xml文檔。
Solr一樣沒有視圖渲染的功能
Lucene是一個全文檢索引擎工具包,它只是一個jar包,不能獨立運行,對外提供服務。
Solr是一個全文檢索服務器,它能夠單獨運行在servlet容器,能夠單獨對外提供搜索和索引功能。
Solr比lucene在開發全文檢索功能時,更快捷、更方便。
Solrcore和solrhome/collection1/conf(核心配置solrconfig.xml)&data
Dashboard、Logging、Cloud、Core Admin、java properties、Tread Dump、Core selector Analysis、dataimport、Document、Query
在schema.xml文件中,主要配置了solrcore的一些數據信息,包括Field和FieldType的定義等信息,
在solr中,Field和FieldType都須要先定義後使用。
Filed
dynamicField
uniqueKey
copyField
FieldType
中文分詞器也須要配置FieldType、Field
配置業務Field-根據需求配置
該插件能夠將數據庫中指定的sql語句的結果導入到solr索引庫中。
Dataimport的jar包
修改solrconfig.xml文件,添加lib標籤
<lib dir="${solr.install.dir:../..}/contrib/dataimporthandler/lib" regex=".*\.jar" />
MySQL數據庫驅動包
<lib dir="${solr.install.dir:../..}/contrib/db/lib" regex=".*\.jar" />
在solrconfig.xml中,添加一個dataimport的requestHandler
配置springmvc.xml--使用org.apache.solr.client.solrj.impl.HttpSolrClient構造注入
FastDFS是用c語言編寫的一款開源的分佈式文件系統。FastDFS爲互聯網量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高性能等指標,使用FastDFS很容易搭建一套高性能的文件服務器集羣提供文件上傳、下載等服務。
富文本編輯器的使用KindEditor
輪播圖
須要使用HttpClient調用rest發佈的服務。把結果傳遞給jsp。Jsp中須要的json數據格式。
freemarker-是一個用Java語言編寫的模板引擎,它基於模板來生成文本輸出
AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不一樣產品,不一樣的開發語言等條件的限制。Erlang中的實現有 RabbitMQ等Erlang(['ə:læŋ])是一種通用的面向併發的編程語言,它由瑞典電信設備製造商愛立信所轄的CS-Lab開發,目的是創造一種能夠應對大規模併發活動的編程語言和運行環境。
MQ全稱爲Message Queue, 消息隊列(MQ)是一種應用程序對應用程序的通訊方法。應用程序經過讀寫出入隊列的消息(針對應用程序的數據)來通訊,而無需專用鏈接來連接它們,RabbitMQ是一個在AMQP基礎上完成的,可複用的企業消息系統。他遵循Mozilla Public License開源協議。
功能:一個生產者P發送消息到隊列Q,一個消費者C接收
生產者實現思路:
建立鏈接工廠ConnectionFactory,設置服務地址127.0.0.1,端口號5672,設置用戶名、密碼、virtual host,從鏈接工廠中獲取鏈接connection,使用鏈接建立通道channel,使用通道channel建立隊列queue,使用通道channel向隊列中發送消息,關閉通道和鏈接。
消費者實現思路
建立鏈接工廠ConnectionFactory,設置服務地址127.0.0.1,端口號5672,設置用戶名、密碼、virtual host,從鏈接工廠中獲取鏈接connection,使用鏈接建立通道channel,使用通道channel建立隊列queue, 建立消費者並監聽隊列,從隊列中讀取消息。
功能描述:一個生產者發送消息到隊列中,有多個消費者共享一個隊列,每一個消費者獲取的消息是惟一的。爲了保證服務器同一時刻只發送一條消息給消費者,保證資源的合理利用。channal.basicQos(1);這樣是爲了保證多個消費者接收的消息數量不同,能者多勞,若是不設置,那麼消費者是平均分配消息(例如10條消息,每一個消費者接收5條)
這個多是消息隊列中最重要的隊列了,其餘的都是在它的基礎上進行了擴展。
功能實現:一個生產者發送消息,多個消費者獲取消息(一樣的消息),包括一個生產者,一個交換機,多個隊列,多個消費者。
思路解讀(重點理解):
(1)一個生產者,多個消費者
(2)每個消費者都有本身的一個隊列
(3)生產者沒有直接發消息到隊列中,而是發送到交換機
(4)每一個消費者的隊列都綁定到交換機上
(5)消息經過交換機到達每一個消費者的隊列
注意:交換機沒有存儲消息功能,若是消息發送到沒有綁定消費隊列的交換機,消息則丟失。
功能:生產者發送消息到交換機並指定一個路由key,消費者隊列綁定到交換機時要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息),例如:咱們能夠把路由key設置爲insert ,那麼消費者隊列key指定包含insert才能夠接收消息,消費者隊列key定義爲update或者delete就不能接收消息。很好的控制了更新,插入和刪除的操做。
說明:此模式實在路由key模式的基礎上,使用了通配符來管理消費者接收消息。
生產者P發送消息到交換機X,type=topic,交換機根據綁定隊列的routing key的值進行通配符匹配;
符號#:匹配一個或者多個詞lazy.# 能夠匹配lazy.irs或者lazy.irs.cor
符號*:只能匹配一個詞lazy.* 能夠匹配lazy.irs或者lazy.cor
RabbitMQ提供了6種模式,
分別是HelloWorld,Work Queue,Publish/Subscribe,Routing,Topics,RPC Request/reply,
其中Publish/Subscribe,Routing,Topics三種模式能夠統一歸爲Exchange模式,只是建立時交換機的類型不同,分別是fanout、direct、topic。Spring提供了rabbitmq的一個實現,因此集成起來很方便。
一個HDFS集羣是由一個Namenode和必定數目的Datanodes組成。Namenode是一箇中心服務器,負責管理文件系統的名字空間(namespace)以及客戶端對文件的訪問。
集羣中的Datanode通常是一個節點一個,負責管理它所在節點上的存儲。HDFS暴露了文件系統的名字空間,用戶可以以文件的形式在上面存儲數據。從內部看,一個文件其實被分紅一個或多個數據塊,這些塊存儲在一組Datanode上。Namenode執行文件系統的名字空間操做,好比打開、關閉、重命名文件或目錄。它也負責肯定數據塊到具體Datanode節點的映射。Datanode負責處理文件系統客戶端的讀寫請求。在Namenode的統一調度下進行數據塊的建立、刪除和複製。
HDFS支持傳統的層次型文件組織結構。用戶或者應用程序能夠建立目錄,而後將文件保存在這些目錄裏。文件系統名字空間的層次結構和大多數現有的文件系統相似:用戶能夠建立、刪除、移動或重命名文件。當前,HDFS不支持用戶磁盤配額和訪問權限控制,也不支持硬連接和軟連接。可是HDFS架構並不妨礙實現這些特性。
Namenode負責維護文件系統的名字空間,任何對文件系統名字空間或屬性的修改都將被Namenode記錄下來。
應用程序能夠設置HDFS保存的文件的副本數目。文件副本的數目稱爲文件的副本系數,這個信息也是由Namenode保存的。
HDFS被設計成可以在一個大集羣中跨機器可靠地存儲超大文件。它將每一個文件存儲成一系列的數據塊,除了最後一個,全部的數據塊都是一樣大小的。
爲了容錯,文件的全部數據塊都會有副本。每一個文件的數據塊大小和副本系數都是可配置的。應用程序能夠指定某個文件的副本數目。
副本系數能夠在文件建立的時候指定,也能夠在以後改變。HDFS中的文件都是一次性寫入的,而且嚴格要求在任什麼時候候只能有一個寫入者。
Namenode全權管理數據塊的複製,它週期性地從集羣中的每一個Datanode接收心跳信號和塊狀態報告(Blockreport)。
接收到心跳信號意味着該Datanode節點工做正常。塊狀態報告包含了一個該Datanode上全部數據塊的列表。
爲了下降總體的帶寬消耗和讀取延時,HDFS會盡可能讓讀取程序讀取離它最近的副本。若是在讀取程序的同一個機架上有一個副本,
那麼就讀取該副本。若是一個HDFS集羣跨越多個數據中心,那麼客戶端也將首先讀本地數據中心的副本。
Namenode啓動後會進入一個稱爲安全模式的特殊狀態。
Namenode上保存着HDFS的名字空間。對於任何對文件系統元數據產生修改的操做,Namenode都會使用一種稱爲EditLog的事務日誌記錄下來
全部的HDFS通信協議都是創建在TCP/IP協議之上。客戶端經過一個可配置的TCP端口鏈接到Namenode,經過ClientProtocol協議與Namenode交互。而Datanode使用DatanodeProtocol協議與Namenode交互
HDFS的架構支持數據均衡策略。若是某個Datanode節點上的空閒空間低於特定的臨界點,按照均衡策略系統就會自動地將數據從這個Datanode移動到其餘空閒的Datanode。
SecondaryNameNode有兩個做用,一是鏡像備份,二是日誌與鏡像的按期合併
Hadoop分佈式文件系統(HDFS)容許管理員爲每一個目錄設置配額。 新創建的目錄沒有配額。 最大的配額是Long.Max_Value。
用戶命令
archive 建立一個hadoop檔案文件
distcp 遞歸地拷貝文件或目錄
fs 運行一個常規的文件系統客戶端
fsck 運行HDFS文件系統檢查工具------查看HDFS文件對應的文件塊信息(Block)和位置信息
jar 運行jar文件。用戶能夠把他們的Map Reduce代碼捆綁到jar文件中,使用這個命令執行
job 用於和Map Reduce做業交互和命令。
pipes 運行pipes做業。
version 打印版本信息。
CLASSNAME hadoop腳本可用於調調用任何類。
管理命令
balancer 運行集羣平衡工具
daemonlog 獲取或設置每一個守護進程的日誌級別
datanode 運行一個HDFS的datanode
dfsadmin 運行一個HDFS的dfsadmin客戶端
jobtracker 運行MapReduce job Tracker節點。
namenode 運行namenode。有關升級,回滾
secondarynamenode 運行HDFS的secondary namenode。
tasktracker 運行MapReduce的task Tracker節點
FS Shell
cat 將路徑指定文件的內容輸出到stdout。
chgrp 改變文件所屬的組
chmod 改變文件的權限
chown 改變文件的擁有者
copyFromLocal 除了限定源路徑是一個本地文件外,和put命令類似。
copyToLocal 除了限定目標路徑是一個本地文件外,和get命令相似
cp 將文件從源路徑複製到目標路徑
du 顯示目錄中全部文件的大小
dus 顯示文件的大小。
expunge 清空回收站。
get 複製文件到本地文件系統
getmerge 接受一個源目錄和一個目標文件做爲輸入,而且將源目錄中全部的文件鏈接成本地目標文件
ls
lsr
mkdir
movefromLocal
mv 將文件從源路徑移動到目標路徑。
put 從本地文件系統中複製單個或多個源路徑到目標文件系統。也支持從標準輸入中讀取輸入寫入目標文件系統。
rm
rmr delete的遞歸版本
setrep 改變一個文件的副本系數
stat 返回指定路徑的統計信息
tail 將文件尾部1K字節的內容輸出到stdout
test
text 將源文件輸出爲文本格式
touchz 建立一個0字節的空文件。
DistCp(分佈式拷貝)是用於大規模集羣內部和集羣之間拷貝的工具。 它使用Map/Reduce實現文件分發,錯誤處理和恢復,以及報告生成
Hadoop Map/Reduce是一個使用簡易的軟件框架,基於它寫出來的應用程序可以運行在由上千個商用機器組成的大型集羣上,並以一種可靠容錯的方式並行處理上T級別的數據集。一個Map/Reduce 做業(job) 一般會把輸入的數據集切分爲若干獨立的數據塊,由 map任務(task)以徹底並行的方式處理它們。框架會對map的輸出先進行排序,而後把結果輸入給reduce任務。一般做業的輸入和輸出都會被存儲在文件系統中。整個框架負責任務的調度和監控,以及從新執行已經失敗的任務。
Map/Reduce框架由一個單獨的master JobTracker 和每一個集羣節點一個slave TaskTracker共同組成。master負責調度構成一個做業的全部任務,這些任務分佈在不一樣的slave上,master監控它們的執行,從新執行已經失敗的任務。而slave僅負責執行由master指派的任務。
MR有兩個階段組成:Map和Reduce,用戶只需實現map()和reduce()兩個函數,便可實現分佈式計算。
1.1 讀取HDFS中的文件。每一行解析成一個<k,v>。每個鍵值對調用一次map函數。 <0,hello you> <10,hello me>
1.2 覆蓋map(),接收1.1產生的<k,v>,進行處理,轉換爲新的<k,v>輸出。 <hello,1> <you,1> <hello,1> <me,1>
1.3 對1.2輸出的<k,v>進行分區。默認分爲一個區。詳見《Partitioner》-Partitioner負責控制map輸出結果key的分割。
1.4 對不一樣分區中的數據進行排序(按照k)、分組。分組指的是相同key的value放到一個集合中。 排序後:<hello,1> <hello,1> <me,1> <you,1> 分組後:<hello,{1,1}><me,{1}><you,{1}>
1.5 (可選)對分組後的數據進行歸約。詳見《Combiner》(文件倒敘索引)
使用IK分詞器分詞。
lucene搜索引擎分析
2.1 多個map任務的輸出,按照不一樣的分區,經過網絡copy到不一樣的reduce節點上。(shuffle)詳見《shuffle過程分析》
2.2 對多個map的輸出進行合併、排序。覆蓋reduce函數,接收的是分組後的數據,實現本身的業務邏輯, <hello,2> <me,1> <you,1>處理後,產生新的<k,v>輸出。
2.3 對reduce輸出的<k,v>寫到HDFS中。
// combiner
public class LastSearchComb extends Reducer<Text, Text, Text, Text> {
@Override
protected void reduce(Text arg0, Iterable<Text> arg1, Context arg2) throws IOException, InterruptedException {
ik分詞器分詞
}
}
file->從map(mappertask) 按group(shuffle)到reduce(reducerTask)->directory
Yarn主要由四個重要角色組成:ResourceManager顧名思義資源管理器--NodeManager節點管理器--ApplicaitonMaster主要負責監控應用---Container容器是資源調度的單位
Kafka總體結構圖、Consumer與topic關係、Kafka消息分發、Consumer的負載均衡、Kafka文件存儲機制、Kafka partition segment
l 1)、 Producer :消息生產者,就是向kafka broker發消息的客戶端。
l 2)、Consumer :消息消費者,向kafka broker取消息的客戶端
l 3)、 Topic :能夠理解爲一個隊列。
l 4)、Consumer Group (CG):這是kafka用來實現一個topic消息的廣播(發給全部的consumer)和單播(發給任意一個consumer)的手段。
一個topic能夠有多個CG。topic的消息會複製(不是真的複製,是概念上的)到全部的CG,但每一個partion只會把消息發給該CG中的一個consumer。若是須要實現廣播,只要每一個consumer有一個獨立的CG就能夠了。要實現單播只要全部的consumer在同一個CG。
用CG還能夠將consumer進行自由的分組而不須要屢次發送消息到不一樣的topic。
l 5)、Broker :一臺kafka服務器就是一個broker。一個集羣由多個broker組成。一個broker能夠容納多個topic。
l 6)、Partition:爲了實現擴展性,一個很是大的topic能夠分佈到多個broker(即服務器)上,一個topic能夠分爲多個partition,每一個partition是一個有序的隊列。partition中的每條消息都會被分配一個有序的id(offset)。kafka只保證按一個partition中的順序將消息發給consumer,不保證一個topic的總體(多個partition間)的順序。
7)、Offset:**kafka的存儲文件都是按照offset.kafka來命名,用offset作名字的好處是方便查找。例如你想找位於2049的位置,
只要找到2048.kafka的文件便可。固然the first offset就是00000000000.kafka
對於Topic中的一條特定的消息,只會被訂閱此Topic的每一個group中的其中一個consumer消費,此消息不會發送給一個group的多個consumer 那麼一個group中全部的consumer將會交錯的消費整個Topic,每一個group中consumer消息消費互相獨立,咱們能夠認爲一個group是一個」訂閱」者。 在kafka中,一個partition中的消息只會被group中的一個consumer消費(同一時刻);
一個Topic中的每一個partions,只會被一個」訂閱者」中的一個consumer消費,不過一個consumer能夠同時消費多個partitions中的消息。 kafka的設計原理決定,對於一個topic,同一個group中不能有多於partitions個數的consumer同時消費,不然將意味着某些consumer將沒法獲得消息。
kafka只能保證一個partition中的消息被某個consumer消費時是順序的;事實上,從Topic角度來講,當有多個partitions時,消息仍不是全局有序的。
Producer客戶端負責消息的分發
Producer消息發送的應答機制
設置發送數據是否須要服務端的反饋,有三個值0,1,-1
0: producer不會等待broker發送ack
1: 當leader接收到消息以後發送ack
-1: 當全部的follower都同步消息成功後發送ack
request.required.acks=0
當一個group中,有consumer加入或者離開時,會觸發partitions均衡.均衡的最終目的,是提高topic的併發消費能力
在Kafka文件存儲中,同一個topic下有多個不一樣partition,每一個partition爲一個目錄,partiton命名規則爲topic名稱+有序序號,第一個partiton序號從0開始,序號最大值爲partitions數量減1。 每一個partion(目錄)至關於一個巨型文件被平均分配到多個大小相等segment(段)數據文件中。但每一個段segment file消息數量不必定相等,這種特性方便old segment file快速被刪除。默認保留7天的數據
集羣時,broker.id=?不能重複
建立topic
查看topic
建立producer
建立consumer
//1、準備一個TopologyBuilder
//storm框架支持多語言,在Java環境下建立一個拓撲,須要使用TopologyBuilder
TopologyBuilder topologyBuilder = new TopologyBuilder();
//MySpout類,在已知的英文句子中,所及發送一條句子出去
topologyBuilder.setSpout("mySpout", new MySpout(), 2); //繼承BaseRichSpout類
//MySplitBolt類,主要是將一行一行的文本內容切割成單詞
topologyBuilder.setBolt("mybolt1", new MySplitBolt(), 2).shuffleGrouping("mySpout"); //繼承BaseRichBolt
//MyCountBolt類,負責對單詞的頻率進行累加
topologyBuilder.setBolt("mybolt2", new MyCountBolt(), 4).fieldsGrouping("mybolt1", new Fields("word")); //繼承BaseRichBolt重寫execute()方法
//2、建立一個configuration,用來指定當前topology 須要的worker的數量
//啓動topology的配置信息
Config config = new Config();
//定義你但願集羣分配多少個工做進程給你來執行這個topology
config.setNumWorkers(2);
//3、提交任務 -----兩種模式 本地模式和集羣模式
//這裏將拓撲名稱寫死了mywordcount,因此在集羣上打包運行的時候,不用寫拓撲名稱了!也可用arg[0]
StormSubmitter.submitTopology("mywordcount", config, topologyBuilder.createTopology());
Storm-Flume+Kafka+Storm+Redis構建大數據實時處理系統
Storm框架主要由7部分組成
1)、Topology:一個實時應用的計算任務被打包做爲Topology發佈,這同Hadoop的MapReduce任務類似。
2)、Spout:Storm中的消息源,用於爲Topology生產消息(數據),通常是從外部數據源(如Message Queue、RDBMS、NoSQL、Realtime Log)
不間斷地讀取數據併發送給Topology消息(tuple元組)。
3)、Bolt:Storm中的消息處理者,用於爲Topology進行消息的處理,Bolt能夠執行過濾,聚合, 查詢數據庫等操做,並且能夠一級一級的進行處理。
4)、Stream:產生的數據(tuple元組)。
5)、Stream grouping:在Bolt任務中定義的Stream進行區分。
6)、Task:每一個Spout或者Bolt在集羣執行許多任務。
7)、Worker:Topology跨一個或多個Worker節點的進程執行。
Storm 分佈式計算結構稱爲 topology(拓撲),由 stream(數據流), spout(數據流的生成者), bolt(運算)組成。
spout 表明了一個 Storm topology 的主要數據入口,充當採集器的角色,鏈接到數據源,將數據轉化爲一個個 tuple,並將 tuple 做爲數據流進行發射。
bolt 能夠理解爲計算程序中的運算或者函數,將一個或者多個數據流做爲輸入,對數據實施運算後,選擇性地輸出一個或者多個數據流。bolt 能夠訂閱多個由 spout 或者其餘bolt 發射的數據流,這樣就能夠創建複雜的數據流轉換網絡。
實現BaseRichBolt接口,重寫單詞計數方法
SentenceSpout spout = new SentenceSpout();
SplitSentenceBolt splitBolt = new SplitSentenceBolt();
WordCountBolt countBolt = new WordCountBolt();
ReportBolt reportBolt = new ReportBolt();
TopologyBuilder builder = new TopologyBuilder();//建立了一個TopologyBuilder實例
//設置兩個Executeor(線程),默認一個
builder.setSpout(SENTENCE_SPOUT_ID, spout,2);
//SplitSentenceBolt單詞分割器設置4個Task,2個Executeor(線程)
builder.setBolt(SPLIT_BOLT_ID, splitBolt,2).setNumTasks(4).shuffleGrouping(SENTENCE_SPOUT_ID);
//WordCountBolt單詞計數器設置4個Executeor(線程)
builder.setBolt(COUNT_BOLT_ID, countBolt,4).fieldsGrouping( SPLIT_BOLT_ID, new Fields("word"));
//globalGrouping是把WordCountBolt發射的全部tuple路由到惟一的ReportBolt
builder.setBolt(REPORT_BOLT_ID, reportBolt).globalGrouping(COUNT_BOLT_ID);
Config config = new Config();//Config類是一個HashMap<String,Object>的子類,用來配置topology運行時的行爲
//設置worker數量
//config.setNumWorkers(2);
LocalCluster cluster = new LocalCluster();
//本地提交
cluster.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());
cluster.killTopology(TOPOLOGY_NAME);
cluster.shutdown();
Spout是storm用於數據生成的一個組件,。一般,Spout會實現一個IRichSpout接口。
IRichSpout接口有如下重要的方法:
1.open:−提供Spout以及spout的執行環境。executors會運行這個方法來初始化spout。
2.nextTuple:經過收集器發送產生的數據。
3.close−關閉Spout時調用close方法。
4.declareOutputFields:聲明輸出元組的schema。
5.ack:處理特定的元組
6.fail:指定一個特定的不用處理和再加工元組。
1、構建四臺服務器,上面會創建管理員,叫作nimbus,用來協調任務、分配任務;
2、在下面的各個服務器上構建supervisor,用來處理數據;在supervisor上會啓動對應的worker,每一個worker對應一個端口:6700、6701、6702;每一個機器都一致。
3、Client提交任務是將其提交給nimbus,由nimbus分配任務:首先nimbus會獲取空閒worker的信息,而後根據併發數分配任務(topology會產生任務信息:
有一個併發數爲myspout爲2,mybolt1爲2,mybolt2爲4,worker爲2等,共造成8個task,每一個worker中有8/2=4個,即每一個worker上有4個線程,
task的劃分是用taskid去取模來分配),分配完成後,將這些信息放在zk上。
4、Supervisor中會經過watcher監聽zk,topology中有三個任務類型,myspout、mybolt1、mybolt2三種,這三種類型會分配給2個worker,
如上圖的前兩個supervisor,此時第三個supervisor會空閒;
5、當這個數據分配好後,開始鏈接外部數據源,而後開始運做,myspout鏈接外面的datasource,myspout隨機發的話數據會經過myspout發送給兩個bolt1(在兩臺服務器上)
在bolt1上將sentence切分開,造成一個個單詞,bolt1將數據分給bolt2的時候是按照field進行的,相同的單詞會給到同一個bolt2上。
Disruptor--Disruptor是由LAMX(歐洲頂級金融公司)設計和開源的大規模、高併發、低延遲的異步處理框架-Worker 內部通訊技術(Disruptor)
Storm的術語包括Stream、Spout、Bolt、Task、Worker、Stream Grouping和Topology。Stream是被處理的數據。Sprout是數據源。Bolt處理數據。
Task是運行於Spout或Bolt中的線程。Worker是運行這些線程的進程。Stream Grouping規定了Bolt接收什麼東西做爲輸入數據。
數據能夠隨機分配(術語爲Shuffle),或者根據字段值分配(術語爲Fields),或者廣播(術語爲All),或者老是發給一個Task(術語爲Global),也能夠不關心該數據(術語爲None),或者由自定義邏輯來決定(術語爲Direct)。Topology是由Stream Grouping鏈接起來的Spout和Bolt節點網絡.
3.2 stream grouping分類
1. Shuffle Grouping: 隨機分組, 隨機派發stream裏面的tuple,保證每一個bolt接收到的tuple數目相同.
2. Fields Grouping:按字段分組,好比按userid來分組,具備一樣userid的tuple會被分到相同的Bolts,而不一樣的userid則會被分配到不一樣的Bolts.
3. All Grouping:廣播發送, 對於每個tuple,全部的Bolts都會收到.
4. Global Grouping: 全局分組,這個tuple被分配到storm中的一個bolt的其中一個task.再具體一點就是分配給id值最低的那個task.
5. Non Grouping: 不分組,意思是說stream不關心到底誰會收到它的tuple.目前他和Shuffle grouping是同樣的效果,有點不一樣的是storm會把這個bolt放到這個bolt的訂閱者同一個線程去執行.
6. Direct Grouping: 直接分組,這是一種比較特別的分組方法,用這種分組意味着消息的發送者舉鼎由消息接收者的哪一個task處理這個消息. 只有被聲明爲Direct Stream的消息流能夠聲明這種分組方法.並且這種消息tuple必須使用emitDirect方法來發射. 消息處理者能夠經過TopologyContext來或者處理它的消息的taskid (OutputCollector.emit方法也會返回taskid)
Storm集羣主要由一個主節點和一羣工做節點(worker node)組成,經過 Zookeeper進行協調。
3.3.1 主節點:
主節點一般運行一個後臺程序 —— Nimbus,用於響應分佈在集羣中的節點,分配任務和監測故障。這個很相似於Hadoop中的Job Tracker。
3.3.2工做節點:
工做節點一樣會運行一個後臺程序 —— Supervisor,用於收聽工做指派並基於要求運行工做進程。每一個工做節點都是topology中一個子集的實現。
而Nimbus和Supervisor之間的協調則經過Zookeeper系統或者集羣。
3.3.3 Zookeeper
Zookeeper是完成Supervisor和Nimbus之間協調的服務。而應用程序實現實時的邏輯則被封裝進Storm中的「topology」。 topology則是一組由Spouts(數據源)和Bolts(數據操做)經過Stream Groupings進行鏈接的圖。下面對出現的術語進行更深入的解析。
3.3.4 Spout:
簡而言之,Spout歷來源處讀取數據並放入topology。Spout分紅可靠和不可靠兩種;當Storm接收失敗時,可靠的Spout會對tuple(元組,數據項組成的列表)進行重發;而不可靠的Spout不會考慮接收成功與否只發射一次。 而Spout中最主要的方法就是nextTuple(),該方法會發射一個新的tuple到topology,若是沒有新tuple發射則會簡單的返回。
3.3.5 Bolt:
Topology中全部的處理都由Bolt完成。Bolt能夠完成任何事,好比:鏈接的過濾、聚合、訪問文件/數據庫、等等。Bolt從Spout中接收數據並進行處理,若是遇到複雜流的處理也可能將tuple發送給另外一個Bolt進行處理。而Bolt中最重要的方法是execute(),以新的tuple做爲參數接收。無論是Spout仍是Bolt,若是將tuple發射成多個流,這些流均可以經過declareStream()來聲明。
3.3.6Stream Groupings:
Stream Grouping定義了一個流在Bolt任務間該如何被切分。這裏有Storm提供的6個Stream Grouping類型:
1. 隨機分組(Shuffle grouping):隨機分發tuple到Bolt的任務,保證每一個任務得到相等數量的tuple。
2. 字段分組(Fields grouping):根據指定字段分割數據流,並分組。例如,根據「user-id」字段,相同「user-id」的元組老是分發到同一個任務,不一樣「user-id」的元組可能分發到不一樣的任務。
3. 所有分組(All grouping):tuple被複制到bolt的全部任務。這種類型須要謹慎使用。
4. 全局分組(Global grouping):所有流都分配到bolt的同一個任務。明確地說,是分配給ID最小的那個task。
5. 無分組(None grouping):你不須要關心流是如何分組。目前,無分組等效於隨機分組。但最終,Storm將把無分組的Bolts放到Bolts或Spouts訂閱它們的同一線程去執行(若是可能)。
6. 直接分組(Direct grouping):這是一個特別的分組類型。元組生產者決定tuple由哪一個元組處理者任務接收
Worker進程間技術(Netty、ZeroMQ)
Worker 內部通訊技術(Disruptor)
啓動Storm
storm nimbus
storm supervisor
storm ui
storm jar
hadoop jar
storm kill 【拓撲名稱】---殺死任務命令
1.Spark中使用的是Sacla2.10。
2.Scala官網6個特徵。
1).Java和scala能夠混編
2).類型推測(自動推測類型)
3).併發和分佈式(Actor)
4).特質,特徵(相似java中interfaces 和 abstract結合)
5).模式匹配(相似java switch)
6).高階函數
windows安裝,配置環境變量
l 新建SCALA_HOME
l 上個步驟完成後,編輯Path變量,在後面追加以下:
;%SCALA_HOME%\bin;%SCALA_HOME%\jre\bin
eclipse 配置scala插件
http://scala-ide.org/download/prev-stable.html
下載網址:http://scala-ide.org/download/sdk.html
idea 中配置scala插件
點擊第三步,彈出選擇SDK,點擊Browse選擇本地安裝的Scala目錄。選擇system.
/** * 定義變量和常量 * 變量 :用 var 定義 ,可修改 * 常量 :用 val 定義,不可修改 */ var name = "zhangsan" println(name) name ="lisi" println(name) val gender = "m" // gender = "m"//錯誤,不能給常量再賦值
|
class Person{ val name = "zhangsan" val age = 18 def sayName() = { "my name is "+ name } } |
object Lesson_Class { def main(args: Array[String]): Unit = { val person = new Person() println(person.age); println(person.sayName()) } } |
class Person(xname :String , xage :Int){ var name = Person.name val age = xage var gender = "m" def this(name:String,age:Int,g:String){ this(name,age) gender = g }
def sayName() = { "my name is "+ name }
}
object Person { val name = "zhangsanfeng"
def main(args: Array[String]): Unit = { val person = new Person("wagnwu",10,"f") println(person.age); println(person.sayName()) println(person.gender) } } |
注意點:
l 建議類名首字母大寫 ,方法首字母小寫,類和方法命名建議符合駝峯命名法。
l scala 中的object是單例對象,至關於java中的工具類,能夠當作是定義靜態的方法的類。object不能夠傳參數。另:Trait不能夠傳參數
l scala中的class類默承認以傳參數,默認的傳參數就是默認的構造函數。
重寫構造函數的時候,必需要調用默認的構造函數。
l class 類屬性自帶getter ,setter方法。
l 使用object時,不用new,使用class時要new ,而且new的時候,class中除了方法不執行,其餘都執行。
l 若是在同一個文件中,object對象和class類的名稱相同,則這個對象就是這個類的伴生對象,這個類就是這個對象的伴生類。能夠互相訪問私有變量。
/** * if else */ val age =18 if (age < 18 ){ println("no allow") }else if (18<=age&&age<=20){ println("allow with other") }else{ println("allow self") } |
/** * to和until * 例: * 1 to 10 返回1到10的Range數組,包含10 * 1 until 10 返回1到10 Range數組 ,不包含10 */
println(1 to 10 )//打印 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 println(1.to(10))//與上面等價,打印 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
println(1 to (10 ,2))//步長爲2,從1開始打印 ,1,3,5,7,9 println(1.to(10, 2))
println(1 until 10 ) //不包含最後一個數,打印 1,2,3,4,5,6,7,8,9 println(1.until(10))//與上面等價
println(1 until (10 ,3 ))//步長爲2,從1開始打印,打印1,4,7
|
/** * for 循環 * */ for( i <- 1 to 10 ){ println(i) } |
//能夠分號隔開,寫入多個list賦值的變量,構成多層for循環 //scala中 不能寫count++ count-- 只能寫count+ var count = 0; for(i <- 1 to 10; j <- 1 until 10){ println("i="+ i +", j="+j) count += 1 } println(count);
//例子: 打印小九九 for(i <- 1 until 10 ;j <- 1 until 10){ if(i>=j){ print(i +" * " + j + " = "+ i*j+" ")
} if(i==j ){ println() }
} |
//能夠在for循環中加入條件判斷 for(i<- 1 to 10 ;if (i%2) == 0 ;if (i == 4) ){ println(i) } |
//將for中的符合條件的元素經過yield關鍵字返回成一個集合 val list = for(i <- 1 to 10 ; if(i > 5 )) yield i for( w <- list ){ println(w) }
/** * while 循環 */ var index = 0 while(index < 100 ){ println("第"+index+"次while 循環") index += 1 } index = 0 do{ index +=1 println("第"+index+"次do while 循環") }while(index <100 ) |
Scala函數的定義
def fun (a: Int , b: Int ) : Unit = { println(a+b) } fun(1,1)
def fun1 (a : Int , b : Int)= a+b println(fun1(1,2)) |
注意點:
l 函數定義語法 用def來定義
l 能夠定義傳入的參數,要指定傳入參數的類型
l 方法能夠寫返回值的類型也能夠不寫,會自動推斷,有時候不能省略,必須寫,好比在遞歸函數中或者函數的返回值是函數類型的時候。
l scala中函數有返回值時,能夠寫return,也能夠不寫return,會把函數中最後一行當作結果返回。當寫return時,必需要寫函數的返回值。
l 若是返回值能夠一行搞定,能夠將{}省略不寫
l 傳遞給方法的參數能夠在方法中使用,而且scala規定方法的傳過來的參數爲val的,不是var的。
l 若是去掉方法體前面的等號,那麼這個方法返回類型一定是Unit的。這種說法不管方法體裏面什麼邏輯都成立,scala能夠把任意類型轉換爲Unit.假設,裏面的邏輯最後返回了一個string,那麼這個返回值會被轉換成Unit,而且值會被丟棄。
遞歸函數
/** * 遞歸函數 * 5的階乘 */ def fun2(num :Int) :Int= { if(num ==1) num else num * fun2(num-1) } print(fun2(5)) |
包含參數默認值的函數
l 默認值的函數中,若是傳入的參數個數與函數定義相同,則傳入的數值會覆蓋默認值。
l 若是不想覆蓋默認值,傳入的參數個數小於定義的函數的參數,則須要指定參數名稱。
/** * 包含默認參數值的函數 * 注意: * 1.默認值的函數中,若是傳入的參數個數與函數定義相同,則傳入的數值會覆蓋默認值 * 2.若是不想覆蓋默認值,傳入的參數個數小於定義的函數的參數,則須要指定參數名稱 */ def fun3(a :Int = 10,b:Int) = { println(a+b) } fun3(b=2) |
可變參數個數的函數
l 多個參數用逗號分開
/** * 可變參數個數的函數 * 注意:多個參數逗號分開 */ def fun4(elements :Int*)={ var sum = 0; for(elem <- elements){ sum += elem } sum } println(fun4(1,2,3,4)) |
匿名函數
l 能夠將匿名函數返回給val定義的值
l 匿名函數不能顯式聲明函數的返回類型
/** * 匿名函數 * 1.有參數匿名函數 * 2.無參數匿名函數 * 3.有返回值的匿名函數 * 注意: * 能夠將匿名函數返回給定義的一個變量 */ //有參數匿名函數 val value1 = (a : Int) => { println(a) } value1(1) //無參數匿名函數 val value2 = ()=>{ println("我愛尚學堂") } value2() //有返回值的匿名函數 val value3 = (a:Int,b:Int) =>{ a+b } println(value3(4,4)) |
嵌套函數
/** * 嵌套函數 * 例如:嵌套函數求5的階乘 */ def fun5(num:Int)={ def fun6(a:Int,b:Int):Int={ if(a == 1){ b }else{ fun6(a-1,a*b) } } fun6(num,1) } println(fun5(5)) |
偏應用函數
偏應用函數是一種表達式,不須要提供函數須要的全部參數,只須要提供部分,或不提供所需參數。
/** * 偏應用函數 */ def log(date :Date, s :String)= { println("date is "+ date +",log is "+ s) }
val date = new Date() log(date ,"log1") log(date ,"log2") log(date ,"log3")
//想要調用log,以上變化的是第二個參數,能夠用偏應用函數處理 val logWithDate = log(date,_:String) logWithDate("log11") logWithDate("log22") logWithDate("log33") |
高階函數
函數的參數是函數,或者函數的返回類型是函數,或者函數的參數和函數的返回類型是函數的函數。
l 函數的參數是函數
l 函數的返回是函數
l 函數的參數和函數的返回是函數
/** * 高階函數 * 函數的參數是函數 或者函數的返回是函數 或者函數的參數和返回都是函數 */
//函數的參數是函數 def hightFun(f : (Int,Int) =>Int, a:Int ) : Int = { f(a,100) } def f(v1 :Int,v2: Int):Int = { v1+v2 }
println(hightFun(f, 1))
//函數的返回是函數 //1,2,3,4相加 def hightFun2(a : Int,b:Int) : (Int,Int)=>Int = { def f2 (v1: Int,v2:Int) :Int = { v1+v2+a+b } f2 } println(hightFun2(1,2)(3,4))
//函數的參數是函數,函數的返回是函數 def hightFun3(f : (Int ,Int) => Int) : (Int,Int) => Int = { f } println(hightFun3(f)(100,200)) println(hightFun3((a,b) =>{a+b})(200,200)) //以上這句話還能夠寫成這樣 //若是函數的參數在方法體中只使用了一次 那麼能夠寫成_表示 println(hightFun3(_+_)(200,200)) |
柯里化函數
l 能夠理解爲高階函數的簡化
/** * 柯里化函數 */ def fun7(a :Int,b:Int)(c:Int,d:Int) = { a+b+c+d } println(fun7(1,2)(3,4)) |
5)、Scala字符串
/** * String && StringBuilder */ val str = "abcd" val str1 = "ABCD"
println(str.indexOf(97)) println(str.indexOf("b"))
println(str==str1) /** * compareToIgnoreCase * * 若是參數字符串等於此字符串,則返回值 0; * 若是此字符串小於字符串參數,則返回一個小於 0 的值; * 若是此字符串大於字符串參數,則返回一個大於 0 的值。 * */ println(str.compareToIgnoreCase(str1))
val strBuilder = new StringBuilder strBuilder.append("abc") // strBuilder.+('d') strBuilder+ 'd' // strBuilder.++=("efg") strBuilder++= "efg" // strBuilder.+=('h') strBuilder+= 'h' strBuilder.append(1.0) strBuilder.append(18f) println(strBuilder)
|
String方法:(見附件)
6)、集合
數組
賦值:arr(0) = xxx
建立兩種方式:
/** * 建立數組兩種方式: * 1.new Array[String](3) * 2.直接Array */
//建立類型爲Int 長度爲3的數組 val arr1 = new Array[Int](3) //建立String 類型的數組,直接賦值 val arr2 = Array[String]("s100","s200","s300") //賦值 arr1(0) = 100 arr1(1) = 200 arr1(2) = 300 |
遍歷兩種方式:
/** * 遍歷兩種方式 */ for(i <- arr1){ println(i) } arr1.foreach(i => { println(i) })
for(s <- arr2){ println(s) } arr2.foreach { x => println(x) }
|
建立二維數組
/** * 建立二維數組和遍歷 */ val arr3 = new Array[Array[String]](3) arr3(0)=Array("1","2","3") arr3(1)=Array("4","5","6") arr3(2)=Array("7","8","9") for(i <- 0 until arr3.length){ for(j <- 0 until arr3(i).length){ print(arr3(i)(j)+" ") } println() }
var count = 0 for(arr <- arr3 ;i <- arr){ if(count%3 == 0){ println() } print(i+" ") count +=1 }
arr3.foreach { arr => { arr.foreach { println } }}
val arr4 = Array[Array[Int]](Array(1,2,3),Array(4,5,6)) arr4.foreach { arr => { arr.foreach(i => { println(i) }) }} println("-------") for(arr <- arr4;i <- arr){ println(i) }
|
數組中的方法:
list
val list = List(1,2,3,4)
foreach ,for
|
//建立 val list = List(1,2,3,4,5) //遍歷 list.foreach { x => println(x)} // list.foreach { println} //filter val list1 = list.filter { x => x>3 } list1.foreach { println}
//count val value = list1.count { x => x>3 } println(value)
//map val nameList = List( "hello bjsxt", "hello xasxt", "hello shsxt" ) val mapResult:List[Array[String]] = nameList.map{ x => x.split(" ") } mapResult.foreach{println}
//flatmap val flatMapResult : List[String] = nameList.flatMap{ x => x.split(" ") } flatMapResult.foreach { println } |
set
注意:set集合會自動去重
foreach,for
//建立 val set1 = Set(1,2,3,4,4) val set2 = Set(1,2,5) //遍歷 //注意:set會自動去重 set1.foreach { println} for(s <- set1){ println(s) } println("*******") /** * 方法舉例 */
//交集 val set3 = set1.intersect(set2) set3.foreach{println} val set4 = set1.&(set2) set4.foreach{println} println("*******") //差集 set1.diff(set2).foreach { println } set1.&~(set2).foreach { println } //子集 set1.subsetOf(set2)
//最大值 println(set1.max) //最小值 println(set1.min) println("****")
//轉成數組,list set1.toArray.foreach{println} println("****") set1.toList.foreach{println}
//mkString println(set1.mkString) println(set1.mkString("\t"))
|
set方法總結
map
注意:建立map時,相同的key被後面的相同的key頂替掉,只保留一個
val map = Map( "1" -> "bjsxt", 2 -> "shsxt", (3,"xasxt") ) |
//獲取值 println(map.get("1").get) val result = map.get(8).getOrElse("no value") println(result) |
//map遍歷 for(x <- map){ println("====key:"+x._1+",value:"+x._2) } map.foreach(f => { println("key:"+ f._1+" ,value:"+f._2) }) |
//遍歷key val keyIterable = map.keys keyIterable.foreach { key => { println("key:"+key+", value:"+map.get(key).get) } } println("---------")
|
//遍歷value val valueIterable = map.values valueIterable.foreach { value => { println("value: "+ value) } }
|
注意:合併map會將map中的相同key的value替換
//合併map val map1 = Map( (1,"a"), (2,"b"), (3,"c") ) val map2 = Map( (1,"aa"), (2,"bb"), (2,90), (4,22), (4,"dd") ) map1.++:(map2).foreach(println)
|
/** * map方法 */ //count val countResult = map.count(p => { p._2.equals("shsxt") }) println(countResult)
//filter map.filter(_._2.equals("shsxt")).foreach(println)
//contains println(map.contains(2))
//exist println(map.exists(f =>{ f._2.equals("xasxt")
}))
|
Map方法總結
元組
與列表同樣,與列表不一樣的是元組能夠包含不一樣類型的元素。元組的值是經過將單個的值包含在圓括號中構成的。
注意:tuple最多支持22個參數
//建立,最多支持22個 val tuple = new Tuple1(1) val tuple2 = Tuple2("zhangsan",2) val tuple3 = Tuple3(1,2,3) val tuple4 = (1,2,3,4) val tuple18 = Tuple18(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18) val tuple22 = new Tuple22(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
//使用 println(tuple2._1 + "\t"+tuple2._2) val t = Tuple2((1,2),("zhangsan","lisi")) println(t._1._2) |
tuple.productIterator獲得迭代器,進而遍歷
//遍歷 val tupleIterator = tuple22.productIterator while(tupleIterator.hasNext){ println(tupleIterator.next()) } |
注意:swap元素翻轉,只針對二元組
/** * 方法 */ //翻轉,只針對二元組 println(tuple2.swap)
//toString println(tuple3.toString()) |
7)、trait特性
Scala Trait(特徵) 至關於 Java 的接口,實際上它比接口還功能強大。
與接口不一樣的是,它還能夠定義屬性和方法的實現。
通常狀況下Scala的類能夠繼承多個Trait,從結果來看就是實現了多重繼承。Trait(特徵) 定義的方式與類相似,但它使用的關鍵字是 trait。
注意:
trait Read { val readType = "Read" val gender = "m" def read(name:String){ println(name+" is reading") } }
trait Listen { val listenType = "Listen" val gender = "m" def listen(name:String){ println(name + " is listenning") } }
class Person() extends Read with Listen{ override val gender = "f" }
object test { def main(args: Array[String]): Unit = { val person = new Person() person.read("zhangsan") person.listen("lisi") println(person.listenType) println(person.readType) println(person.gender)
} } |
object Lesson_Trait2 { def main(args: Array[String]): Unit = { val p1 = new Point(1,2) val p2 = new Point(1,3) println(p1.isEqule(p2)) println(p1.isNotEqule(p2)) } }
trait Equle{ def isEqule(x:Any) :Boolean def isNotEqule(x : Any) = { !isEqule(x) } }
class Point(x:Int, y:Int) extends Equle { val xx = x val yy = y
def isEqule(p:Any) = { p.isInstanceOf[Point] && p.asInstanceOf[Point].xx==xx }
} |
8)、模式匹配match
Scala 提供了強大的模式匹配機制,應用也很是普遍。
一個模式匹配包含了一系列備選項,每一個都開始於關鍵字 case。
每一個備選項都包含了一個模式及一到多個表達式。箭頭符號 => 隔開了模式和表達式。
object Lesson_Match { def main(args: Array[String]): Unit = { val tuple = Tuple6(1,2,3f,4,"abc",55d) val tupleIterator = tuple.productIterator while(tupleIterator.hasNext){ matchTest(tupleIterator.next()) }
} /** * 注意點: * 1.模式匹配不只能夠匹配值,還能夠匹配類型 * 2.模式匹配中,若是匹配到對應的類型或值,就再也不繼續往下匹配 * 3.模式匹配中,都匹配不上時,會匹配到 case _ ,至關於default */ def matchTest(x:Any) ={ x match { case x:Int=> println("type is Int") case 1 => println("result is 1") case 2 => println("result is 2") case 3=> println("result is 3") case 4 => println("result is 4") case x:String => println("type is String") // case x :Double => println("type is Double") case _ => println("no match") } }
} |
9)、樣例類(case classes)
使用了case關鍵字的類定義就是樣例類(case classes),樣例類是種特殊的類。實現了類構造參數的getter方法(構造參數默認被聲明爲val),當構造參數是聲明爲var類型的,它將幫你實現setter和getter方法。
case class Person1(name:String,age:Int)
object Lesson_CaseClass { def main(args: Array[String]): Unit = { val p1 = new Person1("zhangsan",10) val p2 = Person1("lisi",20) val p3 = Person1("wangwu",30)
val list = List(p1,p2,p3) list.foreach { x => { x match { case Person1("zhangsan",10) => println("zhangsan") case Person1("lisi",20) => println("lisi") case _ => println("no match") } } }
} } |
10)、Actor Model
Actor Model是用來編寫並行計算或分佈式系統的高層次抽象(相似java中的Thread)讓程序員沒必要爲多線程模式下共享鎖而煩惱,被用在Erlang 語言上, 高可用性99.9999999 % 一年只有31ms 宕機Actors將狀態和行爲封裝在一個輕量的進程/線程中,可是不和其餘Actors分享狀態,每一個Actors有本身的世界觀,當須要和其餘Actors交互時,經過發送事件和消息,發送是異步的,非堵塞的(fire-andforget),發送消息後沒必要等另外Actors回覆,也沒必要暫停,每一個Actors有本身的消息隊列,進來的消息按先來後到排列,這就有很好的併發策略和可伸縮性,能夠創建性能很好的事件驅動系統。
Actor的特徵:
什麼是Akka
Akka 是一個用 Scala 編寫的庫,用於簡化編寫容錯的、高可伸縮性的 Java 和Scala 的 Actor 模型應用,底層實現就是Actor,Akka是一個開發庫和運行環境,能夠用於構建高併發、分佈式、可容錯、事件驅動的基於JVM的應用。使構建高併發的分佈式應用更加容易。
spark1.6版本以前,spark分佈式節點之間的消息傳遞使用的就是Akka,底層也就是actor實現的。1.6以後使用的netty傳輸。
import scala.actors.Actor
class myActor extends Actor{
def act(){ while(true){ receive { case x:String => println("save String ="+ x) case x:Int => println("save Int") case _ => println("save default") } } } }
object Lesson_Actor { def main(args: Array[String]): Unit = {
//建立actor的消息接收和傳遞 val actor =new myActor() //啓動 actor.start() //發送消息寫法 actor ! "i love you !"
} } |
case class Message(actor:Actor,msg:Any)
class Actor1 extends Actor{ def act(){ while(true){ receive{ case msg :Message => { println("i sava msg! = "+ msg.msg)
msg.actor!"i love you too !" } case msg :String => println(msg) case _ => println("default msg!") } } } }
class Actor2(actor :Actor) extends Actor{ actor ! Message(this,"i love you !") def act(){ while(true){ receive{ case msg :String => { if(msg.equals("i love you too !")){ println(msg) actor! "could we have a date !" } } case _ => println("default msg!") } } } }
object Lesson_Actor2 { def main(args: Array[String]): Unit = { val actor1 = new Actor1() actor1.start() val actor2 = new Actor2(actor1) actor2.start() } } |
import org.apache.spark.SparkConf import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.rdd.RDD.rddToPairRDDFunctions
object WordCount { def main(args: Array[String]): Unit = { val conf = new SparkConf() conf.setMaster("local").setAppName("WC") val sc = new SparkContext(conf) val lines :RDD[String] = sc.textFile("./words.txt") val word :RDD[String] = lines.flatMap{lines => { lines.split(" ") }} val pairs : RDD[(String,Int)] = word.map{ x => (x,1) } val result = pairs.reduceByKey{(a,b)=> {a+b}} result.sortBy(_._2,false).foreach(println)
//簡化寫法 lines.flatMap { _.split(" ")}.map { (_,1)}.reduceByKey(_+_).foreach(println)
} } |
(2)、Spark
1)、Spark初始
什麼是Spark
Apache Spark 是專爲大規模數據處理而設計的快速通用的計算引擎。Spark是UC Berkeley AMP lab (加州大學伯克利分校的AMP實驗室)所開源的類Hadoop MapReduce的通用並行計算框架,Spark擁有Hadoop MapReduce所具備的優勢;但不一樣於MapReduce的是Job中間輸出結果能夠保存在內存中,從而再也不須要讀寫HDFS,所以Spark能更好地適用於數據挖掘與機器學習等須要迭代的MapReduce的算法。
Spark是Scala編寫,方便快速編程。
整體技術棧講解
Spark演變歷史
Spark與MapReduce的區別
¬ 都是分佈式計算框架,Spark基於內存,MR基於HDFS。Spark處理數據的能力通常是MR的十倍以上,Spark中除了基於內存計算外,還有DAG有向無環圖來切分任務的執行前後順序。
Spark運行模式
多用於本地測試,如在eclipse,idea中寫程序測試等。
Standalone是Spark自帶的一個資源調度框架,它支持徹底分佈式。
Hadoop生態圈裏面的一個資源調度框架,Spark也是能夠基於Yarn來計算的。
資源調度框架。
¬ 要基於Yarn來進行資源調度,必須實現AppalicationMaster接口,Spark實現了這個接口,因此能夠基於Yarn。
RDD
u 概念
RDD(Resilient Distributed Dateset),彈性分佈式數據集。
u RDD的五大特性:
u RDD理解圖:
u 注意:
¬ textFile方法底層封裝的是讀取MR讀取文件的方式,讀取文件以前先split,默認split大小是一個block大小。
¬ RDD實際上不存儲數據,這裏方便理解,暫時理解爲存儲數據。
¬ 什麼是K,V格式的RDD?
¬ 哪裏體現RDD的彈性(容錯)?
¬ 哪裏體現RDD的分佈式?
¬ RDD提供計算最佳位置,體現了數據本地化。體現了大數據中「計算移動數據不移動」的理念。
Spark任務執行原理
|
以上圖中有四個機器節點,Driver和Worker是啓動在節點上的進程,運行在JVM中的進程。
Spark代碼流程
Transformations轉換算子
u 概念:
Transformations類算子是一類算子(函數)叫作轉換算子,如map,flatMap,reduceByKey等。Transformations算子是延遲執行,也叫懶加載執行。
u Transformation類算子:
過濾符合條件的記錄數,true保留,false過濾掉。
將一個RDD中的每一個數據項,經過map中的函數映射變爲一個新的元素。 特色:輸入一條,輸出一條數據。
先map後flat。與map相似,每一個輸入項能夠映射爲0到多個輸出項。
隨機抽樣算子,根據傳進去的小數按比例進行又放回或者無放回的抽樣。
將相同的Key根據相應的邏輯進行處理。
做用在K,V格式的RDD上,對key進行升序或者降序排序。 |
Action行動算子
u 概念:
Action類算子也是一類算子(函數)叫作行動算子,如foreach,collect,count等。Transformations類算子是延遲執行,Action類算子是觸發執行。一個application應用程序中有幾個Action類算子執行,就有幾個job運行。
u Action類算子
返回數據集中的元素數。會在結果計算完成後回收到Driver端。
返回一個包含數據集前n個元素的集合。
first=take(1),返回數據集中的第一個元素。
循環遍歷數據集中的每一個元素,運行相應的邏輯。
將計算結果回收到Driver端。
|
ü 思考:一千萬條數據量的文件,過濾掉出現次數多的記錄,而且其他記錄按照出現次數降序排序。
文件:
代碼:
控制算子
u 概念:
控制算子有三種,cache,persist,checkpoint,以上算子均可以將RDD持久化,持久化的單位是partition。cache和persist都是懶執行的。必須有一個action類算子觸發執行。checkpoint算子不只能將RDD持久化到磁盤,還能切斷RDD之間的依賴關係。
u cache
默認將RDD的數據持久化到內存中。cache是懶執行。
¬ 注意:chche () = persist()=persist(StorageLevel.Memory_Only)
文件:見「NASA_access_log_Aug95」文件。
測試代碼:
SparkConf conf = new SparkConf(); conf.setMaster("local").setAppName("CacheTest"); JavaSparkContext jsc = new JavaSparkContext(conf); JavaRDD<String> lines = jsc.textFile("./NASA_access_log_Aug95");
lines = lines.cache(); long startTime = System.currentTimeMillis(); long count = lines.count(); long endTime = System.currentTimeMillis(); System.out.println("共"+count+ "條數據,"+"初始化時間+cache時間+計算時間="+ (endTime-startTime));
long countStartTime = System.currentTimeMillis(); long countrResult = lines.count(); long countEndTime = System.currentTimeMillis(); System.out.println("共"+countrResult+ "條數據,"+"計算時間="+ (countEndTime- countStartTime));
jsc.stop(); |
u persist:
能夠指定持久化的級別。最經常使用的是MEMORY_ONLY和MEMORY_AND_DISK。」_2」表示有副本數。
持久化級別以下:
¬ cache和persist的注意事項:
錯誤:rdd.cache().count() 返回的不是持久化的RDD,而是一個數值了。
u checkpoint
checkpoint將RDD持久化到磁盤,還能夠切斷RDD之間的依賴關係。
SparkConf conf = new SparkConf(); conf.setMaster("local").setAppName("checkpoint"); JavaSparkContext sc = new JavaSparkContext(conf); sc.setCheckpointDir("./checkpoint"); JavaRDD<Integer> parallelize = sc.parallelize(Arrays.asList(1,2,3)); parallelize.checkpoint(); parallelize.count(); sc.stop(); |
3)、集羣搭建以及測試
搭建
1).下載安裝包,解壓
2).更名
3).進入安裝包的conf目錄下,修改slaves.template文件,添加從節點。保存。
4).修改spark-env.sh
SPARK_MASTER_IP:master的ip
SPARK_MASTER_PORT:提交任務的端口,默認是7077
SPARK_WORKER_CORES:每一個worker從節點可以支配的core的個數
SPARK_WORKER_MEMORY:每一個worker從節點可以支配的內存數
5).同步到其餘節點上
6).啓動集羣
進入sbin目錄下,執行當前目錄下的./start-all.sh
7).搭建客戶端
將spark安裝包原封不動的拷貝到一個新的節點上,而後,在新的節點上提交任務便可。
注意:
¬ 8080是Spark WEBUI界面的端口,7077是Spark任務提交的端口。
¬ 修改master的WEBUI端口:
ü 修改start-master.sh便可。
ü 也能夠在Master節點上導入臨時環境變量,只是做用於以後的程序,重啓就無效了。
刪除臨時環境變量:
1). 1,2,3,4,5,7步同standalone。
2).在客戶端中配置:
測試
PI案例:
Standalone提交命令:
./spark-submit --master spark://node1:7077 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
YARN提交命令:
./spark-submit --master yarn --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
4)、兩種提交任務方式
Standalone模式兩種提交任務方式
./spark-submit --master spark://node1:7077 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 1000 |
或者
./spark-submit --master spark://node1:7077 --deploy-mode client --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
client模式適用於測試調試程序。Driver進程是在客戶端啓動的,這裏的客戶端就是指提交應用程序的當前節點。在Driver端能夠看到task執行的狀況。生產環境下不能使用client模式,是由於:假設要提交100個application到集羣運行,Driver每次都會在client端啓動,那麼就會致使客戶端100次網卡流量暴增的問題。
./spark-submit --master spark://node1:7077 --deploy-mode cluster --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
Driver進程是在集羣某一臺Worker上啓動的,在客戶端是沒法查看task的執行狀況的。假設要提交100個application到集羣運行,每次Driver會隨機在集羣中某一臺Worker上啓動,那麼這100次網卡流量暴增的問題就散佈在集羣上。
¬ 總結Standalone兩種方式提交任務,Driver與集羣的通訊包括:
1. Driver負責應用程序資源的申請
2. 任務的分發。
3. 結果的回收。
4. 監控task執行狀況。
Yarn模式兩種提交任務方式
./spark-submit --master yarn --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
或者
./spark-submit --master yarn–client --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
或者
./spark-submit --master yarn --deploy-mode client --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
Yarn-client模式一樣是適用於測試,由於Driver運行在本地,Driver會與yarn集羣中的Executor進行大量的通訊,會形成客戶機網卡流量的大量增長.
¬ ApplicationMaster的做用:
注意:ApplicationMaster有launchExecutor和申請資源的功能,並無做業調度的功能。
./spark-submit --master yarn --deploy-mode cluster --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
或者
./spark-submit --master yarn-cluster --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 |
Yarn-Cluster主要用於生產環境中,由於Driver運行在Yarn集羣中某一臺nodeManager中,每次提交任務的Driver所在的機器都是隨機的,不會產生某一臺機器網卡流量激增的現象,缺點是任務提交後不能看到日誌。只能經過yarn查看日誌。
¬ ApplicationMaster的做用:
¬ 中止集羣任務命令:yarn application -kill applicationID
5)、補充部分算子
transformation
做用在K,V格式的RDD上。根據K進行鏈接,對(K,V)join(K,W)返回(K,(V,W))
¬ join後的分區數與父RDD分區數多的那一個相同。
合併兩個數據集。兩個數據集的類型要一致。
¬ 返回新的RDD的分區數是合併RDD分區數的總和。
取兩個數據集的交集
取兩個數據集的差集
與map相似,遍歷的單位是每一個partition上的數據。
當調用類型(K,V)和(K,W)的數據上時,返回一個數據集(K,(Iterable<V>,Iterable<W>))
相似於mapPartitions,除此以外還會攜帶分區的索引值。
增長或減小分區。會產生shuffle。(多個分區分到一個分區不會產生shuffle)
coalesce經常使用來減小分區,第二個參數是減小分區的過程當中是否產生shuffle。
true爲產生shuffle,false不產生shuffle。默認是false。
若是coalesce設置的分區數比原來的RDD的分區數還多的話,第二個參數設置爲false不會起做用,若是設置成true,效果和repartition同樣。即repartition(numPartitions) = coalesce(numPartitions,true)
做用在K,V格式的RDD上。根據Key進行分組。做用在(K,V),返回(K,Iterable <V>)。
將兩個RDD中的元素(KV格式/非KV格式)變成一個KV格式的RDD,兩個RDD的每一個分區元素個數必須相同。
該函數將RDD中的元素和這個元素在RDD中的索引號(從0開始)組合成(K,V)對。
遍歷的數據是每一個partition的數據。
做用到K,V格式的RDD上,根據Key計數相同Key的數據集元素。
根據數據集每一個元素相同的內容來計數。返回相同內容的元素對應的條數。
根據聚合邏輯聚合數據集中的每一個元素。
6)、術語解釋
7)、窄依賴和寬依賴
RDD之間有一系列的依賴關係,依賴關係又分爲窄依賴和寬依賴。
父RDD和子RDD partition之間的關係是一對一的。或者父RDD一個partition只對應一個子RDD的partition狀況下的父RDD和子RDD partition關係是多對一的。不會有shuffle的產生。
父RDD與子RDD partition之間的關係是一對多。會有shuffle的產生。
8)、Stage
Spark任務會根據RDD之間的依賴關係,造成一個DAG有向無環圖,DAG會提交給DAGScheduler,DAGScheduler會把DAG劃分相互依賴的多個stage,劃分stage的依據就是RDD之間的寬窄依賴。遇到寬依賴就劃分stage,每一個stage包含一個或多個task任務。而後將這些task以taskSet的形式提交給TaskScheduler運行。
stage是由一組並行的task組成。
切割規則:從後往前,遇到寬依賴就切割stage。
pipeline管道計算模式,pipeline只是一種計算思想,模式。
¬ 數據一直在管道里面何時數據會落地?
¬ Stage的task並行度是由stage的最後一個RDD的分區數來決定的 。
¬ 如何改變RDD的分區數?
例如:reduceByKey(XXX,3),GroupByKey(4)
val conf = new SparkConf() conf.setMaster("local").setAppName("pipeline"); val sc = new SparkContext(conf) val rdd = sc.parallelize(Array(1,2,3,4)) val rdd1 = rdd.map { x => { println("map--------"+x) x }} val rdd2 = rdd1.filter { x => { println("fliter********"+x) true } } rdd2.collect() sc.stop() |
9)、Spark資源調度和任務調度
啓動集羣后,Worker節點會向Master節點彙報資源狀況,Master掌握了集羣資源狀況。當Spark提交一個Application後,根據RDD之間的依賴關係將Application造成一個DAG有向無環圖。任務提交後,Spark會在Driver端建立兩個對象:DAGScheduler和TaskScheduler,DAGScheduler是任務調度的高層調度器,是一個對象。DAGScheduler的主要做用就是將DAG根據RDD之間的寬窄依賴關係劃分爲一個個的Stage,而後將這些Stage以TaskSet的形式提交給TaskScheduler(TaskScheduler是任務調度的低層調度器,這裏TaskSet其實就是一個集合,裏面封裝的就是一個個的task任務,也就是stage中的並行度task任務),TaskSchedule會遍歷TaskSet集合,拿到每一個task後會將task發送到計算節點Executor中去執行(其實就是發送到Executor中的線程池ThreadPool去執行)。task在Executor線程池中的運行狀況會向TaskScheduler反饋,當task執行失敗時,則由TaskScheduler負責重試,將task從新發送給Executor去執行,默認重試3次。若是重試3次依然失敗,那麼這個task所在的stage就失敗了。stage失敗了則由DAGScheduler來負責重試,從新發送TaskSet到TaskSchdeuler,Stage默認重試4次。若是重試4次之後依然失敗,那麼這個job就失敗了。job失敗了,Application就失敗了。
TaskScheduler不只能重試失敗的task,還會重試straggling(落後,緩慢)task(也就是執行速度比其餘task慢太多的task)。若是有運行緩慢的task那麼TaskScheduler會啓動一個新的task來與這個運行緩慢的task執行相同的處理邏輯。兩個task哪一個先執行完,就以哪一個task的執行結果爲準。這就是Spark的推測執行機制。在Spark中推測執行默認是關閉的。推測執行能夠經過spark.speculation屬性來配置。
注意:
¬ 對於ETL類型要入數據庫的業務要關閉推測執行機制,這樣就不會有重複的數據入庫。
¬ 若是遇到數據傾斜的狀況,開啓推測執行則有可能致使一直會有task從新啓動處理相同的邏輯,任務可能一直處於處理不完的狀態。
¬ 粗粒度資源申請(Spark)
在Application執行以前,將全部的資源申請完畢,當資源申請成功後,纔會進行任務的調度,當全部的task執行完成後,纔會釋放這部分資源。
優勢:在Application執行以前,全部的資源都申請完畢,每個task直接使用資源就能夠了,不須要task在執行前本身去申請資源,task啓動就快了,task執行快了,stage執行就快了,job就快了,application執行就快了。
缺點:直到最後一個task執行完成纔會釋放資源,集羣的資源沒法充分利用。
¬ 細粒度資源申請(MapReduce)
Application執行以前不須要先去申請資源,而是直接執行,讓job中的每個task在執行前本身去申請資源,task執行完成就釋放資源。
優勢:集羣的資源能夠充分利用。
缺點:task本身去申請資源,task啓動變慢,Application的運行就相應的變慢了。
10)、Spark-Submit提交參數
Options:
MASTER_URL, 能夠是spark://host:port, mesos://host:port, yarn, yarn-cluster,yarn-client, local
DEPLOY_MODE, Driver程序運行的地方,client或者cluster,默認是client。
CLASS_NAME, 主類名稱,含包名
逗號分隔的本地JARS, Driver和executor依賴的第三方jar包
用逗號隔開的文件列表,會放置在每一個executor工做目錄中
spark的配置屬性
Driver程序使用內存大小(例如:1000M,5G),默認1024M
每一個executor內存大小(如:1000M,2G),默認1G
Spark standalone with cluster deploy mode only:
Driver程序的使用core個數(默認爲1),僅限於Spark standalone模式
Spark standalone or Mesos with cluster deploy mode only:
失敗後是否重啓Driver,僅限於Spark alone或者Mesos模式
Spark standalone and Mesos only:
executor使用的總核數,僅限於SparkStandalone、Spark on Mesos模式
Spark standalone and YARN only:
每一個executor使用的core數,Spark on Yarn默認爲1,standalone默認爲worker上全部可用的core。
YARN-only:
driver使用的core,僅在cluster模式下,默認爲1。
QUEUE_NAME 指定資源隊列的名稱,默認:default
一共啓動的executor數量,默認是2個。
11)、資源調度源碼分析
路徑:spark-1.6.0/core/src/main/scala/org.apache.spark/deploy/Master/Master.scala |
路徑:spark-1.6.0/core/src/main/scala/org.apache.spark/ deploy/SparkSubmit.scala |
使用Spark-submit提交任務演示。也能夠使用spark-shell
./spark-submit --master spark://node1:7077 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
./spark-submit --master spark://node1:7077 --executor-cores 1 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
./spark-submit --master spark://node1:7077 --executor-cores 1 --executor-memory 3g --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
注意:一個進程不能讓集羣多個節點共同啓動。
./spark-submit --master spark://node1:7077 --executor-cores 1 --executor-memory 2g --total-executor-cores 3 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
12)、任務調度源碼分析
任務調度能夠從一個Action類算子開始。由於Action類算子會觸發一個job的執行。
DAGScheduler 類中getMessingParentStages()方法是切割job劃分stage。能夠結合如下這張圖來分析:
SparkConf sparkConf = new SparkConf() .setMaster("local") .setAppName("SecondarySortTest"); final JavaSparkContext sc = new JavaSparkContext(sparkConf);
JavaRDD<String> secondRDD = sc.textFile("secondSort.txt");
JavaPairRDD<SecondSortKey, String> pairSecondRDD = secondRDD.mapToPair(new PairFunction<String, SecondSortKey, String>() {
/** * */ private static final long serialVersionUID = 1L;
@Override public Tuple2<SecondSortKey, String> call(String line) throws Exception { String[] splited = line.split(" "); int first = Integer.valueOf(splited[0]); int second = Integer.valueOf(splited[1]); SecondSortKey secondSortKey = new SecondSortKey(first,second); return new Tuple2<SecondSortKey, String>(secondSortKey,line); } });
pairSecondRDD.sortByKey(false).foreach(new VoidFunction<Tuple2<SecondSortKey,String>>() {
/** * */ private static final long serialVersionUID = 1L;
@Override public void call(Tuple2<SecondSortKey, String> tuple) throws Exception { System.out.println(tuple._2); } });
|
public class SecondSortKey implements Serializable,Comparable<SecondSortKey>{ /** * */ private static final long serialVersionUID = 1L; private int first; private int second; public int getFirst() { return first; } public void setFirst(int first) { this.first = first; } public int getSecond() { return second; } public void setSecond(int second) { this.second = second; } public SecondSortKey(int first, int second) { super(); this.first = first; this.second = second; } @Override public int compareTo(SecondSortKey o1) { if(getFirst() - o1.getFirst() ==0 ){ return getSecond() - o1.getSecond(); }else{ return getFirst() - o1.getFirst(); } } }
|
SparkConf conf = new SparkConf() .setMaster("local") .setAppName("TopOps"); JavaSparkContext sc = new JavaSparkContext(conf); JavaRDD<String> linesRDD = sc.textFile("scores.txt");
JavaPairRDD<String, Integer> pairRDD = linesRDD.mapToPair(new PairFunction<String, String, Integer>() {
/** * */ private static final long serialVersionUID = 1L;
@Override public Tuple2<String, Integer> call(String str) throws Exception { String[] splited = str.split("\t"); String clazzName = splited[0]; Integer score = Integer.valueOf(splited[1]); return new Tuple2<String, Integer> (clazzName,score); } });
pairRDD.groupByKey().foreach(new VoidFunction<Tuple2<String,Iterable<Integer>>>() {
/** * */ private static final long serialVersionUID = 1L;
@Override public void call(Tuple2<String, Iterable<Integer>> tuple) throws Exception { String clazzName = tuple._1; Iterator<Integer> iterator = tuple._2.iterator();
Integer[] top3 = new Integer[3];
while (iterator.hasNext()) { Integer score = iterator.next();
for (int i = 0; i < top3.length; i++) { if(top3[i] == null){ top3[i] = score; break; }else if(score > top3[i]){ for (int j = 2; j > i; j--) { top3[j] = top3[j-1]; } top3[i] = score; break; } } } System.out.println("class Name:"+clazzName); for(Integer sscore : top3){ System.out.println(sscore); } } }); |
15)、SparkShell的使用
SparkShell是Spark自帶的一個快速原型開發工具,也能夠說是Spark的scala REPL(Read-Eval-Print-Loop),即交互式shell。支持使用scala語言來進行Spark的交互式編程。
啓動Standalone集羣,./start-all.sh
在客戶端上啓動spark-shell:
./spark-shell --master spark://node1:7077 |
啓動hdfs,建立目錄spark/test,上傳文件wc.txt
啓動hdfs集羣: start-all.sh 建立目錄: hdfs dfs -mkdir -p /spark/test 上傳wc.txt hdfs dfs -put /root/test/wc.txt /spark/test/ wc附件:
|
運行wordcount
sc.textFile("hdfs://node1:9000/spark/test/wc.txt") .flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println) |
廣播變量
val conf = new SparkConf() conf.setMaster("local").setAppName("brocast") val sc = new SparkContext(conf) val list = List("hello xasxt") val broadCast = sc.broadcast(list) val lineRDD = sc.textFile("./words.txt") lineRDD.filter { x => broadCast.value.contains(x) }.foreach { println} sc.stop() |
¬ 能不能將一個RDD使用廣播變量廣播出去?
不能,由於RDD是不存儲數據的。能夠將RDD的結果廣播出去。
¬ 廣播變量只能在Driver端定義,不能在Executor端定義。
¬ 在Driver端能夠修改廣播變量的值,在Executor端沒法修改廣播變量的值。
累加器
val conf = new SparkConf() conf.setMaster("local").setAppName("accumulator") val sc = new SparkContext(conf) val accumulator = sc.accumulator(0) sc.textFile("./words.txt").foreach { x =>{accumulator.add(1)}} println(accumulator.value) sc.stop() |
¬ 累加器在Driver端定義賦初始值,累加器只能在Driver端讀取,在Excutor端更新。
1)、SparkUI界面介紹
能夠指定提交Application的名稱
./spark-shell --master spark://node1:7077 --name myapp |
./spark-shell --master spark://node1:7077 --name myapp1 --conf spark.eventLog.enabled=true --conf spark.eventLog.dir=hdfs://node1:9000/spark/test |
中止程序,在Web Ui中Completed Applications對應的ApplicationID中能查看history。
在客戶端節點,進入../spark-1.6.0/conf/ spark-defaults.conf最後加入:
//開啓記錄事件日誌的功能 spark.eventLog.enabled true //設置事件日誌存儲的目錄 spark.eventLog.dir hdfs://node1:9000/spark/test //設置HistoryServer加載事件日誌的位置 spark.history.fs.logDirectory hdfs://node1:9000/spark/test //日誌優化選項,壓縮日誌 spark.eventLog.compress true |
啓動HistoryServer:
./start-history-server.sh |
訪問HistoryServer:node4:18080,以後全部提交的應用程序運行情況都會被記錄。
(4)、Master HA
1)、Master的高可用原理
Standalone集羣只有一個Master,若是Master掛了就沒法提交應用程序,須要給Master進行高可用配置,Master的高可用能夠使用fileSystem(文件系統)和zookeeper(分佈式協調服務)。
fileSystem只有存儲功能,能夠存儲Master的元數據信息,用fileSystem搭建的Master高可用,在Master失敗時,須要咱們手動啓動另外的備用Master,這種方式不推薦使用。
zookeeper有選舉和存儲功能,能夠存儲Master的元素據信息,使用zookeeper搭建的Master高可用,當Master掛掉時,備用的Master會自動切換,推薦使用這種方式搭建Master的HA。
2)、Master高可用搭建
1) 在Spark Master節點上配置主Master,配置spark-env.sh
export SPARK_DAEMON_JAVA_OPTS=" -Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=node3:2181,node4:2181,node5:2181 -Dspark.deploy.zookeeper.dir=/sparkmaster0821" |
2) 發送到其餘worker節點上
3) 找一臺節點(非主Master節點)配置備用 Master,修改spark-env.sh配置節點上的MasterIP
4) 啓動集羣以前啓動zookeeper集羣:
../zkServer.sh start |
5) 啓動spark Standalone集羣,啓動備用Master
6) 打開主Master和備用Master WebUI頁面,觀察狀態。
3)、注意點
4)、測試驗證
提交SparkPi程序,kill主Master觀察現象。
./spark-submit --master spark://node1:7077,node2:7077 --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000 |
1)、SparkShuffle概念
reduceByKey會將上一個RDD中的每個key對應的全部value聚合成一個value,而後生成一個新的RDD,元素類型是<key,value>對的形式,這樣每個key對應一個聚合起來的value。
問題:聚合以前,每個key對應的value不必定都是在一個partition中,也不太可能在同一個節點上,由於RDD是分佈式的彈性的數據集,RDD的partition極有可能分佈在各個節點上。
如何聚合?
– Shuffle Write:上一個stage的每一個map task就必須保證將本身處理的當前分區的數據相同的key寫入一個分區文件中,可能會寫入多個不一樣的分區文件中。
– Shuffle Read:reduce task就會從上一個stage的全部task所在的機器上尋找屬於己的那些分區文件,這樣就能夠保證每個key所對應的value都會匯聚到同一個節點上去處理和聚合。
Spark中有兩種Shuffle類型,HashShuffl