C#自由组合本地缓存、分布式缓存和数据库的数据
一、背景介绍:
我们在进行数据存储的时候,有时候会加入本地缓存、分布式缓存以及数据库存储三级的结构,当我们取值的时候经常是像下面这样的流程:
1.先取本地缓存,如果值存在直接返回
2.本地缓存不存在,获取分布式缓存,存在直接返回,并更新本地缓存
3.分布式缓存不存在,查询数据库,更新分布式缓存、更新本地缓存,最后返回
但如果对于一些场景,可能只有本地缓存、只有分布式缓存或者说上面三种的几种组合,我们怎么要应对这样的变化,怎么能抽象出一套方式,能够应对各种不同数据存储方式造成的变化。
二、设计思路:
首先我们分析一下上面这个过程的模型,可以抽象出5个方法:
1.GetDataFromLocalCache
2.GetDataFromDistributeCache
3.GetDataFromDB
4.SetDataToLocalCache
5.SetDataToDistributeCache
其实,不同的场景无非就是这几个方法的组合,只不过里面的内容不同罢了,说到这里我们应该已经有思路了,可以利用委托来实现。
三、详细设计:
①定义一个类,包含上面五个方法的委托;
- public class DataOperateInput<T>
- {
- public Func<T> GetDataFromLocalCache { get; set; } = null; //获取本地缓存数据
- public Func<T> GetDataFromDistributeCache { get; set; } = null; //获取分布式缓存数据
- public Func<T> GetDataFromDb { get; set; } = null; //获取DB数据
- public Action<T> SetDataTolocalCache { get; set; } = null; //设置本地缓存数据
- public Action<T> SetDataToDistributeCache { get; set; } = null; //设置分布式缓存数据
- }
②实现一个方法,组合这五个方法。
- public class DataOperate
- {
- /// <summary>
- /// 获取数据入口
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="input"></param>
- /// <returns></returns>
- public T GetData<T>(DataOperateInput<T> input) where T : class, new()
- {
- T result = null;
- //需要从本地缓存取
- if (input.GetDataFromLocalCache != null)
- {
- //调用本地缓存委托方法获取值
- result = input.GetDataFromLocalCache();
- if (result != null)
- {
- return result;
- }
- }
- //获取值为空或者不从本地缓存获取,调用下面的方法,从分布式缓存和Db中获取数据
- return GetDataFromDistributeAndDB(input);
- }
- /// <summary>
- /// 从分布式缓存和Db中获取数据
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="input"></param>
- /// <returns></returns>
- private T GetDataFromDistributeAndDB<T>(DataOperateInput<T> input) where T : class, new()
- {
- T result = null;
- if (input.GetDataFromDistributeCache != null)
- {
- //从缓存中取值
- result = input.GetDataFromDistributeCache();
- //如果需要设置会本地缓存,那么设置
- if (result != null)
- {
- //如果设置本地缓存的委托存在,调用它设置本地缓存
- input.SetDataTolocalCache?.Invoke(result);
- }
- }
- //获取值为空或者不从分布式缓存获取,调用下面的方法,从Db中获取数据
- return GetDataFromDB(input);
- }
- /// <summary>
- /// 从数据库中获取数据
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="input"></param>
- /// <returns></returns>
- private T GetDataFromDB<T>(DataOperateInput<T> input) where T : class, new()
- {
- T result = null;
- if (input.GetDataFromDb != null)
- {
- //从DB中取值
- result = input.GetDataFromDb();
- //如果需要设置会分布式缓存和本地缓存,那么设置
- if (result != null)
- {
- input.SetDataToDistributeCache?.Invoke(result);
- input.SetDataTolocalCache?.Invoke(result);
- }
- }
- return result;
- }
- }
③ 具体实现一个服务类,和各种GetData、SetData方法;
A.定义一个枚举类,通过这个枚举可以自由组合数据源
- /// <summary>
- /// 数据源类别
- /// </summary>
- [Flags]
- public enum DataSourceKind
- {
- /// <summary>
- /// 本地缓存
- /// </summary>
- LocalCache = ,
- /// <summary>
- /// 分布式缓存
- /// </summary>
- DistributeCache = ,
- /// <summary>
- /// 数据库
- /// </summary>
- DataBase =
- }
B.定义一个具体的实体类,举例我这里定义了一个User类
- public class User : IUser
- {
- public long UserId { get; set; }
- public string Name { get; set; }
- public int Age { get; set; }
- public int Sex { get; set; }
- }
C.实现一个获取用户信息的方法
- /// <summary>
- /// 获取用户数据
- /// </summary>
- /// <param name="userId">用户Id(可以是自己相关的业务代码)</param>
- /// <param name="dataSources">数据源类型(调用方可以自己组合)</param>
- /// <param name="needUpdatelocal">是否需要更新本地缓存</param>
- /// <param name="needUpdateDistribute">是否需要更新分布式缓存</param>
- /// <returns></returns>
- public User GetUserInfo(long userId,
- DataSourceKind dataSources = DataSourceKind.LocalCache ,
- bool needUpdatelocal = false,
- bool needUpdateDistribute = false)
- {
- Console.WriteLine($"======数据源:{dataSources.ToString()} 是否更新本地:{needUpdatelocal} 是否更新Redis:{needUpdateDistribute}======");
- //初始化一个输入参数类
- var input = new DataOperateInput<User>();
- //如果包含从本地缓存取值
- if (dataSources.HasFlag(DataSourceKind.LocalCache))
- {
- input.GetDataFromLocalCache = () =>
- {
- //!!这里可以写具体的 获取本地缓存的处理逻辑
- return GetUserFromLocalCache(userId);
- };
- }
- //如果包含从分布式缓存取值
- if (dataSources.HasFlag(DataSourceKind.DistributeCache))
- {
- input.GetDataFromDistributeCache = () =>
- {
- //!!这里可以写具体的获取分布式缓存的处理逻辑
- return GetUserFromRedisCache(userId);
- };
- if (needUpdatelocal)
- {
- input.SetDataTolocalCache = (value) =>
- {
- //!!这里可以写具体的设定本地缓存的处理逻辑
- SetUserToLocalCache(value);
- };
- }
- }
- //如果包含从数据库缓存取值
- if (dataSources.HasFlag(DataSourceKind.DataBase))
- {
- input.GetDataFromDb = () =>
- {
- //!!这里可以写具体的获取数据库数据的处理逻辑
- return GetUserFromDB(userId);
- };
- if (needUpdateDistribute)
- {
- //!!这里可以写具体的设定分布式缓存的处理逻辑
- input.SetDataToDistributeCache = (value) =>
- {
- SetUserToRedisCache(value);
- };
- }
- if (needUpdatelocal)
- {
- //!!这里可以写具体的设定本地缓存的处理逻辑
- input.SetDataTolocalCache = (value) =>
- {
- SetUserToLocalCache(value);
- };
- }
- }
- //执行我们组合好的input
- var result = new DataOperate().GetData(input);
- Console.WriteLine("=============================================\n");
- return result;
- }
上面的代码描述了使用封装好的GetData的方法的使用,其中有些委托的方法是需要具体实现的,这里我没有详细写。下面列出用于测试的GetUserFromLocalCache、GetUserFromRedisCache、GetUserFromDB、SetUserToLocalCache以及SetUserToRedisCache的代码。
- /// <summary>
- /// 从本地缓存获取用户信息
- /// </summary>
- /// <param name="userId"></param>
- /// <returns></returns>
- private User GetUserFromLocalCache(long userId)
- {
- User user = null;
- if (userId == )
- {
- user = new User
- {
- UserId = userId,
- Age = ,
- Name = $"BigOrange_{userId}",
- Sex =
- };
- }
- if (user == null)
- {
- Console.WriteLine($"从本地缓存取值 未查询到 UserId={userId}");
- }
- else
- {
- Console.WriteLine($"从本地缓存取值 UserId={user.UserId} Name={user.Name} ");
- }
- return user;
- }
- /// <summary>
- /// 从Redis缓存获取用户信息
- /// </summary>
- /// <param name="userId"></param>
- /// <returns></returns>
- private User GetUserFromRedisCache(long userId )
- {
- User user = null;
- if (userId == || userId == )
- {
- user = new User
- {
- UserId = userId,
- Age = ,
- Name = $"BigOrange_{userId}",
- Sex =
- };
- }
- if (user == null)
- {
- Console.WriteLine($"从Redis缓存取值 未查询到 UserId={userId}");
- }
- else
- {
- Console.WriteLine($"从Redis缓存取值 UserId={user.UserId} Name={user.Name}");
- }
- return user;
- }
- /// <summary>
- /// 从DB获取用户信息
- /// </summary>
- /// <param name="userId"></param>
- /// <returns></returns>
- private User GetUserFromDB(long userId)
- {
- Console.WriteLine("从数据库中取值");
- User user = null;
- if (userId == || userId == || userId == )
- {
- user = new User
- {
- UserId = userId,
- Age = ,
- Name = $"BigOrange_{userId}",
- Sex =
- };
- }
- if (user == null)
- {
- Console.WriteLine($"从DB取值 未查询到 UserId={userId}");
- }
- else
- {
- Console.WriteLine($"从DB取值 UserId={user.UserId} Name={user.Name}");
- }
- return user;
- }
- /// <summary>
- /// 设置用户信息到本地缓存
- /// </summary>
- /// <param name="userInfo"></param>
- /// <returns></returns>
- private bool SetUserToLocalCache(User userInfo)
- {
- Console.WriteLine($"设置值到本地缓存:useId = {userInfo.UserId}");
- return true;
- }
- /// <summary>
- /// 设置用户信息到Redis缓存
- /// </summary>
- /// <param name="userInfo"></param>
- /// <returns></returns>
- private bool SetUserToRedisCache(User userInfo)
- {
- Console.WriteLine($"设置值到Redis缓存:useId = {userInfo.UserId}");
- return true;
- }
④测试一下
根据上面的代码,写了一些测试用的条目:
- static void Main(string[] args)
- {
- var userInfoService = new UserInfoService();
- /*
- * 测试用例
- 数据库中存在 User1、User2、User3
- 分布式缓存 User1、User2
- 本地缓存 User1
- */
- //1.只从本地缓存取值
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache);
- //2.只从Redis缓存取值
- userInfoService.GetUserInfo(, DataSourceKind.DistributeCache);
- userInfoService.GetUserInfo(, DataSourceKind.DistributeCache);
- //3.只从DB取值
- userInfoService.GetUserInfo(, DataSourceKind.DataBase);
- userInfoService.GetUserInfo(, DataSourceKind.DataBase);
- //4.从本地缓存和Redis取值
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache);
- //不更新到本地
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, false);
- //更新到本地
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, true);
- //5.从Redis和DB取值
- userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase);
- userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, false);
- userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true);
- //6.从本地和DB取值
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase, false,false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase, true, false);
- //7.三者都使用
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true, false);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true);
- userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,true);
- Console.ReadKey();
- }
执行结果:
======数据源:LocalCache 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 UserId=1 Name=BigOrange_1
=============================================
======数据源:LocalCache 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=2
=============================================
======数据源:DistributeCache 是否更新本地:False 是否更新Redis:False======
从Redis缓存取值 UserId=2 Name=BigOrange_2
=============================================
======数据源:DistributeCache 是否更新本地:False 是否更新Redis:False======
从Redis缓存取值 未查询到 UserId=3
=============================================
======数据源:DataBase 是否更新本地:False 是否更新Redis:False======
从DB取值 UserId=3 Name=BigOrange_3
=============================================
======数据源:DataBase 是否更新本地:False 是否更新Redis:False======
从DB取值 未查询到 UserId=4
=============================================
======数据源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 UserId=1 Name=BigOrange_1
=============================================
======数据源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=2
从Redis缓存取值 UserId=2 Name=BigOrange_2
=============================================
======数据源:LocalCache, DistributeCache 是否更新本地:True 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=2
从Redis缓存取值 UserId=2 Name=BigOrange_2
设置值到本地缓存:useId = 2
=============================================
======数据源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
从Redis缓存取值 UserId=2 Name=BigOrange_2
从DB取值 UserId=2 Name=BigOrange_2
=============================================
======数据源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
=============================================
======数据源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
设置值到Redis缓存:useId = 3
=============================================
======数据源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 UserId=1 Name=BigOrange_1
=============================================
======数据源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
=============================================
======数据源:LocalCache, DataBase 是否更新本地:True 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
设置值到本地缓存:useId = 3
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 UserId=1 Name=BigOrange_1
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=2
从Redis缓存取值 UserId=2 Name=BigOrange_2
从DB取值 UserId=2 Name=BigOrange_2
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=2
从Redis缓存取值 UserId=2 Name=BigOrange_2
设置值到本地缓存:useId = 2
从DB取值 UserId=2 Name=BigOrange_2
设置值到本地缓存:useId = 2
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=3
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
从本地缓存取值 未查询到 UserId=3
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
设置值到本地缓存:useId = 3
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
从本地缓存取值 未查询到 UserId=3
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
设置值到Redis缓存:useId = 3
=============================================
======数据源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:True======
从本地缓存取值 未查询到 UserId=3
从Redis缓存取值 未查询到 UserId=3
从DB取值 UserId=3 Name=BigOrange_3
设置值到Redis缓存:useId = 3
设置值到本地缓存:useId = 3
=============================================
四、总结一下
类似上面的用户信息,可能对于不同系统、不同性能要求,获取方式会有所不同。
打个比方:对于一个后台管理系统,用户信息获取是一个低频操作,可能只需要从数据库中获取,此时一般后台系统不会设置本地缓存和分布式缓存,而对于一个接口系统,可能每天有几百万的访问量,此时如果只从数据库获取,很难承受,所以要利用到分布式缓存和本地缓存。层次越多那么变化和组合也就越多,但是每个实体的存取如果都各自实现自己的方式,又比较浪费,所以如果能抽象出一套方法,只需要告诉方法存取的方式,然后得到自己想要的数据,或许这样是比较好的方式,而具体怎么拿、怎么存,还是由调用的人去给出,这样可以应对复杂的规则。这也是为什么要使用这么多委托的原因,由于像上面获取和设定User缓存的方式多种多样,这么做可以把具体的获取和设置缓存的操作开放给使用者。在系统重构方面上,可以将一些通用的方法抽象出来,相对成本较低,扩展性好一些。
五、题外话
上面的代码中对于更新数据,没有做线程安全处理,多个进程去更新分布式缓存、同一进程的多个线程去更新本地缓存,可能都需要进行锁操作。
C#自由组合本地缓存、分布式缓存和数据库的数据的更多相关文章
- mybatis本地缓存&分布式缓存干货分享
前言:干货记录学习mybatis实际开发中缓存的使用. 环境: springboot2.X + mybatis3.x Mybatis是一款持久层框架,它提供了一级缓存和二级缓存. 名词解释 一级缓存( ...
- WEB 应用缓存解析以及使用 Redis 实现分布式缓存
什么是缓存? 缓存就是数据交换的缓冲区,用于临时存储数据(使用频繁的数据).当用户请求数据时,首先在缓存中寻找,如果找到了则直接返回.如果找不到,则去数据库中查找.缓存的本质就是用空间换时间,牺牲数据 ...
- Redis实现分布式缓存
Redis 分布式缓存实现(一) 1. 什么是缓存(Cache) 定义:就是计算机内存中的一段数据: 2. 内存中数据特点 a. 读写快 b. 断电立即丢失 3. 缓存解决了什么问题? a. 提 ...
- 分布式进阶(十八) 分布式缓存之Memcached
分布式缓存 分布式缓存出于如下考虑:首先是缓存本身的水平线性扩展问题,其次是缓存大并发下本身的性能问题,再次避免缓存的单点故障问题(多副本和副本一致性). 分布式缓存的核心技术包括首先是内存本身的管理 ...
- 本地缓存google.guava及分布式缓存redis 随笔
近期项目用到了缓存,我选用的是主流的google.guava作本地缓存,redis作分布式 缓存,先说说我对本地缓存和分布式缓存的理解吧,可能不太成熟的地方,大家指出,一起 学习.本地缓存的特点是速度 ...
- .Net Core 跨平台开发实战-服务器缓存:本地缓存、分布式缓存、自定义缓存
.Net Core 跨平台开发实战-服务器缓存:本地缓存.分布式缓存.自定义缓存 1.概述 系统性能优化的第一步就是使用缓存!什么是缓存?缓存是一种效果,就是把数据结果存在某个介质中,下次直接重用.根 ...
- 【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)
一.缓存 当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象.所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从 ...
- 主要从架构上来做优化,负载均衡、CDN、静态化、数据库的水平切割和纵向切割、读写分离、分布式缓存着手
语言知识一种工具,甚至技术本身也只是一种工具,本身并不值钱,关键在于用于何种行业,产生了什么价值. 但从语言来看,我个人更喜欢php,然后是C#,然后是java从框架而言,先是java,然后C#,再次 ...
- c#实例化继承类,必须对被继承类的程序集做引用 .net core Redis分布式缓存客户端实现逻辑分析及示例demo 数据库笔记之索引和事务 centos 7下安装python 3.6笔记 你大波哥~ C#开源框架(转载) JSON C# Class Generator ---由json字符串生成C#实体类的工具
c#实例化继承类,必须对被继承类的程序集做引用 0x00 问题 类型“Model.NewModel”在未被引用的程序集中定义.必须添加对程序集“Model, Version=1.0.0.0, Cu ...
随机推荐
- 添加zookeeper到服务,并设置开机启动
一.先安装jdk jdk 路径为/usr/local/java 二.再安装zookeeper zk路径为/use/local/zookeeper 三.创建zookeeper脚本 cd /etc/rc. ...
- 【C# 开发技巧】 Application.DoEvents( ) 使用笔记
该方法可以处理当前队列的消息,比如一个for循环 5000次 向TextBox中追加文本,那肯定会假死一会儿的. 此时便可使用Application.DoEvents()来处理队列的信息. 简单说下使 ...
- (生鲜项目)07. api view实现商品列表页
第一步: 环境配置 1. DRF官网: https://www.django-rest-framework.org/ 仔细查看自己当前的python版本以及django版本是否支持DRF, 然后就看看 ...
- opencv 直方图
1.简介 对输入图像进行直方图均衡化处理,提升后续对象检测的准确率在OpenCV人脸检测的代码演示中已经很常见.此外对医学影像图像与卫星遥感图像也经常通过直方图均衡化来提升图像质量. 图像直方图均衡化 ...
- java根据模板生成pdf
原文链接:https://www.cnblogs.com/wangpeng00700/p/8418594.html 在网上看了一些Java生成pdf文件的,写的有点乱,有的不支持写入中文字体,有的不支 ...
- JMeter断言介绍
(1)作用:用于检查测试中得到的响应数据等是否符合预期,用以保证性能测试过程中的数据交互与预期一致 (2)目的:在request的返回层面增加一层判断机制:因为request成功了,并不代表结果一定正 ...
- (二)javaweb开发的常见概念总结(来自那些年的笔记)
目录 WEB开发相关知识 WEB服务器 Tomcat服务器 WEB应用程序 配置WEB应用 配置缺省的WEB应用 Web设置首页 web.xml文件 配置虚拟主机 配置https连接器 为我们的网站, ...
- Word 分栏页码,一个页面两个不同页码的设置
1. 前言 在一些报纸.杂志中,我们可以见到各种各样的排版风格效果,其中有一种效果是一个页面设置了两栏,并且每栏下面都有不同的页码,那么,这种效果是如何实现的呢?这种页码在Word中默认页码样式中是没 ...
- 剑指offer58:对称的二叉树。判断一颗二叉树是不是对称的,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的
1 题目描述 请实现一个函数,用来判断一颗二叉树是不是对称的.注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的. 2 思路和方法 定义一种遍历算法,先遍历右子结点再遍历左子结点:如对称先序 ...
- logstash grok nginx log
#cat logstash.conf input { file { path => "/alidata/logs/nginx/appapi.dayutang.cn.access*.lo ...