说说数据库架构,ORM缓存和路由
为什么在ORM层做缓存,而不是DB层
ORM能有效地提高程序员的开发效率,程序员更喜欢操作对象而不是数据库,他们不关心也不想手写一堆SQL语句,毕竟一个公司里普通程序员要占多数,他们并不是非常熟悉数据库,写出来的SQL执行效率也肯定会有这样那样的问题。
如果让程序员去操作对象,这就是他们的强项了:定义关系、使用ORM的方法和属性、获取/遍历结果等等。同时ORM又可以在内部对SQL语句及对象之间的关系进行优化,尽量保证SQL高效地执行,甚至可以透明地加个缓存。这样一个双赢的结果,何乐而不为呢。
如果是一些比较复杂的查询语句,只能通过写SQL语句来实现,这样的话,可以在语句的执行段外面套一层缓存判断,如:
<?php
$result = $memcache->get('isobamapresident'); // fetch
if ($result === false)
{
// do some database heavy stuff
$db = DB::getInstance();
$votes = $db->prepare( "SELECT COUNT(*) FROM VOTES WHERE vote = 'OBAMA'" )->execute();
$result = ($votes > (USA_CITIZEN_COUNT / 2)) ? 'Sure is!' : 'Nope.'; // well, ideally
$memcache->set('isobamapresident', $result, 0);
}
透明地插入缓存
所谓透明缓存,就是用户正常使用ORM,获取ORM的查询结果。而事实上ORM的结果集很可能是来自缓存而不是数据库。
<?php
//获取1小时前发布的文章
$time = time() - 86400;
ORM::factory('article')->where('created', '>', $time)->findAll();
//正常的结果是通过执行以下SQL语句返回的
//SELECT * FROM article WHERE created > $time
//但实际上可能是从Memcache中读取的结果
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$memcache->get('some_key');
这样一来,php代码不用改变,但因为是从缓存中读取,所以数据的获取速度有保障,同时也减轻了数据库的压力,又是一个双赢的局面。
当然愿望是美好的,现实是残酷的,如果要达到上面所说的效果,需要费不少周折。
数据库架构
在设计ORM的缓存前,先了解以下数据库的大致架构。以netlog的数据库架构变迁为例:
单数据库
主库+从库
保持主库+从库的架构,把读写最频繁的几个表分到单独的数据库服务器
把那几个读写最频繁的表也分成主从
出现了1040 too many connections
Sharding(水平分区)
数据库服务器/数据库/分区
这样基本上就可以应付正常的访问了,如果哪个表数据量过大或连接过多,就Sharding一下。但随之而来的问题也很明显,比如:
<?php
//没有分区之前,可以通过下面几段代码来获取数据
$db = DB::getInstance();
$db->prepare("SELECT title, message FROM BLOG_MESSAGES WHERE userid = {userID}");
$db->assignInt('userID', $userID);
$db->execute();
$results = $db->getResults();
//假设将BLOG_MESSAGES按照用户id分配到了不同的分区上,上面的代码就需要做一些改动
//最简单的就是在getInstance时把用户的id传过去,让ORM内部去找分区,相当于路由
$db = DB::getInstance($userID);
如何对数据进行分片
当要对数据进行分片时,应该考虑这两个问题:使用表的哪一列(sharding key)作为分割的依据;使用怎样的分割算法(sharding scheme)。使用哪个key要看具体的应用。以博客为例,如果想要现实每个用户的博客,那么userID就可以作为sharding key。如何根据sharding key来找到对应的分区一般有三种方法:取模(求余)、数据量、映射表。假设采用映射表的方法,如果要获取用户的博客,先要到映射表里找到该userID对应的分区,再从分区中找到userID对应的博客列表。随之而来的问题是:
不能执行跨分区查询
如果要从不同的分区获取数据,就不能通过JOIN/GROUP BY/ORDER BY/LIMIT来实现了。比如:
//获取最新的10条博客
SELECT * FROM BLOG_MESSAGES ORDER BY created DESC LIMIT 0, 10;
//如果数据在多个分区中,上面这条查询就失效了
要解决这个问题,最好从设计上就避免这些查询语句。也可以通过冗余来实现。
数据一致性得不到保障
因为会在多个数据库之间更新数据,如果要保证数据一致性,就要实现分布式事务。
也可以通过一个小技巧来模拟分布式事务,比如有两台数据库服务器,这时可以先开启一个事务,但只在保证两台服务器都正常的情况下才一一提交事务。当然两次事务的提交也会有延迟,但相对来说更加靠谱。
保持分区平衡
如果基于用户ID进行分区,可能会出现分区之间的不平衡,比如一些活跃的用户都被分到了同一分区,而沉默用户被分到了另一个分区,这时量贩额分区的压力明显不一样。所以分区的算法很重要。
备份策略
因为数据在不同的分区中,备份策略就不想以前那么简单了。
ORM的缓存实现
先声明一下,ORM的缓存不能解决JOIN或者复杂的SQL查询,其实如果考虑到将来会有分区的可能,就应该在设计表时避免JOIN语句。因为复杂的SQL相对来说占的少数,甚至可以对这些SQL单独制定缓存策略。
先不考虑分区,假设有一个用户表和博客表,要达到以下目标:
- 缓存每一条博客记录,更新博客时,更新缓存
- 缓存每个用户的博客列表,用户更新博客时,更新该列表
- 程序员使用ORM时不需要考虑缓存
缓存行实现
缓存行还是比较简单的,用户查询某个id时,缓存该行内容,下次就可以直接读取缓存了。
如果内容被更新/删除了,缓存也同时更新/删除。
缓存列实现
<?php
//如果在find/findAll里传入了参数,则该参数即为key
ORM::factory('article')->where('user_id', '=', '2')->and_where('created', '>', time() - 86400)->findAll(2);
//上面的代码会在Model内部生成一个结构化的字符串,该字符串及对应的值将被放入缓存中
{table_name}-{key}-{md5(sql)}
//类似这样
article-2-c81e728d9d4c2f636f067f89cc14862c
//如果没有传参数,{key}就不会被替代
article-{key}-c81e728d9d4c2f636f067f89cc14862c
//首次执行此代码时,ORM内部会先去缓存中找上面的结构化字符串,没有找到,就会执行SQL语句,然后把返回的结果的id放到缓存中
//这就是要放到缓存中的数据,下次如果再执行此SQL,直接从缓存中获取id(1,43,50),然后再从缓存中获取这些id对应的行内容
//注意到这里有个revision,这是将来要判断该缓存是否已过期的关键。
'article-2-c81e728d9d4c2f636f067f89cc14862c' => array(
'revision' => 1294476790,
'data' => [1, 43, 50],
);
//同时还会生成另一组数据,就是revision
'article-2-revision' => 1294476777,
//如果作者又更新了一篇博客,则上面的查询语句结果就发生了变化。
ORM::factory('article')->values(array(...))->save(2);
//ORM会找到缓存中的一组revision数据,同时更新它
'article-2-revision' => 1294476888,
//如果没有提供key,那就是
'article-{key}-revision' => 1294476888,
//下次再执行上面的ORM查询代码时,会先去查找'article-2-revision'的版本,然后跟'article-2-c81e728d9d4c2f636f067f89cc14862c'的版本号比较,如果前一个版本号>后一个版本号,表示数据有改变,缓存已过期,这时就需要重新执行SQL语句,并更新'article-2-c81e728d9d4c2f636f067f89cc14862c'这个字符串的版本号。如果比较结果是前一个版本号<=后一个版本号,那就直接从缓存中读取。
ORM的路由
上面说的是数据没有分区的情况,如果数据被分区了的话,还要在ORM内部实现路由功能。
<?php
ORM::factory('articles')->where('created', '>', time()-86400)->findAll();
假设文章通过某种算法,被分在了不同的分区上,上面这个ORM编译出来的SQL是无法运行的。但又不能让程序员来关心分库分表的事,这时就可以在ORM内部实现路由机制,在具体的Model层实现路由算法。
<?php
class Model_Article extends ORM
{
protected function _route()
{
//这里可以实现具体算法,改变ORM的一些属性,从而影响SQL的编译
}
}
参考:
--EOF--
说说数据库架构,ORM缓存和路由的更多相关文章
- Django基础--Django基本命令、路由配置系统(URLconf)、编写视图、Template、数据库与ORM
web框架 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构. 使用框架可以帮你快速开发特定的系统. 简单地说,就是你用别人搭建好的舞台来做表演. 尝试搭建一个简单 ...
- 15套java互联网架构师、高并发、集群、负载均衡、高可用、数据库设计、缓存、性能优化、大型分布式 项目实战视频教程
* { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...
- MySQL分布式数据库架构:分库、分表、排序、分页、分组、实现教程
MySQL分库分表总结: 单库单表 : 单库单表是最常见的数据库设计,例如,有一张用户(user)表放在数据库db中,所有的用户都可以在db库中的user表中查到. 单库多表 : 随着用户数量的增加, ...
- MyCat 启蒙:分布式系统的数据库架构演变
文章首发于[博客园-陈树义],点击跳转到原文<MyCat 启蒙:分布式系统的数据库架构演变> 单数据库架构 一个项目在初期的时候,为了尽可能快地验证市场,其对业务系统的最大要求是快速实现. ...
- 五分钟DBA:浅谈伪分布式数据库架构
[IT168 技术]12月25日消息,2010互联网行业技术研讨峰会今日在上海华东理工大学召开.本次峰会以“互联网行业应用最佳实践”为主题,定位于互联网架构设计.应用开发.应用运维管理,同时,峰会邀请 ...
- mysql数据库架构设计与优化
mysql数据库架构设计与优化 2019-04-23 20:51:20 无畏D尘埃 阅读数 179 收藏 更多 分类专栏: MySQL 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA ...
- MySQL/MariaDB数据库的查询缓存优化
MySQL/MariaDB数据库的查询缓存优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL架构 Connectors(MySQL对外提供的交互接口,API): ...
- Mycat分布式数据库架构解决方案--rule.xml详解
echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 该文件 ...
- Mycat分布式数据库架构解决方案--Mycat的介绍
echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 如果我 ...
随机推荐
- 3、使用Lucene实现千度搜索
1.新建Web项目 新建一个Web项目,我命名为SearchEngine,然后导入Java包: 除了上篇博客中的Jar包外,我还引入了 IKAnalyzer2012_FF.jar 包和struts2的 ...
- .NET委托:一个关于C#的睡前故事 【转】
紧耦合 从前,在南方一块奇异的土地上,有个工人名叫彼得,他非常勤奋,对他的老板总是百依百顺.但是他的老板是个吝啬的人,从不信任别人,坚决要求随时知道彼得的工作进度,以防止他偷懒.但是彼得又不想让老板呆 ...
- hadoop结构出现后format变态
14/07/10 18:50:47 FATAL conf.Configuration: error parsing conf file: com.sun.org ...
- css系列教程--选择器
css派生选择器:是指在某个样式表或者dom元素的行内定义行内元素的属性,而其他同名样式在其他dom节点无效的样式表定义方式.例如:div ul li{border:1px solid red;}&l ...
- Spring-----事务配置的五种方式
转载自:http://blog.csdn.net/hekewangzi/article/details/51712821
- LVS客户端启动脚本
在设置LVS客户端时,如果我们使用手工设置的话会比较麻烦.现在我们直接使用脚本来启动lvs-client就OK了,下面是一个简单的脚本. VIP地址:10.0.0.230,把文件放到/etc/init ...
- phpcms v9 万能字段使用
<input type="text" name="info[down]" id="down" value="{FIELD_V ...
- I/O浅析
1.为什么需要I/O? 因为程序需要从别的文件中获取内容或者程序要将自身的内容传入到文件中. 2.流种类的概述 1.字节流和字符流 字节流的基础单位是byte 字符流 ...
- doT.js 模板引擎的使用
dot.js是一个模板框架,在web前端使用. dot.js作为模板引擎, 主要的用途就是,在写好的模板上,放进数据,生成含有数据的html代码. 这是很简单的web前端模板框架, 简单说几个东西,你 ...
- Git 操作常用命令
Git使用 1. git pull 更新服务器代码到本地a). git pull origin master是将origin这个版本库的代码更新到本地的master主分支 2. git push ...