为什么使用分布式事物?
- Service 产生多个节点,一个小的服务单元本事多节点部署 。
- Resource 产生多个节点:数据库等不在一个地方,不同的服务如果保证一致
- 保证数据的可用性,一致性,完整性。(转账的例子)
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
升级自己的操作系统
本书是由史蒂芬·科特勒和杰尔·威尔一起写的,这书名相当的长,下面简称“盗火”。
这是一本讲述“出神状态”的书籍,不知道大家有没有这样一个的体验:在做一些事情时候,很专注去想解决方案而迟迟想不出来,但突然间,解决方案自己在我们的脑海中闪过,我们似乎很轻松地找到了解决方法。很难描述这种感觉,但自己真正地感受到了,这种体验就是出神状态的其中一种。虽然很难捕捉,但世界上有很多人正在研究或利用这种状态,而硅谷、海豹突击队和疯狂科学家都在寻找这种状态,两位作者走访了很多人,把最新的研究写成“盗火”这本书,将这种神秘的体验公开出来。
首先是海豹突击队,海豹突击队执行任务时,环境一般是变化多的、信息模糊、充满不确定性,所以团队非常讲究配合。如何达到高效的配合,海豹给出的答案是团队达到“集体心流”状态,。
本文将以“帖子中心”为例,介绍“1对多”类业务,随着数据量的逐步增大,数据库性能显著降低,数据库水平切分相关的架构实践:
一、什么是1对多关系
所谓的“1对1”,“1对多”,“多对多”,来自数据库设计中的“实体-关系”ER模型,用来描述实体之间的映射关系。
这个是极其普通的登录需求,要的就是一个登录页面,输入账号密码,提交Form表单,后端查询数据库对应用户名的密码,匹配正确则把用户记录到Session,不正确则返回错误。
这种登录,在上学的时候,也许敬爱的老师就已经教过你了。
但可能他没有教你的是,密码需要hash加密,session为什么可以记录登录用户的原理。
密码hash,就是存进数据库的密码是一串密文,密文是明文密码通过不可逆算法得出的。在Nodejs中,你可以使用bcryptjs,它提供了hash以及对应的compare方法,非常适合用于密码的加密和对比。
Session的原理其实还是依赖了Cookie,所以Cookie才是记录用户凭证的真理。它的原理大概是酱紫的:服务器端维护一个session的表,这个表的每一条记录存的就是与某一个客户端的会话,会话会有过期时间,过期的会话会被清理。然后这个会话,会有一个对应的id,一般是一串长长的看不懂的字符串,然后这个字符串会被存储在客户端的cookie中,每一次请求服务器端都会带上这个cookie,服务器端就知道访问的就是哪个客户端了。
欲知更多有关「Session原理」请点击传送门:Session原理
应项目需要,登录逻辑需要独立出来做成一个系统,就是另外一个项目。与原来的主站不是在同一个项目中了。一个域名是 www.site.com,一个则是passport.site.com了。要在不同的域名下进行登录,一般的方法是www.site.com/login 跳转到 passport.site.com/login,passport这边是一个登录页面,用户输入账号密码登录成功之后,passport会通过带着一个可逆加密的包含用户信息的token,重定向到www.site.com提供的回调处理地址,然后进行解密,匹配正确,则登录用户。
要注意的是,这里的加密的信息需要包含一个时间戳,接收方需要认证这个时间戳,过期登录失败。避免token被窃取,被无限登录site系统。
单点登录需要实现的需求,说白了就是在站点A的登录了,那么用户就自动在站点B、站点C、站点E、F、G登录。
这又分两种情况,A站点和B站点是否在同一个二级域名下。
假如是在同一个域名下,例如siteA.site.com
与siteB.site.com
,因为cookie允许设置到二级域名下.site.com
,所以siteA和siteB是可以共享cookie的,用户的信息可以通过可逆加密放在二级域名下的cookie,并且设置http only,就可以一站登录,站站登录。
而如果A站点和B站点不在同一二级域名下,例如www.siteA.com
与www.siteB.com
,他们就无法通过共享cookie的方式共享用户信息,所以需要用到jsonp的方式,用户在siteA登录之后,提供一个jsonp接口获取加密的用户信息,siteB访问这个jsonp获取加密信息。达到共享用户状态的效果。
单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。单点登录在大型网站里使用得非常频繁,例如像阿里巴巴这样的网站,在网站的背后是成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都需要用户认证,不仅用户会疯掉,各子系统也会为这种重复认证授权的逻辑搞疯掉。实现单点登录说到底就是要解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性,因此要点也就以下两个:
- 存储信任
- 验证信任
最简单的单点登录实现方式,是使用cookie作为媒介,存放用户凭证。
用户登录父应用之后,应用返回一个加密的cookie,当用户访问子应用的时候,携带上这个cookie,授权应用解密cookie并进行校验,校验通过则登录当前用户。
不难发现以上方式把信任存储在客户端的Cookie中,这种方式很容易令人质疑:
- Cookie不安全
- 不能跨域实现免登
对于第一个问题,通过加密Cookie可以保证安全性,当然这是在源代码不泄露的前提下。如果Cookie的加密算法泄露,攻击者通过伪造Cookie则可以伪造特定用户身份,这是很危险的。
对于第二个问题,更是硬伤。
对于跨域问题,可以使用JSONP实现。
用户在父应用中登录后,跟Session匹配的Cookie会存到客户端中,当用户需要登录子应用的时候,授权应用访问父应用提供的JSONP接口,并在请求中带上父应用域名下的Cookie,父应用接收到请求,验证用户的登录状态,返回加密的信息,子应用通过解析返回来的加密信息来验证用户,如果通过验证则登录用户。
是通过父应用和子应用来回重定向中进行通信,实现信息的安全传递。
父应用提供一个GET方式的登录接口,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个的登录页面,用户输入账号密码进行登录。如果用户已经登录了,则生成加密的Token,并且重定向到子应用提供的验证Token的接口,通过解密和校验之后,子应用登录当前用户。
这就比较普遍了,现在随随便便做个网站,都接入「微信登录」、「微博登录」、「豆瓣登录」、「QQ登录」、「Github登录」,这些统一叫做:「第三方登录」。
第三方登录都是实现了OAuth2.0协议的,流程大概是酱紫的:
第三方提供一个登录入口,也就是第三方域名下的登录页面。主站需要登录的时候,引导用户重定向到第三方的登录页面,用户输入账号密码之后,登录第三方系统,第三方系统匹配帐号成功之后,带上一个code到主站的回调地址,主站接收到code,短时间内拿着code请求第三方提供获取长期凭证的接口(因为code有一个比较短的过期时间),这个长期凭证叫access_token
,获取之后就把这个access_token
存到数据库中,请求一些第三方提供的API,需要用到这个access_token
,因为这个token就是记录用户在第三方系统的一个身份凭证。
一些系统,在获取access_token
的时候,还会返回一个副参数refresh_token
,因为access_token
是有过期时间的,一旦过期了,主站可以使用refresh_token请求第三方提供的接口获取新的access_token
以及新的refresh_token
。
在Nodejs中,你可以使用passport来给第三方登录提供一个统一解决方案,而如果你是开发「微信公众号」授权,除了passport,也可以使用wechat-oauth
其实登录问题,理解了Session原理是很重要的,这个也不难理解。然后站点之间的用户信息交流,就是通过各种跨域限制,各种加密解密而已。在做这个的时候,需要充分考虑到加密的token是否会被窃取的可能性,还要考虑让这个token加上时间的验证,在一些可能会被窃取,安全需求比较高的情况,就需要把token的时间设置的更短。
加密的方式需要依照需求不同而选择可逆或者不可逆,hash sha1
,还是JWT(Json Web Token)
。
sha1加密,可以使用Nodejs自带的crypto
,php 的 sha1
,JWT可以使用json web token
立即执行函数:函数在定义后立即被执行,有特定的书写模式。例如:
1 | (function(){ |
或者1
2
3(function(){
console.log(123);
}())
这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行;立即执行函数并不是标准的叫法,是自我理解的叫法。
错误的写法1
2
3function(){
console.log(123);
}()
原因:在一个表达式后面加上括号(),该表达式会立即执行,但是在一个语句后面加上括号(),是完全不一样的意思,他的只是分组操作符。
要解决上述问题,非常简单,我们只需要用大括弧将代码的代码全部括住就行了,因为JavaScript里括弧()里面不能包含语句,所以在这一点上,解析器在解析function关键字的时候,会将相应的代码解析成function表达式,而不是function声明。
所以立即执行函数需要有一个()!!!!
1 | (function() { |
如果代码没有被包裹在立即执行函数中,那么局部变量days,today和msg都将成为全局变量,初始化代码的遗留产物。
传递参数方式如下:1
2
3(function (global) {
// access the global object via `global`
}(this));
例子:
1 | (function(who, when) { |
通常,全局变量被作为一个参数传递给立即执行参数,这样它在函数内部不使用window也可以被访问到:这种方式可以让代码在环境(除了浏览器)中更加通用:
例子:1
2
3var result = (function () {
return 2 + 2;
}());
例子2:
先被计算并被储存在立即执行函数的闭包中1
2
3
4
5
6var result = (function(){
var res = 2 * 9;
return function() {
return res;
}
}());
例子3:
立即执行函数也可以用来定义对象的属性;
1 | var o = { |
一般来说,当我们的数据库的数据超过了100w记录的时候就应该考虑分表或者分区了,这次我来详细说说分表的一些方法。目前我所知道的方法都是MYISAM的,INNODB如何做分表并且保留事务和外键,我还不是很了解。
首先,我们需要想好到底分多少个表,前提当然是满足应用。这里我使用了一个比较简单的分表方法,就是根据自增id的尾数来分,也就是说分0-9一共10个表,其取值也很好做,就是对10进行取模。另外,还可以根据某一字段的md5值取其中几位进行分表,这样的话,可以分的表就很多了。
好了,先来创建表吧,代码如下
1 | CREATE TABLE `test`.`article_0` ( |
好了10个表创建完毕了,需要注意的是,这里的id不能设为自增,而且所有的表结构必须一致,包括结构,类型,长度,字段的顺序都必须一致那么对于这个id如何取得呢?后面我会详细说明。现在,我们需要一个合并表,用于查询,创建合并表的代码如下
1 | CREATE TABLE `test`.`article` ( |
这里INSERT_METHOD=0在某些版本可能不工作,需要改成INSERT_METHOD=NO
注意,合并表也必须和前面的表有相同的结构,类型,长度,包括字段的顺序都必须一致这里的INSERT_METHOD=0
表示不允许对本表进行insert操作。好了,当需要查询的时候,我们可以只对article这个表进行操作就可以了,也就是说这个表仅仅只能进行select操作
那么对于插入也就是insert操作应该如何来搞呢,首先就是获取唯一的id了,这里就还需要一个表来专门创建id,代码如下1
2
3
4
5
6
7
8
9
10CREATE TABLE `test`.`create_id` (
`id` BIGINT( 20 ) NOT NULL AUTO_INCREMENT PRIMARY KEY
) ENGINE = MYISAM
也 就是说,当我们需要插入数据的时候,必须由这个表来产生id值,我的php代码的方法如下
function get_AI_ID() {
$sql = "insert into create_id (id) values('')";
$this->db->query($sql);
return $this->db->insertID();
}
好了,现在假设我们要插入一条数据了,应该怎么操作呢?还是继续看代码吧
1 | function new_Article() { |
其实很简单的,对吧,就是先获取id,然后根据id获取应该插入到哪个表,然后就很简单了。
对于update的操作我想应该不需要再说了吧,无非是有了id,然后获取表名,然后进行update操作就好了。
今天在redis
中执行setrange name 1 chun
命令时报了如下错误提示:
(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
大意为:(错误)misconf redis
被配置以保存数据库快照,但misconf redis
目前不能在硬盘上持久化。用来修改数据集合的命令不能用,请使用日志的错误详细信息。
redis
快照,不能持久化引起的,运行info命令查看redis
快照的状态,如下:
1 | rdb_last_bgsave_status:err |
解决方案如下:
运行 config set stop-writes-on-bgsave-error no
命令
关闭配置项stop-writes-on-bgsave-error
解决该问题。