浅谈基于Prism的软件系统的架构设计
很早就想写这么一篇文章来对近几年使用Prism框架来设计软件来做一次深入的分析了,但直到最近才开始整理,说到软件系统的设计这里面有太多的学问,只有经过大量的探索才能够设计出好的软件产品,就本人的理解,一个好的软件必须有良好的设计,这其中包括:易阅读、易扩展、低耦合、模块化等等,如果你想设计一个好的系统当然还要考虑更多的方面,本篇文章主要是基于微软的Prism框架来谈一谈构建模块化、热插拔软件系统的思路,这些都是经过大量项目实战的结果,希望能够通过这篇文章的梳理能够对构建软件系统有更加深刻的理解。
首先要简单介绍一下Prism这个框架:Prism框架通过功能模块化的思想,将复杂的业务功能和UI耦合性进行分离,通过模块化,来最大限度的降低耦合性,很适合我们进行类似插件化的思想来组织系统功能,并且模块之间,通过发布和订阅事件来完成信息的通信,而且其开放性支持多种框架集成。通过这些简单的介绍就能够对此有一个简单的理解,这里面加入了两种依赖注入容器,即:Unity和MEF两种容器,在使用的时候我们首先需要确定使用何种容器,这个是第一步。第二步就是如何构建一个成熟的模块化软件,这个部分需要我们能够对整个软件系统功能上有一个合理的拆分,只有真正地完全理解整个系统才能够合理抽象Module,然后降低Module之间的耦合性。第三步就是关于模块之间是如何进行通讯的,这一部分也是非常重要的部分,今天这篇文章就以Prism的Unity依赖注入容器为例来说明如何构建模块化软件系统,同时也简要说明一下软件系统的构建思路。
这里以百度地图为例来说一下如果使用WPF+Prism的框架来设计的话,该怎样来设计,当然这里只是举一个例子,当然这篇文章不会就里面具体的代码的逻辑来进行分析,事实上我们也不清楚这个里面具体的内部实现,这里仅仅是个人的观点。
图一 百度地图主界面
注意下面所有的代码并非和上面的截图一致,截图仅供参考
如图一所示,整个界面从功能主体上区分的话,就能够很好的分成这几个部分,左侧是一个搜索区域,右边是两个功能区和一个个人信息区域,中间是地图区域,这个是我们在看完这个地图之后第一眼就能想到的使用Prism能够构建的几个模块(Modules)。在定完整个系统可以分为哪几个模块之后我们紧接着就要分析每一个模块包含哪些功能,并根据这些功能能够定义哪些接口,我们可以新建一个类库,专门用于定义整个应用程序的接口,并放在单独的类库中,比如左侧的地图搜索区域我们可以定义一个IMapSearch的接口,用于定于这个部分有哪些具体的功能,如下面代码所示。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace IGIS.SDK
{
public delegate List<Models.SearchResult> OnMapSearchHandle(string keyword);
public interface IMapSearch
{
void AddSearchListener(string type, OnMapSearchHandle handle);
void RemoveSearchListener(string type);
void ShowResults(List<Models.SearchResult> results);
void ClearResults();
System.Collections.ObjectModel.ObservableCollection<Models.SearchResult> GetAllResults();
event EventHandler<string> OnSearchCompleted;
event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnClearSearchResult;
event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnExecuteMultiSelected;
void ShowFloatPanel(Models.SearchResult targetResult, FrameworkElement ui);
}
}
这是第一步,为左侧的搜索区域定义好接口,当然模块化的设计必然包括界面和界面抽象,即WPF中的View层和ViewModel层以及Model层,我们可以单独新建一个项目(自定义控件库为佳)来单独实现这一部分的MVVM,然后生成单独的DLL供主程序去调用,比如新建一个自定义空间库命名为Map.SearchModule,然后分别设计这几个部分,这里列出部分代码仅供参考。
<UserControl x:Class="IGIS.MapSearch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Title="IGIS"
xmlns:cvt="clr-namespace:IGIS.Utils" xmlns:gisui="clr-namespace:IGIS.UI;assembly=IGIS.UI"
xmlns:region="http://www.codeplex.com/CompositeWPF"
xmlns:ui="clr-namespace:X.UI;assembly=X.UI"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
d:DesignHeight="600" d:DesignWidth="1100"> <Grid>
......
</Grid>
</UserControl>
当然最重要的部分代码都是在ViewModel层中去实现的,这个层必须要继承自IMapSearch这个接口,然后和View层通过DataContext绑定到一起,这样一个完整的模块化的雏形就出来了,后面还有几个重要的部分再一一讲述。
using IGIS.SDK.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Collections.ObjectModel;
using X;
using X.Infrastructure; namespace IGIS.ViewModels
{
class SearchManager : X.Infrastructure.VMBase, IGIS.SDK.IMapSearch
{
public SearchManager()
{
Search = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoSearch);
ClearResult = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoClearResult);
ShowSelected = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoShowSelected);
Listeners.Add(new Listener { Name = "全部", Handle = null });
} private void DoShowSelected()
{
if (null != OnExecuteMultiSelected)
{
System.Collections.ObjectModel.ObservableCollection<SearchResult> selected = new ObservableCollection<SearchResult>();
foreach (var itm in SelectedItems)
{
if (itm is SearchResult)
selected.Add(itm as SearchResult);
}
OnExecuteMultiSelected(this, selected);
}
} private static SearchManager _instance; public static SearchManager Instance
{
get
{
if (null == _instance)
_instance = new SearchManager();
return _instance;
}
set { _instance = value; }
} private void DoSearch()
{
ClearResults();
foreach (var ls in Listeners)
{
if (string.IsNullOrEmpty(SelectedType) || SelectedType == "全部" || SelectedType == ls.Name)
if (ls.Handle != null)
{
List<SearchResult> res = null;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
res = ls.Handle.Invoke(Keyword);
}), System.Windows.Threading.DispatcherPriority.Normal);
if (null != res && res.Count > 0)
{
foreach (var itm in res)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Results.Add(itm);
}));
}
}
}
} if (null != OnSearchCompleted)
OnSearchCompleted(Results, Keyword); DoRemoteSearch(SelectedType, Keyword);
} private string _keyword; public string Keyword
{
get { return _keyword; }
set
{
if (_keyword != value)
{
_keyword = value;
OnPropertyChanged("Keyword");
}
}
} private string _selectedType = "全部"; public string SelectedType
{
get { return _selectedType; }
set
{
if (_selectedType != value)
{
_selectedType = value;
OnPropertyChanged("SelectedType");
}
}
} private ICommand _showSelected; public ICommand ShowSelected
{
get { return _showSelected; }
set { _showSelected = value; }
} private ICommand _search; public ICommand Search
{
get { return _search; }
set
{
if (_search != value)
{
_search = value;
OnPropertyChanged("Search");
}
}
} private ICommand _ClearResult; public ICommand ClearResult
{
get { return _ClearResult; }
set { _ClearResult = value; }
} private void DoClearResult()
{
ClearResults();
} private System.Collections.ObjectModel.ObservableCollection<SearchResult> _results
= new System.Collections.ObjectModel.ObservableCollection<SearchResult>(); public System.Collections.ObjectModel.ObservableCollection<SearchResult> Results
{
get { return _results; }
set
{
if (_results != value)
{
_results = value;
OnPropertyChanged("Results");
}
}
} private System.Collections.IList _selectedItems; public System.Collections.IList SelectedItems
{
get { return _selectedItems; }
set { _selectedItems = value; }
} #region SDK public class Listener : X.Infrastructure.NotifyObject
{
private string _name; public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
} private SDK.OnMapSearchHandle _handle; public SDK.OnMapSearchHandle Handle
{
get { return _handle; }
set { _handle = value; }
} } public event EventHandler<string> OnSearchCompleted;
public event EventHandler<System.Collections.ObjectModel.ObservableCollection<SDK.Models.SearchResult>> OnClearSearchResult;
public event EventHandler<ObservableCollection<SearchResult>> OnExecuteMultiSelected; private System.Collections.ObjectModel.ObservableCollection<Listener> _listeners
= new System.Collections.ObjectModel.ObservableCollection<Listener>(); public System.Collections.ObjectModel.ObservableCollection<Listener> Listeners
{
get { return _listeners; }
set
{
if (_listeners != value)
{
_listeners = value;
OnPropertyChanged("Listeners");
}
}
} public System.Collections.ObjectModel.ObservableCollection<SearchResult> GetAllResults()
{
return Results;
} public void AddSearchListener(string type, SDK.OnMapSearchHandle handle)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;
if (null == itm)
{
itm = new Listener() { Name = type };
Listeners.Add(itm);
}
itm.Handle = handle;
}));
} public void RemoveSearchListener(string type)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
try
{
var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;
if (null != itm)
{
Listeners.Remove(itm);
}
}
catch (Exception)
{
}
}));
} public void ShowResults(List<SearchResult> results)
{
ClearResults();
foreach (var itm in results)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Results.Add(itm);
}));
}
} public void ClearResults()
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (null != OnClearSearchResult && Results.Count > 0)
OnClearSearchResult(this, Results);
Results.Clear();
ClearRemoteResults();
}));
} public void ShowFloatPanel(SearchResult targetResult, FrameworkElement ui)
{
if (null != OnShowFloatPanel)
OnShowFloatPanel(targetResult, ui);
} internal event EventHandler<FrameworkElement> OnShowFloatPanel; #endregion #region 大屏端同步命令 void DoRemoteSearch(string type, string keyword)
{
X.Factory.GetSDKInstance<X.IDataExchange>().Send(new IGIS.SDK.Messages.RemoteMapSearchMessage() { SelectedType = this.SelectedType, Keyword = this.Keyword }, "IGISMapSearch");
} void ClearRemoteResults()
{
X.Factory.GetSDKInstance<X.IDataExchange>().Send(new X.Messages.MessageBase(), "IGISClearMapSearch");
} #endregion }
}
如果熟悉Prism的开发者肯定知道这部分可以完整的定义为一个Region,在完成这部分之后,最重要的部分就是将当前的实现接口IGIS.SDK.IMapSearch的对象注入到UnityContainer中从而在其他的Module中去调用,这样就能够实现不同的模块之间进行通信,具体注入的方法请参考下面的代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;
using X; namespace IGIS
{
public class IGISProductInfo : IModule
{
Microsoft.Practices.Prism.Regions.IRegionViewRegistry m_RegionViewRegistry;
public IGISProductInfo(Microsoft.Practices.Unity.IUnityContainer container)
{
m_RegionViewRegistry = _RegionViewRegistry;
container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance);
} public void Initialize()
{
m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch));
}
}
}
首先我们通过m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch))来将当前的View注册到主程序的Shell中,在主程序中我们只需要通过<ContentControl region:RegionManager.RegionName="MapSearchRegion"></ContentControl>就能够将当前的View放到主程序的中,从而作为主程序的界面的一部分,然后通过代码:container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance),就能够将当前实现IMapSearch的接口的实例注入到Prism框架的全局的UnityContainer中,最后一步也是最关键的就是在其它的模块中,如果我们需要调用当前实现IMapSearch的接口的方法,那该怎么来获取到实现这个接口的实例呢?
下面的代码提供了两个方法,一个同步方法和一个异步的方法来获取当前的实例,比如使用同步的方法,我们调用GetSDKInstance这个方法传入类型:IGIS.SDK.IMapSearch时就能够获取到注入到容器中的唯一实例:ViewModels.SearchManager.Instance,这样我们就能够获取到这个实例了。
public static T GetSDKInstance<T>() where T : class
{
if (currentInstances.ContainsKey(typeof(T)))
return currentInstances[typeof(T)] as T;
try
{
var instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();
currentInstances[typeof(T)] = instance;
return instance;
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
} private static object Locker = new object();
public static void GetSDKInstanceAysnc<T>(Action<T> successAction) where T : class
{
if (currentInstances.ContainsKey(typeof(T)))
{
successAction.Invoke(currentInstances[typeof(T)] as T);
return;
}
Task.Factory.StartNew(new Action(() =>
{
lock (Locker)
{
T instance = null;
int tryCount = 0;
while (instance == null && tryCount <= 100)
{
tryCount++;
try
{
instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();
}
catch
{
}
if (null != instance)
{
currentInstances[typeof(T)] = instance;
successAction.Invoke(instance);
return;
}
else
{
System.Threading.Thread.Sleep(50);
}
}
}
}));
}
在看完上面的介绍之后我们似乎对基于Prism的模块化开发思路有了一定的理解了,但是这些模块是在何时进行加载的呢?Prism框架是一种预加载模式,即生成的每一个Module在主程序Shell初始化的时候就会去加载每一个继承自IModule的接口的模块,当然这些模块是分散在程序的不同目录中的,在加载的时候需要为其指定具体的目录,这样在主程序启动时就会加载不同的模块,然后每个模块加载时又会将继承自特定接口的实例注册到一个全局的容器中从而供不同的模块之间相互调用,从而实现模块之间的相互调用,同理图一中的功能区、个人信息区、地图区都能够通过继承自IModule接口来实现Prism框架的统一管理,这样整个软件就可以分成不同的模块,从而彼此独立最终构成一个复杂的系统,当然这篇文章只是做一个大概的分析,为对Prism框架有一定理解的开发者可以有一个指导思想,如果想深入了解Prism的思想还是得通过官方的参考代码去一点点理解其指导思想,同时如果需要对Prism有更多的理解,也可以参考我之前的博客,本人也将一步步完善这个系列。
最后我们要看看主程序如何在初始化的时候来加载这些不同的模块的dll的,请参考下面的代码:
using System;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Prism.UnityExtensions;
using Microsoft.Practices.Prism.Logging; namespace Dvap.Shell.CodeBase.Prism
{
public class DvapBootstrapper : Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper
{
private readonly string[] m_PluginsFolder=new string[3] { "FunctionModules", "DirectoryModules", "Apps"};
private readonly CallbackLogger m_callbackLogger = new CallbackLogger();
#region Override
/// <summary>
/// 创建唯一的Shell对象
/// </summary>
/// <returns></returns>
protected override DependencyObject CreateShell()
{
return this.Container.TryResolve<Dvap.Shell.Shell>();
} protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Window)this.Shell;
Application.Current.MainWindow.Show();
} /// <summary>
/// 创建唯一的Module的清单
/// </summary>
/// <returns></returns>
protected override IModuleCatalog CreateModuleCatalog()
{
return new CodeBase.Prism.ModuleCatalogCollection();
} /// <summary>
/// 配置唯一的ModuleCatalog,这里我们通过从特定的路径下加载
/// dll
/// </summary>
protected override void ConfigureModuleCatalog()
{
try
{
var catalog = ((CodeBase.Prism.ModuleCatalogCollection)ModuleCatalog); foreach (var pluginFolder in m_PluginsFolder)
{
if (pluginFolder.Contains("~"))
{
DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = pluginFolder.Replace("~", AppDomain.CurrentDomain.BaseDirectory) };
catalog.AddCatalog(catApp);
}
else
{
if (!System.IO.Directory.Exists(@".\" + pluginFolder))
{
System.IO.Directory.CreateDirectory(@".\" + pluginFolder);
} foreach (string dic in System.IO.Directory.GetDirectories(@".\" + pluginFolder))
{
DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = dic };
catalog.AddCatalog(catApp);
}
}
}
}
catch (Exception)
{
throw;
}
} protected override ILoggerFacade CreateLogger()
{
return this.m_callbackLogger;
}
#endregion } }
看到没有每一个宿主应用程序都有一个继承自Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper的类,我们需要重写其中的一些方法来实现Prism程序的模块加载,例如重写 override void ConfigureModuleCatalog() 我们的宿主程序就知道去哪里加载这些继承自IModule的dll,还有必须重载CreateShell和InitializeShell()
这些基类的方法来制定主程序的Window,有了这些我们就能够构造一个完整的Prism程序了,对了还差最后一步,就是启动Prism的Bootstrapper,我们一般是在WPF程序的App.xaml.cs中启动这个,例如:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading; namespace Dvap.Shell
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new CodeBase.Prism.DvapBootstrapper().Run();
this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
try
{
if (e.ExceptionObject is System.Exception)
{
WriteLogMessage((System.Exception)e.ExceptionObject);
}
}
catch (Exception ex)
{
WriteLogMessage(ex);
}
} private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
try
{
WriteLogMessage(e.Exception);
e.Handled = true;
}
catch (Exception ex)
{
WriteLogMessage(ex);
}
} public static void WriteLogMessage(Exception ex)
{
//如果不存在则创建日志文件夹
if (!System.IO.Directory.Exists("Log"))
{
System.IO.Directory.CreateDirectory("Log");
}
DateTime now = DateTime.Now;
string logpath = string.Format(@"Log\Error_{0}{1}{2}.log", now.Year, now.Month, now.Day);
System.IO.File.AppendAllText(logpath, string.Format("\r\n************************************{0}*********************************\r\n", now.ToString("yyyy-MM-dd HH:mm:ss")));
System.IO.File.AppendAllText(logpath, ex.Message);
System.IO.File.AppendAllText(logpath, "\r\n");
System.IO.File.AppendAllText(logpath, ex.StackTrace);
System.IO.File.AppendAllText(logpath, "\r\n");
System.IO.File.AppendAllText(logpath, "\r\n*************************************************r\n");
}
}
}
在应用程序启动时调用 new CodeBase.Prism.DvapBootstrapper().Run()启动Prism应用程序,从而完成整个过程,当然上面的讲解只能够说明Prism的冰山一角,了解好这个框架将为我们开发复杂的应用程序提供一种新的思路。
浅谈基于Prism的软件系统的架构设计的更多相关文章
- 浅谈Nginx服务器的内部核心架构设计
前言 Nginx 是一个 免费的 , 开源的 , 高性能 的 HTTP 服务器和 反向代理 ,以及 IMAP / POP3代理服务器. Nginx 以其高性能,稳定性,丰富的功能,简单的配置和低资源消 ...
- 基于laravel4.2的相关架构设计
项目组不久前引进了laravel框架,本人参与了laravel的调研和项目架构设计.个人认为项目架构中基于laravel的有些设计还是比较实用和有借鉴性的,现将一些设计分享给大家,希望能和大家共同学习 ...
- 基于WCF大型分布式系统的架构设计
在大型系统中应用中,一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或多个表.而在架构设计中,各个功能模块相互之间的交互点 越统 ...
- 浅谈MySQL集群高可用架构
前言 高可用架构对于互联网服务基本是标配,无论是应用服务还是数据库服务都需要做到高可用.对于一个系统而言,可能包含很多模块,比如前端应用,缓存,数据库,搜索,消息队列等,每个模块都需要做到高可用,才能 ...
- 浅谈 C/S 和 B/S 架构
概述 在这个信息急剧膨胀的社会,我们不得不说人类正进入一个崭新的时代,那就是信息时代.信息时代的一个主要而显著的特征就是计算机网络的应用.计算机网络从最初的集中式计算,经过了Client/Server ...
- 浅谈MVC、MVP、MVVM架构模式的区别和联系
MVC.MVP.MVVM这些模式是为了解决开发过程中的实际问题而提出来的,目前作为主流的几种架构模式而被广泛使用. 一.MVC(Model-View-Controller) MVC是比较直观的架构模式 ...
- 软件安全测试新武器 ——浅谈基于Dynamic Taint Propagation的测试技术
软件安全测试是保证软件能够安全使用的最主要的手段,如何进行高效的安全测试成为业界关注的话题.多年的安全测试经验告诉我们,做好软件安全测试的必要条件是:一是充分了解软件安全漏洞,二是拥有高效的软件安全测 ...
- 浅谈Redis面试热点之工程架构篇[1]
前言 前面用两篇文章大致介绍了Redis热点面试中的底层实现相关的问题,感兴趣的可以回顾一下:[决战西二旗]|Redis面试热点之底层实现篇[决战西二旗]|Redis面试热点之底层实现篇(续) 接下来 ...
- 浅谈基于Linux的Redis环境搭建
本篇文章主要讲解基于Linux环境的Redis服务搭建,Redis服务配置.客户端访问和防火强配置等技术,适合具有一定Linux基础和Redis基础的读者阅读. 一 Redis服务搭建 1.在根路径 ...
随机推荐
- UVA208-Firetruck(并查集+dfs)
Problem UVA208-Firetruck Accept:1733 Submit:14538 Time Limit: 3000 mSec Problem Description The Ce ...
- 摒弃FORM表单上传图片,异步批量上传照片
之前作图像处理一直在用form表单做图片数据传输, 个人感觉low到爆炸而且用户体验极差,现在介绍一个一部批量上传图片的小技巧,忘帮助他人的同时也警醒自己在代码的编写时不要只顾着方便,也要考虑代码的健 ...
- 负载(Load)分析及问题排查
平常的工作中,在衡量服务器的性能时,经常会涉及到几个指标,load.cpu.mem.qps.rt等.每个指标都有其独特的意义,很多时候在线上出现问题时,往往会伴随着某些指标的异常.大部分情况下,在问题 ...
- CentOS7 安装MySQL5.6
1. 检查是否有MariaDB和MySQL,如果有则卸载掉 [root@--- ~]# rpm -qa | egrep "mariadb|mysql" mariadb-serve ...
- 抛弃配置后的Spring终极教程
一:前言 Spring 有XML配置和注解两种版本,我个人非常喜欢使用注解,相当热衷Spring boot! 对于Spring,核心就是IOC容器,这个容器说白了就是把你放在里面的对象(Bean)进行 ...
- Mysql字段名与保留字冲突导致的异常解决
一:引言 用hibernate建表时经常遇到的一个异常:Error executing DDL via JDBC Statement 方法: 查看报错sql语句.问题就在这里. 我是表名(字段名)与保 ...
- .net core实践系列之短信服务-Sikiro.SMS.Job服务的实现
前言 本篇会继续讲解Sikiro.SMS.Job服务的实现,在我写第一篇的时候,我就发现我当时设计的架构里Sikiro.SMS.Job这个可以选择不需要,而使用MQ代替.但是为了说明调度任务使用实现也 ...
- 腾讯 Omi 5.0 发布 - Web 前端 MVVM 王者归来,mappingjs 强力加持
写在前面 腾讯 Omi 框架正式发布 5.0,依然专注于 View,但是对 MVVM 架构更加友好的集成,彻底分离视图与业务逻辑的架构. 你可以通过 omi-cli 快速体验 MVVM: $ npm ...
- 不容错过的超赞项目管理PPT
不容错过的超赞项目管理PPT(转载) 大公司的一个好处,是各个领域都有牛人,可以为你提供经验分享交流.腾讯庞大的培训体系更是保证了:如果你想学点什么东西,你总可以学到.腾讯内部资源30页PPT曝光 — ...
- Linux 下RPM打包制作流程
原文地址:https://www.cnblogs.com/postgres/p/5726339.html 开始前的准备 安装rpmbuild软件包 yum -y install rpm-build 生 ...