一、基本概念二、安装2.1 安装pythonyum install python3 2.2 pip3 install --user ansibleyum install -y epel-release yum install -y ansible 三、使用3.1 基础：运行命令ansible -i ./hosts remote -v -m ping -u root --private-key=~/.ssh/id_rsa 3.2 剧本（Playbooks）ansible-playbook -i ./hosts nginx.yml 四、文档http://www.ansible.com.cn/index.htmlhttps://blog.csdn.net/pushiqiang/article/details/78126063
Tesla 车机浏览器全屏代码redirect:https://v.xxx.com/search_redirect.html?url=https://www.xxxx.com/xxx/redirec.html?redirect_url=https://sunjianhua.xyz?www.xxxx.com"
1.架构代码说明2.开发规范3.使用示例说明
下载链接https://www.ej-technologies.com/download/install4j/files https://www.ej-technologies.com/download/exe4j/files// install4j License // 适用于install4j最新版的注册码，谁还会想要使用破解版呢。 // v9 scS-NEO_PENG#890808-wm1fa21hg0qqz#82622 // v10 scS-NEO_PENG#890808-3gkd3i1jyekke#262a69
#下载链接： https://www.ej-technologies.com/download/jprofiler/files// JProfiler License // 适用于JProfiler最新版本的注册码，谁还会想要使用破解版呢。 // v13 scS-NEO_PENG#890808-g4tibemn0jen#37bb9 // v14 scS-J14-NEO_PENG#890808-1jqjtz91lywcp9#23624
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.stereotype.Component; import reactor.netty.ConnectionObserver; import reactor.netty.NettyPipeline; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; @Component @Slf4j public class EncodeQueryNettyWebServerCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> { /** * 需要encode的特殊字符 */ private final List<Character> charList = new ArrayList<Character>() { { // this.add('{'); // this.add('}'); // this.add('['); // this.add(']'); this.add('|'); } }; @Override public void customize(NettyReactiveWebServerFactory factory) { factory.addServerCustomizers(httpServer -> httpServer.observe((conn, state) -> { if (state == ConnectionObserver.State.CONNECTED) { conn.channel().pipeline().addAfter(NettyPipeline.HttpCodec, "", new QueryHandler()); } })); } class QueryHandler extends ChannelInboundHandlerAdapter { QueryHandler() { } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; String url = request.uri(); // fix url log.info("url: {}", url); String[] split = url.split("\\?"); StringBuilder fixUrl = new StringBuilder(split[0]); if (split.length > 1) { fixUrl.append("?"); char[] chars = split[1].toCharArray(); for (char aChar : chars) { if (charList.contains(aChar)) { fixUrl.append(URLEncoder.encode(String.valueOf(aChar), "UTF-8")); } else { fixUrl.append(aChar); } } } log.info("fixUrl: {}", fixUrl); request.setUri(fixUrl.toString()); } ctx.fireChannelRead(msg); } } }
一、什么是 ShardingSphere简介: https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/ https://shardingsphere.apache.org/document/current/cn/overview/ 二、使用SHARDING-JDBC2.1 使用手册数据分片读写分离强制路由编排治理分布式事务数据脱敏2.2 配置手册2.3 分片集成步骤1. 引入maven依赖 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-sphere.version}</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-namespace</artifactId> <version>${sharding-sphere.version}</version> </dependency> 2. 规则配置Sharding-JDBC可以通过Java，YAML，Spring命名空间和Spring Boot Starter四种方式配置，开发者可根据场景选择适合的配置方式。详情请参见配置手册。3. 创建DataSource通过ShardingDataSourceFactory工厂和规则配置对象获取ShardingDataSource，ShardingDataSource实现自JDBC的标准接口DataSource。然后即可通过DataSource选择使用原生JDBC开发，或者使用JPA, MyBatis等ORM工具。DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props); 4.基于Java编码的规则配置Sharding-JDBC的分库分表通过规则配置描述，以下例子是根据user_id取模分库, 且根据order_id取模分表的两库两表的配置。// 配置真实数据源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一个数据源 BasicDataSource dataSource1 = new BasicDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://localhost:3306/ds0"); dataSource1.setUsername("root"); dataSource1.setPassword(""); dataSourceMap.put("ds0", dataSource1); // 配置第二个数据源 BasicDataSource dataSource2 = new BasicDataSource(); dataSource2.setDriverClassName("com.mysql.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://localhost:3306/ds1"); dataSource2.setUsername("root"); dataSource2.setPassword(""); dataSourceMap.put("ds1", dataSource2); // 配置Order表规则 TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order","ds${0..1}.t_order${0..1}"); // 配置分库 + 分表策略 orderTableRuleConfig.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")); orderTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order${order_id % 2}")); // 配置分片规则 ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig); // 省略配置order_item表规则... // ... // 获取数据源对象 DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties()); 2.4 详细示例案例1：单库多表一个库中有2个订单表，按照订单id取模，将数据路由到指定的表。###SQL脚本 drop database if exists sj_ds0; create database sj_ds0; use sj_ds0; drop table if exists t_order_0; create table t_order_0( order_id bigint not null primary key, user_id bigint not null, price bigint not null ); drop table if exists t_order_1; create table t_order_1( order_id bigint not null primary key, user_id bigint not null, price bigint not null ); drop table if exists t_user; create table t_user( id bigint not null primary key auto_increment, name varchar(128) not null ); java代码：import com.zaxxer.hikari.HikariDataSource; import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration; import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; import org.apache.shardingsphere.underlying.common.config.properties.ConfigurationPropertyKey; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class Demo1 { public static void main(String[] args) throws SQLException { /** * 1、配置真实数据源 */ Map<String, DataSource> dataSourceMap = new HashMap<>(); dataSourceMap.put("ds0", dataSource1()); /** * 2.配置表的规则 */ TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "ds0.t_order_$->{0..1}"); // 指定表的分片策略（分片字段+分片算法） orderTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_$->{order_id % 2}")); /** * 3、分片规则 */ ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); //将表的分片规则加入到分片规则列表 shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig); /** * 4、配置一些属性 */ Properties props = new Properties(); //输出sql props.put(ConfigurationPropertyKey.SQL_SHOW.getKey(), true); /** * 5、创建数据源 */ DataSource dataSource = ShardingDataSourceFactory. createDataSource(dataSourceMap, shardingRuleConfig, props); /** * 6、获取连接，执行sql */ Connection connection = dataSource.getConnection(); connection.setAutoCommit(false); /** * 测试向t_order表插入8条数据，8条数据会分散到2个表 */ PreparedStatement ps = connection.prepareStatement("insert into t_order (order_id,user_id,price) values (?,?,?)"); for (long i = 1; i <= 8; i++) { int j = 1; ps.setLong(j++, i); ps.setLong(j++, i); ps.setLong(j, 100 * i); System.out.println(ps.executeUpdate()); } connection.commit(); ps.close(); connection.close(); } private static DataSource dataSource1() { HikariDataSource dataSource1 = new HikariDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setJdbcUrl("jdbc:mysql://localhost:3306/sj_ds0?characterEncoding=UTF-8"); dataSource1.setUsername("root"); dataSource1.setPassword("root123"); return dataSource1; } } 三、分片算法策略上面介绍的案例，db的路由、表的路由都是采用取模的方式，这种方式存在一个问题：当查询条件是>, <, >=, <=、BETWEEN AND的时候，就无能为力了，此时要用其他的分片策略来解决，下面来看看如何解决。3.1 分片策略算法知识点白话解释分片策略当我们使用分库分表的时候，目标库和表都存在多个，此时执行sql，那么sql最后会落到哪个库？那个表呢？这就是分片策略需要解决的问题，主要解决2个问题：sql应该到哪个库去执行？这个就是数据库路由策略决定的sql应该到哪个表去执行呢？这个就是表的路由策略决定的所以如果要对某个表进行分库分表，需要指定则两个策略db路由策略，通过TableRuleConfiguration#setDatabaseShardingStrategyConfig进行设置table路由策略，通过TableRuleConfiguration#setTableShardingStrategyConfig进行设置分片键用于分片的数据库字段，是将数据库(表)水平拆分的关键字段。例：将订单表中的订单主键的尾数取模分片，则订单主键为分片字段。 SQL中如果无分片字段，将执行全路由，性能较差。 除了对单分片字段的支持，ShardingSphere也支持根据多个字段进行分片。分片算法通过分片算法将数据分片，支持通过=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要应用方开发者自行实现，可实现的灵活度非常高。目前提供4种分片算法。由于分片算法和业务实现紧密相关，因此并未提供内置分片算法，而是通过分片策略将各种场景提炼出来，提供更高层级的抽象，并提供接口让应用开发者自行实现分片算法。精确分片算法对应PreciseShardingAlgorithm，用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。范围分片算法对应RangeShardingAlgorithm，用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。复合分片算法对应ComplexKeysShardingAlgorithm，用于处理使用多键作为分片键进行分片的场景，包含多个分片键的逻辑较复杂，需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。Hint分片算法对应HintShardingAlgorithm，用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。5种分片策略包含分片键和分片算法，由于分片算法的独立性，将其独立抽离。真正可用于分片操作的是分片键 + 分片算法，也就是分片策略。目前提供5种分片策略。行表达式分片策略（InlineShardingStrategy）对应InlineShardingStrategy。使用Groovy的表达式，提供对SQL语句中的=和IN的分片操作支持，只支持单分片键。对于简单的分片算法，可以通过简单的配置使用，从而避免繁琐的Java代码开发，如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8，而分成8张表，表名称为t_user_0到t_user_7。标准分片策略（StandardShardingStrategy）对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键，提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的，用于处理=和IN的分片。RangeShardingAlgorithm是可选的，用于处理BETWEEN AND, >, <, >=, <=分片，如果不配置RangeShardingAlgorithm，SQL中的BETWEEN AND将按照全库路由处理。复合分片策略（ComplexShardingStrategy）对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键，由于多分片键之间的关系复杂，因此并未进行过多的封装，而是直接将分片键值组合以及分片操作符透传至分片算法，完全由应用开发者实现，提供最大的灵活度。Hint分片策略（HintShardingStrategy）对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。不分片策略对应NoneShardingStrategy。不分片的策略。SQL Hint对于分片字段非SQL决定，而由其他外置条件决定的场景，可使用SQL Hint灵活的注入分片字段。例：内部系统，按照员工登录主键分库，而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。3.2 分片场景及案例3.2.1 行表达式分片策略（InlineShardingStrategy）#适用场景 对应InlineShardingStrategy类 只支持单字段分片 通过分片字段查询，只支持=和in，不支持>=、<=、>、<、BETWEEN范围操作，否则报错 使用Groovy的表达式，对于简单的分片算法，可以通过简单的配置使用，从而避免繁琐的Java代码开发，如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8，而分成8张表，表名称为t_user_0到t_user_7。 3.2.2 复合分片策略（ComplexShardingStrategy）#适用场景 对应ComplexShardingStrategy类 提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持 ComplexShardingStrategy支持多分片键，由于多分片键之间的关系复杂，因此并未进行过多的封装，而是直接将分片键值组合以及分片操作符透传至分片算法，完全由应用开发者实现，提供最大的灵活度。 3.2.3 强制路由策略(HintShardingStrategy)ShardingSphere使用ThreadLocal管理分片键值进行Hint强制路由。可以通过编程的方式向HintManager中添加分片值，该分片值仅在当前线程内生效。#适用场景 分片字段不存在SQL中、数据库表结构中 强制在主库进行某些数据操作 3.2.4 标准分片策略（StandardShardingStrategy）#适用场景 对应StandardShardingStrategy类 提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持 StandardShardingStrategy只支持单分片键，提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法 PreciseShardingAlgorithm是必选的，用于处理=和IN的分片 RangeShardingAlgorithm是可选的，用于处理BETWEEN AND, >, <, >=, <=分片，如果不配置RangeShardingAlgorithm，SQL中的BETWEEN AND将按照全库路由处理 案例：需求ds_sss库中含有2个用户表：t_user_0、t_user_1每个表有2个字段(id,name)，id>0t_user_0：存放id范围在[1,3]内的数据，为了测试方便，范围设置的比较小，重点在于能够掌握用法t_user_1：存放id位于[4,+∞)范围内的数据sql脚本drop database if exists ds_sss; create database ds_sss; use ds_sss; drop table if exists t_user_0; create table t_user_0( id bigint not null primary key, name varchar(64) not null ); drop table if exists t_user_1; create table t_user_1( id bigint not null primary key, name varchar(64) not null ); java代码：import com.google.common.collect.Range; import com.zaxxer.hikari.HikariDataSource; import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration; import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm; import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm; import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue; import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; import org.apache.shardingsphere.underlying.common.config.properties.ConfigurationPropertyKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; public class StandardShardingStrategyTest { private static DataSource dataSource; @BeforeAll public static void init() throws SQLException { HikariDataSource ds = new HikariDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/ds_sss?characterEncoding=UTF-8"); ds.setUsername("root"); ds.setPassword("root123"); /** * 1.配置真实数据源 */ Map<String, DataSource> dataSourceMap = new LinkedHashMap<>(); dataSourceMap.put("ds", ds); //创建一个Map用来存放：id范围和表名映射关系，路由的时候会根据这个信息来找到目标表 Map<Range<Comparable>, String> idRangeTableNameMap = new HashMap<>(); idRangeTableNameMap.put(Range.closed(1, 3), "t_user_0"); idRangeTableNameMap.put(Range.atLeast(4), "t_user_1"); System.out.println(idRangeTableNameMap); /** * 2、配置t_user分片规则 */ TableRuleConfiguration userRuleConfiguration = new TableRuleConfiguration("t_user", "ds.t_user_$->{0..1}"); //设置 =,in 的算法策略 PreciseShardingAlgorithm preciseShardingAlgorithm = new PreciseShardingAlgorithm() { @Override public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) { for (Map.Entry<Range<Comparable>, String> idRangeTableNameEntity : idRangeTableNameMap.entrySet()) { final Range<Comparable> idRange = idRangeTableNameEntity.getKey(); final String tableName = idRangeTableNameEntity.getValue(); final Comparable id = shardingValue.getValue(); if (idRange.contains(id)) { System.out.println(String.format("准确路由,id：%s, tableName：%s", id, tableName)); return tableName; } } return null; } }; //设置 BETWEEN AND, >, <, >=, <= 范围算法策略 RangeShardingAlgorithm rangeShardingAlgorithm = new RangeShardingAlgorithm() { @Override public Collection<String> doSharding(Collection availableTargetNames, RangeShardingValue shardingValue) { List<String> tableNameList = new ArrayList<>(); for (Map.Entry<Range<Comparable>, String> idRangeTableNameEntity : idRangeTableNameMap.entrySet()) { final Range<Comparable> idRange = idRangeTableNameEntity.getKey(); final String tableName = idRangeTableNameEntity.getValue(); final Range valueRange = shardingValue.getValueRange(); //判断2个区间是否有交集 if (idRange.isConnected(valueRange)) { tableNameList.add(tableName); } } System.out.println(String.format("范围路由,id：%s, tableNameList：%s", shardingValue, tableNameList)); return tableNameList; } }; //配置标注路由策略 final StandardShardingStrategyConfiguration userTableShardingStrategy = new StandardShardingStrategyConfiguration("id", preciseShardingAlgorithm, rangeShardingAlgorithm); //设置表的路由策略 userRuleConfiguration.setTableShardingStrategyConfig(userTableShardingStrategy); /** * 3、加入表的分片规则 */ ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(userRuleConfiguration); /** * 4、配置一些属性 */ Properties props = new Properties(); //输出sql props.put(ConfigurationPropertyKey.SQL_SHOW.getKey(), true); /** * 5、创建数据源 */ dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props); } #测试1：插入4条数据，看sql路由情况 @Test public void test1() throws SQLException { String sql = "insert t_user (id,name) value (?,?)"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql);) { for (long id = 1; id <= 4; id++) { int parameterIndex = 1; ps.setLong(parameterIndex++, id); ps.setString(parameterIndex++, "路人-" + id); ps.executeUpdate(); } } } #测试2：批量插入数据，看sql路由情况 #验证insert t_user (id,name) value (?,?), (?,?), (?,?), (?,?)插入效果 @Test public void test2() throws SQLException { String sql = "insert t_user (id,name) value (?,?), (?,?), (?,?), (?,?)"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql);) { int parameterIndex = 1; for (long id = 1; id <= 4; id++) { ps.setLong(parameterIndex++, id); ps.setString(parameterIndex++, "路人-" + id); } System.out.println("count:" + ps.executeUpdate()); } } #测试3：查询所有数据 @Test public void test3() throws SQLException { String sql = "select id,name from t_user"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } #测试4：查询id为1的用户 @Test public void test4() throws SQLException { String sql = "select id,name from t_user where id = 1"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } #测试5：in 查询多个用户 @Test public void test5() throws SQLException { String sql = "select id,name from t_user where id in (1,2,4)"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } #测试6：!=查询 #!=,not in,<>，这种类型的，由于分片规则不知道查询的数据具体在哪个库哪个表，所以会路由到所有表。 @Test public void test6() throws SQLException { String sql = "select id,name from t_user where id != 1"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } #测试7：支持范围查询 #InlineShardingStrategy策略不支持对分片字段采用>=、<=、>、<、BETWEEN查询 @Test public void test7() throws SQLException { String sql = "select id,name from t_user where id between 1 and 2"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } #测试8：复合条件(between & or) @Test public void test8() throws SQLException { String sql = "select id,name from t_user where id between 1 and 2 or id>=4"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { final long id = rs.getLong("id"); final String name = rs.getString("name"); System.out.println(String.format("id:%s,name:%s", id, name)); } } } } 四、常见其他4.1 广播表有时候，某些表需要在所有库中都有一个，且数据是一样的，比如字典表，这种表，插入表，所有表都会写入数据，而查询时，选择一个就可以了，这种场景需要用到shardingsphere中的广播表。如何使用：需要广播的表，需要调用shardingRuleConfig.setBroadcastTable进行设置。代码如下，广播的表，插入数据会同时落到所有的库，查询只会落到一个库。ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); // 这里需要指定t_dict为广播模式 shardingRuleConfig.setBroadcastTables(Arrays.asList("t_dict")); 4.2 表关联指分片规则一致的主表和子表。例如：t_order表和t_order_item表，均按照order_id分片，则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联，关联查询效率将大大提升。举例说明，如果SQL为：SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); 在不配置绑定表关系时，假设分片键order_id将数值10路由至第0片，将数值11路由至第1片，那么路由后的SQL应该为4条，它们呈现为笛卡尔积：SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); 在配置绑定表关系后，路由的SQL应该为2条：SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); 其中t_order在FROM的最左侧，ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略，那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。案例如下：1）需求ds_order库有4张表，2张订单表，2张订单明细表2张订单表：t_order_0（存放id为奇数的数据）、t_order_1（存放id为偶数的数据）2张订单明细表：t_order_0（存放order_id为奇数的数据，和t_order_0分片规则一致）、t_order_1（存放order_id为偶数的数据，和t_order_1分片规则一致）2）sql脚本drop database if exists ds_order; create database ds_order; use ds_order; drop table if exists t_order_0; create table t_order_0( order_id bigint not null primary key, price int not null ); drop table if exists t_order_1; create table t_order_1( order_id bigint not null primary key, price int not null ); drop table if exists t_order_item_0; create table t_order_item_0( id bigint not null primary key, order_id bigint not null, price int not null ); create index idx_order_id on t_order_item_0(order_id); drop table if exists t_order_item_1; create table t_order_item_1( id bigint not null primary key, order_id bigint not null, price int not null ); create index idx_order_id on t_order_item_1(order_id); insert into t_order_0 values (1,20); insert into t_order_1 values (2,30); insert into t_order_item_0 values (1,1,5),(2,1,15); insert into t_order_item_1 values (3,2,10),(4,2,20); 3）java代码：BindingTableGroupsTest代码如下，关键代码就是配置了2张表(t_order、t_order_item)的分片策略@Slf4j public class BindingTableGroupsTest { private static DataSource dataSource; @BeforeAll public static void init() throws SQLException { HikariDataSource ds = new HikariDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/ds_order?characterEncoding=UTF-8"); ds.setUsername("root"); ds.setPassword("root123"); /** * 1.配置真实数据源 */ Map<String, DataSource> dataSourceMap = new LinkedHashMap<>(); dataSourceMap.put("ds", ds); /** * 2、配置2个表的分片规则 */ //t_order分片规则 TableRuleConfiguration orderRuleConfiguration = new TableRuleConfiguration("t_order", "ds.t_order_$->{0..1}"); InlineShardingStrategyConfiguration orderTableShardingStrategy = new InlineShardingStrategyConfiguration("order_id", "t_order_$->{(order_id + 1) % 2}"); orderRuleConfiguration.setTableShardingStrategyConfig(orderTableShardingStrategy); //t_order_item分片规则 TableRuleConfiguration orderItemRuleConfiguration = new TableRuleConfiguration("t_order_item", "ds.t_order_item_$->{0..1}"); InlineShardingStrategyConfiguration orderItemTableShardingStrategy = new InlineShardingStrategyConfiguration("order_id", "t_order_item_$->{(order_id + 1) % 2}"); orderItemRuleConfiguration.setTableShardingStrategyConfig(orderItemTableShardingStrategy); /** * 3、加入表的分片规则 */ ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(orderRuleConfiguration); shardingRuleConfig.getTableRuleConfigs().add(orderItemRuleConfiguration); /** * 关键代码为设置2个表关联 * 当2个表的路由字段相同的时候，可以设置表关联，可以避免笛卡尔积查询，下面设置t_order和t_order_item关联 */ shardingRuleConfig.setBindingTableGroups(Arrays.asList("t_order","t_order_item")); /** * 4、配置一些属性 */ Properties props = new Properties(); //输出sql props.put(ConfigurationPropertyKey.SQL_SHOW.getKey(), true); /** * 5、创建数据源 */ dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props); } } 4）测试：查询@Test public void test1() throws SQLException { String sql = "select a.order_id,b.id as order_item_id,b.price " + "from t_order a,t_order_item b " + "where a.order_id = b.order_id and a.order_id = 1"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery();) { while (rs.next()) { Long order_id = rs.getLong("order_id"); Long order_item_id = rs.getLong("order_item_id"); Integer price = rs.getInt("price"); System.out.println(String.format("order_id：%s，order_item_id：%s, price：%s", order_id, order_item_id, price)); } } } 4.3 读写分离+分片案例1：无事务读取落入从库案例2：事务中直接读取落入从库案例3：事务中写入之后读取落入主库案例4：通过HintManager强制查询走主库4.4 yml和springboot集成4.4.1 yml方式集成纯java api的方式，写起来比较繁琐，shardingsphere为我们提供了更简单的方式：yml配置文件的方式。1、使用步骤创建一个yml格式的文件，将分库分表信息配置到yml中通过yml文件创建DataSource使用DataSource执行db操作2、参考文档https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/configuration/config-yaml/4.4.2 springboot集成1. 引入jar <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency> 2. application.properties中配置分库分表信息 spring.shardingsphere.datasource.names=ds0,ds1 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/sj_ds0?characterEncoding=UTF-8 spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root123 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/sj_ds1?characterEncoding=UTF-8 spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root123 spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1} spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2} spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 2} spring.shardingsphere.props.sql.show: true 3. 创建测试类 import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @SpringBootTest @Slf4j class SpringbootDemoApplicationTests { @Autowired private DataSource dataSource; @Test void test1() throws Exception { String sql = "insert into t_order (order_id,user_id,price) values (?,?,?)"; try (Connection connection = this.dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql);) { // 插入4条数据测试,每个表会落入1条数据 for (long user_id = 1; user_id <= 2; user_id++) { for (long order_id = 1; order_id <= 2; order_id++) { int j = 1; ps.setLong(j++, order_id); ps.setLong(j++, user_id); ps.setLong(j, 100); log.info("count:{}", ps.executeUpdate()); } } } } } 4. 参考文档 https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/configuration/config-spring-boot/ 4.5 最新5.x版本2022.8月，ShardingSphere当前最新版本是 5.1.2，相对于版本4变化挺大的，代码基本重构了，扩展了很多新功能，整个系统的扩展性更强了，不过核心原理没有变，所以还是建议先看下前面12篇，然后再看本文，这样会更轻松。如果直接去看官方文档，快速上手还是有点难度的，所以有了这篇文章，本文基于 5.1.2 版，通过案例介绍其用法，引导大家如何使用，顺便会介绍如何去学习其他功能的用法。官方文档：https://shardingsphere.apache.org/document/current/cn/overview/案例如下：1）需求ds_shardingsphere5 库中有2个用户表：t_user_0，t_user_1，表中有2个字段（id：用户编号，name：姓名）这2个表的分片规则：t_user_0：存放id为偶数的用户数据t_user_1：存放id为奇数的用户数据下面来看具体实现2）执行sql脚本drop database if exists ds_shardingsphere5; create database ds_shardingsphere5; use ds_shardingsphere5; drop table if exists t_user_0; create table t_user_0( id bigint not null primary key, name varchar(128) not null ); drop table if exists t_user_1; create table t_user_1( id bigint not null primary key, name varchar(128) not null );3）项目中引入maven配置<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.1.2</version> </dependency> 4）创建测试类代码如下，有些地方可能大家看不懂，不过没关系，稍后会介绍。public class Test1 { static DataSource dataSource; //@BeforeAll 标注的方法会在 @Test 标注的方法被执行前执行一次 @BeforeAll public static void init() throws SQLException { /** * 1、创建datasource */ Map<String, DataSource> dataSourceMap = createDataSources(); /** * 2、构建具体规则 */ Collection<RuleConfiguration> ruleConfigs = new ArrayList<>(); /** * 2.1、创建分片规则 */ ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration(); //2.2、创建t_user表的分片规则 ShardingTableRuleConfiguration userShardingTableRuleConfiguration = new ShardingTableRuleConfiguration("t_user", //逻辑表 "ds1.t_user_$->{0..1}"); //实际数据节点 //2.3、配置t_user的分表规则，分片字段是：user_id,分片算法是：userShardingAlgorithm（这个算法名称是自定义的，后面会定义这个名称对应的具体算法） userShardingTableRuleConfiguration.setTableShardingStrategy( new StandardShardingStrategyConfiguration( "id", "userShardingAlgorithm")); //2.4、将t_user表的分片规则加入到shardingRuleConfiguration中 shardingRuleConfiguration.getTables().add(userShardingTableRuleConfiguration); //2.5、定义分片 Properties userShardingAlgorithmProperties = new Properties(); userShardingAlgorithmProperties.put( "algorithm-expression", "t_user_$->{id%2}" ); ShardingSphereAlgorithmConfiguration userShardingAlgorithm = new ShardingSphereAlgorithmConfiguration("INLINE", userShardingAlgorithmProperties); //2.6、将定义好的 userShardingAlgorithm 算法加入到算法列表中（算法名称->算法） shardingRuleConfiguration.getShardingAlgorithms(). put("userShardingAlgorithm", userShardingAlgorithm); //2.7、将分片规则加入规则列表 ruleConfigs.add(shardingRuleConfiguration); /** * 3、建属性配置 */ Properties props = new Properties(); props.put(ConfigurationPropertyKey.SQL_SHOW.getKey(), "true"); /** * 4、创建数据源 */ dataSource = ShardingSphereDataSourceFactory.createDataSource( "shardingsphere-demo-db", null, dataSourceMap, ruleConfigs, props); } private static Map<String, DataSource> createDataSources() { Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第 1 个数据源 HikariDataSource dataSource1 = new HikariDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setJdbcUrl("jdbc:mysql://localhost:3306/ds_shardingsphere5?characterEncoding=UTF-8"); dataSource1.setUsername("root"); dataSource1.setPassword("root123"); dataSourceMap.put("ds1", dataSource1); return dataSourceMap; } } 5）测试：插入执行下面测试方法之前，先执行下上面的的sql脚本，清理下数据。下面代码将插入2条数据，按照路由规则，会被路由到不同的用户表，这些信息都会包含在运行结果中，注意看运行结果@Test public void m1() throws Exception { String sql = "insert into t_user values (?,?),(?,?)"; try (Connection connection = dataSource.getConnection();) { connection.setAutoCommit(false); PreparedStatement ps = connection.prepareStatement(sql); int parameterIndex = 1; ps.setInt(parameterIndex++, 1); ps.setString(parameterIndex++, "user-1"); ps.setInt(parameterIndex++, 2); ps.setString(parameterIndex++, "user-2"); ps.executeUpdate(); connection.commit(); } } 运行输出，如下，id为1的被路由到了t_user_1中，id为2的被路由到了t_user_0中Logic SQL: insert into t_user values (?,?),(?,?) Actual SQL: ds1 ::: insert into t_user_1 values (?, ?) ::: [1, user-1] Actual SQL: ds1 ::: insert into t_user_0 values (?, ?) ::: [2, user-2] 6）测试：查询id为1的用户@Test public void m2() throws Exception { String sql = "select id,name from t_user where id = 1"; try (Connection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement(sql);) { ResultSet rs = ps.executeQuery(); while (rs.next()) { long id = rs.getLong("id"); String name = rs.getString("name"); System.out.println(String.format("id:%s, name:%s", id, name)); } } } 运行输出，如下，id为1的被路由到了t_user_1表Logic SQL: select id,name from t_user where id = 1 Actual SQL: ds1 ::: select id,name from t_user_1 where id = 1 id:1, name:user-1 7）问题问题1：上图中具体用的是什么算法呢？用的是org.apache.shardingsphere.sharding.algorithm.sharding.inline.InlineShardingAlgorithm这个算法类问题2：红圈中的 INLINE 字符串是什么意思？INLINE 并不是乱写的，系统会通过INLINE找到其对应的算法类，即InlineShardingAlgorithm这算法。问题3：系统是如何通过INLINE找到InlineShardingAlgorithm的？通过SPI机制找到的，SPI简单来说：通过SPI提供的方法，可以在程序中找到某个接口的所有实现类，并且会将这些实现类都进行实例化，返回返回给调用者。SPI工作机制：1、调用者给spi传入一个接口2、SPI会在当前项目中以及所有的jar中找/META-INF/services/接口完整名称这些文件，这些文件中定义了这个接口具体的实现类列表。3、读取这些文件，得到实现类列表，然后通过反射将这些类实例化，返回给调用者问题4：INLINE 这个名称是在哪里定义的呢？这要说一下shardingsphere中的spi机制了，shardingsphere中有个spi接口：org.apache.shardingsphere.spi.type.typed.TypedSPI，源码如下public interface TypedSPI { /** * Get type. * * @return type */ default String getType() { return ""; } /** * Get type aliases. * * @return type aliases */ default Collection<String> getTypeAliases() { return Collections.emptyList(); } }shardingsphere所有需要使用SPI功能的接口都必须继承上面这个接口，实现类需要实现2个方法了，而使用方可以通过type或者aliases（别名）找到具体的实现类了。INLINE对应org.apache.shardingsphere.sharding.algorithm.sharding.inline.InlineShardingAlgorithm类，这个类实现了TypedSPI接口，uml图如下其type方法如下，清晰了吧@Override public String getType() { return "INLINE"; } 上面的UML图中还可以看出：InlineShardingAlgorithm 还实现了ShardingAlgorithm 接口，而InlineShardingAlgorithm接口对应的spi文件在shardingsphere-sharding-core-5.1.2.jar/META/service/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm中，如下：这里面列出了shardingsphere为我们提供的所有常见的算法，建议都看一遍到这里大家都明了了。若要使用其他算法，只需去看org.apache.shardingsphere.sharding.spi.ShardingAlgorithm接口实现类，以及其type方法返回值，就可以了，通过type的值便引用到具体的算法，大家自行发挥。问题5：SPI得到的实例对象的属性如何设置？通过spi创建出来的对象，他们内部有很多属性，这些属性的值如何设置呢？比如INLINE对应的InlineShardingAlgorithm类，源码如下，其内部有2个属性需要设置，这2个属性是根据init方法传入的Properties来设置的，外部需要传入一个Properteis对象，这个在哪定义的呢？继续向下看algorithmExpression属性是通过getAlgorithmExpression(props)实现的，源码如下private static final String ALGORITHM_EXPRESSION_KEY = "algorithm-expression"; private String getAlgorithmExpression(final Properties props) { String expression = props.getProperty(ALGORITHM_EXPRESSION_KEY); Preconditions.checkState(null != expression && !expression.isEmpty(), "Inline sharding algorithm expression cannot be null or empty."); return InlineExpressionParser.handlePlaceHolder(expression.trim()); } 重点在props.getProperty(ALGORITHM_EXPRESSION_KEY)这行，而ALGORITHM_EXPRESSION_KEY是个常量，其值是algorithm-expression，最终algorithmExpression的值来源于props.getProperty(“algorithm-expression”)，这部分信息和测试案例中的代码遥相呼应，如下，红框中的内容就是用来定义InlineShardingAlgorithm类中属性的值的，一目了然了吧。shardingsphere中spi扩展的类，其属的值都是通过Properties来指定的。
1、如何掌握MyBatis的工作原理1、如何掌握MyBatis的工作原理？关于MyBatis的工作原理，网上的文章是汗牛充栋，但是站长觉得，要结合JDBC来理解MyBatis的工作原理往往才能更透彻。我们知道，JDBC有四个核心对象：（1）DriverManager，用于注册数据库连接（2）Connection，与数据库连接对象（3）Statement/PrepareStatement，操作数据库SQL语句的对象（4）ResultSet，结果集或一张虚拟表而MyBatis也有四大核心对象：（1）SqlSession对象，该对象中包含了执行SQL语句的所有方法【1】。类似于JDBC里面的Connection 【2】。（2）Executor接口，它将根据SqlSession传递的参数动态地生成需要执行的SQL语句，同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。（3）MappedStatement对象，该对象是对映射SQL的封装，用于存储要映射的SQL语句的id、参数等信息。（4）ResultHandler对象，用于对返回的结果进行处理，最终得到自己想要的数据格式或类型。可以自定义返回类型。2、MyBatis的工作原理以及核心流程详解MyBatis的工作原理如下图所示：上面中流程就是MyBatis内部核心流程，每一步流程的详细说明如下文所述：（1）读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件，用于配置数据库连接信息。（2）加载映射文件。映射文件即SQL映射文件，该文件中配置了操作数据库的SQL语句，需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件，每个文件对应数据库中的一张表。（3）构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。（4）创建会话对象。由会话工厂创建SqlSession对象，该对象中包含了执行SQL语句的所有方法。（5）Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库，它将根据SqlSession传递的参数动态地生成需要执行的SQL语句，同时负责查询缓存的维护。（6）MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数，该参数是对映射信息的封装，用于存储要映射的SQL语句的id、参数等信息。（7）输入参数映射。输入参数类型可以是Map、List等集合类型，也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。（8）输出结果映射。输出结果类型可以是Map、List等集合类型，也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
一、微服务架构中常见概念服务注册与服务发现服务注册: 服务提供者将所提供服务的信息(服务器IP和端口、服务访问协议等) 注册/登记到注册中心。服务发现: 服务消费者能够从注册中心获取到较为实时的服务列表，然后根究一定 的策略选择一个服务访问。负载均衡负载均衡即将请求压力分配到多个服务器(应用服务器、数据库服务器等)，以此来提高服务的性能、可靠性。熔断即断路保护。微服务架构中，如果下游服务因访问压力过大而响应变慢或失败，上游服务为了保护系统整体可用性，可以暂时切断对下游服务的调用。这种牺牲局部，保全整体的措施就叫做熔断。链路追踪微服务架构越发流行，一个项目往往拆分成很多个服务，那么一次请求就需要涉及到很多个服务。不同的微服务可能是由不同的团队开发、可能使用不同的编程语言实现、整个项目也有可能部署在了很多服务器上(甚至百台、千台)横跨多个不同的数据中心。所谓链路追踪，就是对一次请求涉及的很多个服务链路进行日志记录、性能监控。API 网关API请求调用统一接入API网关层， 由网关转发请求。API网关更专注在安全、路由、流量等问题的处理上(微服务团队 专注于处理业务逻辑即可)，它的功能：统一接入(路由)安全防护(统一鉴权，负责网关访问身份认证验证，与“访问认证中心”通信，实际认证业务逻辑交移“访问认证中心”处理)黑白名单(实现通过IP地址控制禁止访问网关功能，控制访问)协议适配(实现通信协议校验、适配转换的功能)流量管控(限流)⻓短链接支持容错能力(负载均衡)二、常见的组件2.1 服务注册中心组件 EurekaEureka 交互流程及原理Eureka 包含两个组件:Eureka Server 和 Eureka Client，Eureka Client是一个 Java客户端，用于简化与Eureka Server的交互；Eureka Server提供服务发现的能力，各个微服务启动时，会通过Eureka Client向Eureka Server 进行注册自己 的信息(例如网络信息)，Eureka Server会存储该服务的信息。图中us-east-1c、us-east-1d，us-east-1e代表不同的区也就是不同的机房图中每一个Eureka Server都是一个集群。图中Application Service作为服务提供者向Eureka Server中注册服务， Eureka Server接受到注册事件会在集群和分区中进行数据同步，Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信 息，进行服务调用。微服务启动后，会周期性地向Eureka Server发送心跳(默认周期为30秒) 以续约自己的信息Eureka Server在一定时间内没有接收到某个微服务节点的心跳，Eureka Server将会注销该微服务节点(默认90秒)每个Eureka Server同时也是Eureka Client，多个Eureka Server之间通过复 制的方式完成服务注册列表的同步Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节 点都宕掉，服务消费者依然可以使用缓存中的信息找到服务提供者Eureka通过心跳检测、健康检查和客户端缓存等机制，提高系统的灵活性、可 伸缩性和可用性。Eureka集群：Eureka的 元数据 有两种：标准元数据和自定义元数据。标准元数据: 主机名、IP地址、端口号等信息，这些信息都会被发布在服务注册表中，用于服务之间的调用。自定义元数据: 可以使用eureka.instance.metadata-map配置，符合KEY/VALUE的 存储格式。这 些元数据可以在远程客户端中访问。2.2 Ribbon负载均衡负载均衡一般分为服务器端负载均衡和客户端负载均衡 所谓服务器端负载均衡，比如Nginx、F5这些，请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理。所谓客户端负载均衡，比如我们要说的Ribbon，服务消费者客户端会有一个服务器 地址列表，调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问，负载均衡算法的执行是在请求客户端进行。Ribbon是Netflix发布的负载均衡器。Eureka一般配合Ribbon进行使用，Ribbon利 用从Eureka中读取到服务信息，在调用服务提供者提供的服务时，会根据一定的算法进行负载。2.3 Hystrix熔断器Hystrix(豪猪)，宣言“defend your app”是由Netflix开源的一个 延迟和容错库，用于隔离访问远程系统、服务或者第三方库，防止级联失败，从而 提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。跳闸机制:当某服务的错误率超过一定的阈值时，Hystrix可以跳闸，停止请求 该服务一段时间。资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)(或者信号 量)。如果该线程池已满， 发往该依赖的请求就被立即拒绝，而不是排队等待，从而加速失败判定。监控:Hystrix可以近乎实时地监控运行指标和配置的变化，例如成功、失败、 超时、以及被拒绝 的请求等。回退机制:当请求失败、超时、被拒绝，或当断路器打开时，执行回退逻辑。回退逻辑由开发人员自行提供，例如返回一个缺省值。自我修复:断路器打开一段时间后，会自动进入“半开”状态。扇入、扇出概念：扇入: 代表着该微服务被调用的次数，扇入大，说明该模块复用性好。扇出: 该微服务调用其他微服务的个数，扇出大，说明业务逻辑复杂。扇入大是一个好事，扇出大不一定是好事。微服务架构中，一个应用可能会有多个微服务组成，微服务之间的数据交互通过 远程过程调用完成。这就带来一个问题，假设微服务A调用微服务B和微服务C，微 服务B和微服务C又调用其它的微服务，这就是所谓的“扇出”。如果扇出的链路上某 个微服务的调用响应时间过⻓或者不可用，对微服务A的调用就会占用越来越多的系 统资源，进而引起系统崩溃，所谓的“雪崩效应”。Hystrix舱壁模式：Hystrix工作流程：2.4 Feign远程调用组件Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求， 远程调用的)，是以Java接口注解的方式调用Http请求，而不用像Java中通过封装 HTTP请求报文的方式直接调用，Feign被广泛应用在Spring Cloud 的解决方案中。类似于Dubbo，服务消费者拿到服务提供者的接口，然后像调用本地接口方法一样 去调用，实际发出的是远程的请求。Feign可帮助我们更加便捷，优雅的调用HTTP API:不需要我们去拼接url调用restTemplate的api，在SpringCloud中，使用Feign非常简单，创建一个接口(在消费者服务调用方这一端)，并在接口上添加一些注解，代码就完成了SpringCloud对Feign进行了增强，使Feign支持了SpringMVC注解 (OpenFeign)本质：封装了Http调用流程，更符合面向接口化的编程习惯，类似于Dubbo的服务调用。Feign调用流程：2.5 GateWay网关组件Spring Cloud GateWay是Spring Cloud的一个全新项目，目标是取代Netflix Zuul， 它基于Spring5.0+SpringBoot2.0+WebFlux(基于高性能的Reactor模式响应式通信 框架Netty，异步非阻塞模型)等技术开发，性能高于Zuul，官方测试，GateWay是 Zuul的1.6倍，旨在为微服务架构提供一种简单有效的统一的API路由管理方式。Spring Cloud GateWay不仅提供统一的路由方式(反向代理)并且基于 Filter(定义 过滤器对请求过滤，完成一些功能) 链的方式提供了网关基本的功能，例如:鉴权、 流量控制、熔断、路径重写、日志监控等。GateWay核心概念：Zuul1.x 阻塞式IO 2.x 基于NettySpring Cloud GateWay天生就是异步非阻塞的，基于Reactor模型一个请求到达网关根据一定的条件匹配—匹配成功之后可以将请求转发到指定的服务 地址;而在这个过程中，我们可以进行一些比较具体的控制(限流、日志、黑白名 单)路由(route): 网关最基础的部分，也是网关比较基础的工作单元。路由由一 个ID、一个目标URL(最终路由到的地址)、一系列的断言(匹配条件判断)和 Filter过滤器(精细化控制)组成。如果断言为true，则匹配该路由。断言(predicates):参考了Java8中的断言java.util.function.Predicate，开发 人员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于 nginx中的location匹配一样)，如果断言与请求相匹配则路由。过滤器(filter):一个标准的Spring webFilter，使用过滤器，可以在请求之前或者之后执行业务逻辑。其中，Predicates断言就是我们的匹配条件，而Filter就可以理解为一个无所不能的拦截器，有了这两个元素，结合目标URL，就可以实现一个具体的路由转发。GateWay工作过程：客户端向Spring Cloud GateWay发出请求，然后在GateWay Handler Mapping中 找到与请求相匹配的路由，将其发送到GateWay Web Handler;Handler再通过指 定的过滤器链来将请求发送到我们实际的服务执行业务逻辑，然后返回。过滤器之 间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或者之后(post)执 行业务逻辑。Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等，在“post”类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流 量监控等。核心逻辑： 路由转发+执行过滤器链。GateWay路由规则详解：GateWay过滤器：主要有两个pre和post:pre：这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份 验证、在集群中选择 请求的微服务、记录调试信息等。post：这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加 标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给 客户端等。从过滤器类型的⻆度，Spring Cloud GateWay的过滤器分为GateWayFilter和 GlobalFilter两种：GateWayFilter：应用到单个路由路由上GlobalFilter：应用到所有的路由上2.6 Spring Cloud Config 分布式配置中心Spring Cloud Config是一个分布式配置管理方案，包含了 Server端和 Client端两个部分。Server 端:提供配置文件的存储、以接口的形式将配置文件的内容提供出去， 通过使用@EnableConfigServer注解在 Spring boot 应用中非常简单的嵌入Client 端:通过接口获取配置数据并初始化自己的应用Spring Cloud Bus(基于MQ的，支持RabbitMq/Kafka) 是Spring Cloud中的消息 总线方案，Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新。2.7 Spring Cloud Stream消息驱动组件Spring Cloud Stream 消息驱动组件帮助我们更快速，更方便，更友好的去构建消息 驱动微服务的。当时定时任务和消息驱动的一个对比。(消息驱动:基于消息机制做一些事情) MQ:消息队列/消息中间件/消息代理，产品有很多，ActiveMQ、RabbitMQ、RocketMQ、Kafka。本质：屏蔽掉了底层不同MQ消息中间件之间的差异，统一了MQ的编程模型，降低了学习、开发、维护MQ的成本。Spring Cloud Stream 是一个构建消息驱动微服务的框架。应用程序通过inputs(相 当于消息消费者consumer)或者outputs(相当于消息生产者producer)来与 Spring Cloud Stream中的binder对象交互，而Binder对象是用来屏蔽底层MQ细节 的，它负责与具体的消息中间件交互。Binder绑定器是Spring Cloud Stream 中非常核心的概念，就是通过它来屏蔽底层 不同MQ消息中间件的细节差异，当需要更换为其他消息中间件时，我们需要做的就 是更换对应的Binder绑定器而不需要修改任何应用逻辑(Binder绑定器的实现是框架内置的，Spring Cloud Stream目前支持Rabbit、Kafka等多种消息队列)。2.8 分布式链路追踪技术 Sleuth + Zipkin分布式链路追踪技术核心思想：记录日志，作为一个完整的技术，分布式链路追踪也有自己的理论和概念微服务架构中，针对请求处理的调用链可以展现为一棵树，示意如下：上图标识一个请求链路，一条链路通过TraceId唯一标识，span标识发起的请求信 息，各span通过parrentId关联起来 Trace:服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始，到被追踪系统向客户返回响应(response)为止的过程。Trace ID: 为了实现请求跟踪，当请求发送到分布式系统的入口端点时，只需要服 务跟踪框架为该请求创建一个唯一的跟踪标识Trace ID，同时在分布式系统内部流转 的时候，框架失踪保持该唯一标识，直到返回给请求方。一个Trace由一个或者多个Span组成，每一个Span都有一个SpanId，Span中会记录 TraceId，同时还有一个叫做ParentId，指向了另外一个Span的SpanId，表明父子 关系，其实本质表达了依赖关系。Span ID: 为了统计各处理单元的时间延迟，当请求到达各个服务组件时，也是通过 一个唯一标识Span ID来标记它的开始，具体过程以及结束。对每一个Span来说， 它必须有开始和结束两个节点，通过记录开始Span和结束Span的时间戳，就能统计 出该Span的时间延迟，除了时间戳记录之外，它还可以包含一些其他元数据，比如 时间名称、请求信息等。每一个Span都会有一个唯一跟踪标识 Span ID,若干个有序的 span 就组成了一个 trace。Span可以认为是一个日志数据结构，在一些特殊的时机点会记录了一些日志信息， 比如有时间戳、spanId、TraceId，parentIde等，Span中也抽象出了另外一个概 念，叫做事件，核心事件如下CS :client send/start 客户端/消费者发出一个请求，描述的是一个span开始SR: server received/start 服务端/生产者接收请求 SR-CS属于请求发送的网络延 迟SS: server send/finish 服务端/生产者发送应答 SS-SR属于服务端消耗时间CR:client received/finished 客户端/消费者接收应答 CR-SS表示回复需要的时 间(响应的网络延迟)Spring Cloud Sleuth (追踪服务框架)可以追踪服务之间的调用，Sleuth可以记录 一个服务请求经过哪些服务、服务处理时⻓等，根据这些，我们能够理清各微服务 间的调用关系及进行问题追踪分析。耗时分析:通过 Sleuth 了解采样请求的耗时，分析服务性能问题(哪些服务调 用比较耗时)链路优化:发现频繁调用的服务，针对性优化等 Sleuth就是通过记录日志的方式来记录踪迹数据的我们往往把Spring Cloud Sleuth 和 Zipkin 一起使用，把 Sleuth 的数据信 息发送给 Zipkin 进行聚合，利用 Zipkin 存储并展示数据。2.9 Spring Cloud OAuth2 + JWT微服务架构下统一认证思路基于Session的认证方式： 在分布式的环境下，基于session的认证会出现一个问题，每个应用服务都需要 在session中存储用户身份信息，通过负载均衡将本地的请求分配到另一个应用 服务需要将session信息带过去，否则会重新认证。我们可以使用Session共享、 Session黏贴等方案。Session方案也有缺点，比如基于cookie，移动端不能有效使用等基于token的认证方式： 服务端不用存储认证数据，易维护扩展性强， 客户端可 以把token 存在任意地方，并且可以实现web和app统一认证机制。其缺点也很 明显，token由于自包含信息，因此一般数据量较大，而且每次请求 都需要传 递，因此比较占带宽。另外，token的签名验签操作也会给cpu带来额外的处理 负担。OAuth2开放授权协议/标准OAuth(开放授权)是一个开放协议/标准，OAuth 2.0 的标准是 RFC 6749 文件。允许用户授权第三方应用访问他们存储 在另外的服务提供者上的信息，而不需要将用户名和密码提供给第三方应用或分享 他们数据的所有内容。允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息，而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容角色：资源所有者(Resource Owner):可以理解为用户自己客户端(Client):我们想登陆的网站或应用认证服务器(Authorization Server):可以理解为微信或者QQ资源服务器(Resource Server):可以理解为微信或者QQResource Owner在Authorization Server注册Client信息，通过Client去访问Authorization Server拿到token凭证，通过token凭证去Resource Server获取自己的资源OAuth2的颁发Token授权方式授权码(authorization-code) ：授权码（authorization code）方式，指的是第三方应用先申请一个授权码，然后再用该码获取令牌。密码式(password)：如果你高度信任某个应用，RFC 6749 也允许用户把用户名和密码，直接告诉该应用。该应用就使用你的密码，申请令牌，这种方式称为"密码式"（password）。隐藏式(implicit)：有些 Web 应用是纯前端应用，没有后端。这时就不能用上面的方式了，必须将令牌储存在前端。RFC 6749 就规定了第二种方式，允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤，所以称为（授权码）"隐藏式"（implicit）。客户端凭证(client credentials)：最后一种方式是凭证式（client credentials），适用于没有前端的命令行应用，即在命令行下请求令牌。JWT令牌JSON Web Token(JWT)是一个开放的行业标准(RFC 7519)，它定义了一种简介 的、自包含的协议格式，用于 在通信双方传递json对象，传递的信息经过数字签名 可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公 钥/私钥对来签名，防止被篡改。JWT令牌结构：JWT令牌由三部分组成，每部分中间使用点(.)分隔，比如:xxxxx.yyyyy.zzzzzHeader：头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或 RSA)，例如{ "alg" : "HS256" , "typ" : "JWT" }将上边的内容使用Base64Url编码，得到一个字符串就是JWT令牌的第一部分。 Payload第二部分是负载，内容也是一个json对象，它是存放有效信息的地方，它可以存 放jwt提供的现成字段，比 如:iss(签发者),exp(过期时间戳), sub(面向的 用户)等，也可自定义字段。 此部分不建议存放敏感信息，因为此部分可以解 码还原原始内容。 最后将第二部分负载使用Base64Url编码，得到一个字符串 就是JWT令牌的第二部分。 一个例子:{ "sub" : "1234567890" , "name" : "John Doe" , "iat" : 1516239022 }Signature第三部分是签名，此部分用于防止jwt内容被篡改。 这个部分使用base64url将 前两部分进行编码，编码后使用点(.)连接组成字符串，最后使用header中声 明 签名算法进行签名。HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)base64UrlEncode(header):jwt令牌的第一部分。base64UrlEncode(payload):jwt令牌的第二部分。secret:签名所使用的密钥。三、SpringCloudAlibaba微服务一站式解决方案Spring Cloud Alibaba 是什么微服务全景图1 配置中心 Nacos2 服务注册与发现 Nacos3 分布式事务 Seata4 分布式限流Sentinel 5 分布式消息 spring-cloud-starter-stream-rocketmq6 异构服务 spring-cloud-starter-alibaba-sidecar7 适配GraalVM 网址：https://sca.aliyun.com/zh-cn/