Għaliex taħżen id-dejta kollha fil-memorja?
Biex taħżen dejta tal-websajt jew backend, l-ewwel xewqa tal-biċċa l-kbira tan-nies sani se tkun li jagħżlu database SQL.
Iżda xi drabi l-ħsieb jiġi f'moħħu li l-mudell tad-dejta mhuwiex adattat għall-SQL: pereżempju, meta tibni tfittxija jew graff soċjali, għandek bżonn tfittex relazzjonijiet kumplessi bejn l-oġġetti.
L-agħar sitwazzjoni hija meta taħdem f'tim u kollega ma jkunx jaf kif jibni mistoqsijiet ta' malajr. Kemm qattajt ħin issolvi problemi N+1 u tibni indiċi addizzjonali sabiex il-SELECT fuq il-paġna ewlenija titlesta f'ammont ta' żmien raġonevoli?
Approċċ popolari ieħor huwa NoSQL. Bosta snin ilu kien hemm ħafna hype madwar dan is-suġġett - għal kull okkażjoni konvenjenti huma skjerati MongoDB u kienu kuntenti bit-tweġibiet fil-forma ta 'dokumenti json (mill-mod, kemm kellek ddaħħal krozzi minħabba l-links ċirkolari fid-dokumenti?).
Nissuġġerixxi li tipprova metodu alternattiv ieħor - għaliex ma tippruvax taħżen id-dejta kollha fil-memorja tal-applikazzjoni, perjodikament issalvaha f'ħażna każwali (fajl, database remota)?
Il-memorja saret irħisa, u kwalunkwe data possibbli għall-biċċa l-kbira tal-proġetti żgħar u ta’ daqs medju se tidħol f’1 GB ta’ memorja. (Pereżempju, il-proġett tad-dar favorit tiegħi huwa
Pros:
- L-aċċess għad-dejta jsir aktar faċli - m'għandekx għalfejn tinkwieta dwar mistoqsijiet, tagħbija għażżien, karatteristiċi ORM, taħdem ma 'oġġetti C# ordinarji;
- M'hemm l-ebda problemi assoċjati ma 'aċċess minn ħjut differenti;
- Mgħaġġel ħafna - l-ebda talbiet tan-netwerk, l-ebda traduzzjoni ta 'kodiċi f'lingwa ta' mistoqsija, l-ebda ħtieġa għal (de)serialization ta 'oġġetti;
- Huwa aċċettabbli li tinħażen id-dejta fi kwalunkwe forma - kemm jekk f'XML fuq disk, jew f'SQL Server, jew f'Azure Table Storage.
Cons:
- L-iskala orizzontali jintilef, u b'riżultat ta 'dan, l-iskjerament żero ta' waqfien ma jistax isir;
- Jekk l-applikazzjoni tiġġarraf, tista' titlef id-dejta parzjalment. (Imma l-applikazzjoni tagħna qatt ma tiġġarraf, hux?)
Kif taħdem?
L-algoritmu huwa kif ġej:
- Fil-bidu, tiġi stabbilita konnessjoni mal-ħażna tad-dejta, u titgħabba d-dejta;
- Mudell ta' oġġetti, indiċijiet primarji, u indiċi relazzjonali (1:1, 1:Ħafna) huma mibnija;
- Jinħoloq abbonament għal bidliet fil-proprjetajiet tal-oġġett (INotifyPropertyChanged) u biex jiżdiedu jew jitneħħew elementi fil-kollezzjoni (INotifyCollectionChanged);
- Meta l-abbonament jiġi attivat, l-oġġett mibdul jiġi miżjud mal-kju għall-kitba fil-ħażna tad-dejta;
- Bidliet fil-ħażna jiġu ffrankati perjodikament (fuq tajmer) f'ħajt ta 'sfond;
- Meta toħroġ mill-applikazzjoni, il-bidliet jiġu ssejvjati wkoll fil-ħażna.
Eżempju tal-Kodiċi
Żieda tad-dipendenzi meħtieġa
// Основная библиотека
Install-Package OutCode.EscapeTeams.ObjectRepository
// Хранилище данных, в котором будут сохраняться изменения
// Используйте то, которым будете пользоваться.
Install-Package OutCode.EscapeTeams.ObjectRepository.File
Install-Package OutCode.EscapeTeams.ObjectRepository.LiteDb
Install-Package OutCode.EscapeTeams.ObjectRepository.AzureTableStorage
// Опционально - если нужно хранить модель данных для Hangfire
// Install-Package OutCode.EscapeTeams.ObjectRepository.Hangfire
Aħna niddeskrivu l-mudell tad-dejta li se jinħażen fil-ħażna
public class ParentEntity : BaseEntity
{
public ParentEntity(Guid id) => Id = id;
}
public class ChildEntity : BaseEntity
{
public ChildEntity(Guid id) => Id = id;
public Guid ParentId { get; set; }
public string Value { get; set; }
}
Imbagħad il-mudell tal-oġġett:
public class ParentModel : ModelBase
{
public ParentModel(ParentEntity entity)
{
Entity = entity;
}
public ParentModel()
{
Entity = new ParentEntity(Guid.NewGuid());
}
public Guid? NullableId => null;
// Пример связи 1:Many
public IEnumerable<ChildModel> Children => Multiple<ChildModel>(x => x.ParentId);
protected override BaseEntity Entity { get; }
}
public class ChildModel : ModelBase
{
private ChildEntity _childEntity;
public ChildModel(ChildEntity entity)
{
_childEntity = entity;
}
public ChildModel()
{
_childEntity = new ChildEntity(Guid.NewGuid());
}
public Guid ParentId
{
get => _childEntity.ParentId;
set => UpdateProperty(() => _childEntity.ParentId, value);
}
public string Value
{
get => _childEntity.Value;
set => UpdateProperty(() => _childEntity.Value, value
}
// Доступ с поиском по индексу
public ParentModel Parent => Single<ParentModel>(ParentId);
protected override BaseEntity Entity => _childEntity;
}
U fl-aħħarnett, il-klassi tar-repożitorju nnifisha għall-aċċess għad-dejta:
public class MyObjectRepository : ObjectRepositoryBase
{
public MyObjectRepository(IStorage storage) : base(storage, NullLogger.Instance)
{
IsReadOnly = true; // Для тестов, позволяет не сохранять изменения в базу
AddType((ParentEntity x) => new ParentModel(x));
AddType((ChildEntity x) => new ChildModel(x));
// Если используется Hangfire и необходимо хранить модель данных для Hangfire в ObjectRepository
// this.RegisterHangfireScheme();
Initialize();
}
}
Oħloq istanza ta' ObjectRepository:
var memory = new MemoryStream();
var db = new LiteDatabase(memory);
var dbStorage = new LiteDbStorage(db);
var repository = new MyObjectRepository(dbStorage);
await repository.WaitForInitialize();
Jekk il-proġett se juża HangFire
public void ConfigureServices(IServiceCollection services, ObjectRepository objectRepository)
{
services.AddHangfire(s => s.UseHangfireStorage(objectRepository));
}
Daħħal oġġett ġdid:
var newParent = new ParentModel()
repository.Add(newParent);
Ma 'din is-sejħa, l-oġġett ParentModel huwa miżjud kemm mal-cache lokali kif ukoll fil-kju għall-kitba fid-database. Għalhekk, din l-operazzjoni tieħu O(1), u dan l-oġġett jista 'jinħadem immedjatament.
Pereżempju, biex issib dan l-oġġett fir-repożitorju u tivverifika li l-oġġett mibgħut lura huwa l-istess eżempju:
var parents = repository.Set<ParentModel>();
var myParent = parents.Find(newParent.Id);
Assert.IsTrue(ReferenceEquals(myParent, newParent));
X'inhu jiġri? Issettja () prospetti TableDizzjunarju, li fih Dizzjunarju Konkorrenti u jipprovdi funzjonalità addizzjonali ta 'indiċi primarji u sekondarji. Dan jippermettilek li jkollok metodi għat-tiftix bl-Id (jew indiċijiet arbitrarji oħra tal-utent) mingħajr ma tgħaddi kompletament fuq l-oġġetti kollha.
Meta żżid oġġetti għal Repożitorju tal-Oġġetti abbonament huwa miżjud biex jibdlu l-proprjetajiet tagħhom, għalhekk kwalunkwe bidla fil-proprjetajiet tirriżulta wkoll f'dan l-oġġett jiżdied mal-kju tal-kitba.
L-aġġornament tal-proprjetajiet minn barra jidher l-istess bħal xogħol b'oġġett POCO:
myParent.Children.First().Property = "Updated value";
Tista' tħassar oġġett bil-modi li ġejjin:
repository.Remove(myParent);
repository.RemoveRange(otherParents);
repository.Remove<ParentModel>(x => !x.Children.Any());
Dan iżid ukoll l-oġġett mal-kju tat-tħassir.
Kif jaħdem l-iffrankar?
Repożitorju tal-Oġġetti meta l-oġġetti mmonitorjati jinbidlu (jew iżidu jew iħassru, jew jibdlu l-proprjetajiet), iqajjem avveniment Mudell Mibdulabbonat għal Iħażna. Implimentazzjonijiet Iħażna meta jseħħ avveniment Mudell Mibdul il-bidliet jitqiegħdu fi 3 kjuwijiet - għaż-żieda, għall-aġġornament, u għat-tħassir.
Wkoll implimentazzjonijiet Iħażna mal-inizjalizzazzjoni, joħolqu tajmer li jikkawża li l-bidliet jiġu ffrankati kull 5 sekondi.
Barra minn hekk, hemm API biex tisforza sejħa ta' salvataġġ: ObjectRepository.Save().
Qabel kull iffrankar, l-ewwel jitneħħew operazzjonijiet bla sens mill-kjuwijiet (pereżempju, avvenimenti duplikati - meta oġġett inbidel darbtejn jew oġġetti miżjuda/mneħħija malajr), u mbagħad biss is-salvataġġ innifsu.
Fil-każijiet kollha, l-oġġett kurrenti kollu jiġi ffrankat, għalhekk huwa possibbli li l-oġġetti jiġu ssejvjati f'ordni differenti minn kif ġew mibdula, inklużi verżjonijiet aktar ġodda ta 'oġġetti milli fil-ħin li ġew miżjuda fil-kju.
X'hemm aktar?
- Il-libreriji kollha huma bbażati fuq .NET Standard 2.0. Jista 'jintuża fi kwalunkwe proġett modern .NET.
- L-API huwa thread safe. Kollezzjonijiet interni huma implimentati bbażati fuq Dizzjunarju Konkorrenti, L-immaniġġjar tal-avvenimenti jew għandhom serraturi jew m'għandhomx bżonnhom.
L-unika ħaġa ta’ min jiftakar hi li ċċempel ObjectRepository.Save(); - Indiċijiet arbitrarji (jeħtieġu l-uniċità):
repository.Set<ChildModel>().AddIndex(x => x.Value);
repository.Set<ChildModel>().Find(x => x.Value, "myValue");
Min jużaha?
Personalment, bdejt nuża dan l-approċċ fil-proġetti kollha tal-passatemp għax huwa konvenjenti u ma jeħtieġx spejjeż kbar għall-kitba ta 'saff ta' aċċess għad-dejta jew l-iskjerament ta 'infrastruttura tqila. Personalment, il-ħażna tad-dejta f'litedb jew f'fajl normalment tkun biżżejjed għalija.
Iżda fil-passat, meta l-istartjar EscapeTeams li issa m'għadhiex taħdem (Ħsibt li hawn, flus - imma le, esperjenza mill-ġdid) - użat biex jaħżen id-dejta f'Azure Table Storage.
Pjanijiet għall-futur
Nixtieq nirranġa wieħed mill-iżvantaġġi ewlenin ta 'dan l-approċċ - skalar orizzontali. Biex tagħmel dan, għandek bżonn jew tranżazzjonijiet distribwiti (sic!), jew tieħu deċiżjoni b'rieda qawwija li l-istess data minn każijiet differenti m'għandhiex tinbidel, jew tħallihom jinbidlu skont il-prinċipju "min huwa l-aħħar għandu raġun."
Mil-lat tekniku, nara l-iskema li ġejja possibbli:
- Aħżen EventLog u Snapshot minflok mudell tal-oġġett
- Sib każijiet oħra (żid l-endpoints tal-istanzi kollha mas-settings? udp discovery? master/slave?)
- Irreplika bejn l-istanzi ta' EventLog permezz ta' kwalunkwe algoritmu ta' kunsens, bħal RAFT.
Hemm ukoll problema oħra li tinkwieta lili - tħassir ta 'kaskata, jew skoperta ta' każijiet ta 'tħassir ta' oġġetti li għandhom rabtiet minn oġġetti oħra.
Kodiċi tas-sors
Jekk qrajt it-triq kollha sa hawn, allura kulma fadal huwa li taqra l-kodiċi; jista 'jinstab fuq GitHub:
Sors: www.habr.com