写点什么

CoralCache:一个提高微服务可用性的中间件

发布于: 2021 年 02 月 19 日

摘要:当数据库出问题时能降级从本地缓存的数据中查询数据,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); }
@Override public String convert() { return getValue(); }}
public class EscapeToken extends Token { public EscapeToken(String value) { super(value); }
@Override public String convert() { return getValue(); }}
public class StringToken extends Token { public StringToken(String value) { super(value); }
@Override public String convert() { return Pattern.quote(getValue()); }}
public class WildcardToken extends Token { public WildcardToken(String value) { super(value); }
@Override public String convert() { return ".*"; }}
public class WildcharToken extends Token { public WildcharToken(String value) { super(value); }
@Override public 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 表达式计算引擎详解》,原文作者:超纯的小白兔 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 02 月 19 日阅读数: 20
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
CoralCache:一个提高微服务可用性的中间件