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

作者 : 开心源码 本文共9262个字,预计阅读时间需要24分钟 发布时间: 2022-05-14 共243人阅读

摘要:当数据库出问题时能降级从本地缓存的数据中查询数据,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表达式计算引擎详解》,原文作者:超纯的小白兔 。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » CoralCache:一个提高微服务可用性的中间件

发表回复