當前位置:首頁 » 數據倉庫 » spring資料庫讀寫分離
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

spring資料庫讀寫分離

發布時間: 2023-03-18 12:27:15

Ⅰ spring事務中查詢主庫還是從庫

可以給所有讀的方法添加一個參數,來控制讀從庫還是主庫。

2、數據源如何路由?

spring-jdbc 包中提供了一個抽象類:AbstractRoutingDataSource,實現了javax.sql.DataSource介面,我們用這個類來作為胡攔數據源類,重點是這個類可以用來做數據源的路由,可以在其內部配置多個真實的數據源,最終用哪個數據源,由開發者來決定。

AbstractRoutingDataSource中有個map,用來存儲多個目標數據源

private Map<Object, DataSource> resolvedDataSources;
比如主從庫可以這么存儲

resolvedDataSources.put("master",主庫數據源);
resolvedDataSources.put("salave",從庫數據源);
AbstractRoutingDataSource中還有抽象方法determineCurrentLookupKey,將這個方法的返回值作為key到上面的resolvedDataSources中查找對應的數據源,作為當前操作db的數據源

protected abstract Object determineCurrentLookupKey();
3、讀寫分離在哪控制?

讀寫分離屬於一個通用的功能,可以通過spring的aop來實現,添加一個攔截器,攔截目困茄標方法的之前,在目標方法執行之前,獲取一下當前需要走哪個庫,將這個標志存儲在ThreadLocal中,將這個標志作為AbstractRoutingDataSource.determineCurrentLookupKey()方法的返回值,攔截器中在目標方法執行完畢之後,將這個標志從ThreadLocal中清除。

3、代碼實現

3.1、工程結構圖

3.2、DsType

表示數據源類型,有2個值,用來區分是主庫還是從庫。褲尺胡

package com.javacode2018.readwritesplit.base;

public enum DsType {
MASTER, SLAVE;
}
3.3、DsTypeHolder

內部有個ThreadLocal,用來記錄當前走主庫還是從庫,將這個標志放在dsTypeThreadLocal中

package com.javacode2018.readwritesplit.base;

public class DsTypeHolder {
private static ThreadLocal<DsType> dsTypeThreadLocal = new ThreadLocal<>();

public static void master() {
dsTypeThreadLocal.set(DsType.MASTER);
}

public static void slave() {
dsTypeThreadLocal.set(DsType.SLAVE);
}

public static DsType getDsType() {
return dsTypeThreadLocal.get();
}

public static void clearDsType() {
dsTypeThreadLocal.remove();
}
}
3.4、IService介面

這個介面起到標志的作用,當某個類需要啟用讀寫分離的時候,需要實現這個介面,實現這個介面的類都會被讀寫分離攔截器攔截。

package com.javacode2018.readwritesplit.base;

//需要實現讀寫分離的service需要實現該介面
public interface IService {
}
3.5、ReadWriteDataSource

讀寫分離數據源,繼承ReadWriteDataSource,注意其內部的determineCurrentLookupKey方法,從上面的ThreadLocal中獲取當前需要走主庫還是從庫的標志。

package com.javacode2018.readwritesplit.base;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;

public class ReadWriteDataSource extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DsTypeHolder.getDsType();
}
}
3.6、ReadWriteInterceptor

讀寫分離攔截器,需放在事務攔截器前面執行,通過@1代碼我們將此攔截器的順序設置為Integer.MAX_VALUE - 2,稍後我們將事務攔截器的順序設置為Integer.MAX_VALUE - 1,事務攔截器的執行順序是從小到達的,所以,ReadWriteInterceptor會在事務攔截器org.springframework.transaction.interceptor.TransactionInterceptor之前執行。

由於業務方法中存在相互調用的情況,比如service1.m1中調用service2.m2,而service2.m2中調用了service2.m3,我們只需要在m1方法執行之前,獲取具體要用哪個數據源就可以了,所以下面代碼中會在第一次進入這個攔截器的時候,記錄一下走主庫還是從庫。

下面方法中會獲取當前目標方法的最後一個參數,最後一個參數可以是DsType類型的,開發者可以通過這個參數來控制具體走主庫還是從庫。

package com.javacode2018.readwritesplit.base;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Aspect
@Order(Integer.MAX_VALUE - 2) //@1
@Component
public class ReadWriteInterceptor {

@Pointcut("target(IService)")
public void pointcut() {
}

//獲取當前目標方法的最後一個參數
private Object getLastArgs(final ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
if (Objects.nonNull(args) && args.length > 0) {
return args[args.length - 1];
} else {
return null;
}
}

@Around("pointcut()")
public Object around(final ProceedingJoinPoint pjp) throws Throwable {
//判斷是否是第一次進來,用於處理事務嵌套
boolean isFirst = false;
try {
if (DsTypeHolder.getDsType() == null) {
isFirst = true;
}
if (isFirst) {
Object lastArgs = getLastArgs(pjp);
if (DsType.SLAVE.equals(lastArgs)) {
DsTypeHolder.slave();
} else {
DsTypeHolder.master();
}
}
return pjp.proceed();
} finally {
//退出的時候,清理
if (isFirst) {
DsTypeHolder.clearDsType();
}
}
}
}
3.7、ReadWriteConfiguration

spring配置類,作用

1、@3:用來將com.javacode2018.readwritesplit.base包中的一些類注冊到spring容器中,比如上面的攔截器ReadWriteInterceptor

2、@1:開啟spring aop的功能

3、@2:開啟spring自動管理事務的功能,@EnableTransactionManagement的order用來指定事務攔截器org.springframework.transaction.interceptor.TransactionInterceptor順序,在這里我們將order設置為Integer.MAX_VALUE - 1,而上面ReadWriteInterceptor的order是Integer.MAX_VALUE - 2,所以ReadWriteInterceptor會在事務攔截器之前執行。

package com.javacode2018.readwritesplit.base;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableAspectJAutoProxy //@1
@EnableTransactionManagement(proxyTargetClass = true, order = Integer.MAX_VALUE - 1) //@2
@ComponentScan(basePackageClasses = IService.class) //@3
public class ReadWriteConfiguration {
}
3.8、@EnableReadWrite

這個註解用來開啟讀寫分離的功能,@1通過@Import將ReadWriteConfiguration導入到spring容器了,這樣就會自動啟用讀寫分離的功能。業務中需要使用讀寫分離,只需要在spring配置類中加上@EnableReadWrite註解就可以了。

package com.javacode2018.readwritesplit.base;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ReadWriteConfiguration.class) //@1
public @interface EnableReadWrite {
}
4、案例

讀寫分離的關鍵代碼寫完了,下面我們來上案例驗證一下效果。

4.1、執行sql腳本

下面准備2個資料庫:javacode2018_master(主庫)、javacode2018_slave(從庫)

2個庫中都創建一個t_user表,分別插入了一條數據,稍後用這個數據來驗證走的是主庫還是從庫。

DROP DATABASE IF EXISTS javacode2018_master;
CREATE DATABASE IF NOT EXISTS javacode2018_master;

USE javacode2018_master;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(256) NOT NULL DEFAULT ''
COMMENT '姓名'
);

INSERT INTO t_user (name) VALUE ('master庫');

DROP DATABASE IF EXISTS javacode2018_slave;
CREATE DATABASE IF NOT EXISTS javacode2018_slave;

USE javacode2018_slave;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(256) NOT NULL DEFAULT ''
COMMENT '姓名'
);
INSERT INTO t_user (name) VALUE ('slave庫');
4.2、spring配置類

@1:啟用讀寫分離

masterDs()方法:定義主庫數據源

slaveDs()方法:定義從庫數據源

dataSource():定義讀寫分離路由數據源

後面還有2個方法用來定義JdbcTemplate和事務管理器,方法中都通過@Qualifier(「dataSource」)限定了注入的bean名稱為dataSource:即注入了上面dataSource()返回的讀寫分離路由數據源。

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;
import com.javacode2018.readwritesplit.base.EnableReadWrite;
import com.javacode2018.readwritesplit.base.ReadWriteDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@EnableReadWrite //@1
@Configuration
@ComponentScan
public class MainConfig {
//主庫數據源
@Bean
public DataSource masterDs() {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018_master?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
return dataSource;
}

//從庫數據源
@Bean
public DataSource slaveDs() {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018_slave?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
return dataSource;
}

//讀寫分離路由數據源
@Bean
public ReadWriteDataSource dataSource() {
ReadWriteDataSource dataSource = new ReadWriteDataSource();
//設置主庫為默認的庫,當路由的時候沒有在datasource那個map中找到對應的數據源的時候,會使用這個默認的數據源
dataSource.setDefaultTargetDataSource(this.masterDs());
//設置多個目標庫
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DsType.MASTER, this.masterDs());
targetDataSources.put(DsType.SLAVE, this.slaveDs());
dataSource.setTargetDataSources(targetDataSources);
return dataSource;
}

//JdbcTemplate,dataSource為上面定義的注入讀寫分離的數據源
@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

//定義事務管理器,dataSource為上面定義的注入讀寫分離的數據源
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4.3、UserService

這個類就相當於我們平時寫的service,我是為了方法,直接在裡面使用了JdbcTemplate來操作資料庫,真實的項目操作db會放在裡面。

getUserNameById方法:通過id查詢name。

insert方法:插入數據,這個內部的所有操作都會走主庫,為了驗證是不是查詢也會走主庫,插入數據之後,我們會調用this.userService.getUserNameById(id, DsType.SLAVE)方法去執行查詢操作,第二個參數故意使用SLAVE,如果查詢有結果,說明走的是主庫,否則走的是從庫,這里為什麼需要通過this.userService來調用getUserNameById?

this.userService最終是個代理對象,通過代理對象訪問其內部的方法,才會被讀寫分離的攔截器攔截。

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;
import com.javacode2018.readwritesplit.base.IService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component
public class UserService implements IService {

@Autowired
private JdbcTemplate jdbcTemplate;

@Autowired
private UserService userService;

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public String getUserNameById(long id, DsType dsType) {
String sql = "select name from t_user where id=?";
List<String> list = this.jdbcTemplate.queryForList(sql, String.class, id);
return (list != null && list.size() > 0) ? list.get(0) : null;
}

//這個insert方法會走主庫,內部的所有操作都會走主庫
@Transactional
public void insert(long id, String name) {
System.out.println(String.format("插入數據{id:%s, name:%s}", id, name));
this.jdbcTemplate.update("insert into t_user (id,name) values (?,?)", id, name);
String userName = this.userService.getUserNameById(id, DsType.SLAVE);
System.out.println("查詢結果:" + userName);
}

}
4.4、測試用例

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.;

public class Demo1Test {

UserService userService;

@Before
public void before() {
context = new ();
context.register(MainConfig.class);
context.refresh();
this.userService = context.getBean(UserService.class);
}

@Test
public void test1() {
System.out.println(this.userService.getUserNameById(1, DsType.MASTER));
System.out.println(this.userService.getUserNameById(1, DsType.SLAVE));
}

@Test
public void test2() {
long id = System.currentTimeMillis();
System.out.println(id);
this.userService.insert(id, "張三");
}
}
test1方法執行2次查詢,分別查詢主庫和從庫,輸出:

master庫
slave庫
是不是很爽,由開發者自己控制具體走主庫還是從庫。

test2執行結果如下,可以看出查詢到了剛剛插入的數據,說明insert中所有操作都走的是主庫。

1604905117467
插入數據{id:1604905117467, name:張三}
查詢結果:張三
5、案例源碼

Ⅱ 【sharding-jdbc】spring boot 集成sharding-jdbc 完成一主多從讀寫分離

說明
sharding-jdbc 的官方文檔在我看冊備讓來是比較繞的,尤其是配置文件,看著相當頭大,滾盯仔細看絕對是看得懂的。上面配置一主州局兩從的情況,此框架支持一主,這點是需要注意的。而且框架不復制主從數據的同步。

Ⅲ 資料庫為什麼要讀寫分離

資料庫不一定要讀寫分離,如果程序使用資料庫較多時,而更新少,查詢多的情況下會考慮使用,利用資料庫 主從同步 。可以減少資料庫壓力,提高性能。當然,資料庫也有其它優化方案。memcache 或是 表折分,或是搜索引擎。都是解決方法。

Ⅳ 如何在spring + mybatis 下進行資料庫讀寫分離

讀寫分離是為了減少資料庫的負荷,當用戶高並發訪問時,絕大部分都是用戶查詢,少部分洞岩滾用戶是寫入到資料庫的。這些我們把資料庫拆分成主從兩個納余資料庫,主資料庫用高性能

伺服器承載高並棗鏈發的用戶訪問並加redis緩存。在這里我不講mysql的主從同步配置,大家可以去查下資料,我接下來重點講怎麼動態的給每個sql注入數據源。

Ⅳ spring怎麼實現讀寫分離

  1. 配置兩個連接池,一個讀,一個寫

  2. 寫個Service層或者(DAO層也行)的aop,讀操作使用讀的連接,寫用寫的連接實現分離

希望可以幫到你

Ⅵ Java軟體工程師主要學習哪些課程

第一階段,Java SE基礎:
Java環境搭建、Java流程式控制制語句-for循環、switch選擇判斷、循環嵌套、數組拷貝、多維數組、final關鍵字、構造函數的調用、類的訪問許可權和路徑、面向對象高級特性、Java異常處理、Set,Map,List介面及介面實現類、Java線程、同步阻塞、JavaIO流、文件的操作,復制,讀寫,刪除等。第二階段,JavaWeb:MySQL安裝、管理、創建資料庫、MySQL
UPDATE 查詢、Mysql高級操作、JDBC、JDBC資料庫連接操作,JDBC動態Sql處理、Servlet3.0
網頁重定向、Servlet3.0 新增的註解支持、AJAX、responseText屬性詳解等。第三階段,Java高級框架-SSH:Struts2異常處理、Struts2+Log4j集成、Struts2和JSON實例、Hibernate5、Hibernate集合映射、Hibernate組件映射、Spring4.0、SpringAOP
+ AspectJ框架、Spring 與其它Web框架集成、Spring Hibernate支持等。第四階段,Java高級框架-SSM:SpringMVC、Spring MVC生成JSON數據、MyBatis、MyBatis 環境配置及入門、Mybatis set標簽、Mybatis trim標簽、Shiro、Shiro快速入門教程、Shiro Web應用等。第五階段,SpringBoot+VUE全棧框架:SpringBoot、全局異常處理、過濾器監聽器、EHCache緩存、SpringBoot Quartz定時任務、Vue、Vue.js 安裝、模板語法、計算屬性、事件處理器、Vue.js 自定義指令、Vue.js 路由等第六階段,特色課程:ActiveM環境搭建、生產者和消費者、消息持久化操作、RSA數字加密演算法、Codebar條形碼生成器、zxing二維碼生成器、HighCharts統計圖、Echarts統計圖、網路播放器ckplayer、嵌入式網路播放器,可以瀏覽器和移動端隨意使用第七階段,互聯網框架的高級應用1:分布式服務框架的理解,Dubbo架構設計詳解及其核心要點,框架運行原理分析、SpringData數據訪問、Lucene搜索引擎、Lucene的全文搜索伺服器介紹、索引建立方式、Solr海量數據搜索引擎、Socket網路通信、實現RMI遠程對象通訊、使用JMS消息服務、Kafka分布式消息系統、WebService與Restful
WS等第八階段,互聯網框架的高級應用2:Spring Security安全框架、實現Web應用安全控制、緩存應用與EhCache框架、OSCache與JBossCache框架、MyBatis與Hibernate緩存機制、NoSQL應用與SQL調優、MongoDB
NoSQL資料庫、Redis內存資料庫、實現Redis

Session共享、SQL語句的優化、實現資料庫讀寫分離、WEB應用集群及性能優化、Maven項目管理工具、Web伺服器負載均衡、實現Nginx與Tomcat集群、使用LoadRunner測試工具、性能優化之內存調優、代碼優化與重構的方法等。
對java有興趣的小夥伴們,不妨先從java入門開始!B站上有很多的java教學視頻,從基礎到高級的都有,還挺不錯的,知識點講的很細致,還有完整版的學習路線圖。也可以自己去看看,下載學習試試。

Ⅶ 大型互聯網架構概述,看完文章又漲知識了

1. 大型網站系統的特點

2. 大型網站架構演化歷程

2.1. 初始階段架構

問題:網站運營初期,訪問用戶少,一台伺服器綽綽有餘。

特徵:應用程序、資料庫、文件等所有的資源都在一台伺服器上。

描述:通常伺服器操作系統使用 linux,應用程序使用 PHP 開發,然後部署在 Apache 上,資料庫使用 Mysql,通俗稱為 LAMP。匯集各種免費開源軟體以及一台廉價伺服器就可以開始系統的發展之路了。

2.2. 應用服務和數據服務分離

問題:越來越多的用戶訪問導致性能越來越差,越來越多的數據導致存儲空間不足,一台伺服器已不足以支撐。

特徵:應用伺服器、資料庫伺服器、文件伺服器分別獨立部署。

描述:三台伺服器對性能要求各不相同:應用伺服器要處理大量業務邏輯,因此需要更快更強大的 CPU;資料庫伺服器需要快速磁碟檢索和數據緩存,因此需要更快的硬碟和更大的內存;文件伺服器需要存儲大量文件,因此需要更大容量的硬碟。

2.3. 使用緩存改善性能

問題:隨著用戶逐漸增多,資料庫壓力太大導致訪問延遲。

特徵:由於網站訪問和財富分配一樣遵循二八定律:80% 的業務訪問集中在 20% 的數據上。將資料庫中訪問較集中的少部分數據緩存在內存中,可以減少資料庫的訪問次數,降低資料庫的訪問壓力。

描述:緩存分為兩種:應用伺服器上的本地緩存和分布式緩存伺服器上的遠程緩存,本地緩存訪問速度更快,但緩存數據量有限,同時存在與應用程序爭用內存的情況。分布式緩存可以採用集群方式,理論上可以做到不受內存容量限制的緩存服務。

2.4. 使用應用伺服器集群

問題:使用緩存後,資料庫訪問壓力得到有效緩解。但是單一應用伺服器能夠處理的請求連接有限,在訪問高峰期,成為瓶頸。

特徵:多台伺服器通過負載均衡同時向外部提供服務,解決單一伺服器處理能力和存儲空間不足的問題。

描述:使用集群是系統解決高並發、海量數據問題的常用手段。通過向集群中追加資源,提升系統的並發處理能力,使得伺服器的負載壓力不再成為整個系統的瓶頸。

2.5. 資料庫讀寫分離

問題:網站使用緩存後,使絕大部分數據讀操作訪問都可以不通過資料庫就能完成,但是仍有一部分讀操作和全部的寫操作需要訪問資料庫,在網站的用戶達到一定規模後,資料庫因為負載壓力過高而成為網站的瓶頸。

特徵:目前大部分的主流資料庫都提供主從熱備功能,通過配置兩台資料庫主從關系,可以將一台資料庫伺服器的數據更新同步到一台伺服器上。網站利用資料庫的主從熱備功能,實現資料庫讀寫分離,從而改善資料庫負載壓力。

描述:應用伺服器在寫操作的時候,訪問主資料庫,主資料庫通過主從復制機制將數據更新同步到從資料庫。這樣當應用伺服器在讀操作的時候,訪問從資料庫獲得數據。為了便於應用程序訪問讀寫分離後的資料庫,通常在應用伺服器端使用專門的數據訪問模塊,使資料庫讀寫分離的對應用透明。

2.6. 反向代理和 CDN 加速

問題:中國網路環境復雜,不同地區的用戶訪問網站時,速度差別也極大。

特徵:採用 CDN 和反向代理加快系統的靜態資源訪問速度。

描述:CDN 和反向代理的基本原理都是緩存,區別在於 CDN 部署在網路提供商的機房,使用戶在請求網站服務時,可以從距離自己最近的網路提供商機房獲取數據;而反向代理則部署在網站的中心機房,當用戶請求到達中心機房後,首先訪問的伺服器時反向代理伺服器,如果反向代理伺服器中緩存著用戶請求的資源,就將其直接返回給用戶。

2.7. 分布式文件系統和分布式資料庫

問題:隨著大型網站業務持續增長,資料庫經過讀寫分離,從一台伺服器拆分為兩台伺服器,依然不能滿足需求。

特徵:資料庫採用分布式資料庫,文件系統採用分布式文件系統。

描述:分布式資料庫是資料庫拆分的最後方法,只有在單表數據規模非常龐大的時候才使用。不到不得已時,更常用的資料庫拆分手段是業務分庫,將不同的業務資料庫部署在不同的物理伺服器上。

2.8. 使用 NoSQL 和搜索引擎

問題:隨著網站業務越來越復雜,對數據存儲和檢索的需求也越來越復雜。

特徵:系統引入 NoSQL 資料庫及搜索引擎。

描述:NoSQL 資料庫及搜索引擎對可伸縮的分布式特性具有更好的支持。應用伺服器通過統一數據訪問模塊訪問各種數據,減輕應用程序管理諸多數據源的麻煩。

2.9. 業務拆分

問題:大型網站的業務場景日益復雜,分為多個產品線。

特徵:採用分而治之的手段將整個網站業務分成不同的產品線。系統上按照業務進行拆分改造,應用伺服器按照業務區分進行分別部署。

描述:應用之間可以通過超鏈接建立關系,也可以通過消息隊列進行數據分發,當然更多的還是通過訪問同一個數據存儲系統來構成一個關聯的完整系統。

縱向拆分:將一個大應用拆分為多個小應用,如果新業務較為獨立,那麼就直接將其設計部署為一個獨立的 Web 應用系統。縱向拆分相對較為簡單,通過梳理業務,將較少相關的業務剝離即可。

橫向拆分:將復用的業務拆分出來,獨立部署為分布式服務,新增業務只需要調用這些分布式服務橫向拆分需要識別可復用的業務,設計服務介面,規范服務依賴關系。

2.10. 分布式服務

問題:隨著業務越拆越小,存儲系統越來越龐大,應用系統整體復雜程度呈指數級上升,部署維護越來越困難。由於所有應用要和所有資料庫系統連接,最終導致資料庫連接資源不足,拒絕服務。

特徵:公共業務提取出來,獨立部署。由這些可復用的業務連接資料庫,通過分布式服務提供共用業務服務。

3. 大型網站架構模式

3.1. 分層

大型網站架構中常採用分層結構,將軟體系統分為應用層、服務層、數據層:

分層架構的約束:禁止跨層次的調用(應用層直接調用數據層)及逆向調用(數據層調用服務層,或者服務層調用應用層)。

分層結構內部還可以繼續分層,如應用可以再細分為視圖層和業務邏輯層;服務層也可以細分為數據介面層和邏輯處理層。

3.2. 分割

將不同的功能和服務分割開來,包裝成高內聚低耦合的模塊單元。這有助於軟體的開發和維護,便於不同模塊的分布式部署,提高網站的並發處理能力和功能擴展能力。

3.3. 分布式

大於大型網站,分層和分割的一個主要目的是為了切分後的模塊便於分布式部署,即將不同模塊部署在不同的伺服器上,通過遠程調用協同工作。

分布式意味可以用更多的機器工作,那麼 CPU、內存、存儲資源也就更豐富,能夠處理的並發訪問和數據量就越大,進而能夠為更多的用戶提供服務。

分布式也引入了一些問題:

常用的分布式方案:

3.4. 集群

集群即多台伺服器部署相同應用構成一個集群,通過負載均衡設備共同對外提供服務。

集群需要具備伸縮性和故障轉移機制:伸縮性是指可以根據用戶訪問量向集群添加或減少機器;故障轉移是指,當某台機器出現故障時,負載均衡設備或失效轉移機制將請求轉發到集群中的其他機器上,從而不影響用戶使用。

3.5. 緩存

緩存就是將數據存放在距離最近的位置以加快處理速度。緩存是改善軟體性能的第一手段。

網站應用中,緩存除了可以加快數據訪問速度以外,還可以減輕後端應用和數據存儲的負載壓力。

常見緩存手段:

使用緩存有兩個前提:

3.6. 非同步

軟體發展的一個重要目標和驅動力是降低軟體耦合性。事物之間直接關系越少,彼此影響就越小,也就更容易獨立發展。

大型網站架構中,系統解耦的手段除了分層、分割、分布式等,還有一個重要手段——非同步。

業務間的消息傳遞不是同步調用,而是將一個業務操作拆分成多階段,每個階段間通過共享數據的方式非同步執行進行協作。

非同步架構是典型的生產者消費模式,二者不存在直接調用。非同步消息隊列還有如下特性:

3.7. 冗餘

大型網站,出現伺服器宕機是必然事件。要保證部分伺服器宕機的情況下網站依然可以繼續服務,不丟失數據,就需要一定程度的伺服器冗餘運行,數據冗餘備份。這樣當某台伺服器宕機是,可以將其上的服務和數據訪問轉移到其他機器上。

訪問和負載很小的服務也必須部署 至少兩台伺服器構成一個集群,目的就是通過冗餘實現服務高可用。數據除了定期備份,存檔保存,實現 冷備份 外;為了保證在線業務高可用,還需要對資料庫進行主從分離,實時同步實現 熱備份。

為了抵禦地震、海嘯等不可抗因素導致的網站完全癱瘓,某些大型網站會對整個數據中心進行備份,全球范圍內部署 災備數據中心。網站程序和數據實時同步到多個災備數據中心。

3.8. 自動化

大型網站架構的自動化架構設計主要集中在發布運維方面:

3.9. 安全

4. 大型網站核心架構要素

架構 的一種通俗說法是:最高層次的規劃,難以改變的決定。

4.1. 性能

性能問題無處不在,所以網站性能優化手段也十分繁多:

4.2. 可用性

可用性指部分伺服器出現故障時,還能否對用戶提供服務

4.3. 伸縮性

衡量伸縮的標准就是是否可以用多台伺服器構建集群,是否容易向集群中增刪伺服器節點。增刪伺服器節點後是否可以提供和之前無差別的服務。集群中可容納的總伺服器數是否有限制。

4.4. 擴展性

衡量擴展性的標准就是增加新的業務產品時,是否可以實現對現有產品透明無影響,不需要任何改動或很少改動,既有功能就可以上線新產品。主要手段有:事件驅動架構和分布式服務。

4.5. 安全性

安全性保護網站不受惡意攻擊,保護網站重要數據不被竊取。

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發: 721575865

群內提供免費的Java架構學習資料(裡面有高可用、高並發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間「來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

Ⅷ 資料庫讀寫分離如何保證主從一致性

當我們的資料庫壓力主鍵變大的時候,我們會嘗試增加一些從節點來分攤主節點的查詢壓力。而一般來說,我們是用一主多從的結構來作為讀寫分離的基本結構。

而一般來說我們有兩種常用的方法來實現讀且分離架構:

客戶端直接分離

這種方式是由客戶端,或者我們的微服務直接進行資料庫的讀寫選擇。將讀庫選擇路由到主庫上進行,將查詢路由到從主庫上進行。

這種方式的優點在於因為是直連所以性能比較高,但是需要由業務團隊了解資料庫的實例細節,當資料庫做調整的時候就需要業務側同步改造。

使用數據中間件代理

這種方式是由一層代理層對數據的讀寫做分發,業務層將所有的請求都通過代理來實現。

這種方式的優點在於對於業務層不需要感知到資料庫的存在,但問題在於數據中間件的性能要求較高,還需要專人來進行優化和維護,整體架構較為復雜。

但是我們發現,盡管這兩種方式各有優劣。但核心都是通過數據的寫入、查詢請求的路由而實現的,那麼這就會引發標題的問題:

主備同步存在延遲,所以在延遲時間內對插入的內容進行查詢則無法查詢到最新提交的事務。

那麼如何保證主從一致性的問題,其實就變成了如何處理主從延遲的問題。

根據項目的大小,團隊的規模以及主機的部署模式。我們處理問題的方法也有很多種。

最簡單強硬的就是強制讀主庫。

一般情況下我們在不同的查詢中會有不同程度的一致性要求。我們可以將需要保證數據一致性的請求配置強制查詢主庫,而對於無強依賴的查詢請求仍然查詢備庫。

盡管這個方案不是很優雅,但是是最簡單實現的方法,並且在Spring等框架的支持下一般只需要加一個註解就能實現。但這個方法的問題也是顯而易見的,如果存在大量的強一致性要求的查詢語句,則相當於沒有進行讀寫分離與擴展。那麼這種方法就會導致系統在資料庫層面沒有有效的擴展手段了。

由於問題產生的來源是主從延遲,所以在下一次查詢的時候進行一段時間的等待以彌補這種延遲即可。

所以在進行主庫的數據插入之後,讓資料庫數據連接或者對應的執行線程等待一段時間後返回。通過等待時間來消化掉主從備份的延遲時間。但是這個方法也有一些問題比如:這個等待時間一般是固定的,即便主從已經無延遲了也會繼續等待到時間結束;如果在服務高峰時期,有可能數據在等待時間結束後仍然沒有完成同步則仍然會存在一致性問題。

但這種方法優雅的地方是可以配合業務來進行實現,舉例來說當用戶下單之後,通過下單送卷或者下單抽獎的方式從前端拖住用戶,從而當用戶在一次連續操作中再次查詢自己訂單的時候中間必然會間隔一定時間,也就讓需要再次查詢數據的時候保證了數據的一致性。

上述兩種方案看起來可能不那麼「技術」,感覺有點投機取巧。那麼下面咱們可以分兩種情況來討論用更高技術的方法如何實現一致性。

對於主從復制來說,是當主庫完成一個事務後,通知給從庫,當從庫接受到後,則主庫完成返回客戶端。所以當主庫完成事務後,僅能確保從庫已經接受到了,但是不能保證從庫執行完成,也就是導致了主從備份延遲。

但是從庫執行數據是有進度的,而這個進度是可以通過show slave status語句中的seconds_behind_master來進行描述,這個參數描述從庫落後了主庫數據多少秒,當這個參數為0時,我們可以認為從庫和主庫已經基本上沒有延遲了,那麼這時候就可以查詢請求。

但seconds_behind_master是秒級的,所以只能大概地判斷,由於精度較低,所以還是可能出現不一致的情況。

如果要求精準執行的話,我們可以比較同步文件的執行記錄,具體來說是:

所以當Relay_Master_Log_File和Exec_Master_Log_Pos和其一致的時候,就說明從庫的已執行數據已經追上主庫了,那麼這時就可以說保證了主從一致性了

但是比較同步文件的執行記錄方法的問題在於,如果當前的這個事務的binlog尚未傳入到從庫,即Master_Log_File和Read_Master_Log_Pos未更新,也就無法保證從庫已經包含最新的主庫事務了。

而為了保證在一主一備的情況下,從庫里一定接受到數據了,也就是Master_Log_File和Read_Master_Log_Pos中的數據是和主庫一致的,我們可以開啟semi-sync replication半同步復制。

半同步復制的原理是在主庫提交事務前先將binlog發送給從庫,然後當從庫接受後返回一個應答,主庫只有在接受到這個應答之後才返回事務執行完成。這樣就可以保證從庫的Master_Log_File和Read_Master_Log_Pos與主庫是一致的,從而解決了主從一致的問題。

半同步復制可以解決一主一備的情況,但是當一主多備的時候,只要主庫接受到一個從庫的應答,就會返回事務執行完成。而這時當請求打到未完成同步的從庫上時就會發生主從延遲。

所以針對一主多備的情況,我們可以將目光集中在執行查詢的從庫上,即確保 我們即將查詢的備庫已經執行了我們預期的事務。 那麼我們的問題就變成兩部分:1. 確認主庫事務,2. 查詢數據條件。

確認主庫事務

當我們提交完一個事務後,可以通過執行show master status來得到主庫中的數據事務文件(File)和位置記錄(Position)。

查詢數據條件

當我們要查詢從庫數據的時候,我們可以通過語句select master_pos_wait(File, Position, 1);來查詢當前是否已經執行到了該記錄(當返回值>=0的時候說明已經執行過了)。其中最後的數字1表示阻塞時長。

通過先確認主庫事務記錄,再判確認備庫是否已經執行了了主庫對應的事務。

但是可以發現,這種方法要求查詢的時候知道主庫的事務信息,對場景有很大的限制。

主從一致的問題源自主從延遲,所以我們就是從如何消除延遲來解決問題。簡單點的方案我們可以不走備庫、或者直接等待一段時間來忽略延遲的影響。在一主一備的情況下我們可以粗力度的用seconds_behind_master來判斷或者用Relay_Master_Log_File和Exec_Master_Log_Pos來判斷。而當一主多從的情況下我們則需要在查詢前傳入主庫執行的事務記錄才能保證數據一致性。

可以看出,當數據規模和部署方式變更的時候,好的解決方案將會越來越多。我認為根據實際業務情況選擇最合適的方法才是最重要的。



Ⅸ SpringBoot項目中實現MySQL讀寫分離

但我們仔細觀察我們會發現,當我們的項目都是用的單體資料庫時,那麼就可能會存在如下問題:

為了解決上述提到的兩個問題,我們可以准備兩 (多) 台MySQL,一台主( Master )伺服器,一台從( Slave )伺服器,主庫的 數據變更 (寫、更新、刪除這些操作) ,需要 同步 到從庫中 (主從復制) 。而用戶在訪問我們項目時,如果是 寫操作 (insert、update、delete),則直接操作 主庫 ;如果是 讀操作 (select) ,則直接操作從庫,這種結構就是 讀寫分離 啦。

在這種讀寫分離的結構中,從庫是可以有多個的

MySQL主從復制是一個 非同步 的復制過程,底層是基於Mysql資料庫自帶的 二進制日誌 功能。就是一台或多台MySQL資料庫(slave,即 從庫 )從另一台MySQL資料庫(master,即 主庫 )進行日誌的復制,然後再解析日誌並應用到自身,最終實現 從庫 的數據和 主庫 的數據保持一致。MySQL主從復制是 MySQL資料庫自帶功能,無需藉助第三方工具。

二進制日誌(BINLOG)記錄了所有的 DDL(數據定義語言)語句和 DML(數據操縱語言)語句,但是不包括數據查詢語句。此日誌對於災難時的數據恢復起著極其重要的作用,MySQL的主從復制, 就是通過該binlog實現的。默認MySQL是未開啟該日誌的。

在環境搭建之前,我們需要准備好兩台伺服器,如果生活富裕使用的是兩台雲伺服器的時候記得要開放安全組,即防火牆;如果是比狗子我生活好點但也是用的虛擬機的話,記得別分這么多內存啟動藍屏了(別問怎麼知道的)

這里就不給大家展示資料庫的安裝和防火牆的操作了,這個我感覺網上好多資源都能夠滿足遇到的問題,在搭建主從庫的時候有在網上見到過說MySQL版本要一致的,我也沒太留意直接就在之前的MySQL上操作了,大家可以自己去驗證一下。

伺服器:192.168.150.100(別試了黑不了的,這是虛擬機的ip)

這里有三個方法都能重啟MySQL,最簡單的無疑就是一關一開:

登錄進去MySQL之後才能夠執行下面的命令,因為這是SQL命令,Linux不認識這玩意是啥。

這個時候還 不用退出MySQL ,因為下面的命令還是SQL命令,執行下面的SQL,可以拿到我們後面需要的兩個重要參數。

執行完這一句SQL之後,==不要再操作主庫!不要再操作主庫!不要再操作主庫!==重要的事情說三遍,因為再操作主庫之後可能會導致紅框中的 兩個屬性值會發生變化 ,後面如果發生了錯誤可能就和這里有那麼兩毛錢關系了。

伺服器:192.168.150.101(別試了黑不了的,這也是虛擬機的ip)

這里要注意server-id和主庫以及其他從庫都不能相同,否則後面將會配置不成功。

這里有三個方法都能重啟MySQL,最簡單的無疑就是一關一開:

登錄進去MySQL之後才能夠執行下面的命令,因為這是SQL命令

參數說明:

這個時候還 不用退出MySQL ,因為下面的命令還是SQL命令,執行下面的SQL,可以看到從庫的狀態信息。通過狀態信息中的 Slave_IO_running Slave_SQL_running 可以看出主從同步是否就緒,如果這兩個參數全為 Yes ,表示主從同步已經配置完成。

這可能是由於linux 是復制出來的,MySQL中還有一個 server_uuid 是一樣的,我們也需要修改。 vim /var/lib/mysql/auto.cnf

這應該就是各位大牛設置server_id的時候不小心設置相同的id了,修改過來就行,步驟在上面的配置中。

這是狗子在操作過程中搞出來的一個錯誤……

出錯的原因是在主庫中刪除了用戶信息,但是在從庫中同步的時候失敗導致同步停止,下面記錄自己的操作(是在進入MySQL的操作且是從庫)。

在資料庫中操作時,一定要注意當前所在的資料庫是哪個,作為一個良好的實踐:在SQL語句前加 USE dbname 。

Sharding-JDBC定位為 輕量級Java框架 ,在Java的JDBC層提供的額外服務。 它使用客戶端直連資料庫,以 jar包 形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動, 完全兼容JDBC和各種ORM框架

使用Sharding-JDBC可以在程序中輕松的實現資料庫 讀寫分離

Sharding-JDBC具有以下幾個特點:

下面我們將用ShardingJDBC在項目中實現MySQL的讀寫分離。

在pom.xml文件中導入ShardingJDBC的依賴坐標

在application.yml中增加數據源的配置

這時我們就可以對我們項目中的配置進行一個測試,下面分別調用一個更新介面和一個查詢介面,通過查看日誌中記錄的數據源來判斷是否能夠按照我們預料中的跑。

搞定!!!程序正常按照我們預期的成功跑起來了,成功藉助ShardingJDBC在我們項目中實現了資料庫的讀寫分離。

Ⅹ SpringBoot整合MybatisPius實現資料庫多數據源及讀寫分離

其他主從方式配置模板

@DS 可以註解在方法上和類上扒早,同時存在方法註解優先於類上註解。
強烈建議只註解在service實現上。

某些springBoot的版本上面埋罩可能無法排除(嘗試使用以下方式排春液雀除)