前言 開心一刻 一個女人自朋友圈寫道:我家老公昨天和別人家的老婆出去旅游,迄今未歸,我則被別人家的老公折騰了一天,好累哦! 圈子下麵,評論無數,老公在下麵評論到:能不能好好說話,我只不過陪女兒去畢業旅游行,而你負責在家留守,照顧三歲兒子,要不要寫的這麼刺激、讓人浮想聯翩的? 你是不是有點虎? 諾維斯 ...
前言
開心一刻
一個女人自朋友圈寫道:我家老公昨天和別人家的老婆出去旅游,迄今未歸,我則被別人家的老公折騰了一天,好累哦!
圈子下麵,評論無數,老公在下麵評論到:能不能好好說話,我只不過陪女兒去畢業旅游行,而你負責在家留守,照顧三歲兒子,要不要寫的這麼刺激、讓人浮想聯翩的? 你是不是有點虎?
諾維斯基:你往哪射了?
周子瑜:我只是個娛樂明星,射箭我不是專業的...
路漫漫其修遠兮,吾將上下而求索!
github:https://github.com/youzhibing
碼雲(gitee):https://gitee.com/youzhibing
前情回顧
通過前面的兩篇博文:Mycat - 實現資料庫的讀寫分離與高可用 和 Mycat - 高可用與負載均衡實現,滿滿的乾貨!,我們完成瞭如下圖所示的組件部署
組件結構圖一
SQL請求發給VIP,keepalived完成VIP的映射,並通過lvs將請求轉發mycat,mycat根據SQL請求類型(DML SQL還是SELECT SQL,亦或是強制指定db節點)將SQL分發到具體的db,完成由具體的資料庫服務完成SQL的執行。
但這還只是停留在資料庫層面的部署,還沒集成我們的應用,沒有實際意義,那麼我們如何集成我們的應用,實現mycat的使命呢?
應用集成
如果mycat搭建好了,進行應用集成非常簡單,下麵我們一步一步來實現各種情況下的應用集成
Mysql的讀寫分離與高可用
資料庫的讀寫分離可以在代碼層面實現(可參考:spring集成mybatis實現mysql讀寫分離),但不推薦,代碼的核心職責應該是業務的實現,如果將大篇的代碼用來實現資料庫的讀寫分離與高可用,那就背離了本意、南轅北轍了。
電腦領域有句名言:“電腦科學領域的任何問題都可以通過增加一個間接的中間層來解決”。既然我們的代碼直接對接資料庫不好實現資料庫的讀寫分離與高可用,那就在中間新增一層中間件來實現,從而產生了資料庫中間件(mycat只是實現之一),應用代碼直接與資料庫中間對接,由資料庫中間件來實現資料庫的讀寫分離與高可用。此時的組件結構圖如下
組件結構圖二
具體的部署過程可參考:Mycat - 實現資料庫的讀寫分離與高可用,此時應用如何集成了?其實非常簡單,只需要將我們的連接池配置中的資料庫地址改成mycat的地址即可(將mycat看成資料庫),具體如下
application.yml
server: port: 8886 spring: #連接池配置 datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.1.212:8066/TESTDB?useSSL=false&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 initial-size: 1 #連接池初始大小 max-active: 20 #連接池中最大的活躍連接數 min-idle: 1 #連接池中最小的活躍連接數 max-wait: 60000 #配置獲取連接等待超時的時間 pool-prepared-statements: true #打開PSCache,並且指定每個連接上PSCache的大小 max-pool-prepared-statement-per-connection-size: 20 validation-query: SELECT 1 FROM DUAL validation-query-timeout: 30000 test-on-borrow: false #是否在獲得連接後檢測其可用性 test-on-return: false #是否在連接放回連接池後檢測其可用性 test-while-idle: true #是否在連接空閑一段時間後檢測其可用性 #mybatis配置 mybatis: type-aliases-package: com.lee.mycat.entity #config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/*.xml # pagehelper配置 pagehelper: helperDialect: mysql #分頁合理化,pageNum<=0則查詢第一頁的記錄;pageNum大於總頁數,則查詢最後一頁的記錄 reasonable: true supportMethodsArguments: true params: count=countSql logging: level: com.lee.mycat.mapper: DEBUGView Code
UserWeb.java
package com.lee.mycat.web; import com.lee.mycat.entity.User; import com.lee.mycat.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/mycat") public class UserWeb { @Autowired private IUserService userService; @RequestMapping("/getUserByNameFromMasterDb") public User getUserByNameFromMasterDb(String name) { return userService.getUserByNameFromMasterDb(name); } @RequestMapping("/getUserByNameFromSlaveDb") public User getUserByNameFromSlaveDb(String name) { return userService.getUserByNameFromSlaveDb(name); } @RequestMapping("/getUserByName") public User getUserByName(String name) { return userService.getUserByName(name); } @RequestMapping("/addUser") public Integer addUser(String name, Integer age) { return userService.insertUser(new User(name, age)); } }View Code
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lee.mycat.mapper.UserMapper"> <sql id="Base_Column_List"> id,name,age </sql> <select id="getUserByNameFromMasterDb" resultType="User" parameterType="String"> /*!mycat:db_type=master*/ SELECT <include refid="Base_Column_List" /> FROM tbl_user WHERE name=#{name} </select> <select id="getUserByNameFromSlaveDb" resultType="User" parameterType="String"> /*!mycat:db_type=slave*/ SELECT <include refid="Base_Column_List" /> FROM tbl_user WHERE name=#{name} </select> <select id="getUserByName" resultType="User" parameterType="String"> SELECT <include refid="Base_Column_List" /> FROM tbl_user WHERE name=#{name} </select> <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO tbl_user(name, age) VALUES (#{name}, #{age}) </insert> </mapper>View Code
UserMapper.xml文件中會與我們平時的寫法有些許不同,有時候需要明確指定強制走master還是slave節點。具體細節可查看:spring-boot-mycat
測試結果
如上圖所示,我們一開始新增了一個用戶:Jiraiye,其年齡是50,我們手動改了mysql slave中Jiraiye的年齡為52是為了更直觀的驗證SQL請求最終走的是mysql master還是mysql slave。從上圖可知,一般的Select SQL走的是從庫(DML SQL走主庫這個就不用說了),如在mapper.xml中強制指定了db節點,那麼就會在指定的mysql節點上來執行SQL。
mysql的高可用就沒進行測試了,應用其實是感知不到的;mysql master宕機了,mycat會按我們配置好的進行mysql db的切換,正常服務於我們的引用。
Mycat的高可用
mysql的讀寫分離與高可用我們是實現了,可mycat卻存在高可用問題,一旦mycat宕機了,整個資料庫層就相當於宕機了。可想而知,我們需要實現mycat的高可用。
mycat的高可用搭建過程可參考:Mycat - 高可用與負載均衡實現,滿滿的乾貨!,此時的組件結構圖如下
組件結構圖三
應用工程改動非常小,只需要將資料庫連接配置的url改成VIP即可,如下
jdbc:mysql://192.168.1.212:8066/TESTDB?useSSL=false&useUnicode=true&characterEncoding=utf-8 改成 jdbc:mysql://192.168.1.200:8066/TESTDB?useSSL=false&useUnicode=true&characterEncoding=utf-8
測試結果
mysql的讀寫分離依然正常工作,當mycat master宕機後,mycat slave接管任務,進行sql的轉發,實現了mycat的高可用;期間出現了非常短時間的異常提示,這是因為資料庫連接池中都是212上的mycat連接,212現在已經宕機了,所以會出現一次異常提示,但連接池立馬做出了反應,重新建立資料庫連接,此時連接池中的連接都是連接的110。
Mycat的負載均衡
上述mycat的高可用中,絕大多數情況下,mycat slave一直處於等待狀態,未提供任何服務,因為我們的mycat master一般而言是不會宕機的。那有沒有什麼做法可以讓slave也處理SQL請求,而又和master互備實現mycat高可用呢?那就是實現Mycat的負載均衡,此時mycat不存在主從關係,而是它倆兩兩互備,此時的組件結構圖就是組件結構圖一。應用工程不用變,資料庫連接還是配置VIP。具體就不演示了,大家自行去實踐即可。
總結
1、資料庫中間件可以降低應用代碼的複雜性,讓其專職與業務代碼的實現,而資料庫層面的工作交給資料庫中間件;mycat只是資料庫中間件的一種實現,卻也是比較優秀的實現,她是開源的。
2、併發量不高的情況下,實現mycat的高可用即可,無需實現Mycat的負載均衡;實現mycat的負載均衡需要更多的硬體成本和維護成本,卻沒有帶來質變的收益,就性價比而言,不升反降。
3、具體需要部署成什麼組件結構,需要看具體的需求,很多情況下根本用不到mycat中間件,如果用到了mycat中間件,個人認為最好還是實現mycat的高可用,至於需不需要實現mycat的負載均衡,就看具體的併發量了,這個也沒個標準,就要結合實際情況來排查是不是mycat的負載過高了,如果確實是mycat負載過高,那麼就有必要實現mycat的負載均衡來降低單個mycat的負載了。沒有絕對的最優部署,只有當下最合適的部署。