數據分片產生的背景,能夠查看https://shardingsphere.apache.org/document/current/cn/features/sharding/,包括了垂直拆分和水平拆分的概念.還有這個框架的目標是什麼,都寫得很清楚java
Sharding-JDBC與MyCat:mysql
github地址: https://github.com/apache/incubator-shardingsphere/releases 官網: https://shardingsphere.incubator.apache.org/ 文檔: https://shardingsphere.apache.org/document/current/en/overview/git
功能:github
在數據庫的操做中,寫操做是很是耗時的,而最經常使用的是讀操做,讀寫分離的目的是避免數據庫的寫操做影響讀操做的效率.最重要的目的仍是減小數據庫的壓力,提升性能.web
這只是模仿讀寫分析實戰,流程是建立兩個數據庫,配置兩個數據源,一個是主表,一個是從表,寫修改刪除在主表,查詢是在從表.算法
// 主表 CREATE DATABASE `ds_0` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; // 從表 CREATE DATABASE `ds_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; // 兩個庫中都建立表 CREATE TABLE `user`( id bigint(64) not null auto_increment, city varchar(20) not null, name varchar(20) not null, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // 插入ds_0 insert into user values(1001,'上海','尹吉歡'); // 插入ds_1 insert into user values(1002,'北京','張三');
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>com.dangdang</groupId> <artifactId>sharding-jdbc-config-spring</artifactId> <version>1.5.4.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
// 建立sharding.xml,內容以下 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.dangdang.com/schema/ddframe/rdb http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd "> <!-- 主數據 --> <bean id="ds_0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" primary="true"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/ds_0?serverTimezone=UTC&characterEncoding=utf-8&useInformationSchema=true" /> <property name="username" value="root" /> <property name="password" value="nrblwbb7" /> </bean> <!-- 從數據 --> <bean id="ds_1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/ds_1?serverTimezone=UTC&ccharacterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="nrblwbb7" /> </bean> <!-- 讀寫分離數據源 --> <rdb:master-slave-data-source id="dataSource" master-data-source-ref="ds_0" slave-data-sources-ref="ds_1"/> <!-- 加強版JdbcTemplate --> <!--<bean id="cxytiandiJdbcTemplate" class="com.cxytiandi.jdbc.CxytiandiJdbcTemplate"> <property name="dataSource" ref="dataSource"/> <constructor-arg> <value>com.cxytiandi.shardingjdbc.po</value> </constructor-arg> </bean> --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class User implements Serializable { private static final long serialVersionUID = -1205226416664488559L; private Long id; private String city = ""; private String name = ""; } public interface UserService { void save(User user); Object findAll(); } @Service @Slf4j public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; @Override public void save(User user) { jdbcTemplate.execute("INSERT INTO USER(city,name) values ('"+user.getCity()+"','"+user.getName()+"')"); log.info("進行插入操做, {} : ","插入成功"); } @Override public Object findAll() { Integer integer = jdbcTemplate.queryForObject("SELECT COUNT(id) FROM USER", Integer.class); log.info("從表的數據的條數是 {} 條",integer); return integer; } } @RestController @RequestMapping("user") public class UserController { @Autowired private UserService userService; @GetMapping("/save") public String save(){ userService.save(User.builder().id(1001L).city("運城").name("王智").build()); return "OK"; } @GetMapping("/list") public Object list(){ return userService.findAll(); } }
@SpringBootApplication @Slf4j @ImportResource(locations = {"classpath:sharding.xml"}) public class ShardingJdbcDemoApplication { public static void main(String[] args) { SpringApplication.run(ShardingJdbcDemoApplication.class, args); } }
運行進行訪問,先進行save操做,到數據庫查看能夠看到兩條數據,以後進行list操做,返回結果1,說明插入(寫)操做在主表,查詢在從表.spring
覺得在主表和從表之間同步是須要時間的,因此有的時候在寫完以後就要當即進行讀操做,因此這個時候就須要強制路由,讓從主表中讀取.sql
ShardingSphere使用ThreadLocal管理分片鍵值。能夠經過編程的方式向HintManager中添加分片條件,該分片條件僅在當前線程內生效。數據庫
HintManager.getInstance().setMasterRouteOnly();
在查詢前使用這句能夠指定從主庫中進行讀取數據.express
參考: http://www.javashuo.com/article/p-zuviydej-bn.html
還有不少,不過我以爲我比較喜歡的就這幾個,Hash的也很經常使用,只是我沒有用過.真正用過的就是範圍約定了.
分庫分表就是表面上的意思,將一個庫分爲多個庫,講一個表分爲多個表.
在前一個項目上進行修改
CREATE DATABASE `ds_2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; CREATE TABLE `user_0` ( `id` bigint(64) NOT NULL AUTO_INCREMENT, `city` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8; 依次建立user_1,user_2,user_3
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.dangdang.com/schema/ddframe/rdb http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd "> <!-- inline表達式報錯,就是下面user_${id.longValue() % 4}} --> <context:property-placeholder ignore-unresolvable="true"/> <!-- 主數據 --> <bean id="ds_2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" primary="true"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/ds_2?serverTimezone=UTC&characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="nrblwbb7" /> </bean> <!-- algorithm-class="com.cxytiandi.shardingjdbc.UserSingleKeyTableShardingAlgorithm" --> <!-- user_0,user_1,user_2,user_3 --> <!-- 根據用戶id來進行分表,使用inline表達式 --> <rdb:strategy id="userTableStrategy" sharding-columns="id" algorithm-expression="user_${id.longValue() % 4}"/> <!--使用自定義表達式--> <!--<rdb:strategy id="userTableStrategy" sharding-columns="id" algorithm-class="com.sharding.shardingjdbcdemo.UserSingleKeyTableShardingAlgorithm"/>--> <rdb:data-source id="dataSource"> <rdb:sharding-rule data-sources="ds_2"> <rdb:table-rules> <rdb:table-rule logic-table="user" actual-tables="user_${0..3}" table-strategy="userTableStrategy"/> </rdb:table-rules> <rdb:default-database-strategy sharding-columns="none" algorithm-class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm"/> </rdb:sharding-rule> </rdb:data-source> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
上面在使用分表的時候使用的是inline表達式.還有一種自定義表達式,上面是註釋掉的,使用的是類來進行分表,可是我測試過程一直是類型轉換異常,Integer轉不成Long,這個錯誤清除,不知道發生在哪,由於着急,就不仔細研究了,下面把自定義表達式的類貼出來,有興趣的能夠試試.
public class UserSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> { @Override public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) { for (String each : availableTargetNames) { if (each.endsWith(shardingValue.getValue() % 4 + "")) { return each; } } throw new IllegalArgumentException(); } @Override public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(availableTargetNames.size()); for (Long value : shardingValue.getValues()) { for (String tableName : availableTargetNames) { if (tableName.endsWith(value % 4 + "")) { result.add(tableName); } } } return result; } @Override public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(availableTargetNames.size()); Range<Long> range = (Range<Long>) shardingValue.getValueRange(); for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { for (String each : availableTargetNames) { if (each.endsWith(i % 4 + "")) { result.add(each); } } } return result; } }
@GetMapping("/saves") public String saves(){ for (Long i = 1L; i <= 100L; i++) { User user = User.builder() .name("王智" + i) .city("運城") .build(); user.setId(i); userService.save(user); log.info("插入的數據爲 {} " ,user); } return "ok"; }
這下就能夠測試了,在開始的時候寫的sql不是指明瞭表是User,我就很是疑惑這個是怎麼替換爲user_0~4的,這個是sharding0-jdbc自動幫咱們作的,我以爲應該相似攔截器的實現吧,也沒有細究,只知道有這麼回事.
前面說了單庫分表,那分庫分表呢?同樣的實現.
CREATE DATABASE `sharding_0` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; CREATE DATABASE `sharding_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; // 在每個數據庫中都建立兩張表 CREATE TABLE `user_0`( id bigint(64) not null, city varchar(20) not null, name varchar(20) not null, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user_1`( id bigint(64) not null, city varchar(20) not null, name varchar(20) not null, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.dangdang.com/schema/ddframe/rdb http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd "> <!-- inline表達式報錯 --> <context:property-placeholder ignore-unresolvable="true"/> <!-- 主數據 --> <bean id="ds_0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" primary="true"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/sharding_0?serverTimezone=UTC&characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="nrblwbb7" /> </bean> <bean id="ds_1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/sharding_1?serverTimezone=UTC&characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="nrblwbb7" /> </bean> <!--數據庫按照城市劃分,一個城市一個數據庫--> <rdb:strategy id="databaseShardingStrategyHouseLouDong" sharding-columns="city" algorithm-class="com.sharding.shardingjdbcdemo.SingleKeyDbShardingAlgorithm"/> <!--數據庫的表按照id劃分,奇數id存1,偶數id存0--> <rdb:strategy id="tableShardingStrategyHouseLouDong" sharding-columns="id" algorithm-expression="user_${id.longValue() % 2}" /> <rdb:data-source id="dataSource"> <rdb:sharding-rule data-sources="ds_0, ds_1"> <rdb:table-rules> <rdb:table-rule logic-table="user" actual-tables="user_${0..1}" database-strategy="databaseShardingStrategyHouseLouDong" table-strategy="tableShardingStrategyHouseLouDong"> <rdb:generate-key-column column-name="id"/> </rdb:table-rule> </rdb:table-rules> </rdb:sharding-rule> </rdb:data-source> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
public class SingleKeyDbShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<String> { private static Map<String, List<String>> shardingMap = new ConcurrentHashMap<>(); static { shardingMap.put("ds_0", Arrays.asList("山西")); shardingMap.put("ds_1", Arrays.asList("陝西")); } @Override public String doEqualSharding(final Collection<String> availableTargetNames, final ShardingValue<String> shardingValue) { for (String each : availableTargetNames) { if (shardingMap.get(each).contains(shardingValue.getValue())) { return each; } } return "ds_0"; } @Override public Collection<String> doInSharding(final Collection<String> availableTargetNames, final ShardingValue<String> shardingValue) { Collection<String> result = new LinkedHashSet<>(availableTargetNames.size()); for (String each : availableTargetNames) { if (shardingMap.get(each).contains(shardingValue.getValue())) { result.add(each); } else { result.add("ds_0"); } } return result; } @Override public Collection<String> doBetweenSharding(final Collection<String> availableTargetNames, final ShardingValue<String> shardingValue) { Collection<String> result = new LinkedHashSet<>(availableTargetNames.size()); for (String each : availableTargetNames) { if (shardingMap.get(each).contains(shardingValue.getValue())) { result.add(each); } else { result.add("ds_0"); } } return result; } }
@GetMapping("/saves") public String saves(){ for (Long i = 1L; i <= 100L; i++) { User user = User.builder() .name("王智" + i) .city("山西") .build(); user.setId(i); userService.save(user); log.info("插入的數據爲 {} " ,user); } for (Long i = 1L; i <= 100L; i++) { User user = User.builder() .name("王智" + i) .city("陝西") .build(); user.setId(i); userService.save(user); log.info("插入的數據爲 {} " ,user); } return "ok"; }
這個是基於jdbc作的分庫分表,對於spring,springboot下有不一樣的方法,參考 https://shardingsphere.apache.org/document/current/cn/manual/sharding-jdbc/usage/sharding/
爲了保證插入的主鍵不重複,因此使用分佈式主鍵,其實在前面的xml中已經添加了實現<rdb:generate-key-column column-name="id"/>
,接下來只要修改saves方法和save方法的實現就能夠,也就是不給id賦值,而且插入的時候不給id字段,不過我在實踐過程當中發現生成的id全是偶數,不知道是否是偶然,若是不是,那麼就須要從新找算法或者從新寫分配策略了.
基本就先這樣了,後面有須要的進一步研究,仍是看官方文檔比較好 https://shardingsphere.apache.org/document/current/cn/features/sharding/
上面的例子都親身實踐過,有問題能夠私聊我,我是看了http://cxytiandi.com/course/15 這個視頻課還有官方文檔來寫的,視頻裏用的是做者是進一步封裝了jdbcTemplate,筆者用的是jdbcTemplate.