Java高級工程師面試寶典javascript
答:進程是全部線程的集合,每個線程是進程中的一條執行路徑,線程只是一條執行路徑。html
答:提升程序效率前端
答:繼承Thread或Runnable 接口。java
答:Runnable接口好,由於實現了接口還能夠繼續繼承。繼承Thread類不能再繼承。node
答:主要能體現到多線程提升程序效率。mysql
舉例:分批發送短信、迅雷多線程下載等。linux
答:當多個線程同時共享,同一個全局變量或靜態變量,作寫的操做時,可能會發生數據衝突問題,也就是線程安全問題。作讀操做是不會發生數據衝突問題。nginx
答:使用多線程之間同步或使用鎖(lock)。c++
答:將可能會發生數據衝突問題(線程不安全問題),只能讓當前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其餘線程進行執行。這樣的話就能夠解決線程不安全問題。git
答:當多個線程共享同一個資源,不會受到其餘線程的干擾。
答:就是將可能會發生線程安全問題的代碼,給包括起來。只能讓當前一個線程進行執行,被包裹的代碼執行完成以後才能釋放所,讓後才能讓其餘線程進行執行。
synchronized(同一個數據){
可能會發生線程衝突問題
}
private Object mutex = new Object();// 自定義多線程同步鎖 publicvoid sale() { synchronized (mutex) { if (trainCount > 0) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--; } } } |
在方法上修飾synchronized 稱爲同步函數
publicsynchronizedvoid 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(); 相似waitres. Condition. Signal() 相似notifySignalall 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),有時被稱爲信號燈,是在多線程環境下使用的一種設施, 它負責協調各個線程, 以保證它們可以正確、合理的使用公共資源。
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 { publicstaticvoid main(String[] args) { Singleton sl1 = Singleton.getSingleton(); Singleton sl2 = Singleton.getSingleton(); System.out.println(sl1 == sl2); } }
publicclass Singleton { // 當須要的纔會被實例化 privatestatic Singleton singleton;
private Singleton() {
}
synchronizedpublicstatic Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } returnsingleton; }
}
|
餓漢式代碼
class SingletonTest1 { publicstaticvoid main(String[] args) { Singleton1 sl1 = Singleton1.getSingleton(); Singleton1 sl2 = Singleton1.getSingleton(); System.out.println((sl1 == sl2)+"-"); } }
publicclass Singleton1 { //當class 文件被加載初始化 privatestatic Singleton1 singleton = new Singleton1();
private Singleton1() {
}
publicstatic Singleton1 getSingleton() { returnsingleton; }
} |
實現建立者和調用者分離
publicinterface Car { publicvoid run(); } publicclass AoDi implements Car { @Override publicvoid run() { System.out.println("奧迪...."); } } publicinterface Car { publicvoid 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("奔馳...."); }
}
|
publicclass AoDiChiFactory { staticpublic Car createCar() { returnnew AoDi(); } }
publicinterface BenChiFactory { staticpublic Car createCar() { returnnew BenChi(); } } publicclass Main {
publicstaticvoid 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;
publicclass 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("我是買房中介,開結束你買房了...."); returninvokeSuper;
}
}
class Test22222 { publicstaticvoid 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 * */ publicclass JvmDemo01 {
publicstaticvoid main(String[] args) throws InterruptedException { byte[] b1 = newbyte[1 * 1024 * 1024]; System.out.println("分配了1m"); jvmInfo(); Thread.sleep(3000); byte[] b2 = newbyte[4 * 1024 * 1024]; System.out.println("分配了4m"); Thread.sleep(3000); jvmInfo();
}
/** * 轉換爲m * * @param maxMemory * @return */ staticprivate String toM(longmaxMemory) { floatnum = (float) maxMemory / (1024 * 1024); DecimalFormat df = new DecimalFormat("0.00");// 格式化小數 String s = df.format(num);// 返回的是String類型 returns; }
staticprivatevoid jvmInfo() { // 最大內存 longmaxMemory = Runtime.getRuntime().maxMemory(); System.out.println("maxMemory:" + maxMemory + ",轉換爲M:" + toM(maxMemory)); // 當前空閒內存 longfreeMemory = Runtime.getRuntime().freeMemory(); System.out.println("freeMemory:" +freeMemory+",轉換爲M:"+toM(freeMemory)); // 已經使用內存 longtotalMemory = 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
publicclass JvmDemo02 {
publicstaticvoid main(String[] args) { //-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC byte [] b = null; for (inti = 0; i < 10; i++) { b =newbyte[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
publicstaticvoid main(String[] args) throws InterruptedException { List<Object> list = new ArrayList<>(); Thread.sleep(3000); jvmInfo(); for (inti = 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 設置最大調用深度
publicclass JvmDemo04 { privatestaticintcount; publicstaticvoid count(){ try { count++; count(); } catch (Throwable e) { System.out.println("最大深度:"+count); e.printStackTrace(); } } publicstaticvoid 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 publicclass 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); returnname; }
} |
@RestController publicclass UserController { @Autowired private UserService userService;
@RequestMapping("/regit") public String regit(String name, String pwd) { returnuserService.regit(name, pwd); }
@RequestMapping("/get") public String get(Long id) { String name = userService.get(id); returnname; }
} |
在數據庫集羣架構中,讓主庫負責處理事務性查詢,而從庫只負責處理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軟件結構!!!
1、在瀏覽器中輸入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 publicclass Aop { @Before("execution(* com.itmayiedu.service.UserService.add(..))") publicvoid begin() { System.out.println("前置通知"); }
@After("execution(* com.itmayiedu.service.UserService.add(..))") publicvoid commit() { System.out.println("後置通知"); }
@AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))") publicvoid afterReturning() { System.out.println("運行通知"); }
@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))") publicvoid afterThrowing() { System.out.println("異常通知"); }
@Around("execution(* com.itmayiedu.service.UserService.add(..))") publicvoid 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次接觸和學習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進行設置。
標識爲啓動類
Mybatis是輕量級封裝,Hibernate是重量級封裝
Mybatis 以SQL語句獲得對象,hibernate是以對象獲得SQL語句
優先使用 #{}。由於 ${} 會致使 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; returntrue; } else { returnfalse; }
} </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") publicclass ForwardServlet extends HttpServlet { @Override protectedvoid 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 * */
publicclass FilterDemo implements Filter { public FilterDemo() { System.out.println("FilterDemo 構造函數被執行..."); }
/** * 銷燬 */ publicvoid destroy() { System.out.println("destroy"); }
publicvoid 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);
} /** * 初始化 */ publicvoid 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攻擊 */ publicclass 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); } returnvalue; } } |
跨域緣由產生:在當前域名請求網站中,默認不容許經過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 |
將鍵的整數值增長 |
|
16 |
將鍵的整數值按給定的數值增長 |
|
17 |
將鍵的浮點值按給定的數值增長 |
|
18 |
將鍵的整數值減 |
|
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] |
概述
一、redis的複製功能是支持多個數據庫之間的數據同步。一類是主數據庫(master)一類是從數據庫(slave),主數據庫能夠進行讀寫操做,當發生寫操做的時候自動將數據同步到從數據庫,而從數據庫通常是隻讀的,並接收主數據庫同步過來的數據,一個主數據庫能夠有多個從數據庫,而一個從數據庫只能有一個主數據庫。
二、經過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 ...]] |
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 500502503504 /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服務器、企業關鍵應用服務器和其它關鍵任務服務器等,從而共同完成工做任務。
1、輪詢(默認) |
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) publicclass SessionConfig {
// 冒號後的值爲沒有配置文件時,制動裝載的默認值 @Value("${redis.hostname:localhost}") String HostName; @Value("${redis.port:6379}") intPort;
@Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connection = new JedisConnectionFactory(); connection.setPort(Port); connection.setHostName(HostName); returnconnection; } } |
//初始化Session配置 publicclass 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調優
頁面優化 -》 減小頁面鏈接數、頁面尺寸瘦身
一、動態資源和靜態資源分離;
二、CDN;
三、負載均衡;
四、分佈式緩存;
五、數據庫讀寫分離或數據切分(垂直或水平);
六、服務分佈式部署。
在客戶端與服務器進行通信時.客戶端調用後,必須等待服務對象完成處理返回結果才能繼續執行。
客戶與服務器對象的生命週期緊密耦合,客戶進程和服務對象進程都都必須正常運行;若是因爲服務對象崩潰或者網絡故障致使用戶的請求不可達,客戶會受到異常
點對點通訊: 客戶的一次調用只發送給某個單獨的目標對象。
(畫圖演示)
面向消息的中間件(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有必定的瞭解,若是不瞭解的話能夠看螞蟻課堂SpringBoot課程。
SpringCloud使用Eureka做爲註冊中心,使用rest+ribbon或者feign,斷路器Hystrix、zuul接口網關等。
Duubbo是一個RPC遠程調用框架, 分佈式服務治理框架
什麼是Dubbo服務治理?
服務與服務之間會有不少個Url、依賴關係、負載均衡、容錯、自動註冊服務。
默認用的dubbo協議、Http、RMI、Hessian
分爲四大模塊
生產者、消費者、註冊中心、監控中心
生產者:提供服務
消費者: 調用服務
註冊中心:註冊信息(redis、zk)
監控中心:調用次數、關係依賴等。
首先生產者將服務註冊到註冊中心(zk),使用zk持久節點進行存儲,消費訂閱zk節點,一旦有節點變動,zk經過事件通知傳遞給消費者,消費能夠調用生產者服務。
服務與服務之間進行調用,都會在監控中心中,存儲一個記錄。
Dubox使用http協議+rest風格傳入json或者xml格式進行遠程調用。
Dubbo使用Dubbo協議。
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並非一種強一致性,只能保證順序一致性和最終一致性,只能稱爲達到了僞實時性。
zookeepei中能夠保存數據,正是利用zookeeper能夠保存數據這一特色,咱們的集羣經過在zookeeper裏存取數據來進行消息的傳遞。
zookeeper中保存數據的結構很是相似於文件系統。都是由節點組成的樹形結構。不一樣的是文件系統是由文件夾和文件來組成的樹,而zookeeper中是由ZNODE來組成的樹。
每個ZNODE裏均可以存放一段數據,ZNODE下還能夠掛載零個或多個子ZNODE節點,從而組成一個樹形結構。
數據發佈訂閱
負載均衡
命名服務
分佈式協調
集羣管理
配置管理
分佈式隊列
分佈式鎖
簡單的理解就是:分佈式鎖是一個在不少環境中很是有用的原語,它是不一樣的系統或是同一個系統的不一樣主機之間互斥操做共享資源的有效方法。
分佈式鎖使用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,證實已經得到鎖,返回啦
0.二、返回0,得到鎖失敗,須要檢查鎖超時時間
一、get 獲取到鎖,利用失效時間判斷鎖是否失效。
1.一、取鎖超時時間的時刻可能鎖被刪除釋放,此時並無拿到鎖,應該從新循環加鎖邏輯。
二、取鎖超時時間成功
2.一、鎖沒有超時,休眠一下,從新循環加鎖
2.二、鎖超時,但此時不能直接釋放鎖刪除。由於此時可能多個線程都讀到該鎖超時,若是直接刪除鎖,全部線程均可能刪除上一個刪除鎖又新上的鎖,會有多個線程進入臨界區,產生競爭狀態。
三、此時採用樂觀鎖的思想,用getset再次獲取鎖的舊超時時間。
3.一、若是此時得到鎖舊超時時間成功
3.1.一、等於上一次得到的鎖超時時間,證實兩次操做過程當中沒有別人動過這個鎖,此時已經得到鎖
3.1.二、不等於上一次得到的鎖超時時間,說明有人先動過鎖,獲取鎖失敗。雖然修改了別人的過時時間,但由於衝突的線程相差時間極短,因此修改後的過時時間並沒有大礙。此處依賴全部機器的時間一致。
3.二、若是此時得到鎖舊超時時間失敗,證實當前線程是第一個在鎖失效後又加上鎖的線程,因此也得到鎖
四、其餘狀況都沒有得到鎖,循環setnx吧
關於解鎖的思考:
在鎖的時候,若是鎖住了,回傳超時時間,做爲解鎖時候的憑證,解鎖時傳入鎖的鍵值和憑證。我思考的解鎖時候有兩種寫法:
一、解鎖前get一下鍵值的value,判斷是否是和本身的憑證同樣。但這樣存在一些問題:
get時返回nil的可能,此時表示有別的線程拿到鎖並用完釋放
get返回非nil,可是不等於自身憑證。因爲有getset那一步,當兩個競爭線程都在這個過程當中時,存在持有鎖的線程憑證不等於value,而是value是稍慢那一步線程設置的value。
二、解鎖前用憑證判斷鎖是否已經超時,若是沒有超時,直接刪除;若是超時,等着鎖自動過時就好,省得誤刪別人的鎖。但這種寫法一樣存在問題,因爲線程調度的不肯定性,判斷到刪除之間可能過去好久,並非絕對意義上的正確解鎖。
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); } } } |
鎖沒有失效事件,容易死鎖
非阻塞式
不可重入
實現相對簡單
可靠性高
性能較好
publicclass Demo01 { staticlongcount = 0; publicstaticvoid main(String[] args) { Runnable runnable = new Runnable() { @Override publicvoid 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類實現定時任務 */ publicclass Demo02 { staticlongcount = 0;
publicstaticvoid main(String[] args) { TimerTask timerTask = new TimerTask() {
@Override publicvoid run() { count++; System.out.println(count); } }; Timer timer = new Timer(); // 天數 longdelay = 0; // 秒數 longperiod = 1000; timer.scheduleAtFixedRate(timerTask, delay, period); }
}
|
使用ScheduledExecutorService是從Java
JavaSE5的java.util.concurrent裏,作爲併發工具類被引進的,這是最理想的定時任務實現方式。
publicclass Demo003 { publicstaticvoid main(String[] args) { Runnable runnable = new Runnable() { publicvoid 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> |
publicclass MyJob implements Job {
publicvoid 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類型觸發器有效 longtime= 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保存報文信息,做爲去重和冪等的依據。
② 消費端代碼拋出異常,不須要重試補償,使用日誌記錄報文,下次發版本解決。