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

87次阅读
没有评论

共计 8961 个字符,预计需要花费 23 分钟才能阅读完成。

提高微服务可用性的中间件 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
};

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

关于提高微服务可用性的中间件 CoralCache 问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注丸趣 TV 行业资讯频道了解更多相关知识。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-04发表,共计8961字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)