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翻译对原文的翻译. 让我们想象一下你正在建立一个使用微服务模式的网上商 ...
随机推荐
- umicv cv-summary1-全连接神经网络模块化实现
全连接神经网络模块化实现 Linear与Relu单层实现 LossLayer实现 多层神经网络 不同梯度下降方法 Dropout层 今天这篇博文针对Assignment3的全连接网络作业,对前面学习的 ...
- Redis 7.0 源码环境搭建与阅读技巧
天下武功,无坚不摧,唯快不破!我的名字叫 Redis,全称是 Remote Dictionary Server. 有人说,组 CP,除了要了解她外,还要给机会让她了解你. 那么,作为开发工程师的你,是 ...
- 如何避免JavaScript中的内存泄漏?
前言 过去,我们浏览静态网站时无须过多关注内存管理,因为加载新页面时,之前的页面信息会从内存中删除. 然而,随着单页Web应用(SPA)的兴起,应用程序消耗的内存越来越多,这不仅会降低浏览器性能,甚至 ...
- idea java项目启动后访问html页面乱码
最近在做一个较久的项目,用的还是servlet+html(jsp),代码拉到本地后运行,访问登录页login.html既然乱码,先看个乱码的效果 怎么样,是不是很经典的乱码,别着急,我们一点点来分析乱 ...
- Java Web程序在Tomcat上是如何运行的
https://blog.csdn.net/fuzhongmin05/article/details/104379514 一个JVM是一个进程,JVM上跑Tomcat,Tomcat上可以部署多个应用. ...
- OpenGL 模型加载详解
1. Assimp 目前为止,我们已经可以绘制一个物体,并添加不同的光照效果了.但是我们的顶点数据太过简单,只能绘制简单的立方体.但是房子汽车这种不规则的形状我们的顶点数据就很难定制了.索性,这部分并 ...
- 记一次 .NET 某工控电池检测系统 卡死分析
一:背景 1. 讲故事 前几天有位朋友找到我,说他的窗体程序有卡死现象,让我帮忙看下怎么回事,解决这种问题就需要在卡死的时候抓一个dump下来,拿到dump之后就可以分析了. 二:为什么会卡死 1. ...
- Go 14周年
原文在这里. 由 Russ Cox, for the Go team 发布于2023年11月10日 今天,我们庆祝Go开源发布的第十四个生日!Go在过去一年里取得了巨大的进展,发布了两个功能丰富的版本 ...
- L2-040 哲哲打游戏
这题读懂题目之后就发现它很呆 #include <bits/stdc++.h> using namespace std; const int N = 100010, M = 110; ve ...
- MySQL-安全更新参数
版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin 注意! 生产环境中,updata必须要加where条件 1.开启安全功能,会提示你加where,不加会提示语法不正确. ...