SpringBoot整合RPC框架---Thrift

轉載自:https://blog.csdn.net/lupengfei1009/article/details/100934794

 

什麼是Thrift


Thrift是一種接口描述語言和二進制通訊協議,它被用來定義和創建跨語言的服務。它被當作一個遠程過程調用(RPC)框架來使用,是由Facebook爲“大規模跨語言服務開發”而開發的。它通過一個代碼生成引擎聯合了一個軟件棧,來創建不同程度的、無縫的跨平臺高效服務,可以使用C#、C++(基於POSIX兼容系統)、Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。雖然它以前是由Facebook開發的,但它現在是Apache軟件基金會的開源項目了。

架構


Thrift包含一套完整的棧來創建客戶端和服務端程序。頂層部分是由Thrift定義生成的代碼。而服務則由這個文件客戶端和處理器代碼生成。在生成的代碼裏會創建不同於內建類型的數據結構,並將其作爲結果發送。協議和傳輸層是運行時庫的一部分。有了Thrift,就可以定義一個服務或改變通訊和傳輸協議,而無需重新編譯代碼。除了客戶端部分之外,Thrift還包括服務器基礎設施來集成協議和傳輸,如阻塞、非阻塞及多線程服務器。棧中作爲I/O基礎的部分對於不同的語言則有不同的實現,如下官方示意圖:


支持的通訊協議


TBinaryProtocol
一種簡單的二進制格式,簡單,但沒有爲空間效率而優化。比文本協議處理起來更快,但更難於調試。
TCompactProtocol
更緊湊的二進制格式,處理起來通常同樣高效。
TDebugProtocol
一種人類可讀的文本格式,用來協助調試。
TDenseProtocol
與TCompactProtocol類似,將傳輸數據的元信息剝離。
TJSONProtocol
使用JSON對數據編碼。
TSimpleJSONProtocol
一種只寫協議,它不能被Thrift解析,因爲它使用JSON時丟棄了元數據。適合用腳本語言來解析
支持的傳輸協議
TFileTransport
該傳輸協議會寫文件。
TFramedTransport
當使用一個非阻塞服務器時,要求使用這個傳輸協議。它按幀來發送數據,其中每一幀的開頭是長度信息。
TMemoryTransport
使用存儲器映射輸入輸出。(Java的實現使用了一個簡單的ByteArrayOutputStream。)
TSocket
使用阻塞的套接字I/O來傳輸。
TZlibTransport
用zlib執行壓縮。用於連接另一個傳輸協議。
支持的服務模型
TNonblockingServer
一個多線程服務器,它使用非阻塞I/O(Java的實現使用了NIO通道)。TFramedTransport必須跟這個服務器配套使用。
TSimpleServer
一個單線程服務器,它使用標準的阻塞I/O。測試時很有用。
TThreadPoolServer
一個多線程服務器,它使用標準的阻塞I/O
THsHaServer
YHsHa引入了線程池去處理(需要使用TFramedTransport數據傳輸方式),其模型把讀寫任務放到線程池去處理;Half-sync/Half-async(半同步半異步)的處理模式;Half-sync是在處理IO時間上(sccept/read/writr io),Half-async用於handler對RPC的同步處理
Thrift的優點
跟一些替代選擇,比如SOAP相比,跨語言序列化的代價更低,因爲它使用二進制格式。
它有一個又瘦又幹淨的庫,沒有編碼框架,沒有XML配置文件。
綁定感覺很自然。例如,Java使用java.util.ArrayList;C++使用std::vectorstd::string。
應用層通訊格式與序列化層通訊格式是完全分離的。它們都可以獨立修改。
預定義的序列化格式包括:二進制格式、對HTTP友好的格式,以及緊湊的二進制格式。
兼作跨語言文件序列化。
協議使用軟版本號機制軟件版本管理[需要解釋]。Thrift不要求一箇中心化的和顯式的版本號機制,例如主版本號/次版本號。松耦合的團隊可以輕鬆地控制RPC調用的演進。
沒有構建依賴也不含非標準化的軟件。不存在不兼容的軟件許可證混用的情況
SpringBoot整合Thrift
爲什麼會出現RPC框架
高級語言越來越豐富,同時學習的成本越來越低,從而出現編程語言的百花齊放,同時,每種語言都有其在特定場景下的優勢性,因此一個企業的系統架構可能存在這各種各樣的編程語言發揮着光和熱。因此跨語言間的通訊必須存在一個橋樑;
微服務的盛行,使得服務逐漸模塊化,功能化;單個服務僅僅實現某個特定的功能或者模塊,因此使得服務間的調用變得常見且頻繁;
網絡的發展速度快於了硬件的發展速度,使得服務承載負載壓力越來越大;因此高性能且快速響應的服務調用成了必須去面對的問題
傳統的Http請求能面對跨語言的問題,但是性能遠遠無法達到高併發的要求
多個服務會使用到相同的數據,比如用戶;因此就需要將起模塊化,統一對外提供服務
常見的RPC框架集成套路
編寫接口代碼生成文件(用於生成客戶端服務端代碼)
使用開源框架提供的工具,生成各種語言下的客戶端、服務器源代碼
基於服務器端源代碼啓動並配置業務代碼
客戶端基於生成的客戶端代碼,向遠端服務器獲取對應的數據;整個過程相當於於調用一個本地方法
開擼
示例源碼下載

官網下載代碼生成工具
官網地址


本示例模擬的業務場景


thrift client : 用於對外提供接口,getStudentByName(根據名字獲取學生信息)、save(保存學生信息)
thrift server: 真正getStudentByName和save操作的服務提供者
APP : 服務使用者
最終實現的基礎效果:


編寫接口代碼生成文件
thrift支持的數據類型
當一個RPC框架支持的語言越多,那麼他支持的數據類型就會越少,爲什麼?因爲支持數據必須是所有支持都有的,因此語言越多,交集就越少,以下是thrift支持的數據類型:

bool: A boolean value (true or false)
byte: An 8-bit signed integer
i16: A 16-bit signed integer
i32: A 32-bit signed integer
i64: A 64-bit signed integer
double: A 64-bit floating point number
string: A text string encoded using UTF-8 encoding
編寫腳本
創建student.thrift
注:如果在Idea裏面創建一個thrift文件的話,工具會提醒你安裝插件,安裝之後,編寫腳本會有響應的提醒;可惜,我使用的2018.03的idea貌似有bug,插件無法正常適應;試了其他幾個版本均可以正常使用;

//這裏指明瞭代碼生成之後,所處的文件路徑
namespace java com.lupf.thriftserver.shriftcode
namespace py py.com.lupf.thriftserver.shriftcode

//將shrift的數據類型格式轉換爲java習慣的格式
typedef i16 short
typedef i32 int
typedef i64 long
typedef string String
typedef bool boolean

//定義學生對象
struct Student {
    1:optional String name,
    2:optional int age,
    3:optional String address
}

//定義數據異常
exception DataException {
    //optional 可選 非必傳
    1:optional int code,
    2:optional String message,
    3:optional String dateTime
}

//定義操作學生的服務
service StudentService {
    //根據名稱獲取學生信息 返回一個學生對象  拋出DataException異常
    //required 必傳項
    Student getStudentByName(1:required String name) throws (1:DataException dataException),

    //保存一個學生信息 無返回 拋出DataException異常
    void save(1:required Student student) throws (1:DataException dataException)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
生成源代碼
語法: thrift --gen <language> <Thrift filename>

注:不要以任何的理由去修改這部分自動生成的代碼,一方面很複雜,另一方面,再次生成的時候,你所調整的修改將會丟失

文件說明
DataException.java : 異常對象
Student.java : 交互的數據模型對象
StudentService.java : 服務對象,提供了接口,客戶端連接相關方法
thrift-server服務
通過Idea創建一個基礎的Maven項目
pom文件中添加thrift資源庫
<!--thrift 相關資源-->
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.12.0</version>
</dependency>
1
2
3
4
5
6
將上面生成的代碼拷貝至項目中

實現業務邏輯
創建一個Server服務,實現StudentService.Iface接口,同時這個接口添加@Service註解,將其交由Spring管理
/**
 * 服務端具體的操作的實現
 */
@Service
public class MyServerServiceImpl implements StudentService.Iface {
    @Override
    public Student getStudentByName(String name) throws DataException, TException {
        System.out.println("服務端收到客戶端獲取用戶名:" + name + "信息");
        Student student = new Student();
        student.setName(name);
        student.setAge(100);
        student.setAddress("深圳");

        //模擬耗時
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("模擬獲取成功並返回:" + student);
        return student;
    }

    @Override
    public void save(Student student) throws DataException, TException {
        System.out.println("服務端收到客戶端請求保存學生信息:" + student);
        //模擬耗時
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("模擬保存成功!!!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
配置文件添加端口及線程池基礎配置
server:
  thrift:
    port: 8899 #監聽的端口
    min-thread-pool: 100  #線程池最小線程數
    max-thread-pool: 1000 #線程池最大線程數
1
2
3
4
5
創建服務對象
這裏使用的是半同步半異步的THsHaServer,代碼本身不多,大部分都是註釋;同時將代碼交由Spring管理
@Component
public class ThriftServer2 {
    //監聽的端口
    @Value("${server.thrift.port}")
    private Integer port;

    //線程池最小線程數
    @Value("${server.thrift.min-thread-pool}")
    private Integer minThreadPool;

    //線程池最大線程數
    @Value("${server.thrift.max-thread-pool}")
    private Integer maxThreadPool;

    //業務服務對象
    @Autowired
    MyServerServiceImpl myServerService;

    public void start() {
        try {
            //thrift支持的scoker有很多種
            //非阻塞的socker
            TNonblockingServerSocket socket = new TNonblockingServerSocket(port);
            //THsHaServer 一個高可用的server
            //minWorkerThreads 最小的工作線程2
            //maxWorkerThreads 最大的工作線程4
            //如果這裏Args不使用executorService指定線程池的話,創建THsHaServer會創建一個默認的LinkedBlockingQueue
            THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(minThreadPool).maxWorkerThreads(maxThreadPool);
            //可以自定義指定線程池
            //ExecutorService pool = Executors.newFixedThreadPool(minThreadPool);
            //arg.executorService(pool);

            //Processor處理區  用於處理業務邏輯
            //泛型就是實現的業務
            StudentService.Processor<MyServerServiceImpl> processor = new StudentService.Processor<>(myServerService);

            //---------------thrift傳輸協議------------------------------
            //1. TBinaryProtocol      二進制傳輸協議
            //2. TCompactProtocol     壓縮協議 他是基於TBinaryProtocol二進制協議在進一步的壓縮,使得體積更小
            //3. TJSONProtocol        Json格式傳輸協議
            //4. TSimpleJSONProtocol  簡單JSON只寫協議,生成的文件很容易通過腳本語言解析,實際開發中很少使用
            //5. TDebugProtocol       簡單易懂的可讀協議,調試的時候用於方便追蹤傳輸過程中的數據
            //-----------------------------------------------------------
            //設置工廠
            //協議工廠 TCompactProtocol壓縮工廠  二進制壓縮協議
            arg.protocolFactory(new TCompactProtocol.Factory());
            //---------------thrift傳輸格式------------------------------


            //---------------thrift數據傳輸方式------------------------------
            //1. TSocker            阻塞式Scoker 相當於Java中的ServerSocket
            //2. TFrameTransport    以frame爲單位進行數據傳輸,非阻塞式服務中使用
            //3. TFileTransport     以文件的形式進行傳輸
            //4. TMemoryTransport   將內存用於IO,Java實現的時候內部實際上是使用了簡單的ByteArrayOutputStream
            //5. TZlibTransport     使用zlib進行壓縮,與其他傳世方式聯合使用;java當前無實現所以無法使用
            //傳輸工廠 更加底層的概念
            arg.transportFactory(new TFramedTransport.Factory());
            //arg.transportFactory(new TTransportFactory());
            //---------------thrift數據傳輸方式------------------------------

            //設置處理器(Processor)工廠
            arg.processorFactory(new TProcessorFactory(processor));

            //---------------thrift支持的服務模型------------------------------
            //1.TSimpleServer  簡單的單線程服務模型,用於測試
            //2.TThreadPoolServer 多線程服務模型,使用的標準的阻塞式IO;運用了線程池,當線程池不夠時會創建新的線程,當線程池出現大量空閒線程,線程池會對線程進行回收
            //3.TNonBlockingServer 多線程服務模型,使用非阻塞式IO(需要使用TFramedTransport數據傳輸方式)
            //4.THsHaServer YHsHa引入了線程池去處理(需要使用TFramedTransport數據傳輸方式),其模型把讀寫任務放到線程池去處理;Half-sync/Half-async(半同步半異步)的處理模式;Half-sync是在處理IO時間上(sccept/read/writr io),Half-async用於handler對RPC的同步處理
            //----------------------------
            //根據參數實例化server
            //半同步半異步的server
            TServer server = new THsHaServer(arg);
            //---------------thrift支持的服務模型------------------------------

            System.out.println("shrift server started; port:" + port);
            //啓動server
            // 異步非阻塞的死循環
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
啓動類中添加Server的啓動配置
@SpringBootApplication
public class ThriftServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThriftServerApplication.class, args);
    }

    /**
     * 向Spring註冊一個Bean對象
     * initMethod = "start"  表示會執行名爲start的方法
     * start方法執行之後,就會阻塞接受客戶端的請求
     *
     * @return
     */
    @Bean(initMethod = "start")
    public ThriftServer2 init() {
        ThriftServer2 thriftServer = new ThriftServer2();
        return thriftServer;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
啓動項目
到此,服務端的項目就已經配置完成了,啓動服務出現下圖剪頭所指向的日誌即可;

thrift-client客戶端服務
項目創建
由於這個項目是一箇中間服務,是thrift-server的客戶端,是APP的服務端;因爲要向APP提供接口,因此這裏就創建一個基礎的Web服務
pom文件中添加thrift資源庫
<!--thrift 相關資源-->
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.12.0</version>
</dependency>
1
2
3
4
5
6
將上面生成的代碼拷貝至項目中

配置服務端信息
server:
  port: 8877 #當前服務監聽的端口,給APP端提供服務的接口
  thrift:
    host: 'localhost' #遠端thrift服務IP
    port: 8899 #遠端thrift服務端口
1
2
3
4
5
創建一個遠端連接對象
ThriftClient.java
public class ThriftClient {
    @Setter
    private String host;

    @Setter
    private Integer port;

    private TTransport tTransport;

    private TProtocol tProtocol;

    private StudentService.Client client;

    private void init() {
        tTransport = new TFramedTransport(new TSocket(host, port), 600);
        //協議對象 這裏使用協議對象需要和服務器的一致
        tProtocol = new TCompactProtocol(tTransport);
        client = new StudentService.Client(tProtocol);
    }

    public StudentService.Client getService() {
        return client;
    }

    public void open() throws TTransportException {
        if (null != tTransport && !tTransport.isOpen())
            tTransport.open();
    }

    public void close() {
        if (null != tTransport && tTransport.isOpen())
            tTransport.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
創建一個連接自動注入幫助類
該類用於將ThriftClient注入到Spring容器,交由Spring管理,同時每一個request請求都創建一個新的ThriftClient連接;
@Configuration
public class ThriftClientConfig {
    //服務端的地址
    @Value("${server.thrift.host}")
    private String host;

    //服務端的端口
    @Value("${server.thrift.port}")
    private Integer port;

    //初始化Bean的時候調用對象裏面的init方法
    @Bean(initMethod = "init")
    //每次請求實例化一個新的ThriftClient連接對象
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public ThriftClient init() {
        ThriftClient thriftClient = new ThriftClient();
        thriftClient.setHost(host);
        thriftClient.setPort(port);
        return thriftClient;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
定義調用遠端服務的service接口
public interface StudentServiceInf {
    //根據名稱獲取學生信息
    Student getStudentByName(String name);

    //保存學生信息
    void save(Student student);
}
1
2
3
4
5
6
7
添加Service的具體實現
@Service
public class StudentServiceImpl implements StudentServiceInf {
    @Autowired
    ThriftClient thriftClient;

    @Override
    public Student getStudentByName(String name) {
        try {
            thriftClient.open();
            System.out.println("客戶端請求用戶名爲:" + name + "的數據");
            Student student = thriftClient.getService().getStudentByName(name);
            System.out.println("獲取成功!!!服務端返回的對象:" + student);
            return student;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            thriftClient.close();
        }
        return null;
    }

    @Override
    public void save(Student student) {
        try {
            thriftClient.open();
            System.out.println("客戶端請求保存對象:" + student);
            thriftClient.getService().save(student);
            System.out.println("保存成功!!!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            thriftClient.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
創建前端Controller對象
用於對移動端提供接口
@RestController
@RequestMapping("thrift")
public class StudentController {
    @Autowired
    StudentServiceInf studentService;

    @GetMapping("get")
    public Student getStudeByName(String name) {
        return studentService.getStudentByName(name);
    }

    @GetMapping("save")
    public Student save() {
        //直接模擬前端傳遞的數據
        Student student = new Student();
        student.setName("AAA");
        student.setAge(10);
        student.setAddress("BBB");
        //調用保存服務
        studentService.save(student);
        return student;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
啓動服務

測試


Thrift客戶端連接池
上面的示例中,每次前端有請求上來的時候,thrift-client都是與thrift-server創建了一個新的連接,用完就直接關閉掉;Thrift框架本身是沒有提供客戶端的連接池的,但是,實際使用中,如果每次建立一個socket連接用完就釋放掉,再次使用再次建立連接的話,其實很浪費性能;因此,連接池就是提升性能的一種很好的手段;一個Service建立一個連接之後,放在一個池子裏面,需要使用的時候就直接拿出來用,用完再還回去;如果一定時間內一直沒有人使用,就把資源釋放掉;以上是連接池的一個基本思路;Apache Commons Pool爲我們提供了一個基礎的框架,使得我們能夠很輕鬆、快速的就實現一個連接池的功能;下面我們就來通過Apache Commons Pool實現一個thrift客戶端的連接池。

引入資源
<dependency>
       <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
 </dependency>
1
2
3
4
5
連接池編碼
創建一個客戶端的連接對象
TTSocket.java
public class TTSocket {
    //thrift socket對象
    private TSocket tSocket;

    // 傳輸對象
    private TTransport tTransport;

    // 協議對象
    private TProtocol tProtocol;

    // 服務客戶端對象
    private StudentService.Client client;

    /**
     * 構造方法初始化各個連接對象
     *
     * @param host server的地址
     * @param port server的端口
     */
    public TTSocket(String host, Integer port) {
        tSocket = new TSocket(host, port);
        tTransport = new TFramedTransport(tSocket, 600);
        //協議對象 這裏使用協議對象需要和服務器的一致
        tProtocol = new TCompactProtocol(tTransport);
        client = new StudentService.Client(tProtocol);
    }

    /**
     * 獲取服務客戶端對象
     *
     * @return
     */
    public StudentService.Client getService() {
        return client;
    }

    /**
     * 打開通道
     *
     * @throws TTransportException
     */
    public void open() throws TTransportException {
        if (null != tTransport && !tTransport.isOpen())
            tTransport.open();
    }

    /**
     * 關閉通道
     */
    public void close() {
        if (null != tTransport && tTransport.isOpen())
            tTransport.close();
    }

    /**
     * 判斷通道是否是正常打開狀態
     *
     * @return
     */
    public boolean isOpen() {
        Socket socket = tSocket.getSocket();
        if (socket.isConnected() && !socket.isClosed())
            return true;
        return false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
創建一個工廠對象
public class ThriftClientConnectPoolFactory {

    /**
     * 對象池
     */
    public GenericObjectPool pool;

    /**
     * 實例化池工廠幫助類
     *
     * @param config
     * @param ip
     * @param port
     */
    public ThriftClientConnectPoolFactory(GenericObjectPool.Config config, String ip, int port) {
        ConnectionFactory factory = new ConnectionFactory(ip, port);
        //實例化池對象
        this.pool = new GenericObjectPool(factory, config);
        //設置獲取對象前校驗對象是否可以
        this.pool.setTestOnBorrow(true);
    }

    /**
     * 在池中獲取一個空閒的對象
     * 如果沒有空閒且池子沒滿,就會調用makeObject創建一個新的對象
     * 如果滿了,就會阻塞等待,直到有空閒對象或者超時
     *
     * @return
     * @throws Exception
     */
    public TTSocket getConnect() throws Exception {
        return (TTSocket) pool.borrowObject();
    }

    /**
     * 將對象從池中移除
     *
     * @param ttSocket
     */
    public void invalidateObject(TTSocket ttSocket) {
        try {
            pool.invalidateObject(ttSocket);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 將一個用完的對象返還給對象池
     *
     * @param ttSocket
     */
    public void returnConnection(TTSocket ttSocket) {
        try {
            pool.returnObject(ttSocket);
        } catch (Exception e) {
            if (ttSocket != null) {
                try {
                    ttSocket.close();
                } catch (Exception ex) {
                    //
                }
            }
        }
    }

    /**
     * 池裏面保存的對象工廠
     */
    class ConnectionFactory extends BasePoolableObjectFactory {
        //遠端地址
        private String host;

        //端口
        private Integer port;

        /**
         * 構造方法初始化地址及端口
         *
         * @param ip
         * @param port
         */
        public ConnectionFactory(String ip, int port) {
            this.host = ip;
            this.port = port;
        }

        /**
         * 創建一個對象
         *
         * @return
         * @throws Exception
         */
        @Override
        public Object makeObject() throws Exception {
            // 實例化一個自定義的一個thrift 對象
            TTSocket ttSocket = new TTSocket(host, port);
            // 打開通道
            ttSocket.open();
            return ttSocket;
        }

        /**
         * 銷燬對象
         *
         * @param obj
         */
        public void destroyObject(Object obj) {
            try {
                if (obj instanceof TTSocket) {
                    //嘗試關閉連接
                    ((TTSocket) obj).close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         * 校驗對象是否可用
         * 通過 pool.setTestOnBorrow(boolean testOnBorrow) 設置
         * 設置爲true這會在調用pool.borrowObject()獲取對象之前調用這個方法用於校驗對象是否可用
         *
         * @param obj 待校驗的對象
         * @return
         */
        public boolean validateObject(Object obj) {
            if (obj instanceof TTSocket) {
                TTSocket socket = ((TTSocket) obj);
                if (!socket.isOpen()) {
                    return false;
                }
                return true;
            }
            return false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
修改ThriftClientConfig,將池工廠對象通過Spring管理
@Configuration
public class ThriftClientConfig {
    //服務端的地址
    @Value("${server.thrift.host}")
    private String host;

    //服務端的端口
    @Value("${server.thrift.port}")
    private Integer port;

    //初始化Bean的時候調用對象裏面的init方法
    @Bean(initMethod = "init")
    //每次請求實例化一個新的ThriftClient連接對象
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public ThriftClient init() {
        ThriftClient thriftClient = new ThriftClient();
        thriftClient.setHost(host);
        thriftClient.setPort(port);
        return thriftClient;
    }

    @Bean
    public ThriftClientConnectPoolFactory ThriftClientPool() {
        //對象池的配置對象
        //這裏測試就直接使用默認的配置
        //可以通過config 設置對應的參數
        //參數說明見  http://commons.apache.org/proper/commons-pool/api-1.6/org/apache/commons/pool/impl/GenericObjectPool.html
        GenericObjectPool.Config config = new GenericObjectPool.Config();
        //創建一個池工廠對象
        ThriftClientConnectPoolFactory thriftClientConnectPoolFactory = new ThriftClientConnectPoolFactory(config, host, port);
        //交由Spring管理
        return thriftClientConnectPoolFactory;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
使用示例
@Service
public class StudentServiceImpl implements StudentServiceInf {
    //每次創建一個新的連接的工具類
    //@Autowired
    //ThriftClient thriftClient;

    /**
     * 連接池
     */
    @Autowired
    ThriftClientConnectPoolFactory thriftClientPool;

    @Override
    public Student getStudentByName(String name) {

        TTSocket ttSocket = null;
        try {
            //通過對象池,獲取一個服務客戶端連接對象
            ttSocket = thriftClientPool.getConnect();
            System.out.println(ttSocket);
            System.out.println("客戶端請求用戶名爲:" + name + "的數據");
            //調用遠端對象
            Student student = ttSocket.getService().getStudentByName(name);
            System.out.println("獲取成功!!!服務端返回的對象:" + student);
            //用完之後把對象還給對象池
            thriftClientPool.returnConnection(ttSocket);
            return student;
        } catch (Exception e) {
            e.printStackTrace();
            //出現異常則將當前對象從池子移除
            thriftClientPool.invalidateObject(ttSocket);
        } finally {
        }
        return null;
    }

    @Override
    public void save(Student student) {
        TTSocket ttSocket = null;
        try {
            ttSocket = thriftClientPool.getConnect();
            System.out.println("客戶端請求保存對象:" + student);
            ttSocket.getService().save(student);
            System.out.println("保存成功!!!");
            thriftClientPool.returnConnection(ttSocket);
        } catch (Exception e) {
            e.printStackTrace();
            thriftClientPool.returnConnection(ttSocket);
        } finally {
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
測試

測試兩個併發的情況下,如圖所示的兩個對象具體複用了

END ! ! !
————————————————
版權聲明:本文爲CSDN博主「lupengfei1009」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lupengfei1009/article/details/100934794

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章