缓存更新

This commit is contained in:
法然
2023-01-03 21:37:11 +08:00
parent 81783f8aa4
commit fb475942a2
9 changed files with 192 additions and 763 deletions

View File

@@ -182,6 +182,7 @@ public static void demo() throws ExecutionException, InterruptedException {
}
```
## 3 数据驱逐
Caffeine提供以下几种剔除方式:基于大小、基于权重、基于时间、基于引用
@@ -530,4 +531,45 @@ public class MyTicker implements Ticker {
return this.nanos.get();
}
}
```
```
## 7 缓存常见问题处理
### 缓存穿透——不存在的key
尝试返回一个空对象。
### 缓存击穿——大量线程同时访问同一个Key
* 在一个key失效的瞬间有大量访问进入。
* 多线程单一key互斥(该包已经实现。)
* 如果对 get(K) 的另一个调用当前正在加载键的值,则该线程只是等待该线程完成并返回其加载的值。 请注意,多个线程可以同时加载不同键的值。
* 如果指定的键尚未与值关联,则尝试计算其值并将其输入此缓存,除非为空。 整个方法调用是原子执行的,因此每个键最多应用一次函数。 当计算正在进行时,其他线程对此缓存的一些尝试更新操作可能会被阻塞,因此计算应该简短,并且不得尝试更新此缓存的任何其他映射。
* 在一开始的时候,该key还没有加入有大量key同时访问
* 缓存预热。
```java
@PostConstruct
private void preLoading(){
List<InstanceMetaDO> instanceMetaDOS = instanceMetaDAO.findByQuery();
int nums = 0;
for(InstanceMetaDO metaDO:instanceMetaDOS){
if(StringUtils.isNotEmpty(metaDO.getInstanceId())){
nums++;
if(StringUtils.isNotEmpty(metaDO.getWorkspaceId())){
workspaceIdCache.put(metaDO.getInstanceId(),metaDO.getWorkspaceId());
}
if(StringUtils.isNotEmpty(metaDO.getInstanceId())){
tenantIdCache.put(metaDO.getInstanceId(),metaDO.getTenantId());
}
}
}
log.info("[InstanceMetaCacheService] preload count:{}",nums);
}
```
### 缓存雪崩——大量key同时失效

View File

@@ -0,0 +1,115 @@
在生产环境中,会因为很多的原因造成访问请求绕过了缓存,都需要访问数据库持久层,虽然对Redsi缓存服务器不会造成影响,但是数据库的负载就会增大,使缓存的作用降低
## 1 缓存穿透
### 1、缓存穿透理解
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。
缓存穿透示意图:
![](image/2022-12-30-16-29-15.png)
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
### 2、解决方案
**2.1 缓存空对象**
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:
* 第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
* 第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
**2.2 布隆过滤器拦截**
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器拦截的算法描述:
* 初始状态时,BloomFilter是一个长度为m的位数组,每一位都置为0。
* 添加元素x时,x使用k个hash函数得到k个hash值,对m取余,对应的bit位设置为1。
* 判断y是否属于这个集合,对y使用k个哈希函数得到k个哈希值,对m取余,所有对应的位置都是1,则认为y属于该集合(哈希冲突,可能存在误判),否则就认为y不属于该集合。可以通过增加哈希函数和增加二进制位数组的长度来降低错报率。
**2.3、两种方案的对比**
![](image/2022-12-30-16-31-00.png)
## 2 缓存击穿
### 1、缓存击穿的理解
系统中存在以下两个问题时需要引起注意:
* 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
* 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
### 2、解决方案
**2.1 分布式互斥锁**
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
![](image/2022-12-30-16-31-48.png)
**2.2、永不过期**
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
![](image/2022-12-30-16-33-01.png)
### 3、两种方案对比:
* 分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
* 永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
## 3 缓存雪崩
### 1、概念理解
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
### 2、解决方案
**2.1 缓存层高可用:**
可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
**2.2 做二级缓存,或者双缓存策略:**
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
**2.3 数据预热:**
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
**2.4 加锁排队**
* 限流-- 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1]
* 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
* 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
* SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
## 总结
* 不存在的key——缓存穿透——缓存空对象
* 同一个Key失效大量访问——缓存击穿——加锁互斥
* 大量key同时失效——缓存雪崩——双缓存、队列
GuavaCache本身实现了加锁互斥,有效防止缓存击穿。
* 更新锁定:类似于分布式锁,作用体现于对同一个key,只让一个请求去读源并回填缓存,其他请求阻塞等待。

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

View File

@@ -168,6 +168,22 @@ lambda表达式是为了简化接口的实现的。在lambda表达式中,不
函数引用:引用⼀个已经存在的方法,使其替代lambda表达式完成接口的实现
### 与lambda表达式的不同
lambda和方法引用之间有一个关键的区别:
* Lambda 是惰性的,它们只会在调用方法时调用类构造函数。
* 方法引用,构造函数只会在分配了方法引用的地方被立即调用,而不是在方法调用时调用。
对象方法引用,对象必须已经初始化。类方法引用,类也一定会进行初始化。例如
* lambda表达式,这一行不会创建对象
```java
Runnable universeImpactRunnable = () -> new ChuckNorris().roundHouseKick();
```
* 方法引用,这一行会创建对象。
```
Runnable galaxyImpactRunnable = new ChuckNorris()::roundHouseKick;
```
### 静态方法的引用
```

192
test.json
View File

@@ -1,191 +1 @@
| sofa.ms.sg.apprelations.query | 获取应用关联数据 |
| sofa.ms.sg.apps.list | 获取应用列表 |
| sofa.ms.sg.serviceclients.sync | 微服务-同步客户端下线事件 |
| sofa.ms.sg.serviceheartbeats.sync | 微服务-同步心跳数据 |
| sofa.ms.sg.servicekeyword.query | 获取服务候选词 |
| sofa.ms.sg.serviceparam.batchquery | 微服务-查询全部服务参数信息 |
| sofa.ms.sg.serviceparams.disable | 禁用一个服务下某个服务提供者 |
| sofa.ms.sg.serviceparams.enable | 启动一个服务下某个服务提供者 |
| sofa.ms.sg.serviceparams.reinit | 恢复服务提供者默认设置 |
| sofa.ms.sg.serviceparamweight.set | 设置服务提供者权重 |
| sofa.ms.sg.servicepubkeys.query | 根据ip搜索服务提供方 |
| sofa.ms.sg.servicepublishers.offline | 提供给 paas 平台,发布时先调用下线接口,再发布 |
| sofa.ms.sg.servicepublishers.query | 分页查询发布者信息 |
| sofa.ms.sg.servicepublishers.sync | 微服务-同步服务发布者信息 |
| sofa.ms.sg.servicepubrelations.query | 获取服务发布者关系 |
| sofa.ms.sg.servicepubs.list | 微服务-通过DataId和IP获取pub服务详情 |
| sofa.ms.sg.servicepubs.query | 获取发布者服务详情 |
| sofa.ms.sg.services.query | 微服务-根据关键词搜索 |
| sofa.ms.sg.servicesubkeys.query | 根据ip搜索消费者 |
| sofa.ms.sg.servicesubrelations.query | 获取服务消费者数据 |
| sofa.ms.sg.servicesubs.list | 微服务-通过DataId和IP获取sub服务详情 |
| sofa.ms.sg.servicesubs.query | 获取订阅者服务详情 |
| sofa.ms.sg.servicesubscribers.query | 分页查询消费者 |
| sofa.ms.sg.servicesubscribers.sync | 微服务-同步服务发布者信息 |
| sofa.ms.sg.servicesummarys.query | 微服务-获取服务汇总信息 |
| sofa.ms.sg.serviceapps.list | 获取发布服务的应用列表 |
### DRM动态配置
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.drm.drmapps.all | 查询实例App name |
| sofa.ms.drm.drmattributes.add | 添加drm属性 |
| sofa.ms.drm.drmattributes.delete | 删除drm资源属性 |
| sofa.ms.drm.drmattributes.get | 获取drm资源属性 |
| sofa.ms.drm.drmattributes.update | 更新drm资源属性 |
| sofa.ms.drm.drmcells.all | 获取实例的单元格 |
| sofa.ms.drm.drmclients.query | 搜索客户端信息 |
| sofa.ms.drm.drmdata.add | 推送drm配置 |
| sofa.ms.drm.drmdatacells.list | 获取Drm数据单元信息 |
| sofa.ms.drm.drmgraydata.add | 灰度推送数据 |
| sofa.ms.drm.drmhosts.query | 搜索客户端信息 |
| sofa.ms.drm.drmpushlogs.list | 分页搜索推送日志 |
| sofa.ms.drm.drmpushlogs.query | 按条件分页查询推送日志 |
| sofa.ms.drm.drmresources.add | 添加drm资源 |
| sofa.ms.drm.drmresources.delete | Delete resource |
| sofa.ms.drm.drmresources.export | 当前workspace下所有资源元信息 |
| sofa.ms.drm.drmresources.import | 导入drm资源元信息 |
| sofa.ms.drm.drmresources.query | 分页查询Drm资源 |
| sofa.ms.drm.drmresources.update | 更新drm资源 |
| sofa.ms.sg.resource.export | 资源以文件形式导出 |
| sofa.ms.sg.resource.import | 资源以文件形式导入导出 |
### 服务治理——服务限流
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.guardian.guardianappnames.all | 微服务-查询appName列表 |
| sofa.ms.guardian.guardianapps.add | 添加 监控App |
| sofa.ms.guardian.guardianapps.all | 微服务-获取app列表 |
| sofa.ms.guardian.guardianapps.delete | 删除监控 app |
| sofa.ms.guardian.guardianapps.get | ms -根据appName获取app |
| sofa.ms.guardian.guardianapps.query | 微服务-分页查询app |
| sofa.ms.guardian.guardianapps.update | ws-更新监控app |
| sofa.ms.guardian.guardianconfig.get | 微服务-配置获取 |
| sofa.ms.guardian.guardiangrayrules.add | 灰度推送 |
| sofa.ms.guardian.guardiangrayrulestatus.update | 更改规则灰度状态 |
| sofa.ms.guardian.guardianrulepushhistorys.list | 查询推送历史 |
| sofa.ms.guardian.guardianrulepushhistorys.query | 分页查询推送历史 |
| sofa.ms.guardian.guardianrules.add | 添加规则 |
| sofa.ms.guardian.guardianrules.delete | 删除规则 |
| sofa.ms.guardian.guardianrules.export | 导出guardian路由规则 |
| sofa.ms.guardian.guardianrules.get | 根据id获取规则 |
| sofa.ms.guardian.guardianrules.import | 导入guardian规则 |
| sofa.ms.guardian.guardianrules.query | ms - 规则配置分页 |
| sofa.ms.guardian.guardianrules.update | 更新规则 |
| sofa.ms.guardian.guardianrulestatus.update | 更新规则状态 |
### 服务治理——服务路由
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.routerruleallapps.list | 查询已存在服务路由规则的应用接口 |
| sofa.ms.sg.routerruleallservices.list | 查询已存在服务路由规则的服务接口 |
| sofa.ms.sg.routerruleappnames.list | 微服务-通过DataId获取AppNames |
| sofa.ms.sg.routerruledispatchlogs.query | 微服务-获取调度日志 |
| sofa.ms.sg.routerrulegroup.delete | 删除路由规则组 |
| sofa.ms.sg.routerrulegroup.export | 路由规则导出 |
| sofa.ms.sg.routerrulegroup.import | 路由规则导出 |
| sofa.ms.sg.routerrulegroupapps.list | 查询服务路由一级列表 |
| sofa.ms.sg.routerrulegroups.add | 添加服务路由规则 |
| sofa.ms.sg.routerrulegroups.list | 查询服务路由二级例表 |
| sofa.ms.sg.routerrulegroups.update | 更新服务路由规则 |
| sofa.ms.sg.routerrulegroupstatus.update | 更新路由规则组状态 |
| sofa.ms.sg.routerrules.enable | 微服务-启用路由器规则组 |
| sofa.ms.sg.routerrules.get | 查询路由规则 |
| sofa.ms.sg.routerrulesnapshots.get | 微服务-获取快照最后一个 |
| sofa.ms.sg.routerrulesnapshots.rollback | 微服务-回滚快照最后一个 |
| sofa.ms.sg.routerrulestatus.query | 获取路由规则状态 |
| sofa.ms.sg.routerruleversions.list | 获取路由规则组版本列表 |
### 服务治理——服务熔断
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.circuitbreakerrules.add | 添加熔断规则 |
| sofa.ms.sg.circuitbreakerrules.all | 查询所有的服务熔断的appName及对应的规则数 |
| sofa.ms.sg.circuitbreakerrules.delete | 删除熔断规则 |
| sofa.ms.sg.circuitbreakerrules.enable | 生效熔断规则 |
| sofa.ms.sg.circuitbreakerrules.export | 熔断规则导出 |
| sofa.ms.sg.circuitbreakerrules.get | 通过appName查询熔断规则 |
| sofa.ms.sg.circuitbreakerrules.import | 熔断规则导入 |
| sofa.ms.sg.circuitbreakerrules.list | 获取租户下服务熔断规则 |
| sofa.ms.sg.circuitbreakerrules.query | 根据实例id和服务id获取所有荣富安规则 |
| sofa.ms.sg.circuitbreakerrules.set | 设置熔断规则运行模式 |
| sofa.ms.sg.circuitbreakerrules.update | 更新熔断规则 |
| sofa.ms.sg.downgradegroups.disable | 全局关闭降级规则 |
### 服务治理——服务降级
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.downgradegroups.enable | 全局开启降级规则 |
| sofa.ms.sg.downgradegroups.query | 查询降级规则组列表 |
| sofa.ms.sg.downgrades.add | 新增降级规则 |
| sofa.ms.sg.downgrades.delete | 删除降级规则 |
| sofa.ms.sg.downgrades.disable | 关闭一条降级规则 |
| sofa.ms.sg.downgrades.enable | 开启一条降级规则 |
| sofa.ms.sg.downgrades.export | 导出降级规则 |
| sofa.ms.sg.downgrades.import | 导入降级规则 |
| sofa.ms.sg.downgrades.update | 更新降级规则 |
### 服务治理——故障注入
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.faultinjectallapps.list | 查询已添加故障注入规则的应用 |
| sofa.ms.sg.faultinjectallservices.list | 查询已添加故障注入规则的服务 |
| sofa.ms.sg.faultinjectapps.list | 查询故障注入一级列表 |
| sofa.ms.sg.faultinjectrule.add | 新增故障注入规则 |
| sofa.ms.sg.faultinjectrule.delete | 删除故障注入规则 |
| sofa.ms.sg.faultinjectrule.export | 导出故障注入 |
| sofa.ms.sg.faultinjectrule.import | 导入故障注入 |
| sofa.ms.sg.faultinjectrule.update | 更新故障注入规则 |
| sofa.ms.sg.faultinjectrules.list | 分页查询故障注入二级列表 |
| sofa.ms.sg.faultinjectrulestatus.update | 更新故障注入规则状态 |
| sofa.ms.sg.faulttoleranceapps.list | 故障隔离App列表及其规则数量 |
| sofa.ms.sg.faulttolerancerule.add | 故障隔离规则新增 |
| sofa.ms.sg.faulttolerancerule.delete | 故障隔离规则删除 |
| sofa.ms.sg.faulttolerancerule.enable | 故障隔离规则启用 |
| sofa.ms.sg.faulttolerancerule.export | 导出故障隔离规则 |
| sofa.ms.sg.faulttolerancerule.import | 导入故障隔离规则 |
| sofa.ms.sg.faulttolerancerule.update | 故障隔离规则更新 |
| sofa.ms.sg.faulttolerancerules.list | 故障隔离规则列表 |
### 服务治理——服务鉴权
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.authruleactiveappnames.list | 查找出目前存活的应用名称列表 |
| sofa.ms.sg.authruleactivedataids.list | 查找该应用下存活的服务列表 |
| sofa.ms.sg.authruleappnames.list | 查找出所有包含鉴权规则组的应用名(一般用于服务鉴权列表搜索框查询) |
| sofa.ms.sg.authruledataids.list | 查找出所有包含鉴权规则组的服务ID(一般用于鉴权规则列表搜索框查询) |
| sofa.ms.sg.authrulegroupapps.list | 应用级服务鉴权列表搜索 |
| sofa.ms.sg.authrulegroups.disable | 禁止一份服务鉴权黑、白名单 |
| sofa.ms.sg.authrulegroups.enable | 启用服务鉴权黑、白名单。黑、白名单不能同时生效 |
| sofa.ms.sg.authrulegroups.export | 导出鉴权规则数据 |
| sofa.ms.sg.authrulegroups.import | 导入鉴权规则数据 |
| sofa.ms.sg.authrulegroups.list | 根据服务接口查询服务鉴权规则 |
| sofa.ms.sg.authrulegroups.retry | 服务鉴权规则下发失败,重新下发规则 |
| sofa.ms.sg.authrulegroups.save | 创建服务鉴权规则 |
| sofa.ms.sg.authrulegroupsbatch.add | 添加鉴权规则 (添加服务级鉴权规则可批量添加) |
| sofa.ms.sg.authrulegroupsbatch.disable | 批量关闭鉴权规则 |
| sofa.ms.sg.authrulegroupsbatch.enable | 批量开启鉴权规则 |
| sofa.ms.sg.authrulegroupservices.list | 服务级服务鉴权列表搜索 |
| sofa.ms.sg.authrules.add | 添加一条白、黑名单规则 |
| sofa.ms.sg.authrules.delete | 删除鉴权规则 |
| sofa.ms.sg.authrules.disable | 禁用黑、白名单中的一条规则 |
| sofa.ms.sg.authrules.enable | 启用服务鉴权规则 |
| sofa.ms.sg.authrules.reinit | 清空黑白名单下所有鉴权规则 |
| sofa.ms.sg.authrules.update | 更新一条服务鉴权规则 |
### 审计目录
| 接口名称 | 描述 |
| ---- | ---- |
| sofa.ms.sg.auditlogs.list | 分页查询审计日志 |
"7HBPQOV8F4OK","VRULPU2VVMZN","LARX4EGMTLT9","F5Z2YNZT2RNI","3GIVRPTL0IGK","VRULPU2VVMZN","LARX4EGMTLT9","EQLBTL6URR8H","VRULPU2VVMZN","LARX4EGMTLT9","BFHOBR8AYDYC","ZIC5FSHHEKL2","VIFEZDN1YVYU"

588
test.sh
View File

@@ -1,571 +1,17 @@
# cat zclean.sh
#!/bin/bash
# version: 0.9.2
export LANG=C
export PATH=/sbin:/bin:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/admin/bin
readonly LOGS_DIR=/home/admin/logs/
readonly CONF_FILE=/home/admin/conf/zclean.conf
# 兼容逻辑,部分老应用通过云游部署会带上 app.env 等前缀,这里为了处理这种情况用 grep 来过滤
RESERVE=`([[ -f /opt/antcloud/conf/env.file ]] && cat /opt/antcloud/conf/env.file || env) |grep ZCLEAN_RESERVE_DAYS | awk -F= '{print $2}'`
RESERVE=${RESERVE:-14}
MAX_LOG_DIR_SIZE=`([[ -f /opt/antcloud/conf/env.file ]] && cat /opt/antcloud/conf/env.file || env) |grep ZCLEAN_MAX_LOG_DIR_SIZE | awk -F= '{print $2}'`
MAX_LOG_DIR_SIZE=${MAX_LOG_DIR_SIZE:-100} # unit is G
DELETE_FLAG='-delete'
DEBUG=''
CHUNK_SIZE=''
INTERACTIVE=0
ZCLEAN_DIGEST="${LOGS_DIR}/zclean.log.$(date +%F)"
{
readonly ZCLEAN_OK=1
readonly ZCLEAN_CRUSH=2
readonly ZCLEAN_ERROR=3
readonly ZCLEAN_IGNORE=4
}
[[ ! -d $LOGS_DIR ]] && exit
CMD_PREFIX=''
if $(which ionice >& /dev/null); then
CMD_PREFIX="ionice -c3 "
fi
if $(which nice >& /dev/null); then
CMD_PREFIX="nice -n 19 $CMD_PREFIX"
fi
FIND_CMD="${CMD_PREFIX}find"
RM_CMD="${CMD_PREFIX}rm"
TRUNCATE_CMD=''
if $(which truncate >& /dev/null); then
TRUNCATE_CMD="${CMD_PREFIX}truncate"
fi
LSOF_CMD=''
if $(which lsof >& /dev/null); then
LSOF_CMD="lsof"
fi
LSOF_FILE=/tmp/zclean_lsof.out
if [[ -d /dev/shm ]]; then
shm_mode=$(stat -c "%A" /dev/shm)
if [[ $shm_mode == drwxrwxrwt ]]; then
LSOF_FILE=/dev/shm/zclean_lsof.out
fi
fi
prepare_lsof() {
# walkaroud for Alios7 kenrel bug
if [[ $HOSTNAME =~ paycorecloud-30- ]]; then
FIND_CMD $LOGS_DIR -name '*.log' > $LSOF_FILE
return
fi
if [[ $HOSTNAME =~ paycorecloud-31- ]]; then
FIND_CMD $LOGS_DIR -name '*.log' > $LSOF_FILE
return
fi
if [[ -n $LSOF_CMD ]]; then
ulimit -n 1024
$LSOF_CMD +D $LOGS_DIR 2> /dev/null > $LSOF_FILE
fi
}
delete_lsof() {
$RM_CMD -rf $LSOF_FILE
}
# only return true when all ready
file_in_lsof() {
local fpath=$1
if [[ -n $LSOF_CMD && -f $LSOF_FILE ]]; then
grep -q $fpath $LSOF_FILE
return $?
else
return 1
fi
}
log_error() {
echo $(date +"%F %T") [ERROR] $@ >> $ZCLEAN_DIGEST
}
log_info() {
echo $(date +"%F %T") [INFO] $@ >> $ZCLEAN_DIGEST
}
log_warn() {
echo $(date +"%F %T") [WARN] $@ >> $ZCLEAN_DIGEST
}
log_debug() {
[[ $DEBUG != '-debug' ]] && return
echo $(date +"%F %T") [DEBUG] $@ >> $ZCLEAN_DIGEST
}
delete_files() {
[[ $DELETE_FLAG != '-delete' ]] && return
$RM_CMD -rf "$@" >& /dev/null
}
crush_files() {
[[ $DELETE_FLAG != '-delete' ]] && return
for f in "$@"; do
> $f
done
}
clean_file() {
# eliminates file in a low-speed way (default: 20MB/S)
local fpath=$1
local fsize=$2
local chunksize=${CHUNK_SIZE:-20}
if [[ $DELETE_FLAG != '-delete' || ! -f $fpath ]]; then
return $ZCLEAN_ERROR
fi
local is_open=0
if file_in_lsof $fpath >& /dev/null; then
is_open=1
fi
if [[ $is_open -eq 1 && $fsize -eq 0 ]]; then
log_debug "ignore $fpath(+) size $fsize"
return $ZCLEAN_IGNORE
fi
if [[ $chunksize -eq 0 || -z $TRUNCATE_CMD ]]; then
# fast delete
if [[ $is_open -eq 1 ]]; then
crush_files $fpath
log_debug "removed $fpath(+) size $fsize directly"
else
delete_files $fpath
log_debug "removed $fpath size $fsize directly"
fi
else
# slow delete
local tstart=$SECONDS
local tstake=$((1+tstart))
local loop=$((fsize/(1048576*chunksize)+1))
local tdiff
if [[ $fsize -eq 0 ]]; then
loop=0
fi
for ((i=0; i<loop; ++i)); do
$TRUNCATE_CMD -s "-${chunksize}M" $fpath
tdiff=$((tstake-SECONDS))
if [[ $tdiff -gt 0 ]]; then
sleep $tdiff
fi
tstake=$((tstake+1))
done
if [[ $is_open -eq 1 ]]; then
log_debug \
"removed $fpath(+) size $fsize in $((SECONDS-tstart)) seconds"
else
log_debug \
"removed $fpath size $fsize in $((SECONDS-tstart)) seconds"
fi
fi
# here a time delta between lsof and remove
if [[ -n $LSOF_CMD && $is_open -eq 0 ]]; then
delete_files $fpath
return $ZCLEAN_OK
else
return $ZCLEAN_CRUSH
fi
}
get_home_usage() {
local usage
#usage=$(df $LOGS_DIR|awk 'END {print $5}'|tr -d '%')
usage=$(df $LOGS_DIR|tail -n 1|awk '{print $(NF-1)}'|sed -e 's/%//g')
if [[ -z $usage ]]; then
log_error "can't get home partition usage"
exit 1
fi
usage_by_du=`du -sk $LOGS_DIR | awk '{print $1}'`
usage_by_du=$(( (usage_by_du * 100) / (MAX_LOG_DIR_SIZE * 1024 * 1024) ))
if [[ $usage_by_du -gt $usage ]]; then
log_info "calculate usage based on MAX_LOG_DIR_SIZE, MAX_LOG_DIR_SIZE: $MAX_LOG_DIR_SIZE, usage: $usage_by_du"
usage=$usage_by_du
fi
echo $usage
}
sleep_dif()
{
local secs idc index
if [[ $HOSTNAME =~ ^[a-z0-9]+-[0-9]+-[0-9]+$ ]]; then
idc=$(echo $HOSTNAME|awk -F- '{print $2}')
index=$(echo $HOSTNAME|awk -F- '{print $3}')
secs=$(( (index*19 +idc*7)%233 ))
else
secs=$((RANDOM%133))
fi
sleep $secs
log_info slept $secs seconds
}
clean_expired() {
local keep_days=$((RESERVE-1))
if [[ $HOSTNAME =~ paycorecloud-30- ]]; then
keep_days=1
fi
local fpath fsize fmtime how_long expired
local ret_code=$ZCLEAN_OK
$FIND_CMD $LOGS_DIR \
-type f \
-name '*log*' \
! -name '*\.[0-9]dt\.log*' \
! -name '*\.[0-9][0-9]dt\.log*' \
! -name '*\.[0-9][0-9][0-9]dt\.log*' \
-mtime +$keep_days \
-printf '%p %s\n' | \
while read fpath fsize; do
clean_file $fpath $fsize
ret_code=$?
if [[ $ret_code -eq $ZCLEAN_OK || $ret_code -eq $ZCLEAN_CRUSH ]]; then
log_info "deleted expired file $fpath size $fsize"
fi
done
# http://doc.alipay.net/pages/viewpage.action?pageId=71187095
$FIND_CMD $LOGS_DIR \
-type f \
\( -name '*\.[0-9]dt\.log*' -o \
-name '*\.[0-9][0-9]dt\.log*' -o \
-name '*\.[0-9][0-9][0-9]dt\.log*' \) \
-printf '%p %s %TY-%Tm-%Td\n' | \
while read fpath fsize fmtime; do
how_long=$(echo $fpath | grep -o '[0-9]\+dt' | tr -d '[a-z]')
expired=$(date -d"$how_long days ago" +"%F")
if [[ $fmtime > $expired ]]; then
continue
else
clean_file $fpath $fsize
ret_code=$?
if [[ $ret_code -eq $ZCLEAN_OK || $ret_code -eq $ZCLEAN_CRUSH ]]; then
log_info "deleted expired file $fpath size $fsize"
fi
fi
done
}
clean_huge() {
local blocks big_size fpath fsize
blocks=$(df /home -k|awk 'END {print $2}')
if [[ ! $? ]]; then
log_error "can't get home partition total size"
exit 1
fi
if [[ $blocks -ge ${MAX_LOG_DIR_SIZE}*1024*1024 ]]; then
blocks=$(( MAX_LOG_DIR_SIZE*1024*1024 ))
fi
# 120G
if [[ $blocks -ge 125829120 ]]; then
big_size=50G
else
big_size=30G
fi
$FIND_CMD $LOGS_DIR \
-type f \
-name '*log*' \
-size +$big_size \
-printf '%p %s\n' | \
while read fpath fsize; do
crush_files "$fpath"
log_warn "deleted huge file $fpath size $fsize"
done
}
clean_by_day() {
local how_long=$1
local ret_code=$ZCLEAN_OK
$FIND_CMD $LOGS_DIR \
-type f \
-name '*log*' \
-mtime "+${how_long}" \
-printf '%p %s\n' | \
while read fpath fsize; do
clean_file $fpath $fsize
ret_code=$?
if [[ $ret_code -eq $ZCLEAN_OK || $ret_code -eq $ZCLEAN_CRUSH ]]; then
log_info "deleted $((how_long+1)) days ago file $fpath size $fsize"
fi
done
}
clean_by_hour() {
local how_long=$1
local ret_code=$ZCLEAN_OK
$FIND_CMD $LOGS_DIR \
-type f \
-name '*log*' \
-mmin "+$((how_long*60))" \
-printf '%p %s\n' | \
while read fpath fsize; do
clean_file $fpath $fsize
ret_code=$?
if [[ $ret_code -eq $ZCLEAN_OK || $ret_code -eq $ZCLEAN_CRUSH ]]; then
log_info "deleted $how_long hours ago file $fpath size $fsize"
fi
done
}
clean_largest() {
local fsize fpath fblock
local ret_code=$ZCLEAN_OK
$FIND_CMD $LOGS_DIR \
-type f \
-printf '%b %s %p\n' | \
sort -nr | head -1 | \
while read fblock fsize fpath ; do
# 10G
if [[ $fsize -gt 10737418240 ]]; then
crush_files $fpath
else
clean_file $fpath $fsize
fi
ret_code=$?
if [[ $ret_code -eq $ZCLEAN_OK || $ret_code -eq $ZCLEAN_CRUSH ]]; then
log_info "deleted largest file $fpath size $fsize"
fi
done
}
in_low_traffic() {
local now=$(date '+%R')
if [[ "$now" > "04:00" && "$now" < "04:30" ]]; then
return 0
else
return 1
fi
}
clean_until() {
local from_rate to_rate cur_usage old_usage how_long count force
how_long=$((RESERVE-1))
from_rate=$1
to_rate=$2
force=$3
count=0
cur_usage=$(get_home_usage)
# should exist some huge files
if [[ $cur_usage -ge 97 ]]; then
clean_huge
old_usage=$cur_usage
cur_usage=$(get_home_usage)
if [[ $cur_usage -ne $old_usage ]]; then
log_info "usage from $old_usage to $cur_usage"
fi
fi
if ! in_low_traffic; then
[[ $cur_usage -lt $from_rate ]] && return
fi
prepare_lsof
clean_expired
old_usage=$cur_usage
cur_usage=$(get_home_usage)
if [[ $cur_usage -ne $old_usage ]]; then
log_info "usage from $old_usage to $cur_usage"
fi
# now we have to remove recent logs by date
while [[ $cur_usage -gt $to_rate ]]; do
if [[ $how_long -lt 1 ]]; then
break
else
how_long=$((how_long-1))
fi
clean_by_day $how_long
old_usage=$cur_usage
cur_usage=$(get_home_usage)
if [[ $cur_usage -ne $old_usage ]]; then
log_info "usage from $old_usage to $cur_usage"
fi
done
# in hours
how_long=24
while [[ $cur_usage -gt $to_rate ]]; do
if [[ $how_long -lt 2 ]]; then
break
else
how_long=$((how_long-1))
fi
clean_by_hour $how_long
old_usage=$cur_usage
cur_usage=$(get_home_usage)
if [[ $cur_usage -ne $old_usage ]]; then
log_info "usage from $old_usage to $cur_usage"
fi
done
[[ $force -ne 1 ]] && return
# last resort, find top size logs to deleted
if [[ $CHUNK_SIZE -ne 0 ]]; then
CHUNK_SIZE=100
fi
while [[ $cur_usage -gt $to_rate ]]; do
if [[ $count -gt 5 ]]; then
log_error "give up deleting largest files"
break
fi
count=$((count+1))
clean_largest
old_usage=$cur_usage
cur_usage=$(get_home_usage)
if [[ $cur_usage -ne $old_usage ]]; then
log_info "usage from $old_usage to $cur_usage"
fi
done
delete_lsof
}
ensure_unique() {
local pgid=$(ps -p $$ -o pgid=)
local pids=$(ps -e -o pid,pgid,cmd | \
grep [z]clean | grep bash | \
awk "\$2 != $pgid {print \$1}")
if [[ -n $pids ]]; then
if [[ $INTERACTIVE -eq 1 ]]; then
kill $pids
else
log_info "$0 is running, wait for another round of dispatch"
exit 0
fi
fi
}
_main() {
local to_rate=90
local from_rate=$to_rate
local do_sleep=0
local force=0
# load config
if [[ -f $CONF_FILE && ! "$*" =~ --noconf ]]; then
while read -r line; do
key=$(echo $line|cut -d= -f1)
value=$(echo $line|cut -d= -f2)
case $key in
to)
to_rate=$value;;
block)
CHUNK_SIZE=$value;;
fast)
CHUNK_SIZE=0;;
from)
from_rate=$value;;
max_size)
MAX_LOG_DIR_SIZE=$value;;
sleep)
do_sleep=1;;
debug)
DEBUG='-debug';;
force)
force=1;;
*)
;;
esac
done < $CONF_FILE
fi
# option help
# -r clean to this ratio
# -b wipe this blocksize each time
# -t start cleaning when above this ratio
# -m max size of log dir,unit is G
# -n fast delete (use rm -rf)
# -s random sleep awhile in a app clusters
# -d extra debug logging
# -f force delete largest file
while getopts ":r:b:t:nsdfi" opt; do
case $opt in
r)
if [[ ! $OPTARG =~ ^[0-9]+$ ]]; then
echo "$0: rate $OPTARG is an invalid number" >&2
exit 1;
fi
if [[ $OPTARG -le 1 || $OPTARG -ge 99 ]]; then
echo "$0: rate $OPTARG out of range (1, 99)" >&2
exit 1;
fi
to_rate=$OPTARG ;;
b)
if [[ ! $OPTARG =~ ^[0-9]+[mMgG]?$ ]]; then
echo "$0: block size $OPTARG is invalid" >&2
exit 1;
fi
if [[ $OPTARG =~ [gG]$ ]]; then
CHUNK_SIZE=$(echo $OPTARG|tr -d 'gG')
CHUNK_SIZE=$((CHUNK_SIZE*1024))
else
CHUNK_SIZE=$(echo $OPTARG|tr -d 'mM')
fi ;;
t)
if [[ ! $OPTARG =~ ^[0-9]+$ ]]; then
echo "$0: rate $OPTARG is an invalid number" >&2
exit 1;
fi
if [[ $OPTARG -le 1 || $OPTARG -ge 99 ]]; then
echo "$0: rate $OPTARG out of range (1, 99)" >&2
exit 1;
fi
from_rate=$OPTARG ;;
m)
if [[ ! $OPTARG =~ ^[0-9]+$ ]]; then
echo "$0: max size $OPTARG is invalid" >&2
exit 1;
fi
MAX_LOG_DIR_SIZE=$OPTARG ;;
n)
CHUNK_SIZE=0 ;;
s)
do_sleep=1 ;;
d)
DEBUG='-debug' ;;
f)
force=1 ;;
i)
INTERACTIVE=1 ;;
\?)
echo "$0: invalid option: -$OPTARG" >&2
exit 1;;
:)
echo "$0: option -$OPTARG requires an argument" >&2
exit 1 ;;
esac
done
if [[ $to_rate -ge $from_rate ]]; then
to_rate=$from_rate
fi
ensure_unique
[[ $do_sleep -eq 1 ]] && sleep_dif
clean_until $from_rate $to_rate $force
}
# TODO make a decision whether /home/admin is innocent
# TODO deamonize
_main "$@"
+-----+--------------+---------------+------------------+-----------+----------+---------------------+---------------------+
| id | instance_id | instance_name | workspace_id | tenant_id | operator | gmt_create | gmt_modified |
+-----+--------------+---------------+------------------+-----------+----------+---------------------+---------------------+
| 432 | 7HBPQOV8F4OK | default | acbossprod | ANTCLOUDF | NULL | 2022-02-07 10:13:11 | 2022-02-07 10:13:11 |
| 443 | VRULPU2VVMZN | default | acgatewaypre | ANTCLOUDF | NULL | 2022-03-01 21:00:01 | 2022-03-01 21:00:01 |
| 461 | LARX4EGMTLT9 | default | acgatewayprod | ANTCLOUDF | NULL | 2022-03-25 16:03:15 | 2022-03-25 16:03:15 |
| 425 | F5Z2YNZT2RNI | default | acnbosspre | ANTCLOUDF | NULL | 2021-12-22 21:11:02 | 2021-12-22 21:11:02 |
| 424 | 3GIVRPTL0IGK | default | acnbossprod | ANTCLOUDF | NULL | 2021-12-22 21:10:12 | 2021-12-22 21:10:12 |
| 404 | VRULPU2VVMZN | default | antchainbosspre | ANTCLOUDF | NULL | 2021-10-13 00:52:24 | 2021-10-13 00:52:24 |
| 402 | LARX4EGMTLT9 | default | antchainbossprod | ANTCLOUDF | NULL | 2021-09-30 14:51:19 | 2021-09-30 14:51:19 |
| 473 | EQLBTL6URR8H | default | antstackDoc | ANTCLOUDF | NULL | 2022-05-10 14:41:23 | 2022-05-10 14:41:23 |
| 428 | VRULPU2VVMZN | default | bosspre | ANTCLOUDF | NULL | 2022-01-05 11:17:40 | 2022-01-05 11:17:40 |
| 429 | LARX4EGMTLT9 | default | bossprod | ANTCLOUDF | NULL | 2022-01-05 11:18:10 | 2022-01-05 11:18:10 |
| 409 | BFHOBR8AYDYC | default | cnhzpfmayprekms | ANTCLOUDF | NULL | 2021-10-29 10:33:39 | 2021-10-29 10:33:39 |
| 401 | ZIC5FSHHEKL2 | default | cnhzpfmaypub01 | ANTCLOUDF | NULL | 2021-09-27 17:15:23 | 2021-09-27 17:15:23 |
| 476 | VIFEZDN1YVYU | default | traasprod | ANTCLOUDF | NULL | 2022-05-20 11:51:34 | 2022-05-20 11:51:34 |
+-----+--------------+---------------+------------------+-----------+----------+---------------------+---------------------+