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

Stella981
• 阅读 353

摘要:当数据库出问题时能降级从本地缓存的数据中查询数据, CoralCache就是这样一个提高微服务可用性的中间件。

背景

有些场景下,微服务依赖数据库中一些配置项或者数量很少的数据,但当数据库本身有问题时候,即使数据量很少,这个服务是不能正常工作;因此需要考虑一种能支持全量+极少变更的全局数据的场景,当数据库出问题时能降级从本地缓存的数据中查询数据,CoralCache就是这样一个提高微服务可用性的中间件。

架构

CoralCache中间件架构如下图所示,通过**@EnableLocal**注解开启功能,应用启动后将配置的表数据一次性加载到内存中,内存中的数据逻辑结构和数据库中的逻辑结构一样。

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

图1. 架构图

表达式计算引擎

内存查询引擎的原理是数据库查询降级发生后,Intercepter将拦截到的原始SQL传入查询引擎中,查询引擎解析SQL后得到表名、列名、where条件表达式,遍历InnerDB中对应表的数据行,并通过表达式计算引擎计算结果,计算结果为真则添加到结果集中最后返回给调用方。

计算引擎结构如下图所示,将where条件表达式转为后缀表达式后依次遍历后缀表达式,遇到操作数直接入栈,遇到操作符则根据操作符需要的操作数个数弹栈。

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
待兔 待兔
2个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
皕杰报表(关于日期时间时分秒显示不出来)
在使用皕杰报表设计器时,数据据里面是日期型,但当你web预览时候,发现有日期时间类型的数据时分秒显示不出来,只有年月日能显示出来,时分秒显示为0:00:00。1.可以使用tochar解决,数据集用selecttochar(flowdate,"yyyyMMddHH:mm:ss")fromtablename2.也可以把数据库日期类型date改成timestamp
Stella981 Stella981
2年前
Django之Django模板
1、问:html页面从数据库中读出DateTimeField字段时,显示的时间格式和数据库中存放的格式不一致,比如数据库字段内容为2012082616:00:00,但是页面显示的却是Aug.26,2012,4p.m.答:为了页面和数据库中显示一致,需要在页面格式化时间,需要添加<td{{dayrecord.p\_time|date:
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
京东云开发者 京东云开发者
11个月前
Vitess全局唯一ID生成的实现方案 | 京东云技术团队
为了标识一段数据,通常我们会为其指定一个唯一id,比如利用MySQL数据库中的自增主键。但是当数据量非常大时,仅靠数据库的自增主键是远远不够的,并且对于分布式数据库只依赖MySQL的自增id无法满足全局唯一的需求。因此,产生了多种解决方案,如UUID,Sn
Python进阶者 Python进阶者
8个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这