重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
ORM:object relation mapping,即对象关系映射,简单的说就是对象模型和关系模型的一种映射。可以在PHP开发的业务逻辑层,通过数据访问层来处理。ORM就是数据访问层强大的一种解决方案。通过它,可以最大限度隔离业务逻辑层和数据源之间的耦合度
成都创新互联公司服务项目包括南郑网站建设、南郑网站制作、南郑网页制作以及南郑网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,南郑网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到南郑省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
不支持
在云领域我们常常会听到一个词:多租户。这个词在不同的语境中有着不同的含义。本文将介绍云平台中的多租户的概念以及实现多租户支持的思路。
什么是租户
刚开始接触这个概念时,你肯定感觉“租户”这个词怪怪的。但假设我们换个词,我相信你立即就有感觉了。这个词就是“客户”(这里的客户指的就是商业上面的客户)。
一个租户就是一个客户,比方我们开发的服务是给 XXX 企业使用的,那该企业就是我们的一个客户/租户;假设这个服务是面向互联网的,那么使用该服务的每一个互联网用户都是一个客户/租户。
为什么须要多租户支持
开发人员辛辛苦苦开发出一个服务。提供给了个人/企业使用,这样就完事了么?当然不应该仅仅是这样。我们开发出一个服务。最好是可以同一时候提供给多个个人/企业使用。并且这些客户最好是共享同一套服务执行时(Runtime),这样可以大大减少服务的运维成本:
服务执行时假设分开,则运维的成本与客户数成正比(比方更新部署大量客户的场景)
节省资源(将服务所需资源利用最大化:运维团队统一、硬件使用)
另外,这样也能够减少服务的开发成本:
我们仅仅须要考虑怎样实现单用户的服务逻辑:业务逻辑相应其全部客户都是同样的,不管什么客户来使用,程序提供的服务都是一样的。进一步说,在业务层面我们开发这个服务时理论上不须要考虑多客户支持,我们仅仅用关注该服务的业务逻辑怎样实现
多客户的管理功能能够进行统一:开发人员应该不用考虑客户管理功能,这部分应该是由云平台统一提供的
多租户场景举例
如果我们要开发的服务是一个博客平台,这个服务是面向互联网用户的,每一个互联网用户都是我们的客户(一个用户就是一个租户)。
在不支持多租户的环境中,为了隔离每一个用户的数据,至少我们在设计数据库表时会考虑大多数表都存在一个 user_id 字段。用于 CRUD 数据时使用该字段进行用户隔离。
比方如今的业务是“公布文章”。须要将文章数据保存在 article 表中,在实现时实际上我们关注了两件事情:
CRUD:这是业务逻辑实现的一部分
用户隔离:须要增加 user_id。做业务关联
1 是“纯”业务逻辑部分的实现。这是必须实现的;2 则是为了多用户博客平台而须要考虑的,这并非博客平台本身的业务逻辑。这里假设能得到平台的多租户支持,就不用考虑第 2 点了。这样能够将注意力集中于第 1 点业务逻辑实现上,这是很典型的一个多租户场景。
多租户支持
我们能够这样理解多租户支持:
从服务提供的角度看。我们开发的一个服务执行时能够同一时候提供给多个客户使用。而且客户之间的数据/状态是保持隔离的
从服务使用的角度看,我和你能够作为不同的客户同一时候使用同一个执行的服务,此时我们使用该服务完毕的业务是相互不影响的,就好像我们在使用自己独享的服务一样
那么这个服务就是支持多“客户”的,即该服务支持多租户。这里的“服务”能够是应用,能够是 SaaS 平台,也能够是 PaaS 平台。只是按眼下我们熟悉的云平台看,应用的多租户支持应该是最常规的。这是由于应用面向的是用户,这个群体是非常庞大的。
多租户支持从实现的角度看。“是一种软件架构技术”,之所以强调它是属于架构层面是由于要实现它必须在做技术架构时就要将其考虑在内。
一种租户模型
本文一开始我们提到使用“客户”来置换“租户”来理解租户的含义。再从“商业”这个方面来看的话,我们不难发现租户事实上就是其云环境中的商业模式实现的一部分。商业模式是多样的。这意味着租户的划分也是多样的。这里我们描写叙述当中一种可能的租户栈:
应用程序是提供给用户使用的,对于应用来说,用户就是它的租户(这一点业界比较统一)
SaaS 提供的服务是给应用开发商使用的,对于 SaaS 来说,应用开发商就是它的租户
PaaS 提供的服务是给应用系统使用的,对于 PaaS 来说。相关应用的组合就是它的租户
SaaS 和 PaaS 面向的是开发商、系统等非端用户角色。这一部分通常是由云平台开发人员决定的(捆绑商业模式)。特别是私有/企业云平台一般不会考虑形如“在 PaaS 平台上支持执行多个 SaaS 平台”这种场景。所以以下我们很多其它的是环绕“应用对多租户支持”进行讨论。
应用多租户
应用多租户的使用场景前面已经介绍过了。如今如果我们是一个云平台开发人员,为了满足支持应用支持多租户的需求,在云平台中我们须要提供以下几个支持:
租户管理:CRUD,统计
租户隔离/共享的服务:队列、缓存、数据库等
租户隔离的统计:日志、配额
这些支持能够分为两类:
租户的管理:不会直接面向应用的端用户。面向的是应用的运维。平台应该提供详细实现
租户数据/状态的隔离:从请求开始就应该能够区分这个请求是来自于哪个租户,请求处理时在调用链路上也须要带上租户上下文。数据的存取是依照租户隔离的。调用平台提供的服务时也是租户隔离的
第 1 点比较easy实现。这是一个业务模型方面的问题,能够依据业务域来抽象租户模型,比方企业应用通常是依照“组织机构”来区分租户的;
第 2 点是一个纯技术的需求。须要在平台技术实现上支持按“租户”的执行时隔离,我们强调的是隔离,由于在实现时我们要达到的目标就是隔离,仅仅只是这里是按租户(租户仅仅是一个商业概念,技术层面我们最好能够将其进行抽象。尽量减小商业模式多样化对技术架构的冲击)。我们能够将租户映射到一个抽象概念上,这个抽象概念能够实现我们的隔离需求。
1、PHP动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。PHP本身是用C实现的,因此最终调用的也是C的函数,实际上,我们可以把PHP看做一个C开发的软件。
2、PHP的4层运行体系:
(1)Zend引擎:Zend整体用纯C实现,是PHP的内核部分,他将PHP代码翻译(词法、语法解析等一系列编译过程)为可执行opcode的处理并实现相应的处理方法、实现了基本的数据结构(如:hashtable、OO)、内存分配机制及管理、提供了相应的api方法供外部调用,是一切的核心,所有的外围功能均围绕Zend实现。
(2)Extensions:围绕着Zend引擎,extensions通过组件式的方式提供各种基础服务,我们常见的各种内置函数(array系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension的典型应用)。
(3)Sapi:Sapi全称ServerApplicationProgrammingInterface,也就是服务端应用编程接口,Sapi通过一系列钩子函数,使得PHP可以和外围交互数据,这是PHP非常优雅和成功的设计,通过sapi成功的将PHP本身和上层应用解耦隔离,PHP可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。
(4)上层应用:这就是我们平时编写的PHP程序,通过不同的spai方式得到各种各样的应用模式,如何通过webserver实现web应用、在命令行下已脚本方式运行等等。
在这个互联网高度发达的时代,许多应用的用户动辄成百上千万,甚至上亿。为了支持海量用户的访问,应用服务器集群这种水平扩展的方式是最常用的。这种情形下,就会涉及到许多单机环境下完全不需要考虑的问题,这其中session的创建、共享和存储是最常见之一。
在单机环境中,Session的创建和存储都是由同一个应用服务器实例来完成,而存储也仅是内存中,最多会在正常的停止服务器的时候,把当前活动的Session钝化到本地,再次启动时重新加载。
而多个实例之间,Session数据是完全隔离的。而为了实现Session的高可用,多实例间数据共享是必然的,下面我们以Redis 的SessionManager实现多Tomcat实例Session共享的配置为例,我们来梳理下一般session共享的流程:
添加具体要使用的manager的Jar文件及其依赖
redis session manager依赖jedis, commons-pool, commons-pool2
对应版本的redis session manager的jar文件
在TOMCAT_HOME/conf/context.xml中增加如下配置
Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" /
Manager className="com.radiadesign.catalina.session.RedisSessionManager"
host="localhost"
port="6379" database="0"
maxInactiveInterval="30" /
其中host和port等替换为对应的配置信息
启动多个Tomcat实例,以自带的examples应用为例进行验证
访问examples应用的servlets/servlet/SessionExample,
在页面中添加数据到session中,并查看页面上对应的session信息
访问另一个实例上相同应用的页面,查看session信息,两者应该是一致的
使用redis-cli查看redis中存储的对应数据,相应的sessionId对应的数据已经保存了下来
以上是一个基本的配置过程,而在这些配置与验证的步骤中,第二步是核心逻辑实现。 前面的文章,曾介绍过Tomcat的Valve,在请求处理时,Pipeline中的各个Valve的invoke方法会依次执行。Tomcat的AccessLogValve介绍
此处的session处理,就是以一个自定义Valve的形式进行的。关于Session的文章,前面也写过几篇,会附在结尾处。
以下是RedisSessionhandlerValve的invoke方法,我们看,主要是在Valve执行后进行Session的存储或移除。
public void invoke(Request request, Response response) {
try {
getNext().invoke(request, response);
} finally {
final Session session = request.getSessionInternal(false);
storeOrRemoveSession(session);
manager.afterRequest();
}
}
而session的保存和移除又是通过manager执行的。 manager.save(session); manager.remove(session);
这里,manager就是前面定义的RedisSessionManager。默认单实例情况下,我们使用的都是StandardManager,对比一下两者,标准的Manager对于session的创建和删除,都会调到其父类ManagerBase中相应的方法,
public void add(Session session) {
sessions.put(session.getIdInternal(), session);
int size = getActiveSessions();
if( size maxActive ) {
synchronized(maxActiveUpdateLock) {
if( size maxActive ) {
maxActive = size;
}
}
}
}
public void remove(Session session, boolean update) {
if (session.getIdInternal() != null) {
sessions.remove(session.getIdInternal());
}
}
我们来看,由于其只保存在内存的Map中protected MapString, Session sessions = new
ConcurrentHashMap(),每个Tomcat实例都对于不同的map,多个实例间无法共享数据。
对应到RedisSessionManager对于session的处理,都是直接操作redis,基本代码是下面这个样:
public void save(Session session) throws IOException {
Jedis jedis = null;
Boolean error = true;
try {
RedisSession redisSession = (RedisSession) session;
Boolean sessionIsDirty = redisSession.isDirty();
redisSession.resetDirtyTracking();
byte[] binaryId = redisSession.getId().getBytes();
jedis = acquireConnection();
if (sessionIsDirty || currentSessionIsPersisted.get() != true) {
jedis.set(binaryId, serializer.serializeFrom(redisSession));
}
currentSessionIsPersisted.set(true);
jedis.expire(binaryId, getMaxInactiveInterval());
} }
移除时的操作是这样的
public void remove(Session session, boolean update) {
Jedis jedis = null;
Boolean error = true;
log.trace("Removing session ID : " + session.getId());
try {
jedis = acquireConnection();
jedis.del(session.getId());
error = false;
} finally {
if (jedis != null) {
returnConnection(jedis, error);
}
}
}
而此时,多个Tomcat实例都读取相同的Redis,session数据是共享的,其它实例的初始请求过来时,由于会执行findSession的操作,此时会从Redis中加载session,
public Session findSession(String id) throws IOException {
RedisSession session;
if (id == null) {
session = null;
currentSessionIsPersisted.set(false);
} else if (id.equals(currentSessionId.get())) {
session = currentSession.get();
} else {
session = loadSessionFromRedis(id); // 看这里,会从redis中load
if (session != null) {
currentSessionIsPersisted.set(true);
}
}
currentSession.set(session);
currentSessionId.set(id);
return session;
}
从而可以保证在一个实例被切换后,另外的实例可以继续响应同一个session的请求。
以上即为Redis实现session共享高可用的一些关键内容。有兴趣的朋友可以看下通过Memcached实现高可用,也是这个原理。顺着这个思路,如果你有将Session存储在其它地方的需求时,完全可以写一个出来,自己动手,丰衣足食。
总结一下,我们是通过自定义的Valve来实现请求后session的拦截,同时,使用自定义的SessionManager,来满足不同的session创建与存储的需求。而至于是存储在Redis/Memcached中,还是存储在DB中,只是位置的区别。原理,是一致的。
楼主你可以考虑MYSQL的事务处理功能。
一般来说,事务是必须满足4个条件(ACID)
原子性(Autmic):事务在执行性,要做到“要么不做,要么全做!”,就是说不允许事务部分得执行。即使因为故障而使事务不能完成,在rollback时也要消除对数据库得影响!
一致性(Consistency):事务得操作应该使使数据库从一个一致状态转变倒另一个一致得状态!就拿网上购物来说吧,你只有即让商品出库,又让商品进入顾客得购物篮才能构成事务!
隔离性(Isolation):如果多个事务并发执行,应象各个事务独立执行一样!
持久性(Durability):一个成功执行得事务对数据库得作用是持久得,即使数据库应故障出错,也应该能够恢复!
说白了就是某一个用户进行兑换操作的时候,就把对应的数据表锁定死,只有等操作完成后才解锁。
php单一入口模式可谓是现在一种比较流行的大型web应用开发模式,比如当前比较流行的一些php开发框架,zend,thinkphp,qeephp,还有cakephp
等他们都是采用的单一入口模式的。本文将就什么是单一入口模式,单一入口模式有哪些优点以缺点做一下研究。
什么是单一入口?
在解释什么是单一入口之前,先说说与之对应的多入口。多入口即通过访问不同的 php 文件运行对应的功能。比如刚开始学习 php
的时候,我们做一个项目通常都会如下这样做:
index.php - 网站首页
list.php?page=5 - 内容列表页
info.php?id=12 - 内容详细页
login.php - 用户登录页
对于这个项目来说,这其实就是一个多入口。
那么单一入口的应用程序就是说用一个文件处理所有的HTTP请求,例如不管是内容列表页,用户登录页还是内容详细页,都是通过从浏览器访问 index.php
文件来进行处理的,这里这个 index.php 文件就是这个应用程序的单一入口。
php 是如何实现单一入口的呢?
很简单,一般单一入口程序都是在访问index.php时附带一个特定的参数。例如:index.php?action=list 就可以定义为访问内容列表页,而
index.php?action=info 则可以定义为访问内容详细页等,具体实现代码如下:
//从url中取出action参数,如果没有提供action参数,就设置一个默认的'index'作为参数
$action=$_GET['action']==''?'index':$_GET['action'];
//根据$action参数调用不同的代码文件,从而满足单一入口实现对应的不同的功能
include('files/'.$action.'.php');
以上这个就实现了一个最简单的单一入口模式程序,当然真正的单一入口模式会比这个要复杂很多。但只要懂得如何合理组织各个功能的处理代码并遵循一定的步骤,也可以轻松的解决掉这个难题,下面就一个后台的例子来做一下说明:
比如我们现在要做一个新闻管理的后台。那么首先,对于应用程序的功能要做出一个合理的分解。例如后台的新闻栏目可能包含“添加新闻”、“编辑新闻”、“删除新闻”等多个功能。这时我们就可以将这一组逻辑上关联的功能组合到一个功能模块中,称为“新闻管理”模块。
按照上面的方法整理完应用程序的功能,我们就会得到多个功能模块,而每个模块又是由多个功能组成(实际上,即便不是单一入口应用程序,功能的整理也是必须的步骤)。
整理完功能后,我们就需要确定如何存放各个功能的代码。这里我推荐两种方式:
1、每个功能模块一个子目录,目录里的每一个文件就是一个功能的实现代码。
这
种方式的好处是每个功能的代码都互相隔离,非常便于多人协作。缺点是每个功能之间共享代码和数据不那么方便。例如新闻管理模块中的所有功能都需要一个“取
出新闻栏目记录”的功能,那么采用这种多个独立文件的组织方式,“取出新闻栏目记录”就只能写在另一个文件中,然后由需要该功能的文件include
进去。
2、每个模块一个文件,模块中的每个功能写成一个函数或者一个类方法。
好处不用多说了,非常便于共享代码和数据。缺点就是如果几个人同时改,容易发生冲突。不过借助版本控制软件和差异比较合并工具,冲突还是很容易解决的。
单一入口应用程序对应多入口有哪些优势呢?
单
一入口应用程序的所有http请求都是通过index.php接收并转发到功能代码中去的,所以在index.php里面就能完成许多实际工作(所有页面
都需要做的且都一样的工作)。比如进行集中的安全性检查,访问统计等等,如果不是单一入口,那么开发者就必须记得在每一个文件的开始加上安全性检查代码,
当然,你也许会说,多入口的安全性检查可以写到另一个文件中,然后include一下就可以了。但实际针对一个相对较大型一点的应用项目,在几十个文件中
保持头部的几个include都一致可不是一件让人省心的事。
与安全性检查类似。在入口里,我们还可以对url参数和post进行必要的检查和特殊字符过滤、记录日志、访问统计等等各种可以集中处理的任务。这样就可以看出,由于这些工作都被集中到了index.php来完成,可以减轻我们维护其他功能代码的难度。
单一入口应用程序的缺点?
任何事情都有两面性,单一入口应用程序也不例外。由于所有http请求都是访问 index.php ,所以程序的 url
看起来不那么美观,特别是对搜索引擎来说不太友好。比如下面这个 url:
;action=index
我们知道这种URl不太方便记忆,而且搜索引擎不认它是一个正常的 URL,当然是相比下面这种 URl 来说的:
不过这个也不是什么大问题,可以采用url重写、PATHINFO等方式就可以轻松解决这个问题。
OK,单一入口模式就写这么多了,当然要想深刻理解单一模式,最好的办法还是自己尝试着用单一入口模式写一个小应用出来深刻体会一下。
本文地址: