用AOP思想改造一个服务器的数据存储

简介: 背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增\删\改的时候显式调用API, 更糟糕的是要注明删\改的字段名, 不然就会整个实体重写入.

背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增\删\改的时候显式调用API, 更糟糕的是要注明删\改的字段名, 不然就会整个实体重写入. 实际使用中经常会出现写错字段名, 或是重命名字段之后忘记修改旧的字段名字符串参数, 甚至忘记调用update API导致数据没有保存...(同样的情况也发生在写SQL的时候)

其实增/删/改用到的SQL或是reids api都非常简单, 都可以自动生成, 免去反复书写的麻烦也容易写错的问题. 

于是乎, 我计划通过监视/拦截增/删/改 方法以在数据修改的同时自动生成SQL/Redis调用, 来对改属性值进行保存. 其实增删比较简单, 只要自定义一个集合类型继承下IEnumerable<T>接口, 然后实现增删方法即可, 对原代码的修改量也是极小的. 

但是改就有点麻烦了, 需要拦截属性的set方法, 这是以前没有怎么用过的AOP领域.

一些调查之后, 觉得有两种方式比较适合.

一是静态织入, 就是用类似代码模板的方式, 先写实体类型模板, 然后生成真正的实体类型类库, 在生成的时候进行修改在属性的set方法中注入PropertyChangedNotify事件, 服务中使用的是生成后的实体类库. PostSharp貌似不错, 但是不想引入更多的开销就罢了. 用T4或者上Emit或许不错.

二是动态, 大致上就是生成一个代理类型, 拥有原类型的所有接口, 但是重新给实现了一遍. 可以是组合的方式, 将原类型实例作为私有成员保存, Emit生成所有原类型的开放方法; 也可以是继承的方式, 重载. 考虑到对原代码的修改量, 我觉得继承重载不错. 

比如原实体类型如下:

public class Person
{
    public string Id{get;set;}
    public string Name{get;set;}
}

对这个类实现修改保存的托管, 那么直接使用这个类型是不行的了(除非方案1静态修改注入), 只有使用动态生成的代理实体类, 而如果使用组合方式的代理类型, 在OO的情况下很别扭, 所以我选择用继承重载的方式. 这就需要对这个实体类型进行改造, 将所有需要监视的属性修改成virutal声明 ctrl+H搞定. 

然后, 因为是增删改, 所以Key是必不可少的, 那么实体需要用attribute标记出Key属性, 支持多属性组合Key. 因为原代码用过一段时间的entityframework, 所以这个改造非常顺利. 

最后的类型定义如下:

[DataContract]
public class Person
{
    [Key]  
    public string Id{get;set;}
    public virtual string Name{get;set;}
}

首先用attribute标记Person为需要代理的类型, 便于在实际使用中纠错和预加载, 然后标记Id属性为Key, 并且不需要重载, 其他属性声明为virtual表示需要重载进行监视.

然后手写一个代理类型:

public class PersonProxy : Person
{
    public override string Name
    {
        set
        {
            base.Name= value;
            Interceptor.Invoke(this, "Name", value);
        }
    }  
}

public static class Interceptor
{
    public static void Invoke(object instance, string name, object value)
    {
        //log
    }
}

再定义个Factory的静态类, 实现静态泛型函数Create<T>()来创建Person的代理类型PersonProxy. 其实后面调用的都是代理类型, 只是使用方法和Person一致, 兼容旧代码.

只是这样的话, 还不如使用静态织入了. 所以上面手写的这个PersonProxy需要动态生成, 这样通过泛型来支持所有自定义class, 减少频繁修改. 于是Emit登场了.

不过我对IL和Emit认识浅薄, 我的办法是使用VS自带工具 -- ildasm, 用它打开刚才生成的PersonProxy查看IL代码, 反复对比总结之后终于将这个PersonProxy类型的动态生成用Emit实现成功并修改成泛型通用. 这套特殊的IL学习方法论真是屡试不爽. 方法大致如此抛砖引玉, 所以这里也就不贴emit的代码了. Invoke织入也可以做各种进化, 以达成更高级的功能, 比如invoke在赋值前, 写入失败回退throw exception等等...

这样ProxyFactory.Create<T>()函数就写好了.

在Invoke里生成需要SQL或是redis set方法就可以了. 

最后再实现增删代理的集合类型, 在Add的时候直接将插入的对象通过ProxyFactory.Create<T>()转成proxy子类对象, 为了避免Add之后继续使用传入的原对象而丢失对属性set的监控, 修改Add的声明为Add(ref T obj)强行修改引用为代理对象. 从入口处根绝, 完美. 

理论Ok, 于是封装成类库. 

最后放上代码地址 : https://github.com/Roytin/SmartDb

目录
相关文章
|
2月前
|
存储 弹性计算 运维
可观测性体系问题之ECS管控的metric数据存储对安全合规的考虑如何解决
可观测性体系问题之ECS管控的metric数据存储对安全合规的考虑如何解决
23 4
|
4月前
|
存储 关系型数据库 数据库
服务器数据恢复—ESXi无法识别原数据存储和VMFS文件系统的数据恢复案例
一台某品牌服务器,通过FreeNAS来做iSCSI,然后使用两台同品牌服务器做ESXi虚拟化系统。 FreeNAS层为UFS2文件系统,使用整个存储建一个稀疏模式的文件,挂载到ESXi虚拟化系统。ESXi虚拟化系统中有3台比较重要的虚拟机,这几台虚拟机情况如下: 1、windows server操作系统,运行门户网站,采用ASP.net+PHP混合构架,部署的SqlServer和mysql数据库 。 2、FreeBSD操作系统,运行Mysql数据库,供其他多台虚拟机使用。 3、windows server系统,存放新开发的程序代码。
|
4月前
【基于C++HTTP 服务器的epoll 改造】
【基于C++HTTP 服务器的epoll 改造】
|
运维 Prometheus Kubernetes
节约服务器成本50%以上,独角兽完美日记电商系统容器化改造历程
今年4月,完美日记IT系统实现全面云原生化。
3635 0
节约服务器成本50%以上,独角兽完美日记电商系统容器化改造历程
|
Cloud Native 微服务 容器
数千台服务器,千万用户量:居然之家两年云原生改造历程
导读:传统企业的决策链路通常是自上而下的形式,因此在互联网化改造中,不仅仅是研发层面,整个公司的管理人员都需要做好知识升级和观念更新,这也是躺平设计家在过去几年的上云之路所经历的。本文将聚焦居然之家利用阿里云容器服务(ACK) 进行云原生实践历程,期待能帮助读者了解传统企业从传统单体架构向云原生演变的实践路径。
|
机器学习/深度学习 存储 人工智能
|
应用服务中间件 nginx
nginx改造“显示当前服务器的负载”
      博文题目之所以用改造,而没有用模块开发之类等。只是个人觉得本篇博文并未上升到真正的开发高度,而是在看懂前人代码的基础上,增加代码或者修改代码。使其更加完善,跟业务结合的更完美,效率更高。
749 0
|
11天前
|
Cloud Native Java 编译器
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考
随着云计算技术的不断发展,云服务商们不断推出高性能、高可用的云服务器实例,以满足企业日益增长的计算需求。阿里云推出的倚天实例,凭借其基于ARM架构的倚天710处理器,提供了卓越的计算能力和能效比,特别适用于云原生、高性能计算等场景。然而,有的用户需要将传统基于x86平台的应用迁移到倚天实例上,本文将介绍如何将基于x86架构平台的应用迁移到阿里云倚天实例的服务器上,帮助开发者和企业用户顺利完成迁移工作,享受更高效、更经济的云服务。
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考

热门文章

最新文章