共計(jì) 8961 個(gè)字符,預(yù)計(jì)需要花費(fèi) 23 分鐘才能閱讀完成。
提高微服務(wù)可用性的中間件 CoralCache,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
當(dāng)數(shù)據(jù)庫(kù)出問(wèn)題時(shí)能降級(jí)從本地緩存的數(shù)據(jù)中查詢(xún)數(shù)據(jù),
CoralCache 就是這樣一個(gè)提高微服務(wù)可用性的中間件。
背景
有些場(chǎng)景下,微服務(wù)依賴(lài)數(shù)據(jù)庫(kù)中一些配置項(xiàng)或者數(shù)量很少的數(shù)據(jù),但當(dāng)數(shù)據(jù)庫(kù)本身有問(wèn)題時(shí)候,即使數(shù)據(jù)量很少,這個(gè)服務(wù)是不能正常工作;因此需要考慮一種能支持全量 + 極少變更的全局?jǐn)?shù)據(jù)的場(chǎng)景,當(dāng)數(shù)據(jù)庫(kù)出問(wèn)題時(shí)能降級(jí)從本地緩存的數(shù)據(jù)中查詢(xún)數(shù)據(jù),CoralCache 就是這樣一個(gè)提高微服務(wù)可用性的中間件。
架構(gòu)
CoralCache 中間件架構(gòu)如下圖所示,通過(guò) @EnableLocal 注解開(kāi)啟功能,應(yīng)用啟動(dòng)后將配置的表數(shù)據(jù)一次性加載到內(nèi)存中,內(nèi)存中的數(shù)據(jù)邏輯結(jié)構(gòu)和數(shù)據(jù)庫(kù)中的邏輯結(jié)構(gòu)一樣。
圖 1. 架構(gòu)圖
表達(dá)式計(jì)算引擎
內(nèi)存查詢(xún)引擎的原理是數(shù)據(jù)庫(kù)查詢(xún)降級(jí)發(fā)生后,Intercepter 將攔截到的原始 SQL 傳入查詢(xún)引擎中,查詢(xún)引擎解析 SQL 后得到表名、列名、where 條件表達(dá)式,遍歷 InnerDB 中對(duì)應(yīng)表的數(shù)據(jù)行,并通過(guò)表達(dá)式計(jì)算引擎計(jì)算結(jié)果,計(jì)算結(jié)果為真則添加到結(jié)果集中最后返回給調(diào)用方。
計(jì)算引擎結(jié)構(gòu)如下圖所示,將 where 條件表達(dá)式轉(zhuǎn)為后綴表達(dá)式后依次遍歷后綴表達(dá)式,遇到操作數(shù)直接入棧,遇到操作符則根據(jù)操作符需要的操作數(shù)個(gè)數(shù)彈棧。
圖 2. 表達(dá)式計(jì)算引擎結(jié)構(gòu)
然后根據(jù)操作符和彈出的操作數(shù)進(jìn)行計(jì)算,不同操作符對(duì)應(yīng)不同的計(jì)算方法,并將計(jì)算后的結(jié)果重新作為操作數(shù)入棧執(zhí)到遍歷完成,核心計(jì)算流程代碼如下所示:
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();
}
常見(jiàn)運(yùn)算符的實(shí)現(xiàn)邏輯運(yùn)算
邏輯常見(jiàn)運(yùn)算符為、=、、=、= 等,它們的共性都是需要 2 個(gè)操作數(shù)并且返回值是布爾類(lèi)型。
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;
}
例子,以 = 的實(shí)現(xiàn)來(lái)展示:
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)); // 等于符號(hào)的實(shí)現(xiàn)
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.
}
}
數(shù)學(xué)運(yùn)算
數(shù)學(xué)運(yùn)算和邏輯運(yùn)算的流程都一樣,只不過(guò)運(yùn)算后的結(jié)果為數(shù)字類(lèi)型。
LIKE 運(yùn)算符
除了上面說(shuō)的邏輯運(yùn)算和數(shù)學(xué)運(yùn)算外,還支持進(jìn)行模糊匹配的特殊操作符 LIKE。
LIKE 表達(dá)式語(yǔ)法
常見(jiàn)用法如下
LIKE %HUAWEI 匹配以 HUAWEI 結(jié)尾的字符串
LIKE HUAWEI% 匹配以 HUAWEI 開(kāi)頭的字符串
LIKE A_B 匹配以 A 起頭且以 Z 為結(jié)尾的字串
LIKE A?B 同上
LIKE %[0-9]% 匹配含有數(shù)字的字符串
LIKE %[a-z]% 匹配含有小寫(xiě)字母字符串
LIKE %[!0-9]% 匹配不含數(shù)字的字符串
? 和_都表示單個(gè)字符
JAVA 中實(shí)現(xiàn) LIKE 的方案:將 LIKE 的模式轉(zhuǎn)為 JAVA 中的正則表達(dá)式。
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 類(lèi)
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 .
}
}
創(chuàng)建 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;
}
}
創(chuàng)建 LIKE 到正則表達(dá)式的轉(zhuǎn)換映射
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();
}
}
直接調(diào)用 LikeTranspiler 的 toRegEx 方法將 LIKE 語(yǔ)法轉(zhuǎn)為 JAVA 中的正則表達(dá)式。
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();
}
通過(guò)創(chuàng)建詞法分析器并使用此方法進(jìn)行轉(zhuǎn)換,我們可以防止 LIKE 像這樣的子句被轉(zhuǎn)換為正則表達(dá)式 %abc[%]%,該子句應(yīng)將其中的任何子字符串與其中的子字符串匹配,該子句將與子字符串或匹配任何字符串。abc%.abc[.].abc.abc。
類(lèi)型計(jì)算轉(zhuǎn)換
不同數(shù)據(jù)類(lèi)型在進(jìn)行計(jì)算時(shí)需要轉(zhuǎn)型,具體的轉(zhuǎn)化入下二維數(shù)組中。
// 不同類(lèi)型計(jì)算后的類(lèi)型
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
};
點(diǎn)擊關(guān)注,第一時(shí)間了解華為云新鮮技術(shù)~
關(guān)于提高微服務(wù)可用性的中間件 CoralCache 問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注丸趣 TV 行業(yè)資訊頻道了解更多相關(guān)知識(shí)。