CoralCache:一个提高微服务可用性的中间件
摘要:当数据库出问题时能降级从本地缓存的数据中查询数据,CoralCache就是这样一个提高微服务可用性的中间件。
背景
有些场景下,微服务依赖数据库中一些配置项或者数量很少的数据,但当数据库本身有问题时候,即使数据量很少,这个服务是不能正常工作;因此需要考虑一种能支持全量+极少变更的全局数据的场景,当数据库出问题时能降级从本地缓存的数据中查询数据,CoralCache就是这样一个提高微服务可用性的中间件。
架构
CoralCache中间件架构如下图所示,通过@EnableLocal注解开启功能,应用启动后将配置的表数据一次性加载到内存中,内存中的数据逻辑结构和数据库中的逻辑结构一样。
图1. 架构图
表达式计算引擎
内存查询引擎的原理是数据库查询降级发生后,Intercepter将拦截到的原始SQL传入查询引擎中,查询引擎解析SQL后得到表名、列名、where条件表达式,遍历InnerDB中对应表的数据行,并通过表达式计算引擎计算结果,计算结果为真则添加到结果集中最后返回给调用方。
计算引擎结构如下图所示,将where条件表达式转为后缀表达式后依次遍历后缀表达式,遇到操作数直接入栈,遇到操作符则根据操作符需要的操作数个数弹栈。
图2. 表达式计算引擎结构
然后根据操作符和弹出的操作数进行计算,不同操作符对应不同的计算方法,并将计算后的结果重新作为操作数入栈执到遍历完成,核心计算流程代码如下所示:
public Object calc(Expression where, InnerTable table, InnerRow row) {try {postTraversal(where);} catch (Exception e) {log.warn("calc error: {}", e.getMessage());return false;}for (ExprObj obj : exprList) {switch (obj.exprType()) {case ITEM:stack.push(obj);break;case BINARY_OP: {ExprObj result = calcBinaryOperation(((ExprOperation) obj).getOperationType(), table, row);stack.push(result);break;}case UNARY_OP: {ExprObj result = calcSingleOperation(((ExprOperation) obj).getOperationType(), table, row);stack.push(result);break;}case FUNCTION_OP: {ExprObj result = calcFunctionOperation(((ExprOperation) obj).getOperationType(), table, row);stack.push(result);break;}default:break;}}return stack.pop();}
常见运算符的实现
逻辑运算
逻辑常见运算符为<、<=、>、>=、=等,它们的共性都是需要2个操作数并且返回值是布尔类型。
public ExprItem logicalCalculus(InnerTable table, InnerRow row, LogicalOperation logicalOperation) {ExprObj second = stack.pop();ExprObj first = stack.pop();ExprItem result = new ExprItem();result.setItemType(ItemType.T_CONST_OBJ);Obj firstObj = getObj((ExprItem) first, table, row);Obj secondObj = getObj((ExprItem) second, table, row);boolean value = logicalOperation.apply(firstObj, secondObj);result.setValue(new Obj(value, ObjType.BOOL));return result;}
例子,以"="的实现来展示:
private ExprObj calcBinaryOperation(OperationType type, InnerTable table, InnerRow row) {ExprObj result = null;switch (type) {case T_OP_EQ:result = logicalCalculus(table, row, (a, b) -> ObjUtil.eq(a, b)); // 等于符号的实现break;...default:break;}return result;}public class ObjUtil {private static ObjType resultType(ObjType first, ObjType second) {return ObjType.RESULT_TYPE[first.ordinal()][second.ordinal()];}public static boolean eq(Obj first, Obj second) {ObjType type = resultType(first.getType(), second.getType());switch (type) {case LONG: {long firstValue = first.getValueAsLong();long secondValue = second.getValueAsLong();return firstValue == secondValue;}case DOUBLE: {double firstValue = first.getValueAsDouble();double secondValue = second.getValueAsDouble();return Double.compare(firstValue, secondValue) == 0;}case TIMESTAMP: {java.util.Date firstValue = first.getValueAsDate();java.util.Date secondValue = first.getValueAsDate();return firstValue.compareTo(secondValue) == 0;}...default:break;}throw new UnsupportedOperationException(first.getType() + " and " + second.getType() + " not support '=' operation.");}
}
数学运算
数学运算和逻辑运算的流程都一样,只不过运算后的结果为数字类型。
LIKE运算符
除了上面说的逻辑运算和数学运算外,还支持进行模糊匹配的特殊操作符LIKE。
LIKE表达式语法
常见用法如下
LIKE "%HUAWEI" 匹配以HUAWEI结尾的字符串
LIKE "HUAWEI%" 匹配以HUAWEI开头的字符串
LIKE "A_B" 匹配以"A"起头且以"Z"为结尾的字串
LIKE "A?B" 同上
LIKE "%[0-9]%" 匹配含有数字的字符串
LIKE "%[a-z]%" 匹配含有小写字母字符串
LIKE "%[!0-9]%"匹配不含数字的字符串
?和_都表示单个字符
JAVA中实现LIKE的方案:将LIKE的模式转为JAVA中的正则表达式。
LIKE词法定义
expr := wild-card + expr| wild-char + expr| escape + expr| string + expr| ""wild-card := %
wild-char := _
escape := [%|_]
string := [^%_]+ (One or > more characters that are not wild-card or wild-char)
定义Token类
public abstract class Token {private final String value;public Token(String value) {this.value = value;}public abstract String convert();public String getValue() {return value;}
}public class ConstantToken extends Token {public ConstantToken(String value) {super(value);}@Overridepublic String convert() {return getValue();}
}public class EscapeToken extends Token {public EscapeToken(String value) {super(value);}@Overridepublic String convert() {return getValue();}
}public class StringToken extends Token {public StringToken(String value) {super(value);}@Overridepublic String convert() {return Pattern.quote(getValue());}
}public class WildcardToken extends Token {public WildcardToken(String value) {super(value);}@Overridepublic String convert() {return ".*";}
}public class WildcharToken extends Token {public WildcharToken(String value) {super(value);}@Overridepublic String convert() {return ".";}
}
创建Lexer(Tokenizer)
public class Tokenizer {private Collection<Tuple> patterns = new LinkedList<>();public <T extends Token> Tokenizer add(String regex, Function<String, Token> creator) {this.patterns.add(new Tuple<Pattern, Function<String, Token>>(Pattern.compile(regex), creator));return this;}public Collection<Token> tokenize(String clause) throws RuntimeException {Collection<Token> tokens = new ArrayList<>();String copy = String.copyValueOf(clause.toCharArray());int position = 0;while (!copy.equals("")) {boolean found = false;for (Tuple tuple : this.patterns) {Pattern pattern = (Pattern) tuple.getFirst();Matcher m = pattern.matcher(copy);if (m.find()) {found = true;String token = m.group(1);Function<String, Token> fn = (Function<String, Token>) tuple.getSecond();tokens.add(fn.apply(token));copy = m.replaceFirst("");position += token.length();break;}}if (!found) {throw new RuntimeException("Unexpected sequence found in input string, at " + position);}}return tokens;}
}
创建LIKE到正则表达式的转换映射
public class LikeTranspiler {private static Tokenizer TOKENIZER = new Tokenizer().add("^(\\[[^]]*])", ConstantToken::new).add("^(%)", WildcardToken::new).add("^(_)", WildcharToken::new).add("^([^\\[\\]%_]+)", StringToken::new);public static String toRegEx(String pattern) throws ParseException {StringBuilder sb = new StringBuilder().append("^");for (Token token : TOKENIZER.tokenize(pattern)) {sb.append(token.convert());}return sb.append("$").toString();}
}
直接调用LikeTranspiler的toRegEx方法将LIKE语法转为JAVA中的正则表达式。
private ExprObj calcBinaryOperation(OperationType type, InnerTable table, InnerRow row) {ExprObj result = null;switch (type) {. . .case T_OP_LIKE:result = logicalCalculus(table, row, (a, b) -> ObjUtil.like(a, b));break;. . .}return result;}
public static boolean like(Obj first, Obj second) {Assert.state(first.getType() == ObjType.STRING, OperationType.T_OP_LIKE + " only support STRING.");Assert.state(second.getType() == ObjType.STRING, OperationType.T_OP_LIKE + " only support STRING.");String firstValue = (String) first.getRelValue();String secondValue = (String) second.getRelValue();String regEx = LikeTranspiler.toRegEx(secondValue);return Pattern.compile(regEx).matcher(firstValue).matches();}
通过创建词法分析器并使用此方法进行转换,我们可以防止LIKE像这样的子句被转换为正则表达式%abc[%]%,该子句应将其中的任何子字符串与其中的子字符串匹配,该子句将与子字符串或匹配任何字符串。abc%.abc[.].abc.abc。
类型计算转换
不同数据类型在进行计算时需要转型,具体的转化入下二维数组中。
// 不同类型计算后的类型
ObjType[][] RESULT_TYPE = {//UNKNOWN BYTE SHORT INT LONG FLOAT DOUBLE DECIMAL BOOL DATE TIME TIMESTAMP STRING NULL{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN },// UNKNOWN{ UNKNOWN, LONG, LONG, LONG, LONG, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, LONG, UNKNOWN },// BYTE{ UNKNOWN, LONG, LONG, LONG, LONG, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, LONG, UNKNOWN },// SHORT{ UNKNOWN, LONG, LONG, LONG, LONG, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, LONG, UNKNOWN },// INT{ UNKNOWN, LONG, LONG, LONG, LONG, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, LONG, UNKNOWN },// LONG{ UNKNOWN, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, DOUBLE, UNKNOWN },// FLOAT{ UNKNOWN, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DECIMAL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, DOUBLE, UNKNOWN },// DOUBLE{ UNKNOWN, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, DECIMAL, UNKNOWN },// DECIMAL{ UNKNOWN, BOOL, BOOL, BOOL, BOOL, BOOL, BOOL, BOOL, BOOL, UNKNOWN, UNKNOWN, UNKNOWN, BOOL, UNKNOWN },// BOOL{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, TIMESTAMP, TIMESTAMP, TIMESTAMP, TIMESTAMP, UNKNOWN },// DATE{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, TIMESTAMP, TIMESTAMP, TIMESTAMP, TIMESTAMP, UNKNOWN },// TIME{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, TIMESTAMP, TIMESTAMP, TIMESTAMP, TIMESTAMP, UNKNOWN },// TIMESTAMP{ UNKNOWN, LONG, LONG, LONG, LONG, DOUBLE, DOUBLE, DECIMAL, BOOL, TIMESTAMP, TIMESTAMP, TIMESTAMP, STRING, UNKNOWN },// STRING{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN },// NULL
};
参考资料
[1] https://codereview.stackexchange.com/questions/36861/convert-sql-like-to-regex/207486
本文分享自华为云社区《微服务缓存中间件CoralCache表达式计算引擎详解》,原文作者:超纯的小白兔 。
点击关注,第一时间了解华为云新鲜技术~
CoralCache:一个提高微服务可用性的中间件相关推荐
- 【微服务】Go进阶② 微服务可用性设计
文章目录 微服务可用性设计 隔离 核心隔离 快慢隔离 热点隔离 线程隔离 进程隔离 集群隔离 超时控制 双峰分布 超时原因 超时控制中间件 过载保护 常见限流的缺点 过载保护策略 如何计算接近峰值时的 ...
- Istio:一个用于微服务间通信的服务网格开源项目
http://www.infoq.com/cn/news/2017/05/istio Istio:一个用于微服务间通信的服务网格开源项目 Google.IBM和Lyft开源了微服务管理.保护和监控框架 ...
- eShopOnContainers 是一个基于微服务的.NET Core示例框架
找到一个好的示例框架很难,但不是不可能.大多数是小型Todo风格的应用程序,通常基于SimpleCRUD.值得庆幸的是,Microsoft已经为eShopOnContainers创建了一个基于微服务的 ...
- unifig,是以基于 SpringCloud 的一个分布式 微服务 平台
介绍: unifig,是以基于 SpringCloud 的一个分布式 微服务 平台. 具有 服务发现注册.配置中心.负载均衡.断路器.数据监控 . 当前开发完成b2c商场的主要业务,以及团购逻辑.是二 ...
- 【BPM技术】Zeebe是一个用于微服务编排的工作流引擎。
Zeebe是一个用于微服务编排的工作流引擎. 这篇文章将帮助你确切地了解什么是Zeebe以及它如何可能与你相关.我们将简要介绍Zeebe以及它所解决的问题,然后再进行更详细的介绍. 我们将在整个写作过 ...
- mess-cli : 一个前端微服务架构脚手架(beta版)
阅读本文需要较长的时间,本文介绍了微服务的概念.笔者心中的前端微服务,以及基于mess-cli脚手架,如何快速生成一个前端微服务架构项目 什么是微服务? 相信了解过spring cloud的同学都知道 ...
- (未完待续)浅谈微服务以及 常用中间件( zookeeper redis rabbitmq)
传统的单体框架,已经不满足目前公司战略规划要求,近几年"微服务" 这个字眼,出现的越来越频繁,虽然有过一年多微服务项目经验,也很难把微服务解释清楚,到底何为微服务? Martin ...
- 微服务微应用的安全测试_提高微服务安全性的11个方法
1.通过设计确保安全 设计安全,意味着从一开始就应该将安全性纳入软件设计中.关于安全,其中最常见的一个威胁就是恶意字符. 我问我的朋友罗伯·温奇(Rob Winch)他对删除恶意字符的想法.Rob是S ...
- 编写第一个 .NET 微服务
介绍 本文的目的是:通过创建一个返回列表的简单服务,并在 Docker 容器中运行该服务,让您熟悉使用 .NET 创建微服务的构建过程. 安装 .NET SDK 要开始构建 .NET 应用程序,首先下 ...
最新文章
- EasyUI中Calendar日历的简单使用
- J-4 Java语言基础
- BZOJ1010 [HNOI2008]玩具装箱toy 动态规划 斜率优化
- pythonwhile循环怎么修改数据类型_python基础--数据类型循环
- Windows Notepad 迎来重大更新!
- 创建SpringBoot项目的两种姿势
- python编辑距离正则匹配_详解一道腾讯面试题:编辑距离
- Python数据结构与算法(3.5)——双端队列
- VMware虚拟机端口映射(NAT设置)
- 数据库服务器sa 密码修改,修改vcenter数据库账号密码
- font-family字体-常用字体中英文对照表
- labelme快捷键修改
- 光耦隔离模拟信号的传输方式
- R_leaflet包_最易上手地图教程(一)(上)
- JavaScript数组方法大全(分为会不会改变原数组)
- 微信小程序二手汽车拍卖系统丨可以android studio运行
- linux 压缩固定大小,tar gz压缩文件为指定大小
- 小米便签开源项目本地环境搭建
- 牛客网--关于合法括号序列判断
- 零基础学UI设计能够学会吗?