Java高級工程師面試寶典

Java高級工程師面試寶典javascript

JavaSE

多線程

進程與線程的區別?

答:進程是全部線程的集合,每個線程是進程中的一條執行路徑,線程只是一條執行路徑。html

爲何要用多線程?

 答:提升程序效率前端

多線程建立方式?

  答:繼承Thread或Runnable 接口。java

是繼承Thread類好仍是實現Runnable接口好?

答:Runnable接口好,由於實現了接口還能夠繼續繼承。繼承Thread類不能再繼承。node

你在哪裏用到了多線程?

答:主要能體現到多線程提升程序效率。mysql

舉例:分批發送短信、迅雷多線程下載等。linux

什麼是多線程安全?

答:當多個線程同時共享,同一個全局變量或靜態變量,作寫的操做時,可能會發生數據衝突問題,也就是線程安全問題。作讀操做是不會發生數據衝突問題。nginx

如何解決多線程之間線程安全問題?

答:使用多線程之間同步或使用鎖(lock)c++

爲何使用線程同步或使用鎖能解決線程安全問題呢?

答:將可能會發生數據衝突問題(線程不安全問題),只能讓當前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其餘線程進行執行。這樣的話就能夠解決線程不安全問題。git

什麼是多線程之間同步?

答:當多個線程共享同一個資源,不會受到其餘線程的干擾。

什麼是同步代碼塊?

答:就是將可能會發生線程安全問題的代碼,給包括起來。只能讓當前一個線程進行執行,被包裹的代碼執行完成以後才能釋放所,讓後才能讓其餘線程進行執行。

多線程同步的分類?

1.使用同步代碼塊?

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--;           }

        }

    }

 

2.使用同步函數

在方法上修飾synchronized 稱爲同步函數

publicsynchronizedvoid sale() {

            if (trainCount > 0) {

try {

                    Thread.sleep(40);

                } catch (Exception e) {

                }

                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");

                trainCount--;

            }

    }

 

3.靜態同步函數

方法上加上static關鍵字,使用synchronized 關鍵字修飾 爲靜態同步函數

靜態的同步函數使用的鎖是  該函數所屬字節碼文件對象

同步代碼塊與同步函數區別?

答:

同步代碼使用自定鎖(明鎖)

同步函數使用this鎖

同步函數與靜態同步函數區別?

注意:有些面試會這樣問:例如如今一個靜態方法和一個非靜態靜態怎麼實現同步?

答:

同步函數使用this鎖

靜態同步函數使用字節碼文件,也就是類.class

什麼是多線程死鎖?

答:

同步中嵌套同步,沒法釋放鎖的資源。

解決辦法:同步中儘可能不要嵌套同步

Wait()與Notify ()區別?

Wait讓當前線程有運行狀態變爲等待狀態,和同步一塊兒使用

Notify 喚醒如今正在等待的狀態,和同步一塊兒使用

Wait()與sleep()區別?

對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。

在調用sleep()方法的過程當中,線程不會釋放對象鎖。

而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

獲取對象鎖進入運行狀態。

Lock與Synchronized區別?

Lock 接口能夠嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。若是這一時刻鎖沒有被其餘線程獲取到,則成功獲取並持有鎖。
*Lock 接口能被中斷地獲取鎖 與 synchronized 不一樣,獲取到鎖的線程可以響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。

Lock 接口在指定的截止時間以前獲取鎖,若是截止時間到了依舊沒法獲取鎖,則返回。

Condition用法

 Condition的功能相似於在傳統的線程技術中的,Object.wait()和Object.notify()的功能,

代碼:

Condition condition = lock.newCondition();

res. condition.await();  相似wait

res. Condition. Signal() 相似notify

Signalall notifyALL

 

如何中止線程?

  1. 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。

    2. 使用stop方法強行終止線程(這個方法不推薦使用,由於stop和suspend、resume同樣,也可能發生不可預料的結果)。

3. 使用interrupt方法中斷線程。 線程在阻塞狀態

什麼是守護線程

Java中有兩種線程,一種是用戶線程,另外一種是守護線程。

 當進程不存在或主線程中止,守護線程也會被中止。

 使用setDaemon(true)方法設置爲守護線程

join()方法做用

join做用是讓其餘線程變爲等待,只有當前線程執行完畢後,等待的線程纔會被釋放。

線程三大特性

多線程有三大特性,原子性、可見性、有序性

原子性:保證數據一致性,線程安全。

可見性:對另外一個線程是否課件

有序性:線程之間執行有順序

說說Java內存模型

共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。

 

 

什麼是Volatile做用

Volatile 關鍵字的做用是變量在多個線程之間可見。

什麼是AtomicInteger

AtomicInteger原子類

什麼是ThreadLocal

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, 優先級)執行。

 

說說JDK1.5併發包

名稱

做用

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?

Socket就是爲網絡服務提供的一種機制。

通信的兩端都有Sokcet

網絡通信其實就是Sokcet間的通信

數據在兩個Sokcet間經過IO傳輸。

 

TCP與UDP在概念上的區別

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();

      }

}

 

JDK動態代理(不須要生成代理類)

實現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();

    }

 

}

 

CGLIB動態代理

實現

 

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();

    }

}

 

 

CGLIB與JDK動態代理區別

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端服務器是接口是怎麼設計的?

 

JSON解析框架有哪些?

fastjson(阿里)、gson(谷歌)、jackson(SpringMVC自帶)

XML解析方式?

Dom4j、Sax、Pull

Dom4j與Sax區別

 dom4j不適合大文件的解析,由於它是一會兒將文件加載到內存中,因此有可能出現內存溢出,sax是基於事件來對xml進行解析的,因此他能夠解析大文件的xml,也正是由於如此,因此dom4j能夠對xml進行靈活的增刪改查和導航,而sax沒有這麼強的靈活性,因此sax常常是用來解析大型xml文件,而要對xml文件進行一些靈活(crud)操做就用dom4j。

 

XML與JSON區別

Xml是重量級數據交換格式,佔寬帶比較大。

JSON是輕量級交換格式,xml佔寬帶小。

全部不少互聯網公司都會使用json做爲數據交換格式

不少銀行項目,大多數仍是在使用xml。

什麼是Java反射

就是正在運行,動態獲取這個類的全部信息。

反射機制的做用

  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");

 

反射建立api

方法名稱

做用

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());

 

 

 

 

 

 

 

 

 

 

JVM參數調優

Java虛擬機原理

所謂虛擬機,就是一臺虛擬的機器。他是一款軟件,用來執行一系列虛擬計算指令,大致上虛擬機能夠分爲

系統虛擬機和程序虛擬機,大名鼎鼎的Visual BoxVmare就屬於系統虛擬機,他們徹底是對物理計算的仿真,

提供了一個能夠運行完整操做系統的軟件平臺。

 程序虛擬機典型代碼就是Java虛擬機,它專門爲執行單個計算程序而計算,在Java虛擬機中執行的指令咱們成爲Java

本身碼指令。不管是系統虛擬機仍是程序虛擬機,在上面運行的軟件都被限制於虛擬機提供的資源中。

 Java發展至今,出現過不少虛擬機,作初Sun使用的一款叫ClassIcJava虛擬機,到如今引用最普遍的是HotSpot虛擬

機,除了Sum意外,還有BEAJrockit,目前JrockitHostSopt都被oralce收入旗下,大有整合的趨勢。

 

Java內存結構

 

 

一、  類加載子系統:負責從文件系統或者網絡加載Class信息,加載的信息存放在一塊稱之方法區的內存空間。

二、  方法區:就是存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等。

三、  Java堆:在Java虛擬機啓動的時候創建Java堆,它是Java程序最主要的內存工做區域,幾乎全部的對象實例都存放到

Java堆中,堆空間是全部線程共享。

四、  直接內存:JavaNio庫容許Java程序直接內存,從而提升性能,一般直接內存速度會優於Java堆。讀寫頻繁的場合可能會考慮使用。

五、  每一個虛擬機線程都有一個私有棧,一個線程的Java棧在線程建立的時候被建立,Java棧保存着局部變量、方法參數、同事Java的方法調用、

返回值等。

六、  本地方法棧,最大不一樣爲本地方法棧用於本地方法調用。Java虛擬機容許Java直接調用本地方法(經過使用C語言寫)

七、  垃圾收集系統是Java的核心,也是不可少的,Java有一套本身進行垃圾清理的機制,開發人員無需手工清理,下一節課詳細講。

八、  PCProgram Couneter)寄存器也是每一個線程私有的空間, Java虛擬機會爲每一個線程建立PC寄存器,在任意時刻,

一個Java線程老是在執行一個方法,這個方法稱爲當前方法,若是當前方法不是本地方法,PC寄存器總會執行當前正在被執行的指令,

若是是本地方法,則PC寄存器值爲Underfined,寄存器存放若是當前執行環境指針、程序技術器、操做棧指針、計算的變量指針等信息。

九、  虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,通常戶先進行編譯成機器碼後執行。

堆、棧、方法區概念區別

Java堆

堆內存用於存放由new建立的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,之後就能夠在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量至關於爲數組或者對象起的一個別名,或者代號。

 

根據垃圾回收機制的不一樣,Java堆有可能擁有不一樣的結構,最爲常見的就是將整個Java堆分爲

新生代和老年代。其中新聲帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。

新生代分爲den區、s0區、s1區,s0s1也被稱爲fromto區域,他們是兩塊大小相等而且能夠互相角色的空間。

絕大多數狀況下,對象首先分配在eden區,在新生代回收後,若是對象還存活,則進入s0s1區,以後每通過一次

新生代回收,若是對象存活則它的年齡就加1,對象達到必定的年齡後,則進入老年代。

 

 

Java棧

Java棧是一塊線程私有的空間,一個棧,通常由三部分組成:局部變量表、操做數據棧和幀數據區

局部變量表:用於報錯函數的參數及局部變量

操做數棧:主要保存計算過程的中間結果,同時做爲計算過程當中的變量臨時的存儲空間。

幀數據區:除了局部變量表和操做數據棧之外,棧還須要一些數據來支持常量池的解析,這裏幀數據區保存着

訪問常量池的指針,方便計程序訪問常量池,另外當函數返回或出現異常時賣虛擬機子必須有一個異常處理表,方便發送異常

的時候找到異常的代碼,所以異常處理表也是幀數據區的一部分。

 

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();

      }

}

 

Tomcat內存溢出在catalina.sh 修改JVM堆內存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"

內存溢出與內存泄露的區別

內存溢出 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啓動參數中,能夠設置跟內存、垃圾回收相關的一些參數設置,默認狀況不作任何設置JVM會工做的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能得到最佳性能。經過設置咱們但願達到一些目標:

  • GC的時間足夠的小
  • GC的次數足夠的少
  • 發生Full GC的週期足夠的長

  前兩個目前是相悖的,要想GC時間小必需要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,咱們只能取其平衡。

   (1)針對JVM堆的設置,通常能夠經過-Xms -Xmx限定其最小、最大值,爲了防止垃圾收集器在最小、最大之間收縮堆而產生額外的時間,咱們一般把最大、最小設置爲相同的值
   (2)年輕代和年老代將根據默認的比例(1:2)分配堆內存,能夠經過調整兩者之間的比率NewRadio來調整兩者之間的大小,也能夠針對回收代,好比年輕代,經過 -XX:newSize -XX:MaxNewSize來設置其絕對大小。一樣,爲了防止年輕代的堆收縮,咱們一般會把-XX:newSize -XX:MaxNewSize設置爲一樣大小

   (3)年輕代和年老代設置多大才算合理?這個我問題毫無疑問是沒有答案的,不然也就不會有調優。咱們觀察一下兩者大小變化有哪些影響

  • 更大的年輕代必然致使更小的年老代,大的年輕代會延長普通GC的週期,但會增長每次GC的時間;小的年老代會致使更頻繁的Full GC
  • 更小的年輕代必然致使更大年老代,小的年輕代會致使普通GC很頻繁,但每次的GC時間會更短;大的年老代會減小Full GC的頻率
  • 如何選擇應該依賴應用程序對象生命週期的分佈狀況:若是應用存在大量的臨時對象,應該選擇更大的年輕代;若是存在相對較多的持久對象,年老代應該適當增大。但不少應用都沒有這樣明顯的特性,在抉擇時應該根據如下兩點:(A)本着Full GC儘可能少的原則,讓年老代儘可能緩存經常使用對象,JVM的默認比例1:2也是這個道理 (B)經過觀察應用一段時間,看其餘在峯值時年老代會佔多少內存,在不影響Full GC的前提下,根據實際狀況加大年輕代,好比能夠把比例控制在1:1。但應該給年老代至少預留1/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引用鏈恢復鏈接的對象清除出「即將回收」集合。全部此集合中的內容將被回收。

手動GC回收

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在回收對象...");

      }

}

 

finalize做用

Java技術使用finalize()方法在垃圾收集器將對象從內存中清除出去前,作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,所以全部的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者執行其餘清理工做。finalize()方法是在垃圾收集器刪除對象以前對這個對象調用的。

 

垃圾回收機制算法

引用計數法

概述

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器都爲0的對象就是再也不被使用的,垃圾收集器將回收該對象使用的內存。

優缺點

優勢:

引用計數收集器能夠很快的執行,交織在程序運行中。對程序須要不被長時間打斷的實時環境比較有利。

缺點:

沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.並且每次加減很是浪費內存。

標記清除算法

標記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動做,一個是標記,另外一個就是清除。

標記就是根據特定的算法(如:引用計數算法,可達性分析算法等)標出內存中哪些對象能夠回收,哪些對象還要繼續用。

標記指示回收,那就直接收掉;標記指示對象還能用,那就原地不動留下。


缺點

  1. 1.  標記與清除效率低;
  2. 2.  清除以後內存會產生大量碎片;

因此碎片這個問題還得處理,怎麼處理,看標記-整理算法。

複製算法

S0和s1將可用內存按容量分紅大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象複製到另外一塊內存上去,而後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等複雜狀況,只須要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。

複製算法的缺點顯而易見,可以使用的內存降爲原來一半。

複製算法用於在新生代垃圾回收

標記-壓縮算法

標記壓縮法在標記清除基礎之上作了優化,把存活的對象壓縮到內存一端,然後進行垃圾清理。(java中老年代使用的就是標記壓縮法)

分代收集算法

根據內存中對象的存活週期不一樣,將內存劃分爲幾塊,java的虛擬機中通常把內存劃分爲新生代和年老代,當新建立對象時通常在新生代中分配內存空間,當新生代垃圾收集器回收幾回以後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中沒法找到足夠的連續內存時也直接在年老代中建立。

對於新生代和老年代來講,新生代回收頻率很高,可是每次回收耗時很短,而老年代回收頻率較低,可是耗時會相對較長,因此應該儘可能減小老年代的GC.

 

爲何老年代使用標記壓縮、新生代使用複製算法。

垃圾回收時的停頓現象

 

垃圾回收的任務是識別和回收垃圾對象進行內存清理,爲了讓垃圾回收器能夠更高效的執行,大部分狀況下,會要求系統進如一個停頓的狀態。停頓的目的是爲了終止全部的應用線程,只有這樣的系統纔不會有新垃圾的產生。同時停頓保證了系統狀態在某一個瞬間的一致性,也有利於更好的標記垃圾對象。所以在垃圾回收時,都會產生應用程序的停頓。

垃圾收集器

什麼是Java垃圾回收器

Java垃圾回收器是Java虛擬機(JVM)的三個重要模塊(另外兩個是解釋器和多線程機制)之一,爲應用程序提供內存的自動分配(Memory Allocation)、自動回收(Garbage Collect)功能,這兩個操做都發生在Java堆上(一段內存快)。某一個時點,一個對象若是有一個以上的引用(Rreference)指向它,那麼該對象就爲活着的(Live),不然死亡(Dead),視爲垃圾,可被垃圾回收器回收再利用。垃圾回收操做須要消耗CPU、線程、時間等資源,因此容易理解的是垃圾回收操做不是實時的發生(對象死亡立刻釋放),當內存消耗完或者是達到某一個指標(Threshold,使用內存佔總內存的比列,好比0.75)時,觸發垃圾回收操做。有一個對象死亡的例外,java.lang.Thread類型的對象即便沒有引用,只要線程還在運行,就不會被回收。

串行回收器(Serial Collector)

單線程執行回收操做,回收期間暫停全部應用線程的執行,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回收器)


並行回收器在串行回收器基礎上作了改進,他能夠使用多個線程同時進行垃
圾回收,對於計算能力強的計算機而言,能夠有效的縮短垃圾回收所需的尖
際時間。
ParNew回收器是一個工做在新生代的垃圾收集器,他只是簡單的將串行回收
器多線程快他的回收策略和算法和串行回收器同樣。
使用XX:+UseParNewGC 新生代ParNew回收器,老年代則使用市行回收器
ParNew回收器工做時的線程數量能夠使用XX:ParaleiGCThreads參數指
定,通常最好和計算機的CPU至關,避免過多的栽程影響性能。

並行回收集器(ParallelGC)

老年代ParallelOldGC回收器也是一種多線程的回收器,和新生代的
ParallelGC回收器同樣,也是一種關往吞吐量的回收器,他使用了標記壓縮
算法進行實現。
-XX:+UseParallelOldGC 進行設置
-XX:+ParallelCThread也能夠設置垃圾收集時的線程教量。

 

並CMS(併發GC)收集器

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回收器

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對老年代的回收。

 

MySQL優化

MySQL如何優化

表的設計合理化(符合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')

說明:

  1. 在mysql中fulltext 索引只針對 myisam生效
  2. mysql本身提供的fulltext針對英文生效->sphinx (coreseek) 技術處理中文
  3. 使用方法是 match(字段名..) against(‘關鍵字’)
  4. 全文索引:中止詞,  由於在一個文本中,建立索引是一個無窮大的數,所以,對一些經常使用詞和字符,就不會建立,這些詞,稱爲中止詞.好比(a,b,mysql,the)

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:這個值越高,說明查詢低效。

 

SQL調優

①  使用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

MySQL數據引擎

使用的存儲引擎 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注意事項

若是你的數據庫的存儲引擎是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;

 

建立一個demo項目

POM文件

      <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代碼

@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;

      }

 

}

 

Controller

@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-up與Scale-out區別

Scale Out是指Application能夠在水平方向上擴展。通常對數據中心的應用而言,Scale out指的是當添加更多的機器時,應用仍然能夠很好的利用這些機器的資源來提高本身的效率從而達到很好的擴展性。

Scale Up是指Application能夠在垂直方向上擴展。通常對單臺機器而言,Scale Up值得是當某個計算節點(機器)添加更多的CPU Cores,存儲設備,使用更大的內存時,應用能夠很充分的利用這些資源來提高本身的效率從而達到很好的擴展性。

 

 

解決問題

數據如何不被丟失

備份

讀寫分離

數據庫負載均衡

高可用

 

環境搭建

  1. 準備環境

兩臺windows操做系統 ip分別爲: 172.27.185.1(主)、172.27.185.2(從)

  1. 鏈接到主服務(172.27.185.1)服務器上,給從節點分配帳號權限。

GRANT REPLICATION SLAVE ON *.* TO 'root'@'172.27.185.2' IDENTIFIED BY 'root';

  1. 在主服務my.ini文件新增

server-id=200

log-bin=mysql-bin

relay-log=relay-bin

relay-log-index=relay-bin-index

重啓mysql服務

  1. 在從服務my.ini文件新增

server-id = 210

replicate-do-db =itmayiedu #須要同步數據庫

重啓mysql服務

  1. 從服務同步主數據庫

stop slave;

change

master to master_host='172.27.185.1',master_user='root',master_password='root';

start slave;

show slave status;

 

MyCat

什麼是  Mycat

是一個開源的分佈式數據庫系統,可是由於數據庫通常都有本身的數據庫引擎,而Mycat並無屬於本身的獨有數據庫引擎,全部嚴格意義上說並不能算是一個完整的數據庫系統,只能說是一個在應用和數據庫之間起橋樑做用的中間件。

在Mycat中間件出現以前,MySQL主從複製集羣,若是要實現讀寫分離,通常是在程序段實現,這樣就帶來了一個問題,即數據段和程序的耦合度過高,若是數據庫的地址發生了改變,那麼個人程序也要進行相應的修改,若是數據庫不當心掛掉了,則同時也意味着程序的不可用,而對於不少應用來講,並不能接受;

  引入Mycat中間件能很好地對程序和數據庫進行解耦,這樣,程序只需關注數據庫中間件的地址,而無需知曉底層數據庫是如何提供服務的,大量的通用數據聚合、事務、數據源切換等工做都由中間件來處理;

  Mycat中間件的原理是對數據進行分片處理,從原有的一個庫,被切分爲多個分片數據庫,全部的分片數據庫集羣構成完成的數據庫存儲,有點相似磁盤陣列中的RAID0.

 

 

JavaEE

基礎部分

BS與CS區別?

C/S (Client - Server  客戶端-服務器端)

                                          典型應用:QQ軟件 ,飛秋,紅蜘蛛。

                                          特色:

                                                 1)必須下載特定的客戶端程序。

                                                 2)服務器端升級,客戶端升級。

                            B/S (Broswer -Server 瀏覽器端- 服務器端)

                                          典型應用: 騰訊官方(www.qq.com)  163新聞網站, 螞蟻課堂官網(俗稱:網站)

                                          特色:

                                                 1)不須要安裝特定的客戶端(只須要安裝瀏覽器便可!!)

                                                 2)服務器端升級,瀏覽器不須要升級!!!!

                                          javaweb的程序就是b/s軟件結構!!!

DNS解析過程

1、在瀏覽器中輸入www.qq.com域名,操做系統會先檢查本身本地的hosts文件是否有這個網址映射關係,若是有,就先調用這個IP地址映射,完成域名解析。 
二、若是hosts裏沒有這個域名的映射,則查找本地DNS解析器緩存,是否有這個網址映射關係,若是有,直接返回,完成域名解析。 

 

外網映射工具

外網映射工具的做用,主要將本地服務映射到外網。

應用場景:支付回調、微信開發、對接第三方接口等。

映射工具Ngrok、花生殼等。

靜態資源和動態資源的區別

靜態資源: 當用戶屢次訪問這個資源,資源的源代碼永遠不會改變的資源。

動態資源:當用戶屢次訪問這個資源,資源的源代碼可能會發送改變。

Sevlet的生命週期(重點)

構造方法: 建立servlet對象的時候調用。

默認狀況下,第一次訪問servlet的時候建立servlet對象                                                        只調用1次。證實servlet對象在tomcat是單實例的。

                     init方法: 建立完servlet對象的時候調用。只調用1次。

                     service方法: 每次發出請求時調用。調用n次。

                     destroy方法: 銷燬servlet對象的時候調用。中止服務器或者從新部署web應用時銷燬servlet對象。只調用1次。

怎麼證實Servlet是單例的?

由於Servlet是經過Java反射機制,讀取web.xml配置中的servlet-class 完整路徑,進行反射默認執行無參構造函數,因此只要servlet類執行無參構造函數永遠只執行一遍,則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值進行重定向。

JavaWeb有哪些會話技術

Cookie會話數據保存在瀏覽器客戶端

 服務器建立Cookie,將Cookie內容以響應頭方式發送給客戶端存放在本地,當下次發送請求時.會將Cookie信息以請求方式發送給服務器端

注意:Cookie信息不能誇瀏覽器訪問

Session會話保存與服務器端

   服務器建立Session,Session內容存放服務器端上,以響應頭方式將SessionId發送給客戶端保存,當下次發送請求時,會將SessionID 以請求頭方式發送給服務器端

  注意: 瀏覽器關閉,只是清除了Sessionid,並無清除Session

Cookie的實現原理

         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();

Cookie的應用場景

購物車、顯示用戶上次訪問的時間

Session的實現原理

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

token其實就是一個令牌,具備隨機性,相似於sessionId。

在對接一些第三方平臺的時候,爲了可以保證數據安全性,一般會使用一些令牌進行交互

例如: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

如何自定義token

token生成規則,只要保證token生成一個不重複的惟一字符串便可。

使用jdk自帶的uuid生成規則。

什麼是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組成

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代碼

UUID.randomUUID().toString()

 

什麼是Filter

Filter 是Servlet技術中的過濾器,主要作攔截請求做用,通常用於防護XSS攻擊、權限、登陸判斷等。

 

網絡通信

http部分

什麼是http協議

http協議: 對瀏覽器客戶端 和  服務器端 之間數據傳輸的格式規範

Http格式的分類

  請求行

  請求頭

  請求內容

  響應行

  響應頭

  響應內容

https與http區別

雖說 HTTPS 有很大的優點,但其相對來講,仍是存在不足之處的:

  (1)HTTPS 協議握手階段比較費時,會使頁面的加載時間延長近 50%,增長 10% 到 20% 的耗電;

  (2)HTTPS 鏈接緩存不如 HTTP 高效,會增長數據開銷和功耗,甚至已有的安全措施也會所以而受到影響;

  (3)SSL 證書須要錢,功能越強大的證書費用越高,我的網站、小網站沒有必要通常不會用。

  (4)SSL 證書一般須要綁定 IP,不能在同一 IP 上綁定多個域名,IPv4 資源不可能支撐這個消耗。

  (5)HTTPS 協議的加密範圍也比較有限,在黑客攻擊、拒絕服務攻擊、服務器劫持等方面幾乎起不到什麼做用。最關鍵的,SSL 證書的信用鏈體系並不安全,特別是在某些國家能夠控制 CA 根證書的狀況下,中間人攻擊同樣可行。

https請求方式

常見的請求方式: 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      -- 請求發出的時間

 

客戶端模擬http請求工具

Postmen(谷歌插件)、RestClient

服務器模擬http請求工具

httpclient、HttpURLConnection

前端ajax請求  

  

$.ajax({

            type : 'post',

            dataType : "text",

            url : "http://a.a.com/a/FromUserServlet",

            data : "userName=餘勝軍&userAge=19",

            success : function(msg) {

                alert(msg);

            }

        });

 

 

 

 

 

 

 

 

 

 

框架部分

Spring

Spring概述

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其餘模塊的支持

SpringBeanId重複會怎麼樣?

會報錯。

什麼是SpringIOC

 SpringIOC就是把每一個bean與bean之間的關係交給第三方容器進行管理,這個容器就是Spring。

什麼是AOP

什麼是SpringAOP

Aop  aspect object programming  面向切面編程

         功能: 讓關注點代碼與業務代碼分離!

關注點,

         重複代碼就叫作關注點;

切面,

          關注點造成的類,就叫切面(類)!

          面向切面編程,就是指 對不少功能都有的重複的代碼抽取,再在運行的時候網業務方法上動態植入「切面類代碼」。

切入點,

         執行目標對象方法,動態植入切面代碼。

         能夠經過切入點表達式,指定攔截哪些類的哪些方法; 給指定的類在運行的時候植入切面類代碼。

應用場景:事物、日誌、權限控制

SpringAOP建立方式

註解方式實現AOP編程

步驟:

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編程

 

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是單例仍是多例?

  Spring默認是單例

Spring做用域

singleton 

當一個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

prototype做用域部署的bean,每一次請求(將其注入到另外一個bean中,或者以程序的方式調用容器的 getBean()方法)都會產生一個新的bean實例,至關與一個new的操做,對於prototype做用域的bean,有一點很是重要,那就是Spring不能對一個prototype bean的整個生命週期負責,容器在初始化、配置、裝飾或者是裝配完一個prototype實例後,將它交給客戶端,隨後就對該prototype實例漠不關心了。無論何種做用域,容器都會調用全部對象的初始化生命週期回調方法,而對prototype而言,任何配置好的析構生命週期回調方法都將不會被調用。 清除prototype做用域的對象並釋聽任何prototype bean所持有的昂貴資源,都是客戶端代碼的職責。(讓Spring容器釋放被singleton做用域bean佔用資源的一種可行方式是,經過使用 bean的後置處理器,該處理器持有要被清除的bean的引用。)

request

request表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效,配置實例:

request、session、global session使用的時候首先要在初始化web的web.xml中作以下配置:

session

session做用域表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP session內有效

IOC容器建立對象

建立對象, 有幾種方式:

1) 調用無參數構造器

2) 帶參數構造器

3) 工廠建立對象

工廠類,靜態方法建立對象

工廠類,非靜態方法建立對象

依賴注入有哪些方式

Spring中,如何給對象的屬性賦值?  【DI, 依賴注入】

         1) 經過構造函數

         2) 經過set方法給屬性注入值

         3) p名稱空間

         4) 註解

事物的概述

⑴ 原子性(Atomicity)

  原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。

⑵ 一致性(Consistency)

  一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態,也就是說一個事務執行以前和執行以後都必須處於一致性狀態。

  拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

⑶ 隔離性(Isolation)

  隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。

  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。

  關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。

⑷ 持久性(Durability)

  持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。

例如咱們在使用JDBC操做數據庫時,在提交事務方法後,提示用戶事務操做完成,當咱們程序執行完成直到看到提示後,就能夠認定事務以及正確提交,即便這時候數據庫出現了問題,也必需要將咱們的事務徹底執行完成,不然就會形成咱們看到提示事務處理完畢,可是數據庫由於故障而沒有執行事務的重大錯誤。

Spring事物控制

編程式事務控制

         本身手動控制事務,就叫作編程式事務控制。

         Jdbc代碼:

                   Conn.setAutoCommite(false);  // 設置手動控制事務

         Hibernate代碼:

                   Session.beginTransaction();    // 開啓一個事務

         【細粒度的事務控制: 能夠對指定的方法、指定的方法的某幾行添加事務控制】

         (比較靈活,但開發起來比較繁瑣: 每次都要開啓、提交、回滾.)

 

聲明式事務控制

         Spring提供了對事務的管理, 這個就叫聲明式事務管理。

    Spring聲明事物有xml方式和註解方式

         Spring提供了對事務控制的實現。用戶若是想用Spring的聲明式事務管理,只須要在配置文件中配置便可; 不想使用時直接移除配置。這個實現了對事務控制的最大程度的解耦。

         Spring聲明式事務管理,核心實現就是基於Aop。

         【粗粒度的事務控制: 只能給整個方法應用事務,不能夠對方法的某幾行應用事務。】

         (由於aop攔截的是方法。)

 

         Spring聲明式事務管理器類:

                   Jdbc技術:DataSourceTransactionManager

                   Hibernate技術:HibernateTransactionManager

Spring事物傳播行爲

Spring中事務的定義:

Propagation(key屬性肯定代理應該給哪一個方法增長事務行爲。這樣的屬性最重要的部份是傳播行爲。)有如下選項可供使用:

  • PROPAGATION_REQUIRED--支持當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇。
  • PROPAGATION_SUPPORTS--支持當前事務,若是當前沒有事務,就以非事務方式執行。
  • PROPAGATION_MANDATORY--支持當前事務,若是當前沒有事務,就拋出異常。 
  • PROPAGATION_REQUIRES_NEW--新建事務,若是當前存在事務,把當前事務掛起。 
  • PROPAGATION_NOT_SUPPORTED--以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。 
  • PROPAGATION_NEVER--以非事務方式執行,若是當前存在事務,則拋出異常。

SpringBoot

什麼是SpringBoot

在您第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有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。

 

SpringBoot優勢

爲全部Spring開發者更快的入門

開箱即用,提供各類默認配置來簡化項目配置

內嵌式容器簡化Web項目

沒有冗餘代碼生成和XML配置的要求

本章主要目標完成Spring Boot基礎項目的構建,而且實現一個簡單的Http請求處理,經過這個例子對Spring Boot有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。

 

@RestController

在上加上RestController 表示修飾該Controller全部的方法返回JSON格式,直接能夠編寫

Restful接口

@EnableAutoConfiguration

註解:做用在於讓 Spring Boot   根據應用所聲明的依賴來對 Spring 框架進行自動配置
        這個註解告訴Spring Boot根據添加的jar依賴猜想你想如何配置Spring。因爲spring-boot-starter-web添加了Tomcat和Spring MVC,因此auto-configuration將假定你正在開發一個web應用並相應地對Spring進行設置。

SpringApplication.run(HelloController.class, args);

   標識爲啓動類

 

 

 

Mybatis與Hibernate區別

 Mybatis是輕量級封裝,Hibernate是重量級封裝

 Mybatis 以SQL語句獲得對象,hibernate是以對象獲得SQL語句

Mybatis#與$區別

優先使用 #{}。由於 ${} 會致使 sql 注入的問題

 

安全與防護部分

表單重複提交解決方案(防止Http重複提交。)

產生緣由

網絡延時、從新刷新、點擊瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交

使用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;

}

 

 

6使用後端提交解決

 對於【場景二】和【場景三】致使表單重複提交的問題,既然客戶端沒法解決,那麼就在服務器端解決,在服務器端解決就須要用到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攻擊

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添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持全部網站

使用JSONP

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語句注入

Sql語句若是是經過拼接方式執行的話,傳入參數 ‘  or 1=1 會發生語句成立,致使數據錯誤。

應該使用PreparedStatement 先編譯 在執行 經過?號穿參數的方式進行執行。

怎麼防護DDOC?

 

nginx配置DDOS

限制請求速度

設置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?

NoSQL 是 Not Only SQL 的縮寫,意即"不只僅是SQL"的意思,泛指非關係型的數據庫。強調Key-Value Stores和文檔數據庫的優勢,而不是單純的反對RDBMS。

NoSQL產品是傳統關係型數據庫的功能閹割版本,經過減小用不到或不多用的功能,來大幅度提升產品性能

NoSQL產品 redis、mongodb MembaseHBase 

什麼是Redis?

Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。

Redis 與其餘 key - value 緩存產品有如下三個特色:

Redis支持數據的持久化,能夠將內存中的數據保存在磁盤中,重啓的時候能夠再次加載進行使用。

Redis不只僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。

Redis支持數據的備份,即master-slave模式的數據備份。

 

Redis應用場景

   主要可以體現 解決數據庫的訪問壓力。

   例如:短信驗證碼時間有效期、session共享解決方案

Redis優點

性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。

豐富的數據類型 – Redis支持二進制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 數據類型操做。

原子 – Redis的全部操做都是原子性的,同時Redis還支持對幾個操做全並後的原子性執行。

豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過時等等特性。

Redis與其餘key-value存儲有什麼不一樣?

Redis有着更爲複雜的數據結構而且提供對他們的原子性操做,這是一個不一樣於其餘數據庫的進化路徑。Redis的數據類型都是基於基本數據結構的同時對程序員透明,無需進行額外的抽象。

Redis運行在內存中可是能夠持久化到磁盤,因此在對不一樣數據集進行高速讀寫時須要權衡內存,由於數據量不能大於硬件內存。在內存數據庫方面的另外一個優勢是,相比在磁盤上相同的複雜的數據結構,在內存中操做起來很是簡單,這樣Redis能夠作不少內部複雜性很強的事情。同時,在磁盤格式方面他們是緊湊的以追加的方式產生的,由於他們並不須要進行隨機訪問。

Redis的基本數據類型

字符串類型(String)

redis 127.0.0.1:6379> SET mykey "redis"

OK

redis 127.0.0.1:6379> GET mykey

"redis"

 

在上面的例子中,SETGET是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

在上面的例子中,SETGET是redis中的命令,而mykey是鍵的名稱。

Redis字符串命令

下表列出了一些用於在Redis中管理字符串的基本命令。

編號

命令

描述說明

1

SET key value

此命令設置指定鍵的值。

2

GET key

獲取指定鍵的值。

3

GETRANGE key start end

獲取存儲在鍵上的字符串的子字符串。

4

GETSET key value

設置鍵的字符串值並返回其舊值。

5

GETBIT key offset

返回在鍵處存儲的字符串值中偏移處的位值。

6

MGET key1 [key2..]

獲取全部給定鍵的值

7

SETBIT key offset value

存儲在鍵上的字符串值中設置或清除偏移處的位

8

SETEX key seconds value

使用鍵和到期時間來設置值

9

SETNX key value

設置鍵的值,僅當鍵不存在時

10

SETRANGE key offset value

在指定偏移處開始的鍵處覆蓋字符串的一部分

11

STRLEN key

獲取存儲在鍵中的值的長度

12

MSET key value [key value …]

爲多個鍵分別設置它們的值

13

MSETNX key value [key value …]

爲多個鍵分別設置它們的值,僅當鍵不存在時

14

PSETEX key milliseconds value

設置鍵的值和到期時間(以毫秒爲單位)

15

INCR key

將鍵的整數值增長1

16

INCRBY key increment

將鍵的整數值按給定的數值增長

17

INCRBYFLOAT key increment

將鍵的浮點值按給定的數值增長

18

DECR key

將鍵的整數值減1

19

DECRBY key decrement

按給定數值減小鍵的整數值

20

APPEND key value

將指定值附加到鍵

 

列表類型(List)

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 
對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間以內的元素都將被刪除。

14

RPOP key 
移除並獲取列表最後一個元素

15

RPOPLPUSH source destination 
移除列表的最後一個元素,並將該元素添加到另外一個列表並返回

16

RPUSH key value1 [value2] 
在列表中添加一個或多個值

17

RPUSHX key value 
爲已存在的列表添加值

 

 

集合(Set)

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] 
返回給定全部集合的差集並存儲在 destination 中

5

SINTER key1 [key2] 
返回給定全部集合的交集

6

SINTERSTORE destination key1 [key2] 
返回給定全部集合的交集並存儲在 destination 中

7

SISMEMBER key member 
判斷 member 元素是不是集合 key 的成員

8

SMEMBERS key 
返回集合中的全部成員

9

SMOVE source destination member 
將 member 元素從 source 集合移動到 destination 集合

10

SPOP key 
移除並返回集合中的一個隨機元素

11

SRANDMEMBER key [count] 
返回集合中一個或多個隨機數

12

SREM key member1 [member2] 
移除集合中一個或多個成員

13

SUNION key1 [key2] 
返回全部給定集合的並集

14

SUNIONSTORE destination key1 [key2] 
全部給定集合的並集存儲在 destination 集合中

15

SSCAN key cursor [MATCH pattern] [COUNT count] 
迭代集合中的元素

 

 

有序集合(sorted set)

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 有序集合的基本命令:

序號

命令及描述

1

ZADD key score1 member1 [score2 member2] 
向有序集合添加一個或多個成員,或者更新已存在成員的分數

2

ZCARD key 
獲取有序集合的成員數

3

ZCOUNT key min max 
計算在有序集合中指定區間分數的成員數

4

ZINCRBY key increment member 
有序集合中對指定成員的分數加上增量 increment

5

ZINTERSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 key 中

6

ZLEXCOUNT key min max 
在有序集合中計算指定字典區間內成員數量

7

ZRANGE key start stop [WITHSCORES] 
經過索引區間返回有序集合成指定區間內的成員

8

ZRANGEBYLEX key min max [LIMIT offset count] 
經過字典區間返回有序集合的成員

9

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 
經過分數返回有序集合指定區間內的成員

10

ZRANK key member 
返回有序集合中指定成員的索引

11

ZREM key member [member ...] 
移除有序集合中的一個或多個成員

12

ZREMRANGEBYLEX key min max 
移除有序集合中給定的字典區間的全部成員

13

ZREMRANGEBYRANK key start stop 
移除有序集合中給定的排名區間的全部成員

14

ZREMRANGEBYSCORE key min max 
移除有序集合中給定的分數區間的全部成員

15

ZREVRANGE key start stop [WITHSCORES] 
返回有序集中指定區間內的成員,經過索引,分數從高到底

16

ZREVRANGEBYSCORE key max min [WITHSCORES] 
返回有序集中指定分數區間內的成員,分數從高到低排序

17

ZREVRANK key member 
返回有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序

18

ZSCORE key member 
返回有序集中,成員的分數值

19

ZUNIONSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的並集,並存儲在新的 key 中

20

ZSCAN key cursor [MATCH pattern] [COUNT count] 
迭代有序集合中的元素(包括元素成員和元素分值)

 

哈希(Hash)

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 
查看哈希表 key 中,指定的字段是否存在。

3

HGET key field 
獲取存儲在哈希表中指定字段的值。

4

HGETALL key 
獲取在哈希表中指定 key 的全部字段和值

5

HINCRBY key field increment 
爲哈希表 key 中的指定字段的整數值加上增量 increment 。

6

HINCRBYFLOAT key field increment 
爲哈希表 key 中的指定字段的浮點數值加上增量 increment 。

7

HKEYS key 
獲取全部哈希表中的字段

8

HLEN key 
獲取哈希表中字段的數量

9

HMGET key field1 [field2] 
獲取全部給定字段的值

10

HMSET key field1 value1 [field2 value2 ] 
同時將多個 field-value (域-值)對設置到哈希表 key 中。

11

HSET key field value 
將哈希表 key 中的字段 field 的值設爲 value 。

12

HSETNX key field value 
只有在字段 field 不存在時,設置哈希表字段的值。

13

HVALS key 
獲取哈希表中全部值

14

HSCAN key cursor [MATCH pattern] [COUNT count] 
迭代哈希表中的鍵值對。

 

什麼是redis的主從複製

概述

一、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事物

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持久化,就是將內存數據保存到硬盤。

Redis 持久化存儲 (AOF 與 RDB 兩種模式)

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時間,如下分別表示更改了1key時間隔900s進行持久化存儲;更改了10key300s進行存儲;更改10000key60s進行存儲。

save 900 1

save 300 10

save 60 10000

##snapshot時出現錯誤沒法繼續時,是否阻塞客戶端變動操做錯誤可能由於磁盤已滿/磁盤故障/OS級別異常等 

stop-writes-on-bgsave-error yes 

##是否啓用rdb文件壓縮,默認爲「yes」,壓縮每每意味着額外的cpu消耗,同時也意味這較小的文件尺寸以及較短的網絡傳輸時間 

rdbcompression yes 

 

AOF持久化

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 過程並不阻塞客戶端請求。系統會開啓一個子進程來完成。

 

AOF與RDB區別

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發佈訂閱

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 ...]] 
指退訂給定的頻道。

 

 

Redis如何作集羣?

nginx

什麼是nginx

 

nginx是一款高性能的http 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器。由俄羅斯的程序設計師Igor Sysoev所開發,官方測試nginx可以支支撐5萬併發連接,而且cpu、內存等資源消耗卻很是低,運行很是穩定,因此如今不少知名的公司都在使用nginx。

nginx應用場景

一、http服務器。Nginx是一個http服務能夠獨立提供http服務。能夠作網頁靜態服務器。

二、虛擬主機。能夠實如今一臺服務器虛擬出多個網站。例如我的網站使用的虛擬主機。

三、反向代理,負載均衡。當網站的訪問量達到必定程度後,單臺服務器不能知足用戶的請求時,須要用多臺服務器集羣能夠使用nginx作反向代理。而且多臺服務器能夠平均分擔負載,不會由於某臺服務器負載高宕機而某臺服務器閒置的狀況。

 

 

nginx優缺點

佔內存小,能夠實現高併發鏈接、處理響應快。

能夠實現http服務器、虛擬主機、反向代理、負載均衡。

nginx配置簡單

能夠不暴露真實服務器IP地址

nginx.conf 介紹

 nginx.conf文件的結構

nginx的配置由特定的標識符(指令符)分爲多個不一樣的模塊。 
指令符分爲簡單指令塊指令

  • 簡單指令格式:[name parameters;]
  • 塊指令格式:和簡單指令格式有同樣的結構,但其結束標識符不是分號,而是大括號{},塊指令內部能夠包含simple directives 和block directives, 能夠稱塊指令爲上下文(e.g. events, http, server, location)

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;

        }

 

nginx實現反向代理

什麼是反向代理?

反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。

 

啓動一個Tomcat 127.0.0.1:8080

使用nginx反向代理 8080.itmayiedu.com 直接跳轉到127.0.0.1:8080

 

Host文件新增

127.0.0.1 8080.itmayiedu.com

127.0.0.1 b8081.itmayiedu.com

nginx.conf 配置

 

配置信息:

    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;

        }

    }

 

 

nginx實現負載均衡

什麼是負載均衡

負載均衡 創建在現有網絡結構之上,它提供了一種廉價有效透明的方法擴展網絡設備和服務器的帶寬、增長吞吐量、增強網絡數據處理能力、提升網絡的靈活性和可用性。

    負載均衡,英文名稱爲Load Balance,其意思就是分攤到多個操做單元上進行執行,例如Web服務器、FTP服務器、企業關鍵應用服務器和其它關鍵任務服務器等,從而共同完成工做任務。

 

 

負載均衡策略

1、輪詢(默認)
每一個請求按時間順序逐一分配到不一樣的後端服務器,若是後端服務器down掉,能自動剔除。 
upstream backserver { 
server 192.168.0.14; 
server 192.168.0.15; 


2、指定權重
指定輪詢概率,weight和訪問比率成正比,用於後端服務器性能不均的狀況。 
upstream backserver { 
server 192.168.0.14 weight=10; 
server 192.168.0.15 weight=10; 


3、IP綁定 ip_hash
每一個請求按訪問ip的hash結果分配,這樣每一個訪客固定訪問一個後端服務器,能夠解決session的問題。 
upstream backserver { 
ip_hash; 
server 192.168.0.14:88; 
server 192.168.0.15:80; 

配置代碼

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 環境下的組件)

 

 

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;

        }

    }

 

nginx配置防盜鏈

location ~ .*\.(jpg|jpeg|JPG|png|gif|icon)$ {

        valid_referers blocked http://www.itmayiedu.com www.itmayiedu.com;

        if ($invalid_referer) {

            return 403;

        }

                   }

 

 

nginx配置DDOS

限制請求速度

設置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

Keepalived是一個免費開源的,用C編寫的相似於layer3, 4 & 7交換機制軟件,具有咱們平時說的第3層、第4層和第7層交換機的功能。主要提供loadbalancing(負載均衡)和 high-availability(高可用)功能,負載均衡實現須要依賴Linux的虛擬服務內核模塊(ipvs),而高可用是經過VRRP協議實現多臺機器之間的故障轉移服務。 
 
上圖是Keepalived的功能體系結構,大體分兩層:用戶空間(user space)和內核空間(kernel space)。 
內核空間:主要包括IPVS(IP虛擬服務器,用於實現網絡服務的負載均衡)和NETLINK(提供高級路由及其餘相關的網絡功能)兩個部份。 
用戶空間

  • WatchDog:負載監控checkers和VRRP進程的情況
  • VRRP Stack:負載負載均衡器之間的失敗切換FailOver,若是隻用一個負載均稀器,則VRRP不是必須的。
  • Checkers:負責真實服務器的健康檢查healthchecking,是keepalived最主要的功能。換言之,能夠沒有VRRP Stack,但健康檢查healthchecking是必定要有的。
  • IPVS wrapper:用戶發送設定的規則到內核ipvs代碼
  • Netlink Reflector:用來設定vrrp的vip地址等。

Keepalived的全部功能是配置keepalived.conf文件來實現的。

 

集羣狀況下Session共享解決方案

 nginx或者haproxy作的負載均衡)

用Nginx 作的負載均衡能夠添加ip_hash這個配置,

用haproxy作的負載均衡能夠用 balance source這個配置。

從而使同一個ip的請求發到同一臺服務器。

利用數據庫同步session

利用cookie同步session數據原理圖以下

 

 

缺點:安全性差、http請求都須要帶參數增長了帶寬消耗

 使用Session集羣存放Redis

使用spring-session框架,底層實現原理是重寫httpsession

引入maven依賴

      <!--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>

 

建立SessionConfig

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服務器的鏈接

//maxInactiveIntervalInSecondsSpringSession的過時時間(單位:秒)

@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

//初始化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介紹

什麼是JMS?

JMS是java的消息服務,JMS的客戶端之間能夠經過JMS服務進行異步的消息傳輸。

什麼是消息模型

○ Point-to-Point(P2P) --- 點對點

○ Publish/Subscribe(Pub/Sub)---  發佈訂閱

即點對點和發佈訂閱模型

 

P2P (點對點)

P2P

  1. P2P模式圖 
  2. 涉及到的概念 

    1. 消息隊列(Queue)
    2. 發送者(Sender)
    3. 接收者(Receiver)
    4. 每一個消息都被髮送到一個特定的隊列,接收者從隊列中獲取消息。隊列保留着消息,直到他們被消費或超時。
  3. P2P的特色
    1. 每一個消息只有一個消費者(Consumer)(即一旦被消費,消息就再也不在消息隊列中)
    2. 發送者和接收者之間在時間上沒有依賴性,也就是說當發送者發送了消息以後,無論接收者有沒有正在運行,它不會影響到消息被髮送到隊列
    3. 接收者在成功接收消息以後需向隊列應答成功

若是你但願發送的每一個消息都應該被成功處理的話,那麼你須要P2P模式。

應用場景

A用戶與B用戶發送消息

 

 

Pub/Sub (發佈與訂閱)

Pub/Sub模式圖 

涉及到的概念 

主題(Topic)

發佈者(Publisher)

訂閱者(Subscriber) 
客戶端將消息發送到主題。多個發佈者將消息發送到Topic,系統將這些消息傳遞給多個訂閱者。

Pub/Sub的特色

每一個消息能夠有多個消費者

發佈者和訂閱者之間有時間上的依賴性。針對某個主題(Topic)的訂閱者,它必須建立一個訂閱者以後,才能消費發佈者的消息,並且爲了消費消息,訂閱者必須保持運行的狀態。

爲了緩和這樣嚴格的時間相關性,JMS容許訂閱者建立一個可持久化的訂閱。這樣,即便訂閱者沒有被激活(運行),它也能接收到發佈者的消息。

若是你但願發送的消息能夠不被作任何處理、或者被一個消息者處理、或者能夠被多個消費者處理的話,那麼能夠採用Pub/Sub模型

消息的消費 
在JMS中,消息的產生和消息是異步的。對於消費來講,JMS的消息者能夠經過兩種方式來消費消息。 
○ 同步 
訂閱者或接收者調用receive方法來接收消息,receive方法在可以接收到消息以前(或超時以前)將一直阻塞 
○ 異步 
訂閱者或接收者能夠註冊爲一個消息監聽器。當消息到達以後,系統自動調用監聽器的onMessage方法。

  應用場景:

   用戶註冊、訂單修改庫存、日誌存儲

   畫圖演示

 

 

 

MQ產品的分類

 

RabbitMQ

是使用Erlang編寫的一個開源的消息隊列,自己支持不少的協議:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它變的很是重量級,更適合於企業級的開發。同時實現了一個經紀人(Broker)構架,這意味着消息在發送給客戶端時先在中心隊列排隊。對路由(Routing),負載均衡(Load balance)或者數據持久化都有很好的支持。

Redis

是一個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

ZeroMQ

號稱最快的消息隊列系統,尤爲針對大吞吐量的需求場景。ZMQ可以實現RabbitMQ不擅長的高級/複雜的隊列,可是開發人員須要本身組合多種技術框架,技術上的複雜度是對這MQ可以應用成功的挑戰。ZeroMQ具備一個獨特的非中間件的模式,你不須要安裝和運行一個消息服務器或中間件,由於你的應用程序將扮演了這個服務角色。你只須要簡單的引用ZeroMQ程序庫,能夠使用NuGet安裝,而後你就能夠愉快的在應用程序之間發送消息了。可是ZeroMQ僅提供非持久性的隊列,也就是說若是down機,數據將會丟失。其中,Twitter的Storm中使用ZeroMQ做爲數據流的傳輸。

ActiveMQ

是Apache下的一個子項目。 相似於ZeroMQ,它可以以代理人和點對點的技術實現隊列。同時相似於RabbitMQ,它少許代碼就能夠高效地實現高級應用場景。RabbitMQ、ZeroMQ、ActiveMQ均支持經常使用的多種語言客戶端 C++、Java、.Net,、Python、 Php、 Ruby等。

Jafka/Kafka

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怎麼保證消息冪等問題

 

  1. 發送端MQ-client 將消息發送給服務端MQ-server
  2. 服務端MQ-server將消息落地
  3. 服務端MQ-server 回ACK(表示確認) 2.若是3丟失 發送端在超時後,又會發送一遍,此時重發是MQ-client發起的,消息處理的是MQ-server 爲了不2 重複落地,對每條MQ消息系統內部須要生成一個inner-msg-id,做爲去重和冪等的依據,這個內部消息ID 的特色是

在分佈式環境中,MQ通信產生網絡延遲,重試補償中,會形成MQ重複消費。

解決辦法:

①     使用日誌+msg-id保存報文信息,做爲去重和冪等的依據。

②     消費端代碼拋出異常,不須要重試補償,使用日誌記錄報文,下次發版本解決。

MQ有哪些協議

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遠程調用?

RPC 的全稱是 Remote Procedure Call 是一種進程間通訊方式。
它容許程序調用另外一個地址空間(一般是共享網絡的另外一臺機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節。即不管是調用本地接口/服務的仍是遠程的接口/服務,本質上編寫的調用代碼基本相同。
好比兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數或者方法,因爲不在一個內存空間,不能直接調用,這時候須要經過就能夠應用RPC框架的實現來解決

什麼是SOA?與SOAP區別是什麼?

SOA是一種面向服務架構,是將相同業務邏輯抽取出來組成單獨服務。

SOAP是WebService面向服務協議, 採用xml,由於比較中,如今不是特別流行。

什麼是微服務架構

微服務架構師一種架構模式,它提倡將單一應用程序劃分紅一組小的服務,服務之間互相

協調、互相配合沒用戶提供最終價值。每一個服務運行在其獨立的進程中,服務與服務間採用輕量級的同窗機制互相溝通(通暢採用Http+restful API),每一個服務都圍繞着具體業務進行構建,而且可以被獨立的部署到生成環境、類生存環境等。另外,應儘可能避免同一的、集中式服務管理機制。

微服務與SOA區別

SOA實現

微服務架構實現

企業級,自頂向下開展實施

團隊級,自定向上開展實施

服務由多個子系統組成

一個系統被拆分紅多個服務

集成式服務(esb、ws、soap)

集成方式簡單(http、rest、json)

 

RPC遠程調用有哪些框架?

SpringCloud、Dubbo、Dubbox、Hessian、HttpClient、thrift等。

 

什麼是SpringCloud

SpringCloud 爲開發人員提供了快速構建分佈式系統的一些工具,包括配置管理、服務發現(Eureka)、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分佈式會話等等。它運行環境簡單,能夠在開發人員的電腦上跑。另外說明spring cloud是基於Springboot的,因此須要開發中對Springboot有必定的瞭解,若是不瞭解的話能夠看螞蟻課堂SpringBoot課程。

SpringCloud使用Eureka做爲註冊中心,使用rest+ribbon或者feign,斷路器Hystrix、zuul接口網關等。

什麼是Dubbo?

Duubbo是一個RPC遠程調用框架, 分佈式服務治理框架

什麼是Dubbo服務治理?

服務與服務之間會有不少個Url、依賴關係、負載均衡、容錯、自動註冊服務。

Dubbo有哪些協議?

默認用的dubbo協議、Http、RMI、Hessian

Dubbo整個架構流程

分爲四大模塊

生產者、消費者、註冊中心、監控中心

生產者:提供服務

消費者: 調用服務

註冊中心:註冊信息(redis、zk)

監控中心:調用次數、關係依賴等。

首先生產者將服務註冊到註冊中心(zk),使用zk持久節點進行存儲,消費訂閱zk節點,一旦有節點變動,zk經過事件通知傳遞給消費者,消費能夠調用生產者服務。

服務與服務之間進行調用,都會在監控中心中,存儲一個記錄。

 

Dubbox與Dubbo區別?

Dubox使用http協議+rest風格傳入json或者xml格式進行遠程調用。

Dubbo使用Dubbo協議。

SpringCloud與Dubbo區別?

相同點:

dubbospringcloud均可以實現RPC遠程調用。

dubbospringcloud均可以使用分佈式、微服務場景下。

區別:

dubbo有比較強的背景,在國內有必定影響力。

dubbo使用zkredis做爲做爲註冊中心

springcloud使用eureka做爲註冊中心

dubbo支持多種協議默認使用dubbo協議。

Springcloud只能支持http協議。

Springcloud是一套完整的微服務解決方案。

Dubbo目前已經中止更新,SpringCloud更新速度快。

 

什麼是Zookeeper

Zookeeper是一個工具,能夠實現集羣中的分佈式協調服務。

所謂的分佈式協調服務,就是在集羣的節點中進行可靠的消息傳遞,來協調集羣的工做。

 

Zookeeper之因此可以實現分佈式協調服務,靠的就是它可以保證分佈式數據一致性。

所謂的分佈式數據一致性,指的就是能夠在集羣中保證數據傳遞的一致性。

 Zookeeper可以提供的分佈式協調服務包括:數據發佈訂閱、負載均衡、命名服務、分佈式協調/通知、集羣管理、分佈式鎖、分佈式隊列等功能

Zookeeper特色

Zookeeper工做在集羣中,對集羣提供分佈式協調服務,它提供的分佈式協調服務具備以下的特色:

順序一致性

從同一個客戶端發起的事務請求,最終將會嚴格按照其發起順序被應用到zookeeper中

原子性

全部事物請求的處理結果在整個集羣中全部機器上的應用狀況是一致的,即,要麼整個集羣中全部機器都成功應用了某一事務,要麼都沒有應用,必定不會出現集羣中部分機器應用了改事務,另一部分沒有應用的狀況。

單一視圖

不管客戶端鏈接的是哪一個zookeeper服務器,其看到的服務端數據模型都是一致的。

可靠性

一旦服務端成功的應用了一個事務,並完成對客戶端的響應,那麼該事務所引發的服務端狀態變動將會一直保留下來,除非有另外一個事務又對其進行了改變。

實時性

zookeeper並非一種強一致性,只能保證順序一致性和最終一致性,只能稱爲達到了僞實時性。

zookeeper的數據模型

zookeepei中能夠保存數據,正是利用zookeeper能夠保存數據這一特色,咱們的集羣經過在zookeeper裏存取數據來進行消息的傳遞。

zookeeper中保存數據的結構很是相似於文件系統。都是由節點組成的樹形結構。不一樣的是文件系統是由文件夾和文件來組成的樹,而zookeeper中是由ZNODE來組成的樹。

每個ZNODE裏均可以存放一段數據,ZNODE下還能夠掛載零個或多個子ZNODE節點,從而組成一個樹形結構。

 

Zookeeper應用場景

數據發佈訂閱

負載均衡

命名服務

分佈式協調

集羣管理

配置管理

分佈式隊列

分佈式鎖

 

什麼是分佈式鎖

簡單的理解就是:分佈式鎖是一個在不少環境中很是有用的原語,它是不一樣的系統或是同一個系統的不一樣主機之間互斥操做共享資源的有效方法。

Zookeeper實現分佈式鎖

分佈式鎖使用zk,在zk上建立一個臨時節點,使用臨時節點做爲鎖,由於節點不容許重複。

若是能建立節點成功,生成訂單號,若是建立節點失敗,就等待。臨時節點zk關閉,釋放鎖,其餘節點就能夠從新生成訂單號。

Redis分佈式鎖思考

通常的鎖只能針對單機下同一進程的多個線程,或單機的多個進程。多機狀況下,對同一個資源訪問,須要對每臺機器的訪問進程或線程加鎖,這即是分佈式鎖。分佈式鎖能夠利用多機的共享緩存(例如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);

        }

    }

}

 

Zookeeper與 Redis實現分佈式鎖的區別

基於緩存實現分佈式鎖

 鎖沒有失效事件,容易死鎖

 非阻塞式

不可重入

基於Zookeeper實現分佈式鎖

 實現相對簡單

 可靠性高

 性能較好

 

Java實現定時任務有哪些方式

Thread

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

/**

 * 使用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

使用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);

      }

}

 

Quartz

建立一個quartz_demo項目

引入maven依賴

      <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 namegroup

              .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的操做放在一個本地事務域中管理。通俗點描述,仍是以上述提到的「跨行轉帳」爲例,咱們很難保證在扣款完成以後對MQ投遞消息的操做就必定能成功。這樣一致性彷佛很難保證。 
先從消息生產者這端來分析,請看僞代碼:

 

根據上述代碼及註釋,咱們來分析下可能的狀況:

1. 操做數據庫成功,向MQ中投遞消息也成功,皆大歡喜 
2. 操做數據庫失敗,不會向MQ中投遞消息了 
3. 操做數據庫成功,可是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操做將被回滾

從上面分析的幾種狀況來看,貌似問題都不大的。那麼咱們來分析下消費者端面臨的問題:

1. 消息出列後,消費者對應的業務操做要執行成功。若是業務執行失敗,消息不能失效或者丟失。須要保證消息與業務操做一致 
2. 儘可能避免消息重複消費。若是重複消費,也不能所以影響業務結果

如何保證消息與業務操做一致,不丟失?

主流的MQ產品都具備持久化消息的功能。若是消費者宕機或者消費失敗,均可以執行重試機制的(有些MQ能夠自定義重試次數)。

如何避免消息被重複消費形成的問題?

1. 保證消費者調用業務的服務接口的冪等性 
2. 經過消費日誌或者相似狀態表來記錄消費狀態,便於判斷(建議在業務上自行實現,而不依賴MQ產品提供該特性)


總結:這種方式比較常見,性能和吞吐量是優於使用關係型數據庫消息表的方案。若是MQ自身和業務都具備高可用性,理論上是能夠知足大部分的業務場景的。不過在沒有充分測試的狀況下,不建議在交易業務中直接使用。

 

MQ(事務消息)

其餘補償方式

作過支付寶交易接口的同窗都知道,咱們通常會在支付寶的回調頁面和接口裏,解密參數,而後調用系統中更新交易狀態相關的服務,將訂單更新爲付款成功。同時,只有當咱們回調頁面中輸出了success字樣或者標識業務處理成功相應狀態碼時,支付寶纔會中止回調請求。不然,支付寶會每間隔一段時間後,再向客戶方發起回調請求,直到輸出成功標識爲止。 
其實這就是一個很典型的補償例子,跟一些MQ重試補償機制很相似。

通常成熟的系統中,對於級別較高的服務和接口,總體的可用性一般都會很高。若是有些業務因爲瞬時的網絡故障或調用超時等問題,那麼這種重試機制實際上是很是有效的。

固然,考慮個比較極端的場景,假如系統自身有bug或者程序邏輯有問題,那麼重試1W次那也是無濟於事的。那豈不是就發生了「明明已經付款,卻顯示未付款不發貨」相似的悲劇?

其實爲了交易系統更可靠,咱們通常會在相似交易這種高級別的服務代碼中,加入詳細日誌記錄的,一旦系統內部引起相似致命異常,會有郵件通知。同時,後臺會有定時任務掃描和分析此類日誌,檢查出這種特殊的狀況,會嘗試經過程序來補償並郵件通知相關人員。

在某些特殊的狀況下,還會有「人工補償」的,這也是最後一道屏障。

補充資料

什麼是兩段提交協議

 

1、協議概述

 兩階段提交協議(two phase commit protocol,2PC)能夠保證數據的強一致性,許多分佈式關係型數據管理系統採用此協議來完成分佈式事務。它是協調全部分佈式原子事務參與者,並決定提交或取消(回滾)的分佈式算法。同時也是解決一致性問題的一致性算法。該算法可以解決不少的臨時性系統故障(包括進程、網絡節點、通訊等故障),被普遍地使用。可是,它並不可以經過配置來解決全部的故障,在某些狀況下它還須要人爲的參與才能解決問題。參與者爲了可以從故障中恢復,它們都使用日誌來記錄協議的狀態,雖然使用日誌下降了性能可是節點可以從故障中恢復。

  在兩階段提交協議中,系統通常包含兩類機器(或節點):
  協調者coordinator,一般一個系統中只有一個;
  事務參與者 participants,cohorts或workers,通常包含多個;
  在數據存儲系統中能夠理解爲數據副本的個數,協議中假設:
  每一個節點都會記錄寫前日誌並持久性存儲,即便節點發生故障日誌也不會丟失;
  節點不會發生永久性故障並且任意兩個節點均可以互相通訊;

  當事務的最後一步完成以後,協調器執行協議,參與者根據本地事務,可以成功完成回覆贊成提交事務或者回滾事務。

2、執行過程

  顧名思義,兩階段提交協議由兩個階段組成。在正常的執行下,這兩個階段的執行過程以下所述:

(1)階段1:請求階段(commit-request phase,或稱表決階段,voting phase)

  在請求階段,協調者將通知事務參與者準備提交或取消事務,而後進入表決過程。在表決過程當中,參與者將告知協調者本身的決策:贊成(事務參與者本地做業執行成功)或取消(本地做業執行故障)。

(2)階段2:提交階段(commit phase)

  在該階段,協調者將基於第一個階段的投票結果進行決策:提交或取消。當且僅當全部的參與者贊成提交事務協調者才通知全部的參與者提交事務,不然協調者將通知全部的參與者取消事務。參與者在接收到協調者發來的消息後將執行響應的操做。

  注意  兩階段提交協議與兩階段鎖協議不一樣,兩階段鎖協議爲一致性控制協議。

(3)該協議的執行過程能夠經過下圖來描述:

 

               (a)成功                                   (b)失敗

3、協議的特色

  兩階段提交協議最大的劣勢是其經過阻塞完成的協議,在節點等待消息的時候處於阻塞狀態,節點中其餘進程則須要等待阻塞進程釋放資源才能使用。若是協調器發生了故障,那麼參與者將沒法完成事務則一直等待下去。如下狀況可能會致使節點發生永久阻塞:

  (1)若是參與者發送贊成提交消息給協調者,進程將阻塞直至收到協調器的提交或回滾的消息。若是協調器發生永久故障,參與者將一直等待,這裏能夠採用備份的協調器,全部參與者將回復發給備份協調器,由它承擔協調器的功能。

  (2)若是協調器發送「請求提交」消息給參與者,它將被阻塞直到全部參與者回覆了,若是某個參與者發生永久故障,那麼協調器也不會一直阻塞,由於協調器在某一時間內還未收到某參與者的消息,那麼它將通知其餘參與者回滾事務。

  同時兩階段提交協議沒有容錯機制,一個節點發生故障整個事務都要回滾,代價比較大。

4、工做過程

  下面咱們經過一個例子來講明兩階段提交協議的工做過程:

  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

正回調中,項目宕機。

使用對帳(第三方支付交易接口),去進行查詢。

對帳(第三方交易平臺交易結果)

網頁受權OAuth2.

 

 

聯合登陸步驟:

螞蟻課堂生成受權鏈接,跳轉到騰訊企業

選擇受權QQ用戶,受權成功後,就會跳轉到原地址

 

 

 

受權鏈接:

回調地址 :受權成功後,跳轉到回調地址

跳轉到回調地址:傳一些參數

 

 

跳轉到回調地址:

傳一個受權code有效期 10分鐘  受權code使用完畢以後,直接刪除,不能重複使用

受權碼的做用:使用受權碼換取aess_token,使用aess_token換取openid

 

openid做用: 惟一用戶主鍵(受權系統會員主鍵,不代碼騰訊userid)

 

 

 

openid和咱們用戶表中存放一個openid進行關聯

 

使用openid調用騰訊會員接口查詢QQ信息

本地回調

 

大家登陸流程

 

 

你在開發中遇到了那些難題,是怎麼解決的?

跨域

跨域緣由產生:在當前域名請求網站中,默認不容許經過ajax請求發送其餘域名。

XMLHttpRequest cannot load 跨域問題解決辦法

使用後臺response添加header

後臺response添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持全部網站

使用JSONP

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

MQ冪等性問題

解決辦法:

①     使用日誌+msg-id保存報文信息,做爲去重和冪等的依據。

②     消費端代碼拋出異常,不須要重試補償,使用日誌記錄報文,下次發版本解決。

相關文章
相關標籤/搜索