• 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);
        }
    
        @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表达式计算引擎详解》,原文作者:超纯的小白兔 。

     

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

  • 相关阅读:
    opencv::Laplance算子
    opencv::Sobel算子
    Win10小娜关闭或删除进程
    python练习六十九:urllib爬取练习
    python模块之json
    导入json文件报错,TypeError expected string or buffer
    宝塔面板简单介绍
    复制虚拟机出现”适配器 的mac地址在保留地址范围内‘’
    Windows无法启动 VMware Workstation server服务解决方法
    navicat premiun连接mysql数据库报错,错误代码:1251
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/14414910.html
Copyright © 2020-2023  润新知