






















本文档基于项目中的 ESService 类代码,梳理 Elasticsearch 中 Data Stream 与 ILM / DSL 生命周期管理的核心概念、工作原理及实际代码示例。
Data Stream 是 Elasticsearch 针对时序数据(日志、指标、事件)提供的一种抽象存储层。它对外表现为一个逻辑名称,底层自动管理一组后备索引(Backing Index)。用户只需向 Data Stream 写入文档,无需关心索引的创建、滚动和删除。
Data Stream 的名称必须遵循以下模式之一(用于自动匹配内置模板):
logs-{dataset}-{namespace} (日志)metrics-{dataset}-{namespace} (指标)traces-{dataset}-{namespace} (链路追踪)本项目中使用:logs-myapp-default
当向 Data Stream 写入第一条文档时,Elasticsearch 会根据匹配的 Index Template 自动创建第一个后备索引,命名格式为:
.ds-{data_stream_name}-{yyyy.MM.dd}-{generation}
例如:.ds-logs-myapp-default-2026.07.13-000001
Elasticsearch 提供两种管理 Data Stream 生命周期的方式:ILM(Index Lifecycle Management) 和 DSL(Data Stream Lifecycle)。
ILM 是一种基于索引级别的策略系统。管理员定义一个策略(Policy),其中包含多个阶段(Phase),如 hot、warm、cold、frozen、delete。每个阶段可配置动作(Actions),例如 rollover、shrink、force merge、searchable snapshot、delete 等。
index.lifecycle.name setting。data_stream: {}。_ilm/retry 才能重新评估。DSL 是一种基于 Data Stream 级别的简化生命周期管理。它只关心一个参数:数据保留时长(data_retention)。rollover 由 Elasticsearch 自动管理(默认 50GB 或根据保留时长自动计算滚动间隔)。
template.lifecycle 中设置 data_retention。PUT _data_stream/{name}/_lifecycle 直接对已有 Data Stream 设置。DataRetention("30d") 即可。项目 ESService 类提供了两种实现,分别演示 ILM 和 DSL 的用法。
DataStreamLifeCycle 方法)public void DataStreamLifeCycle(int retentionDays,string logMessage)
{
var retention = $"{retentionDays}d";
// 1. 创建 Index Template(幂等),设置 Data Retention 为 30 分钟
var putTemplateResponse = client.Indices.PutIndexTemplate("logs-myapp-template", it => it
.IndexPatterns("logs-myapp-*")
.Template(t => t
.Lifecycle(l => l.DataRetention(retention )) // DSL 核心:保留 30 分钟
.Mappings(m => m
.Properties(props => props
.Text("Message")
.Keyword("ServiceName")
.Keyword("LogLevel")
.Date("@timestamp")
)
)
)
.DataStream()
.Priority(500)
);
// 2. 更新所有已存在的 Data Stream(使其立即应用新 retention)
var listDsResponse = client.Indices.GetDataStream(ds => ds.Name("logs-myapp-*"));
if (listDsResponse.IsValidResponse && listDsResponse.DataStreams.Any())
{
foreach (var dataStream in listDsResponse.DataStreams)
{
client.Indices.PutDataLifecycle(dataStream.Name, p => p.DataRetention(retention ));
}
}
// 3. 写入一条日志,Data Stream 自动创建
var logDoc = new LogEntry
{
Timestamp = DateTime.UtcNow,
Message = logMessage,
ServiceName = "MyService",
LogLevel = "Info"
};
client.Index(logDoc, idx => idx.Index("logs-myapp-default"));
}
要点:
.Lifecycle(l => l.DataRetention("30m")) 定义了 DSL 保留期。PutDataLifecycle 使其立即生效。LogEntry,并通过 [JsonPropertyName("@timestamp")] 确保字段名正确。WriteLogToDataStreamAsync 方法)public void WriteLogToDataStreamAsync(int retentionDays, string logMessage)
{
var ilmPolicyName = $"logs-myapp-retention-{retentionDays}d";
// 1. 创建 ILM 策略
client.IndexLifecycleManagement.PutLifecycle(ilmPolicyName, policy => policy
.Policy(p => p
.Phases(ph => ph
.Hot(h => h
.Actions(a => a
.Rollover(r => r.MaxAge("7d").MaxPrimaryShardSize("50gb"))
)
)
.Delete(d => d
.MinAge($"{retentionDays}d")
.Actions(da => da.Delete(del => del.DeleteSearchableSnapshot()))
)
)
)
);
// 2. 创建 Component Template(绑定 ILM 策略)
var componentTemplateName = "logs-myapp-settings";
client.Cluster.PutComponentTemplate(componentTemplateName, ct => ct
.Template(t => t
.Settings(s => s
.NumberOfShards(1)
.NumberOfReplicas(1)
.Lifecycle(l => l.Name(ilmPolicyName)) // 绑定 ILM 策略
)
.Mappings(m => m
.Properties(props => props
.Text("Message")
.Keyword("ServiceName")
.Keyword("LogLevel")
.Date("@timestamp")
)
)
)
);
// 3. 创建 Index Template(引用 Component Template)
client.Indices.PutIndexTemplate("logs-myapp-template", it => it
.IndexPatterns("logs-myapp-*")
.ComposedOf(componentTemplateName)
.DataStream()
.Priority(500)
);
// 4. 写入日志
var logDoc = new LogEntry { ... };
client.Index(logDoc, idx => idx.Index("logs-myapp-default"));
}
要点:
Hot 阶段配置 rollover(7天或50GB),Delete 阶段配置保留天数。ComposedOf 引用 Component Template,并标记 DataStream()。| 场景 | 推荐方案 |
|---|---|
| 只需按时间保留数据,无 warm/cold 需求 | DSL(ES ≥ 8.11) |
| 需要 warm/cold 降冷、searchable snapshot | ILM |
| 自建集群,版本较低(< 8.11) | ILM |
| 希望最小化配置,快速上线 | DSL |
| 已有大量 ILM 策略,不愿迁移 | 继续 ILM |
注意:两者不能混用。如果在 Index Template 中同时设置了 index.lifecycle.name(ILM)和 template.lifecycle(DSL),ES 默认 index.lifecycle.prefer_ilm: true,ILM 会覆盖 DSL。若要使用 DSL,必须移除 ILM 绑定。
POST /{index}/_ilm/retry 或等待下一次 ILM 检查周期(默认10分钟)。DataRetention 后,可通过 PUT _data_stream/{name}/_lifecycle 立即生效,无需额外操作。@timestamp@timestamp 字段(类型 date)。[JsonPropertyName("@timestamp")],或使用 Dictionary<string, object> 显式设置键名。Priority >= 300,确保覆盖内置模板。封装通用日志服务(同步版本,仅支持 DSL 模式),提供单条和批量创建方法
public class EsDataStreamOptions
{
/// <summary>
/// Data Stream 名称,如 "logs-myapp-default"
/// </summary>
public string DataStreamName { get; set; } = "logs-bll-default";
/// <summary>
/// 索引模板名称
/// </summary>
public string IndexTemplateName { get; set; } = "logs-bll-default-template";
/// <summary>
/// 索引匹配模式,如 "logs-default-*"
/// DataStream 模板只匹配“后备索引(Backing Index)”的命名规则
/// </summary>
public string IndexPattern { get; set; } = "blllogs-default*";
/// <summary>
/// 数据保留天数(DSL 的 data_retention)
/// </summary>
public int RetentionDays { get; set; } = 30;
/// <summary>
/// 索引设置(分片、副本)
/// </summary>
public int NumberOfShards { get; set; } = 1;
public int NumberOfReplicas { get; set; } = 1;
}
/// <summary>
/// 日志基础接口,所有自定义日志类必须实现此接口
/// </summary>
public class BaseLogEntry
{
/// <summary>
///
/// </summary>
public string Id { get; set; }
/// <summary>
///
/// </summary>
[JsonPropertyName("@timestamp")]
public DateTime Timestamp { get; set; }
}
/// <summary>
/// 通用日志服务(同步版本,仅支持 DSL 模式)
/// </summary>
public class GenericEsLogService<TLog> where TLog : BaseLogEntry
{
private readonly ElasticsearchClient _client;
private EsDataStreamOptions _options;
private readonly ILogger<GenericEsLogService<TLog>> _logger;
public GenericEsLogService(
ElasticsearchClient client,
ILogger<GenericEsLogService<TLog>> logger = null)
{
_client = client;
_logger = logger;
}
/// <summary>
///
/// </summary>
public EsDataStreamOptions GetOption()
{
return _options;
}
/// <summary>
///
/// </summary>
public string? GetIndexName()
{
return GetOption()?.DataStreamName;
}
/// <summary>
/// 初始化 Data Stream 模板和生命周期(同步调用)
/// </summary>
public void Initialize(EsDataStreamOptions option)
{
this._options = option;
//var retention = $"{_options.RetentionDays}d";
var retention = $"{_options.RetentionDays}s";
// 1. 创建/更新 Index Template(包含 DSL 生命周期)
var putTemplateResponse = _client.Indices.PutIndexTemplate(
_options.IndexTemplateName,
it => it
.IndexPatterns(_options.IndexPattern)
.Template(t => t
.Lifecycle(l => l.DataRetention(retention))
.Mappings(m => m
.Properties(props =>
{
props.Date("@timestamp");
})
)
.Settings(s => s
.NumberOfShards(option.NumberOfShards) // 只对新索引生效
.NumberOfReplicas(option.NumberOfReplicas)
.Lifecycle(f =>
f
.Name(string.Empty)
.PreferIlm(false)
)
)
)
//.ComposedOf("logs")
.DataStream()
.Priority(200)
);
if (!putTemplateResponse.IsValidResponse)
throw new Exception($"Failed to create DSL template: {putTemplateResponse.DebugInformation}");
_logger?.LogInformation("DSL initialization completed for {DataStream}", _options.DataStreamName);
}
/// <summary>
/// 如果存在已有的数据可以更新已有数据的配置
/// </summary>
public void UpdateExistIndex()
{
// 2. 更新所有已存在的 Data Stream(使其立即应用新的 retention)
//var listDsResponse = _client.Indices.GetDataStream(ds => ds.Name(_options.IndexPattern));
var listDsResponse = _client.Indices.GetDataStream(ds => ds.Name(this._options.DataStreamName));
if (listDsResponse.IsValidResponse && listDsResponse.DataStreams.Any())
{
foreach (var ds in listDsResponse.DataStreams)
{
var putLifecycleResponse = _client.Indices.PutDataLifecycle(
ds.Name,
p => p.DataRetention(_options.RetentionDays));
if (!putLifecycleResponse.IsValidResponse)
_logger?.LogWarning("Failed to update lifecycle for DS {DsName}", ds.Name);
foreach (var index in ds.Indices)
{
index.PreferIlm = false;
var updateSettingsResponse = _client.Indices.PutSettings(index.IndexName, s => s
.Settings(q => q.NumberOfReplicas(_options.NumberOfReplicas)
.Lifecycle(l => l
.Name("") // 空字符串 = 移除 ILM 策略绑定
.PreferIlm(false) // 关掉 ILM 优先,让 DSL 接管,保证日志正确删除
)
)
);
if (!updateSettingsResponse.IsValidResponse)
_logger?.LogWarning("Failed to update replicas for {Index}", index.IndexName);
}
}
}
}
/// <summary>
/// 写入一条日志(同步)
/// </summary>
public string WriteLog(TLog logEntry)
{
if (logEntry.Timestamp == default)
logEntry.Timestamp = DateTime.UtcNow;
//var indexResponse = _client.Create(logEntry, _options.DataStreamName, logEntry.Id);
//ds只允许新增
var indexResponse = _client.Create(logEntry, _options.DataStreamName, logEntry.Id);
if (!indexResponse.IsValidResponse)
throw new Exception($"Failed to write log: {indexResponse.DebugInformation}");
_logger?.LogDebug("Log written to {DataStream}, ID={Id}", _options.DataStreamName, indexResponse.Id);
return indexResponse.Id;
}
/// <summary>
/// 批量写入日志(同步)
/// </summary>
public void BulkWrite(IEnumerable<TLog> logEntries)
{
var bulkDescriptor = new BulkRequestDescriptor();
foreach (var entry in logEntries)
{
if (entry.Timestamp == default)
entry.Timestamp = DateTime.UtcNow;
// 新版:用 .Create(idx => idx.Index(...)) 链式追加,每个操作对应一个 Create
bulkDescriptor.Create<TLog>(entry).Index(_options.DataStreamName);
}
var bulkResponse = _client.Bulk(bulkDescriptor);
if (!bulkResponse.IsValidResponse)
throw new Exception($"Bulk write failed: {bulkResponse.DebugInformation}");
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。