在大多數業務場景下,當我們的數據量達到一定數量級TB級或者PB級的時候,對數據的讀寫操作將會變得相當困難,這也將會造成系統性能瓶頸,系統的吞吐量將會明顯降低。想要解決這一瓶頸問題,可能最簡單的有三種解決方案:
增加單個資料庫節點的CUP和內存數量;
採用分布式資料庫,對數據進行分庫分表;
採用簡單的讀寫分離技術,降低單個資料庫節點的負荷;
在上一篇文章ShardingJDBC實現多資料庫節點分庫分表 中,我將大致講解了一下如何實現對資料庫進行分庫分表操作,本篇文章我們將大致講解下如何通過ShardingJDBC對資料庫進行讀寫分離操作。當然,關於讀寫分離的技術在實施的過程中有以下幾種方法:
基於公有雲(阿里雲)的基礎設施服務天然支持資料庫的讀寫分離,只需對服務做簡單的配置即可;
基於原始碼的硬編碼方式,採用雙數據源(datasource);
基於中間件的方式,例如mycat等開源中間件;
基於數據源代理jar包+配置項的方式,例如:shardingsphere等;
下面我們將通過一個例子大概講解下,ShardingJDBC是如何實現資料庫的讀寫分離,文末將添附原始碼地址。
ShardingJDBC只能實現數據的讀寫分離技術,它不能實現資料庫的主從複製功能,所以資料庫的主從複製功能需要採用另外的技術實現,基於公有雲資料庫或者自己託管資料庫對資料庫進行主從複製配置。
一,首先我們採用Docker快速搭建一套資料庫的主從複製環境,也可用參考這篇文章MySQL資料庫讀寫分離技術實踐 實施
Master資料庫配置文件如下:
root@ubuntu18:~# cat master.cnf
[mysqld]
# master server id
server-id = 1
# bin log
log_bin = mysql-master-bin
Slave資料庫配置文件如下:
root@ubuntu18:~# cat slave.cnf
[mysqld]
# slave server id
server-id = 2
# bin log
log_bin = mysql-slave-bin
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1
分別啟動兩個Docker資料庫:
root@ubuntu18:~# docker run --name master -p 3310:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:master
d34d8767560b3f08e3eb4205ac80388456c546dde524c9c2707496f22e9c5523
root@ubuntu18:~# docker run --link master:master --name slave -p 3320:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:slave
664857039ab3a933ff7b12be50be36244ea14712d6dc2b495c815510acef7844
root@ubuntu18:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
664857039ab3 mysql:slave "docker-entrypoint.s…" 5 seconds ago Up 4 seconds 33060/tcp, 0.0.0.0:3320->3306/tcp slave
d34d8767560b mysql:master "docker-entrypoint.s…" 39 seconds ago Up 38 seconds 33060/tcp, 0.0.0.0:3310->3306/tcp master
master資料庫暴露3310埠;
slave資料庫暴露3320埠;
配置Master資料庫:
mysql> create user 'repl'@'%' identified by 'repl-pwd';
Query OK, 0 rows affected (0.00 sec)
mysql> grant replication slave on *.* to 'repl'@'%';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> show master status;
+++----+---+----+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+++----+---+----+
| mysql-master-bin.000003 | 855 | | | |
+++----+---+----+
1 row in set (0.00 sec)
mysql> alter user 'repl'@'%' identified by 'repl-pwd' password expire never;
Query OK, 0 rows affected (0.01 sec)
mysql> alter user 'repl'@'%' identified with mysql_native_password by 'repl-pwd';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
配置Slave資料庫:
mysql> change master to master_host='master', master_user='repl', master_password='repl-pwd', master_log_file='mysql-master-bin.000003', master_log_pos=0;
Query OK, 0 rows affected, 2 warnings (0.01 sec)
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: master
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-master-bin.000003
Read_Master_Log_Pos: 1640
Relay_Log_File: mysql-relay-bin.000002
Relay_Log_Pos: 1869
Relay_Master_Log_File: mysql-master-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 1640
Relay_Log_Space: 2078
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 4143f5a8-0a10-11eb-8197-0242ac110003
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
二,準備一張表結構如下:
create database ds;
use ds;
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE IF NOT EXISTS `t_order`
(
`order_id` INT UNSIGNED,
`order_name` VARCHAR(255) NOT NULL,
`order_date` VARCHAR(255) NOT NULL,
PRIMARY KEY (`order_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
三,新建一個SpringBoot項目:
ShardingJDBC配置項如下:
debug: true
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.0.8:3310/ds
username: root
password: root
slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.0.8:3320/ds
username: root
password: root
masterslave:
load-balance-algorithm-type: round_robin
name: ms
master-data-source-name: master
slave-data-source-names: slave
props:
sql:
show: true
management:
health:
db:
enabled: false
ShardingJDBC支持一主多從的場景,也就是說可以有多個slave資料庫,但是只能存在一個master資料庫;
從庫負載均衡算法類型,可選值:ROUND_ROBIN,RANDOM;
本例中我們採用一主一從的情況;
master資料庫採用3310埠,slave資料庫採用3320埠;
DAO層:
@Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Integer> {
}
實體類:
@Data
@Entity
@Table(name = "t_order")
public class OrderEntity {
@Id
@Column(name = "order_id")
private int id;
@Column(name = "order_name")
private String name;
@Column(name = "order_date")
private String date;
}
Service層:
@Service
public class ShardingReadWriteService {
@Autowired
private OrderRepository orderRepository;
public void save(int i) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
OrderEntity orderEntity = new OrderEntity();
orderEntity.setId(i);
orderEntity.setName(UUID.randomUUID().toString());
orderEntity.setDate(sdf.format(new Date()));
orderRepository.save(orderEntity);
}
public List<OrderEntity> list() {
return orderRepository.findAll();
}
}
通過Junit進行測試:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingReadWriteTest {
@Autowired
private ShardingReadWriteService shardingReadWriteService;
@Test
public void save() {
for (int i = 0; i < 100; i++) {
shardingReadWriteService.save(i);
}
System.out.println("save done!");
}
@Test
public void list() {
List<OrderEntity> list = shardingReadWriteService.list();
System.out.println(JSONArray.fromObject(list));
System.out.println("total count:" + list.size());
}
}
在IDEA中運行插入數據單元測試效果如下:
在IDEA中運行查詢數據單元測試效果如下:
對比兩個測試結果,我們可以清楚的發現:
參考:
https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/configuration/config-spring-boot/#讀寫分離
demo源碼:
https://github.com/bq-xiao/sharding-jdbc-demo.git
不積跬步,無以至千裡;不積小流,無以成江海!