摘要:当数据库出问题时能降级从本地缓存的数据中查问数据,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.stackexcha...

本文分享自华为云社区《微服务缓存中间件CoralCache表达式计算引擎详解》,原文作者:超纯的小白兔 。

点击关注,第一工夫理解华为云陈腐技术~