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表达式计算引擎详解》,原文作者:超纯的小白兔 。
CoralCache:一个提高微服务可用性的中间件的更多相关文章
- 服务注册中心之ZooKeeper系列(二) 实现一个简单微服务之间调用的例子
上一篇文章简单介绍了ZooKeeper,讲了分布式中,每个微服务都会部署到多台服务器上,那服务之间的调用是怎么样的呢?如图: 1.集群A中的服务调用者如何发现集群B中的服务提供者呢? 2.集群A中的服 ...
- 【spring cloud】子模块module -->导入一个新的spring boot项目作为spring cloud的一个子模块微服务,怎么做/或者 每次导入一个新的spring boot项目,IDEA不识别子module,启动类无法启动/右下角没有蓝色图标
如题:导入一个新的spring boot项目作为spring cloud的一个子模块微服务,怎么做 或者说每次导入一个新的spring boot项目,IDEA不识别,启动类无法启动,怎么解决 下面分别 ...
- eShopOnContainers 是一个基于微服务的.NET Core示例框架
找到一个好的示例框架很难,但不是不可能.大多数是小型Todo风格的应用程序,通常基于SimpleCRUD.值得庆幸的是,Microsoft已经为eShopOnContainers创建了一个基于微服务的 ...
- Compoxure 微服务组合proxy 中间件
Compoxure 是一个不错的微服务组合中间件,使用此工具我们可以快速的进行micro frontends 应用的开发 使用此工具我们可以替换esi+ ssi 的开发模型(尽管都挺不错). 同时支持 ...
- 【spring cloud】一个ms微服务想要给注册中心eureka发现,需要满足这些条件,微服务不能被eureka注册中心发现的解决方案
在spring cloud中,一个新的微服务想要被注册中心发现,需要注意几个地方: 1.pom.xml文件依赖中需要有这个依赖 spring boot 2.x 需要这个依赖 <dependenc ...
- 编写第一个 .NET 微服务
介绍 本文的目的是:通过创建一个返回列表的简单服务,并在 Docker 容器中运行该服务,让您熟悉使用 .NET 创建微服务的构建过程. 安装 .NET SDK 要开始构建 .NET 应用程序,首先下 ...
- 0202年,您真的需要Thrift这样一个RPC微服务框架来拯救一下传统HTTP接口(api)了
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_104 目前市面上类似Django的drf框架基于json的http接口解决方案大行其道,人们也热衷于在接口不多.系统与系统交互较少 ...
- 从 1.5 开始搭建一个微服务框架——日志追踪 traceId
你好,我是悟空. 前言 最近在搭一个基础版的项目框架,基于 SpringCloud 微服务框架. 如果把 SpringCloud 这个框架当做 1,那么现在已经有的基础组件比如 swagger/log ...
- WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例
最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架 ...
- 翻译-微服务API Gateway
原文地址:http://microservices.io/patterns/apigateway.html,以下是使用google翻译对原文的翻译. 让我们想象一下你正在建立一个使用微服务模式的网上商 ...
随机推荐
- 🎁平平无奇的 Docker 命令(日常流)
Docker search docker search 命令用于在 Docker Hub 上搜索镜像,语法如下: docker search [OPTIONS] TERM 常用的选项包括: --fil ...
- 每天5分钟复习OpenStack(七)内存虚拟化
标题中的存储虚拟化,涉及到两个方面,分别是内存和磁盘的虚拟化技术.内存的虚拟化就不得不提EPT和VPID 技术. 首先申明下本人其实不想写一些纯理论的东西,但是架不住面试经被问,为此特将一些特别复杂的 ...
- MySQL防止被黑,通过跳板机ssh隧道访问
更新了另外一篇,比这篇的方法更好:[https://www.cnblogs.com/scottyzh/p/17745527.html](服务器没有开放3306端口 远程访问MySQL数据库方法) 一. ...
- H.264中的帧
导言 高级视频编码 (AVC) 也称为 H.264,是使用最广泛的视频压缩标准.它与所有主要的流式传输协议和容器格式兼容. 当我们使用播放器播放一个视频时,通常会经过:解协议,解封装,音视频解码,音视 ...
- 🔥🔥Java开发者的Python快速进修指南:面向对象基础
当我深入学习了面向对象编程之后,我首先感受到的是代码编写的自由度大幅提升.不同于Java中严格的结构和约束,Python在面向对象的实现中展现出更加灵活和自由的特性.它使用了一些独特的关键字,如sel ...
- 平台工程时代的 Kubernetes 揭秘:2023年生产状况报告深度剖析
Kubernetes 在生产环境中的复杂性已经成为常态,在2023年这个平台工程盛行的时代,容器管理的最大亮点可能在于其灵活性,然而在运维政策和治理等方面仍然存在诸多挑战.八年过去了,在生产环境中使用 ...
- 【Codeforces Global Round 12】 C2 - Errich-Tac-Toe题解(思维)
题面 题目要求不能有有三个连续相同的'X'或'O',注意到这样的连续串它们的横纵坐标之和是连续变化的,考虑将它们按照横纵坐标之和对 \(3\)的模值分组,因为这样分组后相邻的三个相同字符就被分到了三个 ...
- 明解Java第一章练习题答案
@ 目录 练习1-1 练习1-2 练习1-3 <明解Java>书籍其他章节答案 练习1-1 如果没有表示程序语句末尾的分号,结果会怎么样呢?请编译程序进行确认. 答:编译器报错 练习1-2 ...
- ACPM高效C++组件管理让音视频终端SDK性能更好、稳定性更高
本专栏将分享阿里云视频云MediaBox系列技术文章,深度剖析音视频开发利器的技术架构.技术性能.开发能效和最佳实践,一起开启音视频的开发之旅.本文为MediaBox技术架构篇,重点从 ACPM介绍. ...
- Kylin Linux Advanced Server V10 上安装 Nacos详细步骤
要在 Kylin Linux Advanced Server V10 上安装 Nacos,可以按照以下进行操作:1.安装 Java JDK:首先确保已在 Kylin Linux Advanced Se ...