Redis整合MySQL主從集群的示例代碼
Redis作為承擔(dān)緩存作用的數(shù)據(jù)庫,一般會應(yīng)用在高并發(fā)的場景里,而在這些高并發(fā)應(yīng)用場景的數(shù)據(jù)庫層面還會用到其他數(shù)據(jù)庫的組件或集群以提升性能,比如用MySQL主從集群實(shí)現(xiàn)讀寫分離效果、用MyCAT組件實(shí)現(xiàn)分庫分表的功能。另外,Redis本身會以集群的形式對外提供緩存服務(wù)。
1、用Docker搭建MySQL主從集群
這里用Docker容器搭建如下圖所示的MySQL主從集群。
- 在主MySQL服務(wù)器里操作的動(dòng)作會自動(dòng)同步到從nysql服務(wù)器,比如在主服務(wù)器里發(fā)起的“建數(shù)據(jù)庫”“通過insert語句插入數(shù)據(jù)”和“通過delete語句刪除數(shù)據(jù)”的動(dòng)作都會同步到從服務(wù)器,并且這些操作都會在從服務(wù)器上被執(zhí)行。通過這種同步的動(dòng)作能確保主從數(shù)據(jù)庫間的數(shù)據(jù)一致性。
- 在項(xiàng)目里,一般是向“主服務(wù)器”里寫數(shù)據(jù),從“從服務(wù)器”里讀數(shù)據(jù),用這種“讀寫分離”的操作方式提升數(shù)據(jù)庫性能。
具體搭建的步驟如下:
1.1 拉取mysql鏡像
開啟一個(gè)命令窗口,在其中運(yùn)行docker pull mysql:latest
,下載最新的mysql鏡像。下載完成后,通過docker images mysql
能看到如下圖所示的鏡像i信息。
1.2 創(chuàng)建配置文件夾
新建/root/redisconf/masterMySQL/conf
和/root/redisconf/masterMySQL/data
兩個(gè)目錄,在其中將會保存主mysql服務(wù)器的配置信息和數(shù)據(jù)。同時(shí)新建/root/redisconf/slaveMySQL/conf
和/root/redisconf/slaveMySQL/data
兩個(gè)目錄,在其中將會保存從MySQL服務(wù)器的配置信息和數(shù)據(jù)。當(dāng)然,目錄可自行更改。
1.3 編寫主服務(wù)器的配置文件信息
在/root/redisconf/masterMySQL/conf目錄里新建一個(gè)my.cnf文件,在其中編寫針對主mysql服務(wù)器的配置信息,主mysql服務(wù)器在啟動(dòng)時(shí)會讀取其中的配置,具體代碼如下所示:
[mysqld] pid-file =/var/run/mysqld/mysqld.pid socket =/var/run/mysqld/mysqld.sock datadir =/var/lib/mysql server-id =1 log-bin=mysql-master-bin
第二行到第四行給出了MYSQL運(yùn)行時(shí)的參數(shù),在第五行里定義了該服務(wù)器的id(這個(gè)id需要和之后編寫的從MySQL服務(wù)器的server-id不一樣,否則會出錯(cuò)),在第6行里制定了二進(jìn)制文件的名字(為了搭建主從集群,建議加上這行配置)
1.4 啟動(dòng)mysql主服務(wù)器的容器
docker run -itd --privileged=true -p 3306:3306 \ --name myMasterMysql -e MYSQL_ROOT_PASSWORD=123456\ -v /root/redisconf/masterMySQL/conf:/etc/mysql/conf.d\ -v /root/redisconf/masterMySQL/data:/var/lib/mysql mysql:latest
-p3306:3306參數(shù)指定Docker容器里MySQL的工作端口3306映射到主機(jī)的3306端口
-itd參數(shù)指定該容器以后臺交互模式的方式啟動(dòng)
--name
參數(shù)指定該容器的名字
通過-e MYSQL_ROOT_PASSWORD=123456參數(shù)指定該容器運(yùn)行時(shí)的環(huán)境變量,具體到這個(gè)場景,配置以用戶名root登錄到MySQL服務(wù)器時(shí)所用到的密碼123456.
兩個(gè)-v參數(shù)指定外部主機(jī)和Docker容器間映射的目錄。由于在第三步把MySQL啟動(dòng)時(shí)需要加載的my.cnf文件放在了/root/redisconf/masterMySQL/conf目錄里,因此這里需要把/root/redisconf/masterMySQL/conf目錄映射成容器內(nèi)部MYSQL服務(wù)器的相關(guān)路徑。
通過mysql:latest參數(shù)指定該容器是基于這個(gè)鏡像生成的。
查看啟動(dòng)的容器:docker ps
由此能確認(rèn)myMasterMysql啟動(dòng)成功。
查看該Docker容器的IP地址:docker inspect myMasterMysql
可以看到,這里是172.17.0.2,這也是主mysql服務(wù)器所在的Ip地址。
1.5 觀察主服務(wù)器狀態(tài)
運(yùn)行docker exec -it myMasterMysql /bin/bash
命令后進(jìn)入該myMasterMysql容器的命令行窗口,再運(yùn)行mysql -u root -p
命令,進(jìn)入MYSQL服務(wù)器的命令行窗口,在這個(gè)Mysql命令里,以-u參數(shù)指定用戶名,隨后需要輸入密碼(剛才設(shè)置的123456).
進(jìn)入mysql服務(wù)器之后,再運(yùn)行show master status
命令觀察主服務(wù)器的狀態(tài)。
可以看到,主從集群同步所用到的日志文件是mysql-master-bin.000003
,當(dāng)前同步的位置是156,每次運(yùn)行這個(gè)命令看到的結(jié)果未必相同,請記住這兩個(gè)值,在設(shè)置從mysql服務(wù)器的主從同步關(guān)系時(shí)會用到。
1.6 配置mysql從服務(wù)器
在/root/redisconf/slaveMaster/conf目錄里,新建一個(gè)名為my.cnf的文件,編寫針對從MYSQL服務(wù)器的配置信息。同樣的,從Mysql服務(wù)器在啟動(dòng)時(shí)也會讀取其中的配置,具體代碼如下所示。
[mysqld] pid-file=/var/run/mysqld/mysqld.pid socket=/var/run/mysqld/mysqld.sock datadir=/var/lib/mysql server-id=2 log-bin=mysql-slave-bin
該配置文件和第三步創(chuàng)建的主服務(wù)器的配置文件很相似,只不過在第5行更改了server-id(這里的取值不能和主mysql服務(wù)器的一致)。在第6行也是設(shè)置二進(jìn)制文件的名字
1.7 啟動(dòng)mysql從服務(wù)器
docker run -itd --privileged=true -p 3316:3306\ --name mySlaveMysql -e MYSQL_ROOT_PASSWORD=123456\ -v /root/redisconf/slaveMySQL/conf:/etc/mysql/conf.d\ -v /root/redisconf/slaveMySQL/data:/var/lib/mysql\ mysql:latest
這里在-p參數(shù)之后使用主機(jī)的3316端口映射Docker容器的3306端口,因?yàn)橹爸鱩ysql服務(wù)器的Docker容器已經(jīng)映射到了3306端口,其他的參數(shù)和之前創(chuàng)建myMasterMysql容器時(shí)很相似,就不再重復(fù)了。
隨后docker exec -it mySlaveMysql /bin/bash
進(jìn)入容器,進(jìn)入后可以運(yùn)行mysql -h 172.17.0.2 -u root -p
命令,嘗試在這個(gè)容器里連接主MySQL服務(wù)器。其中172.17.0.2是主服務(wù)器的地址。隨后輸入root用戶的密碼123456,即可確認(rèn)連接。
確認(rèn)鏈接后,通過exit
命令退出指向myMasterMysql的連接,再通過mysql -h 127.0.0.1 -u root -p
命令連接到本Docker容器包含的從MySQL服務(wù)器上。
1.8 確認(rèn)主從關(guān)系
change master to master_host='172.17.0.2',master_port=3306,\ master_user='root',master_password='123456',\ master_log_pos=156,\ master_log_file='mysql-master-bin.000003';
本命令運(yùn)行在mySlaveMysql容器中的從Mysql服務(wù)器里,通過master_host和master_port指定主服務(wù)器的ip地址和端口號,通過master_user和master_password設(shè)置了連接所用的用戶名和密碼。
注意master_logpos和master_log_file兩個(gè)參數(shù)的值需要和第5步圖中的結(jié)果一致。
運(yùn)行完成后,需要再運(yùn)行start slave
命令啟動(dòng)主從復(fù)制的動(dòng)作。運(yùn)行后可以通過show slave status\G;
命令查看主從復(fù)制的狀態(tài),如果Slave_IO_Running和Slave_SQL_Running這兩項(xiàng)都是Yes,并且沒有其他異常,就說明配置主從復(fù)制成功。
此時(shí)如果再到主mysql服務(wù)器里運(yùn)行create database redisDemo
創(chuàng)建一個(gè)數(shù)據(jù)庫,那么從庫里雖然沒有運(yùn)行命令,但是也能看到redisDemo數(shù)據(jù)庫,這說明已經(jīng)成功地搭建了MySQL主從復(fù)制集群。其中,主庫地IP地址和端口號是172.17.0.2:3306,從庫是172.17.0.3:3306.
主庫
從庫
2、準(zhǔn)備數(shù)據(jù)
由于已經(jīng)成功地設(shè)置了主從復(fù)制模式,因此如下地建表和插入語句都只需要在主庫里運(yùn)行。
2.1 創(chuàng)建數(shù)據(jù)庫
create database redisDemo
進(jìn)入redisDemo數(shù)據(jù)庫use redisDemo
2.2 創(chuàng)建student數(shù)據(jù)表
create table student( id int not null primary key, name char(20), age int, score float );
2.3 向student表插入幾條數(shù)據(jù)
insert into student(id,name,age,score) values(1,'Peter',18,100); insert into student(id,name,age,score) values(2,'Tom',17,98); insert into student(id,name,age,score) values(3,'John',17,99);
從庫里查看
3、用Java代碼讀寫MySQL集群和Redis
3.1 引入redis和mysql依賴
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency>
Java應(yīng)用程序是向主mySQL服務(wù)器寫數(shù)據(jù),這樣寫入地?cái)?shù)據(jù)會自動(dòng)同步到從mysql服務(wù)器上,而讀數(shù)據(jù)時(shí)會先從Redis緩存里讀,讀不到時(shí)再到從mysql里讀。以下用代碼實(shí)現(xiàn)
3.2 代碼整合
MySQLClusterDemo.java
import redis.clients.jedis.Jedis; import java.sql.*; public class MySQLClusterDemo { //創(chuàng)建操作Redis和數(shù)據(jù)庫的對象 private Jedis jedis; private Connection masterConn; //連接主庫的對象 private Connection slaveConn; //連接從庫的對象 PreparedStatement masterPs=null; //對主庫進(jìn)行操作的對象 PreparedStatement slavePs=null; //對從庫進(jìn)行操作的對象 //初始化環(huán)境 private void init(){ //MYSQL的連接參數(shù) String mySQLDriver="com.mysql.cj.jdbc.Driver"; String masterUrl="jdbc:mysql://192.168.159.33:3306/redisDemo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; String slaveUrl="jdbc:mysql://192.168.159.33:3316/redisDemo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; String user="root"; String pwd="123456"; try{ Class.forName(mySQLDriver); masterConn= DriverManager.getConnection(masterUrl,user,pwd); slaveConn= DriverManager.getConnection(slaveUrl,user,pwd); jedis=new Jedis("192.168.159.33",6379); }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } private void insertData(){ //是向主MySQL服務(wù)器插入數(shù)據(jù) try{ masterPs=masterConn.prepareStatement("insert into student(id,name,age,score) values(10,'Frank',18,100)"); masterPs.executeUpdate(); }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } private String getNameByID(String id){ String key="Stu"+id; String name=""; //如果存在于Redis,就先從Redis里獲取 if(jedis.exists(key)){ System.out.println("ID:"+key+" exists in Redis"); name=jedis.get(key); System.out.println("Name is :"+jedis.get(key)); return name; }else{ //如果沒在Redis里,就到從MySQL里去讀 try { slavePs=slaveConn.prepareStatement("select name from student where id=10"); ResultSet rs=slavePs.executeQuery(); if(rs.next()){ System.out.println("ID: "+key+" exists in Slave MySQL"); name=rs.getString("name"); System.out.println("Name is: "+name); //放入Redis緩存 jedis.set(key,name); } return name; }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } return name; } public static void main(String[] args) { MySQLClusterDemo tool=new MySQLClusterDemo(); tool.init(); tool.insertData(); //場景1 沒有從Redis中找到,就到從MySQL服務(wù)器中去讀 System.out.println(tool.getNameByID("10")); //場景2,當(dāng)前ID=10的數(shù)據(jù)已存在于Redis,所有直接讀緩存 System.out.println(tool.getNameByID("10")); } }
運(yùn)行結(jié)果
4、MySQL主從集群整合Redis主從集群
上面的mysql主從集群至整合了一個(gè)Redis主機(jī),在這種模式里如果Redis服務(wù)器失效了,那么整個(gè)緩存可能都會失效。可以在次基礎(chǔ)上引入Redis主從復(fù)制集群,以提升緩存的可用性以及性能,改進(jìn)后的框架圖如下所示。
應(yīng)用程序同樣是向主mysql服務(wù)器里寫數(shù)據(jù),這些數(shù)據(jù)同步到從mysql數(shù)據(jù)庫里。
應(yīng)用程序先到“從Redis服務(wù)器”里讀取緩存,如果找不到,就再到從mysql數(shù)據(jù)庫里去讀。
如果從“從mysql數(shù)據(jù)庫”里讀到數(shù)據(jù),那么需要寫入“主Redis”,而根據(jù)Redis集群的主從復(fù)制機(jī)制,該數(shù)據(jù)會被寫入“從Redis服務(wù)器”。這種針對Redis集群的讀寫分離機(jī)制能提升讀寫緩存的性能。
4.1 搭建Redis主從復(fù)制集群
4.1.1 創(chuàng)建redis-master容器
docker run -itd --name redis-master -p 6379:6379 redis:latest
4.1.2 創(chuàng)建resis-slave容器
docker run -itd --name redis-slave -p 6380:6379 redis:latest
4.1.3 查看redis服務(wù)器的ip
docker inspect redis-master
可以看到,redis-master的ip地址為172.17.0.4
4.1.4 主從配置
在redis-slave容器的窗口里,通過docker exec -it redis-slave /bin/bash
命令進(jìn)入容器的命令行窗口。運(yùn)行如下的slaveof
命令,指定當(dāng)前服務(wù)器為從服務(wù)器,該命令的格式是slaveof IP地址 端口號
,這里指向172.17.0.2:6379
所在的主服務(wù)器。
slaveof 172.17.0.4 6379
運(yùn)行完該命令后,在redis-slave客戶端里再次運(yùn)行info replication
可以看到,該redis-slave已經(jīng)成為從服務(wù)器,從屬于172.17.0.2:6379所在的Redis服務(wù)器。
4.2、代碼整合
MySQLClusterImprovedDemo.java
import redis.clients.jedis.Jedis; import java.sql.*; public class MySQLClusterImprovedDemo { //創(chuàng)建操作Redis和數(shù)據(jù)庫的對象 private Jedis masterJedis; //指向主Redis服務(wù)器 private Jedis slaveJedis; //指向從Redis服務(wù)器 private Connection masterConn; //連接主庫的對象 private Connection slaveConn; //連接從庫的對象 PreparedStatement masterPs=null; //對主庫進(jìn)行操作的對象 PreparedStatement slavePs=null; //對從庫進(jìn)行操作的對象 private void init(){ //MYSQL的連接參數(shù) String mySQLDriver="com.mysql.cj.jdbc.Driver"; String masterUrl="jdbc:mysql://192.168.159.33:3306/redisDemo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; String slaveUrl="jdbc:mysql://192.168.159.33:3316/redisDemo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; String user="root"; String pwd="123456"; try{ Class.forName(mySQLDriver); masterConn= DriverManager.getConnection(masterUrl,user,pwd); slaveConn= DriverManager.getConnection(slaveUrl,user,pwd); masterJedis=new Jedis("192.168.159.33",6379); slaveJedis=new Jedis("192.168.159.33",6380); }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } private void insertData(){ //是向主MySQL服務(wù)器插入數(shù)據(jù) try{ masterPs=masterConn.prepareStatement("insert into student(id,name,age,score) values(10,'Frank',18,100)"); masterPs.executeUpdate(); }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } private String getNameByID(String id){ String key="Stu"+id; String name=""; //如果存在于Redis,就先從Redis里獲取 if(slaveJedis.exists(key)){ //到從Redis服務(wù)器去找 System.out.println("ID: "+key+" exists in Redis."); name=slaveJedis.get(key);//找到后到從Redis里讀 System.out.println("Name is: "+slaveJedis.get(key)); return name; }else{ //沒在Redis,就到從MySQL去讀 try{ slavePs=slaveConn.prepareStatement("select name from student where id=10"); ResultSet rs=slavePs.executeQuery(); if(rs.next()) { System.out.println("ID: "+key+" exists in Slave MySQL"); name=rs.getString("name"); System.out.println("Name is: "+name); //放入主Redis緩存 masterJedis.set(key,name); } return name; }catch (SQLException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } return name; } public static void main(String[] args) { MySQLClusterImprovedDemo tool=new MySQLClusterImprovedDemo(); tool.init(); tool.insertData(); //場景1 在主Redis中沒有讀到,則到從MySQL服務(wù)器中讀 System.out.println(tool.getNameByID("10")); //場景2 當(dāng)前ID=10已經(jīng)存在于Redis,所以直接讀緩存 System.out.println(tool.getNameByID("10")); } }
為了突出重點(diǎn),這里我并沒有設(shè)置“緩存失效時(shí)間”和“防止緩存穿透”等方面的實(shí)施代碼,但是這些要點(diǎn)同樣重要。
到此這篇關(guān)于Redis整合MySQL主從集群的示例代碼的文章就介紹到這了,更多相關(guān)Redis整合MySQL主從集群內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis.clients.jedis.exceptions.JedisDataException:?NOAUTH?
本文主要介紹了redis.clients.jedis.exceptions.JedisDataException:?NOAUTH?Authentication?required數(shù)據(jù)操作異常的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05Redis集群的三種部署方式及三種應(yīng)用問題的處理
這篇文章主要介紹了Redis集群的三種部署方式及三種應(yīng)用問題的處理,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04Redis和Nginx實(shí)現(xiàn)限制接口請求頻率的示例
限流就是限制API訪問頻率,當(dāng)訪問頻率超過某個(gè)閾值時(shí)進(jìn)行拒絕訪問等操作,本文主要介紹了Redis和Nginx實(shí)現(xiàn)限制接口請求頻率的示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Redis高級玩法之利用SortedSet實(shí)現(xiàn)多維度排序的方法
Redis的SortedSet是可以根據(jù)score進(jìn)行排序的,以手機(jī)應(yīng)用商店的熱門榜單排序?yàn)槔?,根?jù)下載量倒序排列。接下來通過本文給大家分享Redis高級玩法之利用SortedSet實(shí)現(xiàn)多維度排序的方法,一起看看吧2019-07-07