首页 > 其他分享 >[Zebra] 分片路由和寻找分片键值的基本过程

[Zebra] 分片路由和寻找分片键值的基本过程

时间:2024-08-13 21:39:14浏览次数:14  
标签:String List 分片 tableName simpleName 键值 Zebra sql

路由规则匹配

分库分表路由规则是表+字段的维度,首先要将 sql 中的表识别出来,然后和规则进行匹配, 然后才能根据规则确定分库分表是用哪个字段、按照什么分片算法,比如 userId 8 库 128表;

zebra 中 DefaultShardRouter#router 路由器首先进行 sql 解析 SQLParsedResult parsedResult = SQLParser.parseWithCache(sql); 生成 druid ast 语法树 SQLStatement

	  MySqlLexer lexer = new MySqlLexer(sql);
		HintCommentHandler commentHandler = new HintCommentHandler();
		lexer.setCommentHandler(commentHandler);
		lexer.nextToken();

		SQLStatementParser parser = new MySqlStatementParser(lexer);
		List<SQLStatement> stmtList = parser.parseStatementList();
		if (stmtList.size() == 1) {
			SQLParsedResult sqlParsedResult = parseInternal(stmtList.get(0));
			sqlParsedResult.getRouterContext().setSqlhint(sqlhint);
			return sqlParsedResult;
		}
		//.... 省略

并通过 MySqlASTVisitorAdapter 访问器提取出 sql 语句中的表名 和 字段

public class AbstractMySQLASTVisitor extends MySqlASTVisitorAdapter {

	protected SQLParsedResult result;

	public AbstractMySQLASTVisitor(SQLParsedResult result) {
		this.result = result;
	}

 /** 遍历表名,存到解析结果中 **/
	@Override
	public boolean visit(SQLExprTableSource x) {
		SQLName table = (SQLName) x.getExpr();
		String simpleName = table.getSimpleName();
		String tableName = simpleName.startsWith("`") ? parseTableName(simpleName) : simpleName;

		result.getRouterContext().getTableSet().add(tableName);

		return true;
	}

	private String parseTableName(String tableName) {
		StringBuilder sb = new StringBuilder(tableName.length());
		for (int i = 0; i < tableName.length(); ++i) {
			if (tableName.charAt(i) != '`') {
				sb.append(tableName.charAt(i));
			}
		}

		return sb.toString();
	}

	public SQLParsedResult getResult() {
		return result;
	}
}

然后,就可以根据 表名 和路由规则进行匹配,获取表对应的路由规则

分片键的值

经过前面的步骤,sql 里边表的路由规则已经确定,接下来就要获取分片键的实际值, 然后根据分片算法定位到目标表的索引 比如 userId = 1 那么目标表就是 t_user_1

这是个繁琐的过程,需要遍历 SQLStatement 语法树,根据 sql 的不同类型,先找到对应的分片键 再找分片键是值, 值可能是参数化的 也可能是字面量; 如果是带子查询、联表等场景就更复杂了, 不过个人认为既然是分库分表场景 就不应该去支持太多复杂sql

这边简单看下 insert sql 分片键值查找过程
com.dianping.zebra.shard.router.DefaultShardRouter#routerOneRule
-> ShardEvalResult shardResult = tableShardRule.eval(new ShardEvalContext(parsedResult, params, optimizeIn));
-> TableShardRule#evalDimension()

ShardColumnValueUtil#eval() 解析列值

	private static Collection<Object> evalInsert(SQLParsedResult parseResult, String column, List<Object> params,
	      boolean isBatchInsert) {
		MySqlInsertStatement stmt = (MySqlInsertStatement) parseResult.getStmt();

		List<SQLExpr> columns = stmt.getColumns();
		List<SQLInsertStatement.ValuesClause> valuesList = stmt.getValuesList();  // 取出 insert 语句的 values() 部分

		if (isBatchInsert) {
			List<Object> evalList = new LinkedList<Object>();
			parseBatchValueList(evalList, params, columns, valuesList, column);
			return evalList;
		} else {
			// use the first value in the values 
			// 解析insert值
			Set<Object> evalSet = new LinkedHashSet<Object>();
			parseValueList(evalSet, params, columns, valuesList, column);
			return evalSet;
		}
		
		
	 private static void parseValueList(Set<Object> evalSet, List<Object> params, List<SQLExpr> columns,
	      List<SQLInsertStatement.ValuesClause> valuesList, String column) {
		SQLInsertStatement.ValuesClause values = valuesList.get(0);
		for (int i = 0; i < columns.size(); i++) {
			SQLName columnObj = (SQLName) columns.get(i);
			if (evalColumn(columnObj.getSimpleName(), column)) {
				SQLExpr sqlExpr = values.getValues().get(i); 
				if (sqlExpr instanceof SQLVariantRefExpr) {  // 如果 sql insert 分片键的值是占位符,则从参数列表中取出来
					SQLVariantRefExpr ref = (SQLVariantRefExpr) sqlExpr;
					evalSet.add(params.get(ref.getIndex()));
				} else if (sqlExpr instanceof SQLValuableExpr) { // 如果分片键的值是字面量,则直接拿字面量的值返回
					evalSet.add(((SQLValuableExpr) sqlExpr).getValue()); 
				}
				break;
			}
		}
	}
	

对于 insert 语句,需要从 values() 语句部分去遍历目标分片键的位置和值, 对于 select/update/delete 以及其他类型,过程则是根据 sql 去处理

表改写

取到目标分片键的值后,就可以根据路由算法计算出最终物理表,并进行改写; druid ast中提供比较方便的 MySqlOutputVisitor 访问器,可以在遍历之前生产的语法树 SQLStatment 反向打印 sql 的过程中,对sql语句进行改写

Zebra 中 ShardRewriteTableOutputVisitor 继承 MysqlOutputVisitor 重写了 visit(SQLExprTableSource) 方法

	public boolean visit(SQLExprTableSource x) {
			SQLName name = (SQLName) x.getExpr();
			String simpleName = name.getSimpleName();
			boolean hasQuote = simpleName.charAt(0) == '`';
			String tableName = hasQuote ? parseTableName(simpleName) : simpleName;  // 获取逻辑表名
			String finalTable = tableMapping.get(tableName); // 获取计算好的目标分表 

			if (finalTable != null) {
				if (hasQuote) {
					print0("`" + finalTable + "`"); // 替换成目标分表名
				} else {
					print0(finalTable);
				}
			} else {
				x.getExpr().accept(this);
			}

			if (x.getAlias() != null) {
				print(' ');
				print0(x.getAlias());
			}

			for (int i = 0; i < x.getHintsSize(); ++i) {
				print(' ');
				x.getHints().get(i).accept(this);
			}

			return false;
		}

标签:String,List,分片,tableName,simpleName,键值,Zebra,sql
From: https://www.cnblogs.com/mushishi/p/18357695

相关文章

  • 0224-网络层的分片
    环境Time2022-11-20WSL-Ubuntu22.04Rust1.65.0pnet0.31.0tun-tap0.1.3前言说明参考:https://docs.rs/pnet/latest/pnet/index.html目标通过ping命令来认识网络层中的分片。查看MTU可以看到最大的MTU为1500。root@jiangbo12490:~#ipaddrshowdevtun0......
  • 传知代码-动态键值记忆网络解决知识追踪(论文复现)
    代码以及视频讲解本文所涉及所有资源均在传知代码平台可获取1.论文概述复现论文:DynamicKey-ValueMemoryNetworksforKnowledgeTracing(DKVMN)知识追踪(KT)是追踪学生在一系列学习活动中知识状态演变的任务。其目的是个性化地指导学生的学习,帮助他们高效地掌握知识概......
  • 使用ansible安装mongodb分片集群
    【说明】使用ansible安装一个分片集群,三台服务器,三个mongos,三个config,三个分片节点,每三个分片有三个副本(每个节点运行三个端口的mongod)  [mongo_servers]10.x.x.21ansible_user=rootansible_ssh_pass=xxxxxxxxcluster_role=mongo1......
  • 如何更新 jinja2 中的字典键值列表
    我已将字典的键domain设置为空字符串“”。我正在检查dictkeyssluser的var字段是否已定义,如果是,则我需要从提供的用户值中填写电子邮件域数据。{%setsslusers=[{'ssluser':SSL_VPN_User01,'sslusercred':SSL_USER_CRED......
  • 如何按特定键值有效过滤字典列表?
    我正在开发一个Python项目,我需要根据特定键的值过滤字典列表。例如,给定一个如下所示的字典列表:data=[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Charlie","age":35}......
  • mongodb数据库范围分片数据分布不均匀
    【说明】当前使用mongodb分片,三个分片mongos>sh.status()---ShardingStatus---shardingversion:{"_id":1,"minCompatibleVersion":5,"currentVersion":6,"clusterId":ObjectId(&quo......
  • vue-simple-uploader 支持分片上传,多文件上传,断点续传等多种功能的文件上传组件
    vue-simple-uploader特性:1、支持文件、多文件、文件夹上传2、支持拖拽文件、文件夹上传3、统一对待文件和文件夹,方便操作管理4、可暂停、继续上传5、错误处理6、支持“快传”,通过文件判断服务端是否已存在从而实现“快传”7、上传队列管理,支持最大并发上传8、分块上传9、......
  • java 集合框架-map(键值对集合)
    一、Map接口 (键值对集合)1.实现类(1).线程不安全HashMap1.特点:        ①无序          ②查找效率高:根据key,查找value2.数据结构:数组(哈希表)+链表(链地址法解决哈希表冲突)+红黑树(自平衡二叉树,提高查找效率)      ①数组(哈希表):Ha......
  • 如何将 json 文件的文件名写入同一个 json 文件的键值对?
    我在每个图像的json文件中存储了注释,但这些文件没有图像的文件名作为其中的键,因此我想输入这些json文件的文件名作为其中的字段。eg:|||应包含文件名字段。Train1.jpeg.json我正在尝试使这些文件对coco格式有效。{"filename":"train1.jpeg""d......
  • PowerShell 命令来操作 Windows 注册表 Get-ItemProperty 命令可以获取指定注册表路径
    PowerShell提供了一些命令和方法来操作Windows注册表。以下是一些常用的PowerShell命令和示例:1.获取注册表项的值使用Get-ItemProperty命令可以获取指定注册表路径下的键值信息。powershellCopyCode#获取注册表项的值Get-ItemProperty-Path"HKCU:\Software\Micro......