摘要:当数据库出问题时能降级从本地缓存的数据中查询数据,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);}@Overridepublic String convert() {return getValue();}
}public class EscapeToken extends Token {public EscapeToken(String value) {super(value);}@Overridepublic String convert() {return getValue();}
}public class StringToken extends Token {public StringToken(String value) {super(value);}@Overridepublic String convert() {return Pattern.quote(getValue());}
}public class WildcardToken extends Token {public WildcardToken(String value) {super(value);}@Overridepublic String convert() {return ".*";}
}public class WildcharToken extends Token {public WildcharToken(String value) {super(value);}@Overridepublic 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:一个提高微服务可用性的中间件相关推荐

  1. 【微服务】Go进阶② 微服务可用性设计

    文章目录 微服务可用性设计 隔离 核心隔离 快慢隔离 热点隔离 线程隔离 进程隔离 集群隔离 超时控制 双峰分布 超时原因 超时控制中间件 过载保护 常见限流的缺点 过载保护策略 如何计算接近峰值时的 ...

  2. Istio:一个用于微服务间通信的服务网格开源项目

    http://www.infoq.com/cn/news/2017/05/istio Istio:一个用于微服务间通信的服务网格开源项目 Google.IBM和Lyft开源了微服务管理.保护和监控框架 ...

  3. eShopOnContainers 是一个基于微服务的.NET Core示例框架

    找到一个好的示例框架很难,但不是不可能.大多数是小型Todo风格的应用程序,通常基于SimpleCRUD.值得庆幸的是,Microsoft已经为eShopOnContainers创建了一个基于微服务的 ...

  4. unifig,是以基于 SpringCloud 的一个分布式 微服务 平台

    介绍: unifig,是以基于 SpringCloud 的一个分布式 微服务 平台. 具有 服务发现注册.配置中心.负载均衡.断路器.数据监控 . 当前开发完成b2c商场的主要业务,以及团购逻辑.是二 ...

  5. 【BPM技术】Zeebe是一个用于微服务编排的工作流引擎。

    Zeebe是一个用于微服务编排的工作流引擎. 这篇文章将帮助你确切地了解什么是Zeebe以及它如何可能与你相关.我们将简要介绍Zeebe以及它所解决的问题,然后再进行更详细的介绍. 我们将在整个写作过 ...

  6. mess-cli : 一个前端微服务架构脚手架(beta版)

    阅读本文需要较长的时间,本文介绍了微服务的概念.笔者心中的前端微服务,以及基于mess-cli脚手架,如何快速生成一个前端微服务架构项目 什么是微服务? 相信了解过spring cloud的同学都知道 ...

  7. (未完待续)浅谈微服务以及 常用中间件( zookeeper redis rabbitmq)

    传统的单体框架,已经不满足目前公司战略规划要求,近几年"微服务" 这个字眼,出现的越来越频繁,虽然有过一年多微服务项目经验,也很难把微服务解释清楚,到底何为微服务? Martin ...

  8. 微服务微应用的安全测试_提高微服务安全性的11个方法

    1.通过设计确保安全 设计安全,意味着从一开始就应该将安全性纳入软件设计中.关于安全,其中最常见的一个威胁就是恶意字符. 我问我的朋友罗伯·温奇(Rob Winch)他对删除恶意字符的想法.Rob是S ...

  9. 编写第一个 .NET 微服务

    介绍 本文的目的是:通过创建一个返回列表的简单服务,并在 Docker 容器中运行该服务,让您熟悉使用 .NET 创建微服务的构建过程. 安装 .NET SDK 要开始构建 .NET 应用程序,首先下 ...

最新文章

  1. EasyUI中Calendar日历的简单使用
  2. J-4 Java语言基础
  3. BZOJ1010 [HNOI2008]玩具装箱toy 动态规划 斜率优化
  4. pythonwhile循环怎么修改数据类型_python基础--数据类型循环
  5. Windows Notepad 迎来重大更新!
  6. 创建SpringBoot项目的两种姿势
  7. python编辑距离正则匹配_详解一道腾讯面试题:编辑距离
  8. Python数据结构与算法(3.5)——双端队列
  9. VMware虚拟机端口映射(NAT设置)
  10. 数据库服务器sa 密码修改,修改vcenter数据库账号密码
  11. font-family字体-常用字体中英文对照表
  12. labelme快捷键修改
  13. 光耦隔离模拟信号的传输方式
  14. R_leaflet包_最易上手地图教程(一)(上)
  15. JavaScript数组方法大全(分为会不会改变原数组)
  16. 微信小程序二手汽车拍卖系统丨可以android studio运行
  17. linux 压缩固定大小,tar gz压缩文件为指定大小
  18. 小米便签开源项目本地环境搭建
  19. 牛客网--关于合法括号序列判断
  20. 零基础学UI设计能够学会吗?

热门文章

  1. Bootstrap 排版正文
  2. ES6规格之数组的空位
  3. amd平台linux驱动安装失败,ati闭源驱动安装失败,问题解觉不了。
  4. mysql phpmyadmin 安装_phpmyadmin怎么安装
  5. Vrep线程之间的切换
  6. java dbcursor_优化JAVA查询Mongodb数量过大,查询熟读慢的方法
  7. Javascript中document.execCommand()的用法
  8. Python学习笔记之头部文件
  9. echo图片延迟加载js
  10. Activity切换动画---点击哪里从哪放大