Introducción a los antecedentes
Tenemos un proyecto web y el método de implementación es implementación distribuida lograda por una sola aplicación (dotnet sin núcleo) + balanceador de carga (nginx). Debido al reciente aumento en el número de usuarios, el número de máquinas de carga ha aumentado de 2 a 8. Esto crea otro problema. Cada vez que agregue una máquina, actualice el software o revierta la versión, será muy problemático y doloroso. Ya no podrá pescar correctamente y su vida se desperdiciará en tareas repetitivas con sin contenido técnico superior.
Algunos veteranos pueden haber dicho, ¿por qué no deberíamos crear un sistema de arquitectura de microservicios? Utilice Kubernetes para mostrar sus habilidades, lo que le permitirá gestionar fácilmente cientos o miles de grupos de máquinas. Mientras habla y se ríe, todo desaparecerá. ¿No será hermoso?
Todo lo que puedo decir es, Tiezi, también quiero disfrutar del placer de controlar todo con una mano, pero la situación actual no lo permite todavía, pero ya me estoy desarrollando activamente en esa dirección.
El agua lejana no puede calmar la sed cerca. La situación actual requiere urgentemente una solución semitemporal para resolver este problema de duplicación de trabajo.
Como no existe una solución ya preparada, ¡cree una usted mismo!
La vida entera
Proceso manual
Piénselo cuando la carga sea liviana, todo el proceso de actualización del sitio generalmente incluye los siguientes enlaces.
- Para evitar errores de actualización, antes de actualizar, haga una copia de seguridad de la versión del sitio original en ejecución estable. Por supuesto, no es necesario realizar una copia de seguridad de todos los archivos, lo que causará presión en el almacenamiento. Solo se realiza una copia de seguridad de los archivos clave según la situación real, pero en cuanto a qué archivos clave, debe determinarse de acuerdo con la situación cada vez.
- Empaquetar archivos recién publicados
- Subir al servidor y reemplazar el archivo original.
- Si cada actualización incluye archivos del lado del servidor, debe reiniciar el sitio iis (actualmente nuestro negocio del lado web se ejecuta en iis, pero en realidad debería extenderse a apache, tomcat, nginx y otros servidores del sitio)
Proceso de operación automática
Después de ordenar todo el proceso de implementación de la actualización, lo he organizado en las siguientes etapas:
- Monitoreo: en esta etapa, el programa monitorea regularmente los cambios en un determinado archivo. El archivo que configuré aquí es un archivo de formato json, que registra el número de versión y la dirección de descarga del nuevo archivo. Cada vez que se produce un cambio en el número de versión detectado, comenzar a automatizar procesos posteriores;
- Descargar archivo: esta parte es para descargar el archivo a la ubicación especificada según la dirección de descarga obtenida durante la etapa de escucha;
- Pausar el sitio (opcional): dado que no todas las actualizaciones requieren reiniciar el sitio, este paso es opcional;
- Copia de seguridad: según los parámetros de copia de seguridad entrantes, realice una copia de seguridad de archivos y genere registros de copia de seguridad;
- Descompresión: una vez completada la copia de seguridad, puede descomprimir el segundo archivo descargado y completar el reemplazo de los archivos nuevos y antiguos;
- Reinicie el sitio (opcional): si el sitio está en pausa, debe reiniciarlo nuevamente después de completar la descompresión y la transición;
- Notificación: una vez completado todo el proceso de actualización, se debe enviar una notificación por correo electrónico al personal de desarrollo, operación y mantenimiento relevante para informarles la información clave de esta actualización automática.
Después de aclarar los procesos principales anteriores, ¡puede comenzar a codificar!
Lo que uso aquí es la estructura de la consola y uso varios paquetes de herramientas importantes, incluidos Coravel, Serilog, SharpCompress, Hosting, etc.
La dirección del código completo se proporciona al final del artículo.
monitor
Debido a que es un programa de consola, para monitorear la acción de acceder a las URL como un programa web, debe introducir el paquete Microsoft.Extensions.Hosting.
Después de la introducción, puede combinarlo con Coravel para realizar un seguimiento programado.
public static void UseCoravelService(string[] args)
{
// Renders each item with own style
bool intervelFlag = true;
var process = new Rule().RuleStyle("#FDE047");
var host = new HostBuilder()
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.AddEnvironmentVariables(prefix: "PREFIX_");
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
// Add Coravel's Scheduling...
services.AddScheduler();
})
.Build();
host.Services.UseScheduler(scheduler =>
scheduler
.Schedule(async () =>
{
//核心业务
//...
})
.EveryFifteenSeconds()
);
host.Run();
}
Después de introducir el proxy de red y Coravel, la operación posterior es el código comercial.
/// <summary>
/// 监听
/// </summary>
internal class StepMonitor
{
public static async Task<bool> getJsonFile(string address)
{
try
{
if (string.IsNullOrEmpty(address))
address = "<我这里给了一个默认地址,也可以不要>";
HttpClient hc = new HttpClient();
var content = await hc.GetStringAsync(address);
var json = JsonHelper.JsonDeserialize<VersionModel>(content);
string versionFile = "currVersion.txt";
string currVersion = "0";
currVersion = await MainScheduling.ReadFile(versionFile);
if(currVersion.Trim()==json.Version)
{
return false;//线上版本和本地版本相同,继续等待;
}
await MainScheduling.WriteFile("currVersion.txt", json.Version);
await MainScheduling.WriteFile("DownloadList.json", JsonHelper.JsonSerialize(json.Items));
return true;
}
catch (Exception ex)
{
AnsiConsole.WriteLine($"[red]下载更新索引文件失败,{ex.Message}[/]");
throw;
}
}
}
Después de definir los métodos de monitoreo relevantes, puede volver a Programar y llamarlos.
host.Services.UseScheduler(scheduler =>
scheduler
.Schedule(async () =>
{
string cache = CacheManager.Default.Get<string>("step");
AnsiConsole.MarkupLine($"[#1E9FFF]当前状态:【{cache}】,{DateTime.Now.ToString()}[/]");
if (cache == "waiting")
{
intervelFlag = false;
OutputStep(0, "监听中(waiting),正在执行...");
string file = GetParamValue(args, "file");
if (await StepMonitor.getJsonFile(file))
{
//CacheManager.Default.Set_SlidingExpire<string>("step", "download", TimeSpan.FromMinutes(10));
await SetStep("download");
}
intervelFlag = true;
}
})
El efecto de seguimiento final es el siguiente
descargar
No hay mucho que decir sobre esta parte. Simplemente puede descargar el archivo de la forma que desee. No es imposible incluso llamar a herramientas de modelado como wget. Solo necesita hacer un buen trabajo interactuando con la información de el programa.
No publicaré el código de descarga aquí. Puedes verlo en el código fuente. La captura de pantalla es la siguiente.
Cerrar sitio (opcional)
Juzgo si debo cerrar el sitio aquí en función de si el archivo de versión contiene la palabra clave estática.
string currVersion = await ReadFile("currVersion.txt");
if (currVersion.Contains("static"))
{
OutputStep(2, "仅更新静态文件,无需重启站点");
}
else
{
OutputStep(2, "站点关闭中(stopweb),正在执行...");
string appSite = GetParamValue(args, "appSite");
string appPool = GetParamValue(args, "appPool");
StepIISManager.Stop(appSite, appPool);
}
Las capturas de pantalla de las dos situaciones son las siguientes.
archivo de respaldo
Para los archivos de respaldo, he configurado un parámetro de entrada especial "-subpath" aquí. Si no se pasa este parámetro, se realizará una copia de seguridad completa del sitio. Si se pasa, la copia de seguridad se basará en los archivos pasados o directorios.
//这里的参数是解压地址
string inputPath = GetParamValue(args, "output");
string outputPath = GetParamValue(args, "backuppath");
string currVersion = await ReadFile("currVersion.txt");
string subPath = GetParamValue(args, "subpath");
OutputStep(3, "站点备份中(backup),正在执行...");
if (!string.IsNullOrEmpty(subPath))
{
AnsiConsole.MarkupLine("[#FDE047]检测到子目录参数,不再进行全量备份,正在顺序备份[/]");
if (!string.IsNullOrEmpty(subPath))
{
string[] parts = subPath.Split(',');
var paths = parts.Where(u => !u.Contains(".")).ToList();
var files = parts.Where(u => u.Contains(".")).ToList();
if (files.Any()&&paths.Any(u=>!u.Equals("_temp")))
{
paths.Add("_temp");
}
foreach(var file in files)
{
AnsiConsole.MarkupLine($"[#FDE047]文件{file}备份中...[/]");
string subInputPath = Path.Combine(inputPath, file);
if (File.Exists(subInputPath))
{
string tempPath = Path.Combine(inputPath, "_temp");
if (!Directory.Exists(tempPath))
{
Directory.CreateDirectory(tempPath);
}
File.Copy(subInputPath, Path.Combine(tempPath, file), true);
}
}
foreach (var item in paths)
{
AnsiConsole.MarkupLine($"[#FDE047]{item}备份中...[/]");
string subInputPath = Path.Combine(inputPath, item);
await StepDeCompress.ZipCompress(subInputPath, outputPath, item);
}
}
}
else
{
await StepDeCompress.ZipCompress(inputPath, outputPath);
}
Abrir la cremallera
La descompresión consiste en descomprimir el archivo descargado en el segundo paso en el directorio especificado, asumiendo la capacidad del componente Sharpcompress.
/// <summary>
/// 解压文件
/// </summary>
/// <param name="inputFile">解压文件路径</param>
/// <param name="outputFile">解压文件后路径</param>
public static void Decompression(string inputFile, string outputFile)
{
try
{
if (!inputFile.Contains(":"))
{
inputFile = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), inputFile);
}
SharpCompress.Readers.ReaderOptions options = new SharpCompress.Readers.ReaderOptions();
options.ArchiveEncoding.Default = Encoding.GetEncoding("utf-8");
var archive = ArchiveFactory.Open(inputFile, options);
AnsiConsole.Status()
.Start("解压文件...", ctx =>
{
foreach (var entry in archive.Entries)
{
if (!entry.IsDirectory)
{
entry.WriteToDirectory(outputFile, new ExtractionOptions { ExtractFullPath = true, Overwrite = true });
}
}
AnsiConsole.MarkupLine("[#1E9FFF]解压完成[/]");
});
}
catch (Exception ex)
{
AnsiConsole.WriteLine($"[red]{ex.Message}[/]");
throw;
}
}
representaciones
Reiniciar sitio (opcional)
Los mismos pasos que para pausar el sitio, excepto que el comando entrante se convierte en iniciar
if (currVersion.Contains("static"))
{
OutputStep(5, "仅更新静态文件,无需重启站点");
}
else
{
OutputStep(5, "站点启动中(startweb),正在执行...");
string appSite = GetParamValue(args, "appSite");
string appPool = GetParamValue(args, "appPool");
StepIISManager.Start(appSite, appPool);
Console.WriteLine(DateTime.Now.ToString());
}
Efecto
Enviar notificación
En realidad, esta parte es evidente. Se trata de enviar un correo electrónico al administrador designado. El contenido del correo electrónico debe incluir los detalles de la actualización.
No publicaré el código aquí, echemos un vistazo a las representaciones.
Resumir
Aunque ahora estamos en la era de la nube nativa y están surgiendo uno tras otro varios marcos que recurren a los microservicios, un hecho que no se puede ignorar es que las aplicaciones monolíticas todavía representan una proporción considerable en la actualidad. Especialmente en los proyectos de pequeñas y microempresas e incluso en bastantes medianas y grandes empresas, las aplicaciones monolíticas aún deberían representar la corriente principal. Debido a las limitaciones de su propio negocio, algunas empresas pueden no ser adecuadas para ser transferidas a las filas de Microservicios desde el nacimiento hasta la muerte. Esta es la realidad y también una lástima.
Mi actitud personal hacia los microservicios es básicamente la de un creyente y estoy adoptando activamente la transformación. El líder del equipo también nos apoya para convertir el negocio existente en una arquitectura de microservicios paso a paso en función de la situación real. Sin embargo, después de todo, Es un proyecto que se ha puesto en producción y todavía necesitamos. La estabilidad es nuestra principal prioridad, por lo que avanzamos mucho más lento. Un gran número de empresas todavía funcionan basándose en la estructura de una única aplicación, por lo que las principales tareas de desarrollo y mantenimiento siguen basándose en el negocio real. Somos cautelosamente optimistas sobre la transformación. Creo que esta es una actitud objetiva, positiva y correcta. La innovación en sí misma es algo fascinante con un futuro incierto, pero involucrarse demasiado o incluso realizar algunas operaciones extremas causará riesgos impredecibles. Partir de la realidad, combinar las condiciones comerciales y avanzar paso a paso es el camino correcto. También creo que en 2023 finalmente daremos el paso más crítico en el camino hacia la transformación.
adjunto
Dirección del código: Para ti mismo_Bring Salt/AutoDeploy · GitCode