Session,Token相关区别

1. 为什么要有session的出现?

答:是由于网络中http协议造成的,因为http本身是无状态协议,这样,无法确定你的本次请求和上次请求是不是你发送的。如果要进行类似论坛登陆相关的操作,就实现不了了。

2. session生成方式?

答:浏览器第一次访问服务器,服务器会创建一个session,然后同时为该session生成一个唯一的会话的key,也就是sessionid,然后,将sessionid及对应的session分别作为key和value保存到缓存中,也可以持久化到数据库中,然后服务器再把sessionid,以cookie的形式发送给客户端。这样浏览器下次再访问时,会直接带着cookie中的sessionid。然后服务器根据sessionid找到对应的session进行匹配;

还有一种是浏览器禁用了cookie或不支持cookie,这种可以通过URL重写的方式发到服务器;

简单来讲,用户访问的时候说他自己是张三,他骗你怎么办? 那就在服务器端保存张三的信息,给他一个id,让他下次用id访问。

3. 为什么会有token的出现?

答:首先,session的存储是需要空间的,其次,session的传递一般都是通过cookie来传递的,或者url重写的方式;而token在服务器是可以不需要存储用户的信息的,而token的传递方式也不限于cookie传递,当然,token也是可以保存起来的;

4. token的生成方式?

答:浏览器第一次访问服务器,根据传过来的唯一标识userId,服务端会通过一些算法,如常用的HMAC-SHA256算法,然后加一个密钥,生成一个token,然后通过BASE64编码一下之后将这个token发送给客户端;客户端将token保存起来,下次请求时,带着token,服务器收到请求后,然后会用相同的算法和密钥去验证token,如果通过,执行业务操作,不通过,返回不通过信息;

5. token和session的区别?

token和session其实都是为了身份验证,session一般翻译为会话,而token更多的时候是翻译为令牌;

session服务器会保存一份,可能保存到缓存,文件,数据库;同样,session和token都是有过期时间一说,都需要去管理过期时间;

其实token与session的问题是一种时间与空间的博弈问题,session是空间换时间,而token是时间换空间。两者的选择要看具体情况而定。

虽然确实都是“客户端记录,每次访问携带”,但 token 很容易设计为自包含的,也就是说,后端不需要记录什么东西,每次一个无状态请求,每次解密验证,每次当场得出合法 /非法的结论。这一切判断依据,除了固化在 CS 两端的一些逻辑之外,整个信息是自包含的。这才是真正的无状态。 

而 sessionid ,一般都是一段随机字符串,需要到后端去检索 id 的有效性。万一服务器重启导致内存里的 session 没了呢?万一 redis 服务器挂了呢? 

方案 A :我发给你一张身份证,但只是一张写着身份证号码的纸片。你每次来办事,我去后台查一下你的 id 是不是有效。 

方案 B :我发给你一张加密的身份证,以后你只要出示这张卡片,我就知道你一定是自己人。

​Java面试通关要点汇总集 核心篇

核心篇

数据存储

MySQL 索引使用的注意事项

1.索引不会包含有NULL的列

 只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。

2.使用短索引

 对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

3.索引列排序

 mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。

4.like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,注意正确的使用方式。like %aaa%’不会使用索引,而like aaa%’可以使用索引。

5.不要在列上进行运算

6.不使用NOT IN <>、!=操作,但<,<==>,>=,BETWEEN,IN是可以用到索引的

7.索引要建立在经常进行select操作的字段上。

 这是因为,如果这些列很少用到,那么有无索引并不能明显改变查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

8.索引要建立在值比较唯一的字段上。

9.对于那些定义为textimagebit数据类型的列不应该增加索引。因为这些列的数据量要么相当大,要么取值很少。

10.wherejoin中出现的列需要建立索引。

11.where的查询条件里有不等号(where column != ),mysql将无法使用索引。

12.如果where字句的查询条件里使用了函数(如:where DAY(column)=),mysql将无法使用索引。

13.join操作中(需要从多个数据表提取数据时)mysql只有在主键和外键的数据类型相同时才能使用索引,否则及时建立了索引也不会使用。

说说分库与分表设计

垂直分表在日常开发和设计中比较常见,通俗的说法叫做“大表拆小表”

拆分是基于关系型数据库中的“列”(字段)进行的。

通常情况,某个表中的字段比较多,可以新建立一张“扩展表”,将不经常使用或者长度较大的字段拆分出去放到“扩展表”中。

在字段很多的情况下,拆分开确实更便于开发和维护(笔者曾见过某个遗留系统中,一个大表中包含100多列的)。

某种意义上也能避免“跨页”的问题(MySQLMSSQL底层都是通过“数据页”来存储的“跨页”问题可能会造成额外的性能开销,拆分字段的操作建议在数据库设计阶段就做好。

如果是在发展过程中拆分,则需要改写以前的查询语句,会额外带来一定的成本和风险,建议谨慎。

垂直分库在“微服务”盛行的今天已经非常普及了。

基本的思路就是按照业务模块来划分出不同的数据库,而不是像早期一样将所有的数据表都放到同一个数据库中。

系统层面的“服务化”拆分操作,能够解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制类似,我们也能对不同业务类型的数据进行“分级”管理、维护、监控、扩展等。

众所周知,数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。

数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。

然后,很多人并没有从根本上搞清楚为什么要拆分,也没有掌握拆分的原则和技巧,只是一味的模仿大厂的做法。导致拆分后遇到很多问题(例如:跨库join,分布式事务等)

 

水平分表也称为横向分表

比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。

最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。

水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。

 

水平分库

水平分库分表与上面讲到的水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法。

某种意义上来讲,有些系统中使用的冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。

 

在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)。

以上摘抄自: http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table

分库与分表带来的分布式困境与应对之策

数据迁移与扩容问题

前面介绍到水平分表策略归纳总结为随机分表和连续分表两种情况。

连续分表有可能存在数据热点的问题,有些表可能会被频繁地查询从而造成较大压力,热数据的表就成为了整个库的瓶颈,而有些表可能存的是历史数据,很少需要被查询到。

连续分表的另外一个好处在于比较容易,不需要考虑迁移旧的数据,只需要添加分表就可以自动扩容。

随机分表的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,分表扩展需要迁移旧的数据

针对于水平分表的设计至关重要,需要评估中短期内业务的增长速度,对当前的数据量进行容量规划,综合成本因素,推算出大概需要多少分片。

对于数据迁移的问题,一般做法是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。

 

表关联问题

在单库单表的情况下,联合查询是非常容易的。但是,随着分库与分表的演变,联合查询就遇到跨库关联和跨表关系问题

在设计之初就应该尽量避免联合查询,可以通过程序中进行拼装,或者通过反范式化设计进行规避。

 

分页与排序问题

一般情况下,列表分页时需要按照指定字段进行排序。

在单库单表的情况下,分页和排序也是非常容易的。

但是,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。

为了最终结果的准确性,需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。

 

分布式事务问题

随着分库与分表的演变,一定会遇到分布式事务问题,那么如何保证数据的一致性就成为必须面对的问题。

目前,分布式事务并没有很好的解决方案,难以满足数据强一致性

一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致。

 

分布式全局唯一ID

在单库单表的情况下,直接使用数据库自增特性来生成主键ID,这样确实比较简单。

在分库分表的环境中,数据分布在不同的分表上,不能再借助数据库自增长特性。

需要使用全局唯一 ID,例如 UUIDGUID等。

 

摘抄自:http://blog.csdn.net/jiangpingjiangping/article/details/78069480

说说 SQL 优化之道

一、一些常见的SQL实践

1)负向条件查询不能使用索引

select from order where status!=0 and stauts!=1

not in/not exists都不是好习惯

可以优化为in查询:

select from order where status in(2,3)

 

2)前导模糊查询不能使用索引

select from order where desc like '%XX'

而非前导模糊查询则可以:

select from order where desc like 'XX%'

 

3)数据区分度不大的字段不宜使用索引

select from user where sex=1

原因:性别只有男,女,每次过滤掉的数据很少,不宜使用索引。

经验上,能过滤80%数据时就可以使用索引。

对于订单状态,如果状态值很少,不宜使用索引,如果状态值很多,能够过滤大量数据,则应该建立索引。

 

4)在属性上进行计算不能命中索引

select from order where YEAR(date) < = '2017'

即使date上建立了索引,也会全表扫描,可优化为值计算:

select from order where date < = CURDATE()

或者:

select from order where date < = '2017-01-01'

 

二、并非周知的SQL实践

5)如果业务大部分是单条查询,使用Hash索引性能更好,例如用户中心

select from user where uid=?

select from user where login_name=?

原因:B-Tree索引的时间复杂度是O(log(n))Hash索引的时间复杂度是O(1)

 

6)允许为null的列,查询有潜在大坑

单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集

select from user where name != 'shenjian'

如果name允许为null,索引不存储null值,结果集中不会包含这些记录。

所以,请使用not null约束以及默认值

 

7)复合索引最左前缀,并不是值SQL语句的where顺序要和复合索引一致

用户中心建立了(login_name, passwd)的复合索引

select from user where login_name=? and passwd=?

select from user where passwd=? and login_name=?

都能够命中索引

select from user where login_name=?

也能命中索引,满足复合索引最左前缀

select from user where passwd=?

不能命中索引,不满足复合索引最左前缀

 

8)使用ENUM而不是字符串

ENUM保存的是TINYINT,别在枚举中搞一些“中国”“北京”“技术部”这样的字符串,字符串空间又大,效率又低。

 

三、小众但有用的SQL实践

9)如果明确知道只有一条结果返回,limit 1能够提高效率

select from user where login_name=?

可以优化为:

select from user where login_name=? limit 1

原因:你知道只有一条结果,但数据库并不知道,明确告诉它,让它主动停止游标移动

 

10)把计算放到业务层而不是数据库层,除了节省数据的CPU,还有意想不到的查询缓存优化效果

select from order where date < = CURDATE()

这不是一个好的SQL实践,应该优化为:

$curDate = date('Y-m-d');

$res = mysqlquery(

'select from order where date < = $curDate');

原因:

释放了数据库的CPU

多次调用,传入的SQL相同,才可以利用查询缓存

 

11)强制类型转换会全表扫描

select from user where phone=13800001234

你以为会命中phone索引么?大错特错了?

 

末了,再加一条,不要使用select *(潜台词,文章的SQL都不合格 ==),只返回需要的列,能够大大的节省数据传输量,与数据库的内存使用量哟。

 

整理自:https://cloud.tencent.com/developer/article/1054203

MySQL 遇到的死锁问题

死锁(Deadlock

所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

blob.png

产生死锁的四个必要条件:

1) 互斥条件:

一个资源每次只能被一个进程使用。

—— 只有一副钥匙

2 请求与保持条件:

一个进程因请求资源而阻塞时,对已获得的资源保持不放。

—— 拿着红钥匙的人在没有归还红钥匙的情况下,又提出要蓝钥匙

3 不剥夺条件:

进程已获得的资源,在未使用完之前不能被剥夺,只能在使用完时由自己释放。

—— 除非归还了钥匙,不然一直占用着钥匙

4 循环等待条件:

若干进程之间形成一种头尾相接的循环等待资源关系资源的环形链

——拿着红钥匙的人在等蓝钥匙,同时那个拿着蓝钥匙的人在等红钥匙

 

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

常见的避免死锁的方法

方法一 . 破坏互斥条件

只有一副钥匙,这是形成死锁的最关键的原因。

显然,如果我们能在两个线程跑之前,能给每个线程单独拷贝一份钥匙的副本,就能有效的避免死锁了。

当然,这种方法试用范围并不广。因为有时如果系统拷贝那副钥匙的成本极高,而线程又很多的话,这种方法就不适用了。

 

方法 . 破坏请求和保持条件

任何一个线程“贪心”,都可能会导致死锁。大致就是说有了一把钥匙还没还就要另一把。

这里我们可以通过规定在任何情况下,一个线程获取一把钥匙之后,必须归还了钥匙之后才能请求另一把钥匙,就可以有效解决这个问题。

 

方法三 . 破坏不剥夺条件

除非线程自己还钥匙,否则线程会一直占有钥匙,是形成不可剥夺条件的原因。

这里,我们可以通过设置一个最长占用时间的阈值来解决这个问题——如果过了10分钟仍然没有进入下一个步骤,则归还已有的钥匙。

这样的话,两个线程都能取到所需的钥匙继续下去了。

 

方法 . 破坏环路等待条件

会出现死锁的两两组合,一定都是一个线程先取了红钥匙而另一个线程先取了蓝钥匙,从而导致了可能形成了“环路等待”。

所以我们可以强制规定线程取钥匙的顺序只能是 “先取蓝钥匙再取红钥匙”的话,就能避免死锁了。

 

下列方法有助于最大限度地降低死锁:

1)按同一顺序访问对象。

2)避免事务中的用户交互。

3)保持事务简短并在一个批处理中。

4)使用低隔离级别。

5)使用绑定连接。

 

整理自: http://onwise.xyz/2017/04/20/mysql-%E6%AD%BB%E9%94%81%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/

存储引擎的 InnoDB MyISAM

   1.InnoDB不支持FULLTEXT类型的索引。

  ◆2.InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。

  ◆3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。

  ◆4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。

  ◆5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。

另外,InnoDB表的行锁也不是绝对的,假如在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like %aaa%

数据库索引的原理

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

为什么要用 B-tree

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

聚集索引与非聚集索引的区别

1).聚集索引:

物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序

2) .非聚集索引:

物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序.

 

1).聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个

2).聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续

索引是通过二叉树的数据结构来描述的

我们可以这么理解聚簇索引:索引的叶节点就是数据节点。

而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。

limit 20000 加载很慢怎么解决

Mysql的性能低是因为数据库要去扫描N + M条记录,然后又要放弃之前N条记录,开销很大

解决思略:

1前端加缓存,或者其他方式,减少落到库的查询操作

例如某些系统中数据在搜索引擎中有备份的,可以用es等进行搜索

2使用延迟关联

即先通用limit得到需要数据的索引字段,然后再通过原表和索引字段关联获得需要数据

select a.* from a,(select id from table_1 where is_deleted='N' limit 100000,20) b where a.id = b.id

3、从业务上实现,不分页如此多

例如只能分页前100页,后面的不允许再查了

4不使用limit N,M,而是使用limit N

即将offset转化为where条件。

Mysql数据库中的查询语句有关limit语句的优化。

一般limit是用在分页的程序的分页上的,当应用数据量够小时,也许感觉不到limit语句的任何问题,但当查询数据量达到一定程度时,limit的性能就会急剧下降。 这个是通过大量实例得出来的结论。

对同一张表在不同的地方取10条数据:

1offset比较小时  

代码示例: select * from user limit 10,10;

这条sql语句多次运行,时间保持在0.0004-0.0005之间。  

代码示例:

select * from user where uid >=( select uid from user order by uid limit 10,1 ) limit 10;

     这条sql语句多次运行,时间保持在0.0005-0.0006之间,主要是0.0006

结论:偏移offset较小时,直接使用limit较优。

2offset大时  

代码示例: select * from user limit 10000,10;

这条sql语句多次运行,时间保持在0.0187左右  

代码示例:

 select * from user where uid >=( select uid from user order by uid limit 10000,1 ) limit 10;

这条sql语句多次运行,时间保持在0.0061左右,只有前者的1/3

可以预计offset越大,后者越优。这个显然是子查询的原因。

 

通过以上对比,得出mysql limit查询语句优化经验:

使用limit语句时,当数据量偏移量较小时可以直接使用limit,当数据量偏移量较大时,可以适当的使用子查询来做相关的性能优化

选择合适的分布式主键方案

1. 数据库自增长序列或字段

2. UUID

3. 使用UUID to Int64的方法

4. Redis生成ID

5. Twittersnowflake算法

6. 利用zookeeper生成唯一ID

7. MongoDBObjectId

选择合适的数据存储方案

关系型数据库 MySQL

MySQL 是一个最流行的关系型数据库,在互联网产品中应用比较广泛。

一般情况下,MySQL 数据库是选择的第一方案,基本上有 80% ~ 90% 的场景都是基于 MySQL 数据库的。

因为,需要关系型数据库进行管理,

此外,业务存在许多事务性的操作,需要保证事务的强一致性。

同时,可能还存在一些复杂的 SQL 的查询。

值得注意的是,前期尽量减少表的联合查询,便于后期数据量增大的情况下,做数据库的分库分表。

 

内存数据库 Redis

随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求。

因此,Redis 基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充。

例如,为了提高服务端接口的访问速度,尽可能将读频率高的热点数据存放在 Redis 中。

这个是非常典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,通过增加系统的内存消耗,来加快程序的运行速度。

在某些场景下,可以充分的利用 Redis 的特性,大大提高效率。

这些场景包括:

缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户判定信息,交集、并集和差集,热门列表与排行榜,最新动态等。

使用 Redis 做缓存的时候,需要考虑:

数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。

文档数据库 MongoDB

MongoDB 是对传统关系型数据库的补充

它非常适合高伸缩性的场景,它是可扩展性的表结构。

基于这点,可以将预期范围内,表结构可能会不断扩展的 MySQL 表结构,通过 MongoDB 来存储,这就可以保证表结构的扩展性。

此外,日志系统数据量特别大,如果用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和 MapReduce 的能力,是个很好的选择。

MongoDB 还适合存储大尺寸的数据,GridFS 存储方案就是基于 MongoDB 的分布式文件存储系统。

列族数据库 HBase

HBase 适合海量数据的存储与高性能实时查询

它是运行于 HDFS 文件系统之上,并且作为 MapReduce 分布式处理的目标数据库,以支撑离线分析型应用。

在数据仓库、数据集市、商业智能等领域发挥了越来越多的作用,在数以千计的企业中支撑着大量的大数据分析场景的应用。

全文搜索引擎 ElasticSearch

在一般情况下,关系型数据库的模糊查询,都是通过 like 的方式进行查询。

其中,like value%” 可以使用索引,但是对于 like %value%” 这样的方式,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情。

ElasticSearch 作为一个建立在全文搜索引擎 Apache Lucene 基础上的实时的分布式搜索和分析引擎,适用于处理实时搜索应用场景。

此外,使用 ElasticSearch 全文搜索引擎,还可以支持多词条查询、匹配度与权重、自动联想、拼写纠错等高级功能。因此,可以使用 ElasticSearch 作为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到 ElasticSearch 上,达到处理复杂的业务与提高查询速度的目的。

ElasticSearch 不仅仅适用于搜索场景,还非常适合日志处理与分析的场景。

著名的 ELK 日志处理方案,由 ElasticSearchLogstash Kibana 三个组件组成,包括了日志收集、聚合、多维度查询、可视化显示等。

 

摘抄自:http://blog.720ui.com/2017/db_better_db_use/

MongoDB _id生成规则

MongoDB的文档必须有一个_id键。目的是为了确认在集合里的每个文档都能被唯一标识。

 

ObjectId _id 的默认类型。

ObjectId 采用12字节的存储空间,每个字节两位16进制数字,是一个24位的字符串。

12位生成规则:

 

[0,1,2,3] [4,5,6]  [7,8] [9,10,11]

时间戳 |机器码 |PID |计数器

 

前四位是时间戳,可以提供秒级别的唯一性。

接下来三位是所在主机的唯一标识符,通常是机器主机名的散列值。

接下来两位是产生ObjectIdPID,确保同一台机器上并发产生的ObjectId是唯一的。

前九位保证了同一秒钟不同机器的不同进程产生的ObjectId时唯一的。

最后三位是自增计数器,确保相同进程同一秒钟产生的ObjectId是唯一的。

 

https://github.com/qianjiahao/MongoDB/wiki/MongoDB%E4%B9%8B_id%E7%94%9F%E6%88%90%E8%A7%84%E5%88%99

 

聊聊 MongoDB 使用场景

MongoDB是对传统关系型数据库的补充,但是 MongoDB 不支持事务,因此对事务性有要求的程序不建议使用 MongoDB。此外,MongoDB 不支持表联合查询,而这个是关系型数据库擅长的事情。

高伸缩性的场景

MongoDB 非常适合高伸缩性的场景,它是可扩展性的表结构。基于这点,可以将预期范围内,表结构可能会不断扩展的 MySQL 表结构,通过 MongoDB 来存储,这就可以保证表结构的扩展性。

日志系统的场景

日志系统数据量特别大,如果用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和 MapReduce 的能力,是个很好的选择。

分布式文件存储

MongoDB 还适合存储大尺寸的数据,之前介绍的 GridFS 存储方案,就是基于 MongoDB 的分布式文件存储系统。

摘抄自: http://blog.720ui.com/2017/mongodb_core_use/

 

聊聊 ElasticSearch 使用场景

全文搜索

这个是用的最多的。加上分词插件、拼音插件什么的可以做成强大的全文搜索引擎。

数据库

挺奇葩的用法,因为ES存数相同数据,更费空间,不过确实不错,因为他的强大统计分析汇总能力,再加上分布式P2P扩展能力,现在硬件又那么便宜,所以就有人拿来当数据库了。

在线统计分析引擎

日志系统。logstash,不用解释了吧。可以实时动态分析数据,很是爽。

倒排索引

倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案

是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。

它是文档检索系统中最常用的数据结构。

有两种不同的反向索引形式:

一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。

一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。

说说反模式设计

简单的来说,反模式是指在对经常面对的问题经常使用的低效,不良,或者有待优化的设计模式/方法。甚至,反模式也可以是一种错误的开发思想/理念。

在这里我举一个最简单的例子:

在面向对象设计/编程中,有一条很重要的原则, 单一责任原则(Single responsibility principle)。其中心思想就是对于一个模块,或者一个类来说,这个模块或者这个类应该只对系统/软件的一个功能负责,而且该责任应该被该类完全封装起来。当开发人员需要修改系统的某个功能,这个模块/类是最主要的修改地方。

相对应的一个反模式就是上帝类(God Class),通常来说,这个类里面控制了很多其他的类,同时也依赖其他很多类。整个类不光负责自己的主要单一功能,而且还负责了其他很多功能,包括一些辅助功能。

很多维护老程序的开发人员们可能都遇过这种类,一个类里有几千行的代码,有很多功能,但是责任不明确单一。单元测试程序也变复杂无比。维护/修改这个类的时间要远远超出其他类的时间。

很多时候,形成这种情况并不是开发人员故意的。很多情况下主要是由于随着系统的年限,需求的变化,项目的资源压力,项目组人员流动,系统结构的变化而导致某些原先小型的,符合单一原则类慢慢的变的臃肿起来。

最后当这个类变成了维护的噩梦(特别是原先熟悉的开发人员离职后),重构该类就变成了一个不容易的工程。

 

缓存使用

Redis 有哪些类型

Redis中有五种数据类型

String ———-字符串

Hash ————-字典

List  ————-列表

Set  ————-集合

Sorted Set —–有序集合

Redis 内部结构

Redis 内部使用一个 redisObject 对象来表示所有的 key value

blob.png

 

redisObject 核心对象

type

代表一个 value 对象具体是何种数据类型。

encoding

是不同数据类型在 redis 内部的存储方式

比如:

type = string 代表 value 存储的是一个普通字符串

那么对应的 encoding 可以是 raw 或者是 int

如果是 int 则代表实际 redis 内部是按数值型类存储和表示这个字符串的

当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。

vm

只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。

Redis 使用 redisObject 来表示所有的 key/value 数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给 Redis 不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用。

 

Key(键值)

过期删除

过期数据的清除从来不容易,为每一条key设置一个timer,到点立刻删除的消耗太大,每秒遍历所有数据消耗也大,Redis使用了一种相对务实的做法:

client主动访问key会先对key进行超时判断,过时的key会立刻删除。

如果clien永远都不再get那条key呢?

它会在Master的后台,每秒10次的执行如下操作: 随机选取100key校验是否过期,如果有25个以上的key过期了,立刻额外随机选取下100key(不计算在10次之内)

可见,如果过期的key不多,它最多每秒回收200条左右,如果有超过25%key过期了,它就会做得更多,但只要key不被主动get,它占用的内存什么时候最终被清理掉只有天知道。

常用操作

Key的长度限制Key的最大长度不能超过1024字节,在实际开发时不要超过这个长度,但是Key的命名不能太短,要能唯一标识缓存的对,作者建议按照在关系型数据库中的库表唯一标识字段的方式来命令Key的值,用分号分割不同数据域,用点号作为单词连接。

 

Key的查询Keys,返回匹配的key,支持通配符如 keys a* keys a?c,但不建议在生产环境大数据量下使用。

 

Key对应的Value进行的排序Sort命令对集合按数字或字母顺序排序后返回或另存为list,还可以关联到外部key等。因为复杂度是最高的O(N+Mlog(M))*(N是集合大小,M 为返回元素的数量),有时会安排到slave上执行。

 

Key的超时操作Expire(指定失效的秒数)/ExpireAt(指定失效的时间戳)/Persist(持久化)/TTL(返回还可存活的秒数),关于Key超时的操作。默认以秒为单位,也有p字头的以毫秒为单位的版本

 

String(字符串类型的Value

 

可以是String,也可是是任意的byte[]类型的数组,如图片等。String redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr,decr 等操作时会转成数值型进行计算,此时 redisObject encoding 字段为int

 

1. 大小限制:最大为512Mb,基本可以存储任意图片啦。

 

2. 常用命令的时间复杂度为O(1),读写一样的快。

 

3. String代表的数字进行增减操作(没有指定的Key则设置为0值,然后在进行操作):Incr/IncrBy/IncrByFloat/Decr/DecrBy(原子性),

** 可以用来做计数器,做自增序列,也可以用于限流,令牌桶计数等**

key不存在时会创建并贴心的设原值为0IncrByFloat专门针对float

 

4. 设置Value的安全性:SetNx命令仅当key不存在时才Set(原子性操作)。

可以用来选举Master或做分布式锁:所有Client不断尝试使用SetNx master myName抢注Master,成功的那位不断使用Expire刷新它的过期时间。

如果Master倒掉了key就会失效,剩下的节点又会发生新一轮抢夺。SetExSet + Expire 的简便写法,p字头版本以毫秒为单位。

 

5. 获取:GetSet(原子性), 设置新值,返回旧值。

比如一个按小时计算的计数器,可以用GetSet获取计数并重置为0

这种指令在服务端做起来是举手之劳,客户端便方便很多。

MGet/MSet/MSetNx, 一次get/set多个key

 

6. 其他操作:Append/SetRange/GetRange/StrLen,对文本进行扩展、替换、截取和求长度

只对特定数据格式如字段定长的有用,json就没什么用。

 

7. BitMap的用法:GetBit/SetBit/BitOp,与或非/BitCount

BitMap的玩法,比如统计今天的独立访问用户数时,每个注册用户都有一个offset,他今天进来的话就把他那个位设为1,用BitCount就可以得出今天的总人数。

 

HashHashMap,哈希映射表)

 

Redis Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口。

Hash将对象的各个属性存入Map里,可以只读取/更新对象的某些属性。另外不同的模块可以只更新自己关心的属性而不会互相并发覆盖冲突。

不同程序通过 key(用户 ID+ field(属性标签)就可以并发操作各自关心的属性数据

 

常用操作

 

O(1)操作:hgethset等等

O(n)操作:hgetallRedis 可以直接取到全部的属性数据,但是如果内部 Map 的成员很多,那么涉及到遍历整个内部 Map 的操作,由于 Redis 单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

List(双向链表)

Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一

比如 twitter 的关注列表,粉丝列表等都可以用 Redis list 结构来实现

还提供了生产者消费者阻塞模式(B开头的命令),常用于任务队列,消息队列等。

实现方式

 

Redis list 的实现为一个双向链表,即可以支持反向查找和遍历

更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

 

常用操作

 

复合操作RPopLPush/ BRPopLPush

弹出来返回给client的同时,把自己又推入另一个list,是原子操作。 

按值进行的操作LRem(按值删除元素)LInsert(插在某个值的元素的前后),复杂度是O(N)NList长度,因为List的值不唯一,所以要遍历全部元素,而Set只要O(log(N))

按下标进行操作(下标从0开始,队列从左到右算,下标为负数时则从右到左,-1为右端第一个元素)

时间复杂度为O(N)

LSet :按下标设置元素值。(NList的长度)

LIndex:按下标返回元素。(Nindex的值)

LTrim:限制List的大小,保留指定范围的元素。(N是移除元素的个数)

LRange:返回列表内指定范围下标的元素,常用于分页。(N = start+range

set(HashSet

Set就是HashSet,可以将重复的元素随便放入而Set会自动去重,底层实现也是HashMap,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

set 的内部实现是一个 value 永远为 null HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。

常用操作

 

增删改查:

SAdd/SRem/SIsMember/SCard/SMove/SMembers等等。除了SMembers都是O(1)

 

集合操作:

SInter/SInterStore/SUnion/SUnionStore/SDiff/SDiffStore,各种集合操作。

交集运算可以用来显示在线好友(在线用户 交集 好友列表),共同关注(两个用户的关注列表的交集)O(N),并集和差集的N是集合大小之和,交集的N是小的那个集合的大小的2倍。

Sorted Set(插入有序Set集合)

set 不是自动有序的,而** sorted set 可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序**。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set 数据结构,比如 twitter public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。

实现方式

内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序

 

Sorted Set的实现是HashMap(element->score, 用于实现ZScore及判断element是否在集合内),和SkipList(score->element,score排序)的混合体。SkipList有点像平衡二叉树那样,不同范围的score被分成一层一层,每层是一个按score排序的链表。

 

常用操作

ZAdd/ZRemO(log(N))ZRangeByScore/ZRemRangeByScoreO(log(N)+M)NSet大小,M是结果/操作元素的个数。复杂度的log取对数很关键,可以使,1000万大小的Set,复杂度也只是几十不到。但是,如果一次命中很多元素M很大则复杂度很高。

ZRange/ZRevRange,按排序结果的范围返回元素列表,可以为正数与倒数。

 

ZRangeByScore/ZRevRangeByScore,按score的范围返回元素,可以为正数与倒数。

 

ZRemRangeByRank/ZRemRangeByScore,按排序/score的范围限删除元素。

 

ZCount,统计按score的范围的元素个数。

 

ZRank/ZRevRank ,显示某个元素的正/倒序的排名。

 

ZScore/ZIncrby,显示元素的Score/增加元素的Score

 

ZAdd(Add)/ZRem(Remove)/ZCard(Count)ZInsertStore(交集)/ZUnionStore(并集),与Set相比,少了IsMember和差集运算。

Redis使用与内存优化

上面的一些实现上的分析可以看出 redis 实际上的内存管理成本非常高,即占用了过多的内存,属于用空间换时间。作者对这点也非常清楚,所以提供了一系列的参数和手段来控制和节省内存

建议不要开启VM(虚拟内存)选项

VM 选项是作为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,将严重地拖垮系统的运行速度,所以要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled no

设置最大内存选项

最好设置下 redis.conf 中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的 Redis 不会因为使用了过多的物理内存而导致 swap,最终严重影响性能甚至崩溃。

设置内存饱和回收策略

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

控制内存使用的参数

Redis 为不同数据类型分别提供了一组参数来控制内存使用

Hash

redis.conf 配置文件中下面2

**hash-max-zipmap-entries 64 **

含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个以下的成员就是使用线性紧凑存储zipmap,超过该值自动转成真正的 HashMap(ht)

hash-max-zipmap-value 512

hash-max-zipmap-value 含义是当 value 这个 Map 内部的每个成员值长度不超过

多少字节就会采用线性紧凑存储zipmap来节省空间。

以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,但是也不是越大越好(空间和查改效率需要根据实际情况来权衡)

List

list-max-ziplist-entries 512

list 数据类型多少节点以下会采用去指针的紧凑存储格式ziplist

list-max-ziplist-value 64

list 数据类型节点值大小小于多少字节会采用紧凑存储格式ziplist

Set

set-max-intset-entries 512

set 数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储

Redis内部的优化

Redis 内部实现没有对内存分配方面做过多的优化,在一定程度上会存在内存碎片,不过大多数情况下这个不会成为 Redis 的性能瓶颈。

 

Redis 缓存了一定范围的常量数字作为资源共享,在很多数据类型是数值型则能极大减少内存开销,默认为1-10000,可以重新编译配置修改源代码中的一行宏定义 REDIS_SHARED_INTEGERS

总结

根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。

当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量。

如果需要使用持久化,根据是否可以容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及 diskstore 方式。

不要让你的 Redis 所在机器物理内存使用超过实际内存总量的3/5

Redis 的持久化使用了 Buffer IO ,所谓 Buffer IO 是指 Redis 对持久化文件的写入和读取操作都会使用物理内存的 Page Cache,而当 Redis 的持久化文件过大操作系统会进行Swap,这时你的系统就会有内存还有余量但是系统不稳定或者崩溃的风险。

作者:zhanglbjames

链接:https://www.jianshu.com/p/f09480c05e42

聊聊 Redis 使用场景

随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求。因此,Redis 基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充。在某些场景下,可以充分的利用 Redis 的特性,大大提高效率。

缓存

对于热点数据,缓存以后可能读取数十万次,因此,对于热点数据,缓存的价值非常大。例如,分类栏目更新频率不高,但是绝大多数的页面都需要访问这个数据,因此读取频率相当高,可以考虑基于 Redis 实现缓存。

会话缓存

此外,还可以考虑使用 Redis 进行会话缓存。例如,将 web session 存放在 Redis 中。

时效性

例如验证码只有60秒有效期,超过时间无法使用,或者基于 Oauth2 Token 只能在 5 分钟内使用一次,超过时间也无法使用。

访问频率

出于减轻服务器的压力或防止恶意的洪水攻击的考虑,需要控制访问频率,例如限制 IP 在一段时间的最大访问量。

计数器

数据统计的需求非常普遍,通过原子递增保持计数。例如,应用数、资源数、点赞数、收藏数、分享数等。

社交列表

社交属性相关的列表信息,例如,用户点赞列表、用户分享列表、用户收藏列表、用户关注列表、用户粉丝列表等,使用 Hash 类型数据结构是个不错的选择。

记录用户判定信息

记录用户判定信息的需求也非常普遍,可以知道一个用户是否进行了某个操作。例如,用户是否点赞、用户是否收藏、用户是否分享等。

交集、并集和差集

在某些场景中,例如社交场景,通过交集、并集和差集运算,可以非常方便地实现共同好友,共同关注,共同偏好等社交关系。

热门列表与排行榜

按照得分进行排序,例如,展示最热、点击率最高、活跃度最高等条件的排名列表。

最新动态

按照时间顺序排列的最新动态,也是一个很好的应用,可以使用 Sorted Set 类型的分数权重存储 Unix 时间戳进行排序。

消息队列

Redis 能作为一个很好的消息队列来使用,依赖 List 类型利用 LPUSH 命令将数据添加到链表头部,通过 BRPOP 命令将元素从链表尾部取出。同时,市面上成熟的消息队列产品有很多,例如 RabbitMQ。因此,更加建议使用 RabbitMQ 作为消息中间件。

摘抄自:http://blog.720ui.com/2017/redis_core_use/

 

用作消息队列中防止数据丢失的解决方法

如果消费者把jobPop走了又没处理完就死机了怎么办?

 

消息生产者保证不丢失

加多一个sorted se t,分发的时候同时发到listsorted set,以分发时间为score,用户把job做完了之后要用ZREM消掉sorted set里的job,并且定时从sorted set中取出超时没有完成的任务,重新放回list。 如果发生重复可以在sorted set中在查询确认一遍,或者将消息的消费接口设计成幂等性。

 

消息消费者保证不丢失

为每个worker多加一个的list,弹出任务时改用RPopLPush

简述 OAuth 2.0 的运作流程

作者: 小胡子哥  https://www.barretlee.com/blog/2016/01/10/oauth2-introduce/

本文将以用户使用 github 登录网站留言为例,简述 OAuth 2.0 的运作流程。

假如我有一个网站,你是我网站上的访客,看了文章想留言表示「朕已阅」,留言时发现有这个网站的帐号才能够留言,此时给了你两个选择:一个是在我的网站上注册拥有一个新账户,然后用注册的用户名来留言;一个是使用 github 帐号登录,使用你的 github 用户名来留言。前者你觉得过于繁琐,于是惯性地点击了 github 登录按钮,此时 OAuth 认证流程就开始了。

传统方法是,用户将自己的github用户名和密码,告诉网站,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)第三方网站会获取用户的密码,这样很不安全。

(2)单纯的密码登录并不安全。

(3)获取用户储存在github所有资料的权力,用户没法限制"第三方网站"获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予"第三方网站"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

OAuth就是为了解决上面这些问题而诞生的。

需要明确的是,即使用户刚登录过 github,我的网站也不可能向 github 发一个什么请求便能够拿到访客信息,这显然是不安全的。就算用户允许你获取他在 github 上的信息,github 为了保障用户信息安全,也不会让你随意获取。所以操作之前,我的网站与 github 之间需要要有一个协商。

1. 网站和 Github 之间的协商

Github 会对用户的权限做分类,比如读取仓库信息的权限、写入仓库的权限、读取用户信息的权限、修改用户信息的权限等等。如果我想获取用户的信息,Github 会要求我,先在它的平台上注册一个应用,在申请的时候标明需要获取用户信息的哪些权限,用多少就申请多少,并且在申请的时候填写你的网站域名,Github 只允许在这个域名中获取用户信息。

此时我的网站已经和 Github 之间达成了共识,Github 也给我发了两张门票,一张门票叫做 Client Id,另一张门票叫做 Client Secret。

2. 用户和 Github 之间的协商

用户进入我的网站,点击 github 登录按钮的时候,我的网站会把上面拿到的 Client Id 交给用户,让他进入到 Github 的授权页面,Github 看到了用户手中的门票,就知道这是我的网站让他过来的,于是它就把我的网站想要获取的权限摆出来,并询问用户是否允许我获取这些权限。

// 用户登录 github,协商
GET //github.com/login/oauth/authorize
// 协商凭证
params = {
  client_id: "xxxx",
  redirect_uri: "http://my-website.com"
}

如果用户觉得我的网站要的权限太多,或者压根就不想我知道他这些信息,选择了拒绝的话,整个 OAuth 2.0 的认证就结束了,认证也以失败告终。如果用户觉得 OK,在授权页面点击了确认授权后,页面会跳转到我预先设定的 redirect_uri 并附带一个盖了章的门票 code。

// 协商成功后带着盖了章的 code
Location: http://my-website.com?code=xxx

这个时候,用户和 Github 之间的协商就已经完成,Github 也会在自己的系统中记录这次协商,表示该用户已经允许在我的网站访问上直接操作和使用他的部分资源。

3. 告诉 Github 我的网站要来拜访了

第二步中,我们已经拿到了盖过章的门票 code,但这个 code 只能表明,用户允许我的网站从 github 上获取该用户的数据,如果我直接拿这个 code 去 github 访问数据一定会被拒绝,因为任何人都可以持有 code,github 并不知道 code 持有方就是我本人。

还记得之前申请应用的时候 github 给我的两张门票么,Client Id 在上一步中已经用过了,接下来轮到另一张门票 Client Secret。

// 网站和 github 之间的协商
POST //github.com/login/oauth/access_token
// 协商凭证包括 github 给用户盖的章和 github 发给我的门票
params = {
  code: "xxx",
  client_id: "xxx",
  client_secret: "xxx",
  redirect_uri: "http://my-website.com"
}

拿着用户盖过章的 code 和能够标识个人身份的 client_id、client_secret 去拜访 github,拿到最后的绿卡 access_token。

// 拿到最后的绿卡
response = {
  access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a"
  scope: "user,gist"
  token_type: "bearer",
  refresh_token: "xxxx"
}

4. 用户开始使用 github 帐号在我的页面上留言

// 访问用户数据
GET //api.github.com/user?access_token=e72e16c7e42f292c6912e7710c838347ae178b4a

上一步 github 已经把最后的绿卡 access_token 给我了,通过 github 提供的 API 加绿卡就能够访问用户的信息了,能获取用户的哪些权限在 response 中也给了明确的说明,scope 为 user 和 gist,也就是只能获取 user 组和 gist 组两个小组的权限,user 组中就包含了用户的名字和邮箱等信息了。

// 告诉我用户的名字和邮箱
response = {
  username: "barretlee",
  email: "barret.china@gmail.com"
}

回顾:

OAuth 2.0 运行流程

674fbfab246da67516a2ff730e1506ad_bg2014051203.png

(1)Third-party application:第三方应用程序,本文中又称"客户端"(client)。
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的github。
(3)Resource Owner:资源所有者,本文中又称"用户"(user)。
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

整个 OAuth2 流程在这里也基本完成了,文章中的表述很粗糙,比如 access_token 这个绿卡是有过期时间的,如果过期了需要使用 refresh_token 重新签证。

重点是让读者理解整个流程,细节部分可以阅读 RFC6749 文档。

希望对你理解 OAuth 2.0 有帮助。(本文完)

基于 Token 的身份验证

基于 Token 的身份验证

作者:王皓 https://ninghao.net/blog/2834

最近了解下基于 Token 的身份验证,跟大伙分享下。很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强,也更安全点,非常适合用在 Web 应用或者移动应用上。Token 的中文有人翻译成 “令牌”,我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡。

传统身份验证的方法

HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。

解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。

基于 Token 的身份验证方法

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

客户端使用用户名跟密码请求登录

服务端收到请求,去验证用户名与密码

验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端

客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里

客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

JWT

实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:

header

payload

signature

中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDU
iLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

Header

header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 HS256。

{
  "typ": "JWT",
  "alg": "HS256"
}

上面的内容要用 Base64 的形式编码一下,所以就变成这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

iss:Issuer,发行者

sub:Subject,主题

aud:Audience,观众

exp:Expiration time,过期时间

nbf:Not before

iat:Issued at,发行时间

jti:JWT ID

比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。

{
 "iss": "ninghao.net",
 "exp": "1438955445",
 "name": "wanghao",
 "admin": true
}

使用 Base64 编码以后就变成了这个样子:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ

Signature

JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

header

payload

secret

var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); 
HMACSHA256(encodedString, 'secret');

处理完成以后看起来像这样:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDU
iLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。

相关链接

http://jwt.io/

https://github.com/firebase/php-jwt

https://scotch.io/tutorials/the-anatomy-of-a-json-web-token

https://github.com/auth0/jwt-decode


相关课程

《JWT:JSON Web Token》

《Node.js:基于 Token 的身份验证》

《WordPress 开发:身份验证(JWT)》

《微信小程序:应用后台__身份验证 #3》

DB ETL DW OLAP DM BI

(1)DB/Database/数据库

    这里一般指的就是OLTP数据库,在线事物数据库,用来支持生产的,比如超市的买卖系统,DB保留的是数据信息的最新状态,只有一个状态!比如,每天早上起床洗脸照镜子,看到的就是当时的状态,至于之前的每天的状态,不会出现的你的眼前,这个眼前就是db。

(2)DW/Data Warehouse/数据仓库

    这里保存的是DB中的不同时间点的状态,比如,每天早上洗完照镜子时,都拍一张照片,天天这样,这些照片放入到一个相册中,之后就可以查看每一天的状态了,这个相册就是数据仓库,他保存的是数据在不同时间点的状态,对同一个数据信息,保留不同时间点的状态,就便于我们做统计分析了。

(3)ETL/Extraction-Transformation-Loading——用于完成DB到DW的数据转存

    它将DB中的某一个时间点的状态,“抽取”出来,根据DW的存储模型要求,“转换”一下数据格式,然后再“加载”到DW的一个过程,这里需要强调的是,DB的模型是ER模型,遵从范式化设计原则,而DW的数据模型是雪花型结构或者星型结构,用的是面向主题,面向问题的设计思路,所以DB和DW的模型结构不同,需要进行转换。

(4)OLAP——在线分析系统(联机分析处理)

    简单说就是报表系统,销售报表,统计报表,等等,这个大家都熟悉,当然,OLAP的统计要更复杂更丰富一些,比如切面,钻取等等。相对的还有OLTP — online transaction processing

(5)DM /Data Mart — 数据集市

  数据集市也叫数据市场,是一个从操作的数据和其他的为某个特殊的专业人员团体服务的数据源中收集数据的仓库。从范围上来说,数据是从企业范围的数据库、数据仓库,或者是更加专业的数据仓库中抽取出来的。数据中心的重点就在于它迎合了专业用户群体的特殊需求,在分析、内容、表现,以及易用方面。数据中心的用户希望数据是由他们熟悉的术语表现的。

    数据集市就是企业级数据仓库的一个子集,他主要面向部门级业务,并且只面向某个特定的主题。为了解决灵活性和性能之间的矛盾,数据集市就是数据仓库体系结构中增加的一种小型的部门或工作组级别的数据仓库。数据集市存储为特定用户预先计算好的数据,从而满足用户对性能的需求。数据集市可以在一定程度上缓解访问数据仓库的瓶颈。

(5)DM/Data Mining/数据挖掘

    这个挖掘,不是简单的统计了,他是根据概率论的或者其他的统计学原理,将DW中的大数据量进行分析,找出我们不能直观发现的规律.

    比如,如果我们每天早上照相,量身材的时候,还记录下头一天吃的东西,黄瓜,猪腿,烤鸭,以及心情,如果记录上10年,形成了3650天的相貌和饮食心情的数据,我们每个人都记录,有20万人记录了,那么,我们也许通过这些记录,可以分析出,身材相貌和饮食的客观规律;

    再说一个典型的实例,就是英国的超市,在积累了大量数据之后,对数据分析挖掘之后,得到了一个规律:将小孩的尿布和啤酒放在一起,销量会更好——业务专家在得到该结论之后,仔细分析,知道了原因,因为英国男人喜欢看足球的多,老婆把小孩给男人看管,小孩尿尿需要尿布,而男人看足球喜欢喝酒,所以两样商品有密切的关系,放在一起销售会更好!

(6)BI/Business Intelligence/商业智能

    领导,决策者,在获取了OLAP的统计信息,和DM得到的科学规律之后,对生产进行适当的调整,比如,命令超市人员将啤酒喝尿布放在一起销售,这就反作用于DB修改存货数据了——这就是整个BI的作用!

Python 入门

1、Python 环境准备

官网:https://www.python.org/ 

windows 资源: https://www.python.org/ftp/python/3.6.4/python-3.6.4-amd64.exe

python3

2314d8ad-0cff-490b-b570-e965ee77d0c0-366614.jpg

安装步骤

在默认地址安装 并 添加环境变量

4bf82e8a-b647-4c1a-95fe-6ad80ed6e535-366614.jpg

测试

cmd python

12cdfa9e-bc90-458a-bca0-b254c9052e3f-366614.jpg

IDE 工具:

PyCharm

安装

faf0e3f3-7808-4e76-85ba-308271835609-366614.jpg

创建项目

81f4314b-d104-4d39-aaba-1130debece71-366614.jpg

2、网页构造

基本网页结构

查看网页源代码 HTML CSS JavaScript

889a7f73-0a30-49c6-bf55-dec1b3cd2ae4-366614.jpg

自己写一个网页源码

head body div ul li img h p

引入CSS样式

9bb09e74-5719-4521-b762-5dbe2836e753-366614.jpg

3、网页解析

读取本地的 html 页面

先 open 获取文件 在read读取内容

转为字符串输出

c5f56b9c-823d-47a3-b886-b2951625187a-366614.jpg

解析字符串中的xpath路径

1. 安装第三方库 requests 和 lxml

d3cce850-5b82-45b3-8b17-bbcea6c11168-366614.jpg

 

2. 导入并使用  from lxml import etree

38e0ff45-d70a-411e-a175-2edf2a761b4a-366614.jpg

 

3. .xpath 解析字符路径 为节点

循环获取图片地址

a6c92beb-c09f-47b4-b481-75d5eb6b04f6-366614.jpg

4、真实网页请求(链家二手房信息)

通过 request 请求网络地址

get请求 输出获取到的文本

ef3af8b9-54f1-4117-ba29-e703c8da4824-366614.jpg

使用 xpath 解析

XPath定位 根据Class属性 找到对应的ul>li

fd0376eb-477c-4aa3-a8e9-26eb88edf755-366614.jpg

循环获取所有需要的数据

urls = ["https://cd.lianjia.com/ershoufang/pg{}/".format(str(i))for i in range(1,12)]

5ca2a318-5b18-4164-b722-c0adc4205b51-366614.jpg

5、Excel存储

使用 xlwt 库文件

Demo

dff75d28-90ee-41ae-99b5-da1e47446574-366614.jpg

案例代码

执行代码

a4b91902-5d08-4357-8374-8b4b2bde0aff-366614.jpg

结果

bf234591-c8b6-4ebe-9151-7e05f2a4f05c-366614.jpg

 

6、项目实战(微博数据)

浏览器调整到手机调试模式 方便获取数据

模拟登录:

随便一个页面的请求头中,拷贝cookie 和 用户代理信息

7742138c-ac12-4ee5-9049-0d14b3ff5804-366614.jpg

数据来源

Ajax 异步请求

542a1e97-c518-468b-89d9-0a7187a615fb-366614.jpg

基本用户信息的获取

使用 requests 请求库

用准备好的 cookie 通过 get 请求访问接口

b78e61d5-2bca-472c-8512-474ef7d429dd-366614.jpg

解析数据

json 库解析数据

把 json 字符串 转换为对象,获取对象的属性值

a76943cf-3c90-483f-8e00-74f02a1a1d26-366614.jpg

获取更多详细的信息

用户的详细信息

跟踪 user 的 uid,获取 containerid 和接口地址

94845eb5-7d81-4a58-a5ae-5e7cc649093d-366614.jpg

实例代码

获取根据 ui 获取到 对应用户的Json数据

api 地址通过 Chrome 浏览器复制

947d4a28-0630-4888-9be8-82ec72150aea-366614.jpg

正则表达式解析

通过 item_name 获取 key ; item_content 获取 Value

1 .在多行模式下不能匹配空格用\w\W代替;

2 ?非贪婪获取更多的字段 

3 * 任意多次 +至少一次 

测试

010f9cb6-b327-464c-87cf-b4791b8e4c95-366614.jpg

实例代码

python 正则查找方法 re.findall

10b244f0-27ce-41cf-9e84-b23da0c1f05f-366614.jpg

保存到数据库

MySQL 数据库 — 使用 pymyql 库

9d9b481b-077f-4a1c-ad5d-c65ff19f7dd4-366614.jpg

原文地址: http://www.52xjava.cn/2018/02/10/python-start/

后期更多猛料放出,关注 阿凯不错 公众号关注实时动态:

1526201714541657.jpg

Java 中的闭包 Closure

我们常常将数据、代码保存起来,以后再使用。但代码指令执行时候,会处于一定的环境,单纯将代码保存下来,还是不够的,需要将代码所处的环境也保存下来。

闭包其实是,将代码跟代码所处于的环境做为一个整体来看待,把相关参数和变量都保存在一起,从一个函数传递到另一个函数,以后再调用

这个概念和 对象(Objects)、代码块(block)、匿名函数(lambda)是相通的。

我们先从普通的函数来看:

1. 组合子(Combinator)
普通我们常说的一个“函数”:
f(x,y)=x+y
函数有两个“自变量”(术语:约束变量),x和y。函数的返回值,也就是应变量,是自变量一系列操作的结果。比如例子里是返回x和y的和。这样的一个它内部操作依赖的变量全部由参数提供了的”自给自足“的函数,叫“组合子(Combinator)”。

blob.png

Java代码表示就是:

public int add(int x, int y){
    return x+y;}

换到编程的概念,强调的就是函数的**“作用域”**。大多数编程语言都是用一对花括号**"{}"**标识出作用域。上面代码里的add()函数被调用之后,
int sum=add(2,3);
编译器编译之后,可以理解成是这个样子,函数的参数x和y,是包含在函数add()的作用域里的。

add(){
    int x=2;
    int y=3;
    return x+y;
}

或者,函数像下面这样写也可以。这时候x作为函数参数出现,而y作为函数局部变量出现。效果和上面的例子是一样的。

public int add(int x){
    int y=3;
    return x+y;
}

2. 自由变量
但有的时候,函数也可以有它自身作用域以外的参数参与。这些在函数作用域以外,由函数的外部环境提供的参数就叫“自由变量(Free Variable)”。

简单但不严格的说,一个函数的“自由变量”就是既不是参数也不是局部变量的变量。

比如下面这个x 的函数,返回xy 的和。这里的y就是自由变量。

f(x)=x+y
写成代码就是这样,

int y=3;
add(){
    int x=2;
    return x+y;
}

blob.png

一个纯粹(无副作用)的函数如果不含有自由变量,那么每次用相同的参数调用后的得到的结果肯定是一样的。

但如果一个函数含有自由变量,那么调用返回的结果不但依赖于参数的值,还依赖于自由变量的值。

因此一个含有自由变量的函数要正确执行,必须保证其所依赖的外围环境的存在。

3. 闭包(Closure)
大白话不怎么严谨的说法就是三点:

  1. 一个依赖于自由变量的函数
  2. 处在含有这些自由变量的一个外围环境
  3. 这个函数能够访问外围环境里的自由变量

看下面这个Javascript闭包的例子:

function Add(y) {  
    return function(x) {  
        return x + y  
    }  
} 

对内部函数function(x)来讲,y就是自由变量,而且function(x)的返回值,依赖于这个外部自由变量y。而往上推一层,外围Add(y)函数正好就是那个包含自由变量y的环境。而且Javascript的语法允许内部函数function(x)访问外部函数Add(y)的局部变量。满足这三个条件,所以这个时候,外部函数Add(y)对内部函数function(x)构成了闭包。

闭包的结构,如果用λ演算表达式来写,就是多参数的Currying技术。
> λx.λy.x+y

但在Java中我们看不到这样的结构。因为Java主流语法不允许这样的直接的函数套嵌和跨域访问变量。

4. 类和对象
但Java中真的不存在闭包吗?正好相反,Java到处都是闭包,所以反而我们感觉不出来在使用闭包。因为Java的“对象”其实就是一个闭包。其实无论是闭包也好,对象也好,都是一种数据封装的手段。看下面这个类,

class Add{
    private int x=2;
    public int add(){
	int y=3;
    	return x+y;
    }
}

看上去x在函数add()的作用域外面,但是通过Add类实例化的过程,变量”x“和数值”2“之间已经绑定了,而且和函数add()也已经打包在一起。add()函数其实是透过this关键字来访问对象的成员字段的。

5. 内部类是闭包:包含指向外部类的指针
Java中的内部类就是一个典型的闭包结构。代码如下

public class Outer {
    private class Inner{
        private x=100;
        public int innerAdd(){
            return x+y;
        }
    }
    private int y=100;
}

下图画的就是上面代码的结构。内部类(Inner Class)通过包含一个指向外部类的引用,做到自由访问外部环境类的所有字段,变相把环境中的自由变量封装到函数里,形成一个闭包。

blob.png

6、别扭的匿名内部类

但Java匿名内部类就做得比较尴尬。下面这个例子中,getAnnoInner负责返回一个匿名内部类的引用。

interface AnnoInner(){addXYZ();}
public
class Outer {    public AnnoInner getAnnoInner(final int x){        final int y=100;        return new AnnoInner(){            int z=100;            public int addXYZ(){return x+y+z;}
           //这个函数无法修改外部环境中的自由变量y。
           //public void changeY(){y+=1;}
       };    }    private int num=100;}

匿名内部类因为是匿名,所以不能显式地声明构造函数,也不能往构造函数里传参数。不但返回的只是个叫AnnoInner的接口,而且还没有和它外围环境getAnnoInner()方法的局部变量x和y构成任何类的结构。但它的addXYZ()函数却直接使用了x和y这两个自由变量来计算结果。这就说明,外部方法getAnnoInner()事实上已经对内部类AnnoInner构成了一个闭包。

但这里别扭的地方是这两个x和y都必须用final修饰,不可以修改。如果用一个changeY()函数试图修改外部getAnnoInner()函数的成员变量y,编译器通不过,

error: cannot assign a value to final variable y

这是为什么呢?因为这里Java编译器支持了闭包,但支持地不完整。说支持了闭包,是因为编译器编译的时候其实悄悄对函数做了手脚,偷偷把外部环境方法的x和y局部变量,拷贝了一份到匿名内部类里。如下面的代码所示。

interface AnnoInner(){addXYZ();}
public class Outer {    public AnnoInner getAnnoInner(final int x){        final int y=100;        return new AnnoInner(){
          //编译器相当于拷贝了外部自由变量x的一个副本到匿名内部类里。
          int copyX=x;
          //编译器相当于拷贝了外部自由变量y的一个副本到匿名内部类里。
   int copyY=y;            int z=100;

          //这个函数无法修改外部环境中的自由变量y。
          //public void changeY(){y+=1;}

           public int addXYZ(){return x+y+z;}                    };    }    private int num=100;}

所以用R大回答里的原话说就是:

Java编译器实现的只是capture-by-value,并没有实现capture-by-reference。

而只有后者才能保持匿名内部类和外部环境局部变量保持同步。

但Java又不肯明说,只能粗暴地一刀切,就说既然内外不能同步,那就不许大家改外围的局部变量。

7. 其他和匿名内部类相似的结构
《Think in Java》书里,只点出了匿名内部类来自外部闭包环境的自由变量必须是final的。但实际上,其他几种不太常用的内部类形式,也都有这个特性。

比如在外部类成员方法内部的内部类。

public class Outer {
    public foo(final int x){
        final int y=100;
        public class MethodInner{
	    int z=100;
            public int addXYZ(){return x+y+z;}
        }
    }}

比如在一个代码块block里的内部类。

public class Outer {
    {
        final int x=100;
        final int y=100;
        class BlockInner{
            int z=100;
            public int addXYZ(){return x+y+z;}
        }
        BlockInner bi=new BlockInner();
        num=bi.addXYZ();
    }
	private int num;}

HBase Shell 基本操作 查询过滤 命令

0.进入hbase shell

# hbase shell
hbase > help
hbase > help “get” #查看单独的某个命令的帮助

1. 一般命令

  1. status 查看状态

  2. version 查看版本

2.DDL(数据定义语言Data Definition Language)命令

    1. 创建表

        create ‘表名称’,’列名称1’,’列名称2’,’列名称3’ 

create 'student','info','grade'

    2.列出所有的表

list 
blob.png

list ‘abc.*’ #显示abc开头的表

    3.获得表的描述

describe ‘table_name’ 

blob.png

blob.png

Table student is ENABLED
student
COLUMN FAMILIES DESCRIPTION
{
NAME => 'grade', 
BLOOMFILTER => 'ROW', # bloom filter的作用是对一个region下查找记录所在的hfile有用。一个region下hfile数量越多,bloom filter的作用越明显。
VERSIONS => '1', # 版本 需要历史版本数据的应用通常可以设置为 3
IN_MEMORY => 'false', # 内存缓存
KEEP_DELETED_CELLS =>'FALSE',  
DATA_BLOCK_ENCODING => 'NONE', 
TTL => 'FOREVER', 
COMPRESSION => 'NONE', # 文件压缩处理
MIN_VERSIONS => '0',
BLOCKCACHE => 'true', 
BLOCKSIZE => '65536', 
REPLICATION_SCOPE => '0'
}

    4.删除一个列族 

alter,disable, enable

disable 'member'
# 删除列族时必须先将表给disable
alter 'member',{NAME=>'member_id',METHOD=>'delete'}
# 删除后继续
enable 'member'enable 'member'

    5.删除表

disable 'table_name'drop 'table_name'

blob.png

    6.查询表是否存在

exists 'table_name'1

    7.判断表是否enabled

is_enabled 'table_name'1

    8.更改表名

//快照 
//需要开启快照功能,在hbase-site.xml文件中添加如下配置项:

<property>
<name>hbase.snapshot.enabled</name>
<value>true</value>
</property>

//命令
hbaseshell> disable 'tableName'
hbaseshell> snapshot 'tableName', 'tableSnapshot'
hbaseshell> clone_snapshot 'tableSnapshot', 'newTableName'
hbaseshell> delete_snapshot 'tableSnapshot'
hbaseshell> drop 'tableName'

3.DML(data manipulation language)操作

    1.插入

blob.png

在ns1:t1或者t1表里的r1行,c1列中插入值,ts1是时间
  # t是table 't1'表的引用
  t.put 'r1','c1','value',ts1,{ATTRIBUTES=>{'mykey'=>'myvalue'}}

blob.png

    2.获取一条数据

get <table>,<rowkey>,[<family:column>,….]

blob.png

# 获取一个id的所有数据
get 'table_name','row_index'

# 获取一个id,一个列族的所有数据
get 'table_name','row_index','info'

# 获取一个id,一个列族中一个列的所有数据
get 'table_name','row_index','info:age'

blob.png

    3.更新一条记录

通过 put 覆盖列值

put 'table_name','row_index','info:column','value'

    4.通过timestrap来获取两个版本的数据

# 得到某个时间版本的记录
get'table_name','row',{COLUMN=>'info:column',TIMESTRAP=>1321586238965}

# 得到另一个个时间版本的记录
get'table_name','row',{COLUMN=>'info:column',TIMESTRAP=>1321586271843}

    5.全表扫描

scanner 相当于:select * from table_name

scan <table>, {COLUMNS => [ <family:column>,.... ], LIMIT => num}
# 另外,还可以添加STARTROW、TIMERANGE和FITLER等高级功能

2018-01-22_234806.png

scan 'hbase:meta'
scan 'hbase:meta',{COLUMNS => 'info:regioninfo'}
scan 'ns1:t1',{COLUMNS=>['c1','c2'],LIMIT=>10,STARTROW=>'xyz'}
scan 't1',{COLUMNS=>'c1',TIMERANGE=>[1303668804,1303668904]}
scan 't1',{REVERSED=>true}
scan 't1',{
    ROWPREFIXFILTER=>'row2',
    FILTER=>"(QualifierFilter(>=,'binary:xyz')) 
    AND (TimestampsFilter(123,456))"}
scan 't1',{FILTER => org.apache.hadoop.hbase.filter.ColumnPaginationFilter.new(1,0)}
scan 't1',{CONSISTENCY=>'TIMELINE'}

# 设置操作属性:
scan 't1',{COLUMNS => ['c1','c2'],ATTRIBUTES=>{'mykey'=>'myvalue'}}
scan 't1',{COLUMNS=>['c1','c2'],AUTHORIZATIONS=>['PRIVATE','SECRET']}
有个额外的选项:CACHE_BLOCKS,默认为true
还有个选项:RAW,返回所有cells(包括删除的markers和uncollected deleted cells,不能用来选择特定的columns,默认为default)
如:scan 't1',{RAW=>true,VERSIONS=>10}

blob.png

    6.删除记录

blob.png

# 删除指定rowkey的 'info:age' 字段
delete 'table_name','row_index','info:age'

# 删除整行
deleteall 'table_name','row_index'

blob.png

    7.查询表中有多少行

 hbase> count 'ns1:t1'  # namespace命名空间分组
 hbase> count 't1'
 hbase> count 't1', INTERVAL => 100000 # 每隔多少行显示一次count,默认是1000
 hbase> count 't1', CACHE => 1000 # 每次去取的缓存区大小,默认是10,调整该参数可提高查询速度
 
 hbase> count 't1', INTERVAL => 10, CACHE => 1000

    8.清空表

truncate 'students'
先删除表,再重建

truncate 日志:
hbase shell>truncate 'students'
Truncating 'students' table (it may take a while):
- Disabling table...
- Dropping table...
- Creating table ...

5.高级 scan 查询

  1.限制条件

限制查找列:

scan ‘table_name’,{COLUMNS=> 'column-familyinfo’}  # 列族
scan ‘table_name’,{COLUMNS=> 'info:regioninfo’}    # 字段
scan ‘table_name’,{COLUMNS=>[‘c1’,’c2’]}    # 多个列、字段

blob.png

限制查找条数:

scan 'table_name', {COLUMNS => [ 'c'], LIMIT => n}

blob.png

限制时间范围:

scan 'table_name', {TIMERANGE=>[ minStamp, maxStamp]}

blob.png

  2.FILTER 过滤器

    1.rowkey过滤

PrefixFilter:

行键前缀过滤器

scan 'table_name',{FILTER => "PrefixFilter('rowkey_prefix')"}

blob.png

    2.列族过滤

QuanlifierFilter:

列名限定符过滤器

scan 'table_name',{FILTER => "QualifierFilter(CompareOp,'BinaryComparator')"} 
# 参数是关系比较运算符 和 二进制比较器

blob.png

ColumnPrexfixFilter:

列名前缀过滤器

scan 'table_name',{FILTER => "ColumnPrefixFilter('colunm')"}

blob.png

MultipleColumnPrexfixFilter:

多个列名前缀过滤器

scan 'table_name',{FILTER => "MultipleColumnPrefixFilter('c1','c2')" }

blob.png

    3.列值过滤

SingleColumnValueFilter:

列值过滤器

# 需要导入类
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter
import org.apache.hadoop.hbase.filter.CompareFilter
import org.apache.hadoop.hbase.filter.SubstringComparator

scan 'table_name',{FILTER => SingleColumnValueFilter.new(
  Bytes.toBytes('info'),  # 列族
  Bytes.toBytes('column'),    # 字段
  CompareFilter::CompareOp.valueOf('EQUAL'), # 比较器
  Bytes.toBytes('my value')) # 值
}

blob.png

参数

有两个参数类在各类Filter中常常出现。统一介绍下:

(1)比较运算符 CompareFilter.CompareOp
EQUAL                      相等
GREATER                    大于
GREATER_OR_EQUAL           大于等于
LESS                       小于
LESS_OR_EQUAL              小于等于
NOT_EQUAL                  不等于

(2)比较器  
BinaryComparator           匹配完整字节数组 
BinaryPrefixComparator     匹配字节数组前缀 
RegexStringComparator      正则表达式匹配
SubstringComparator        子串匹配

    4.组合过滤器

    多个过滤器可以通过 AND OR 连接进行组合过滤

# 列如:
hbase(main):008:0> scan 'emp', {FILTER => "(MultipleColumnPrefixFilter('sal','COMM','deptno'))
 AND (SingleColumnValueFilter('empinfo','deptno',=,'substring:20'))"}

blob.png

本文地址: 

  http://www.52xjava.cn/2018/01/23/hbase-shell-command/

参考文章: 

https://www.cnblogs.com/mengfanrong/p/5337799.html

https://www.cnblogs.com/skyl/p/4807793.html

http://blog.csdn.net/power0405hf/article/details/49824579

http://blog.csdn.net/qq_27078095/article/details/56482010

Validate 表单验证插件

    说明:

required:true 必须输入的字段。
remote:"check.php" 使用 ajax 方法调用 check.php 验证输入值。
email:true 必须输入正确格式的电子邮件。
url:true 必须输入正确格式的网址。
date:true 必须输入正确格式的日期。日期校验 ie6 出错,慎用。
dateISO:true 必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22。只验证格式,不验证有效性。
number:true 必须输入合法的数字(负数,小数)。
digits:true 必须输入整数。
creditcard: 必须输入合法的信用卡号。
equalTo:"#field" 输入值必须和 #field 相同。
accept: 输入拥有合法后缀名的字符串(上传文件的后缀)。
maxlength:5 输入长度最多是 5 的字符串(汉字算一个字符)。
minlength:10 输入长度最小是 10 的字符串(汉字算一个字符)。
rangelength:[5,10] 输入长度必须介于 5 和 10 之间的字符串(汉字算一个字符)。
range:[5,10] 输入值必须介于 5 和 10 之间。
max:5 输入值不能大于 5。
min:10 输入值不能小于 10。

    还可以自定义验证规则

    addMethod:name, method, message
$.validator.addMethod("checkUsername",function(value,element,params){
    var flag = false;
    $.ajax({
        async:false, //改为同步  
        type:"POST",
        url:"${pageContext.request.contextPath}/check",
        data:{"username":value},
        dataType:"json",
        success:function(data){
            flag = data.isExist;
            return !flag;
        }
    });
    return !flag;
})

    debug,只验证不提交表单

    如果这个参数为true,那么表单不会提交,只进行检查,调试时十分方便。

$().ready(function(){
    $("#myForm").validate({
        debug:true
    })
});

    案例:

<!-- 引入jQuery核心js文件 -->
<script src="${pageContext.request.contextPath }/js/jquery-1.11.3.min.js"></script>
<!-- 引入validate插件 -->
<script src="${pageContext.request.contextPath }/js/jquery.validate.js"></script>
<style>
body {
    margin-top: 20px;
    margin: 0 auto;
}

font {
    color: #666;
    font-size: 22px;
    font-weight: normal;
    padding-right: 17px;
}
.error{
    color:red;
    font-size: 12pt;
}
</style>
<script type="text/javascript">
$(function(){

       //定义校验用户名是否存在的方法
       $.validator.addMethod("checkUsername",function(value,element,params){
           var flag = false;
           $.ajax({
               async:false, //改为同步  
               type:"POST",
               url:"${pageContext.request.contextPath}/check",
               data:{"username":value},
               dataType:"json",
               success:function(data){
                   flag = data.isExist;
                   return !flag;
               }
           });
           return !flag;
       })
                         
       $.validator.addMethod("telephone",function(value,element,params){
           //定义一个手机号码的正则
           var reg = /^1[3|4|5|8][0-9]\d{4,8}$/;
           return reg.test(value);
       });
       
       $("#regForm").validate({
           rules:{
               "username":{
                   "required":true,
                   "checkUsername":true
               },
               "password":{
                   "required":true,
                   "rangelength":[6,12]
               },
               "confirmpwd":{
                   "equalTo":"#password"
               },
               "name":{
                   "required":true
               },
               "email":{
                   "required":true,
                   "email":true
               },
               "telephone":{
                   "telephone":true
               },
                "validateCode"{
                    "required":true
               }
           },
           messages:{
               "username":{
                   "required":"用户名不能为空",
                   "checkUsername":"用户名已经存在"
               },
               "password":{
                   "required":"密码不能为空",
                   "rangelength":"密码长度应为6-12位"
               },
               "confirmpwd":{
                   "equalTo":"两次输入的密码不一致"
               },
               "name":{
                   "required":"姓名不能为空"
               },
               "email":{
                   "required":"邮箱不能为空",
                   "email":"邮箱格式不正确"
               },
               "telephone":{
                   "telephone":"手机格式不正确"
               },
                "validateCode":{
                   "telephone":"验证码不能为空"
               }
           }
       });
   })
</script>
<form id="regForm" class="form-horizontal" style="margin-top:5px;" 
   action="${pageContext.request.contextPath }/regist" method="post">
       <div class="form-group">
           <label for="username" class="col-sm-3 control-label">用户名</label>
           <div class="col-sm-8">
<input type="text" class="form-control" name="username" id="username" placeholder="请输入用户名">
</div>
       </div>
       <div class="form-group">
           <label for="password" class="col-sm-3 control-label">密码</label>
           <div class="col-sm-8">
               <input type="password" class="form-control" name="password" id="password" placeholder="请输入密码">
           </div>
       </div>
       <div class="form-group">
           <label for="confirmpwd" class="col-sm-3 control-label">确认密码</label>
           <div class="col-sm-8">
               <input type="password" class="form-control" name="confirmpwd" id="confirmpwd" placeholder="请输入确认密码">
           </div>
       </div>
       <div class="form-group">
           <label for="email" class="col-sm-3 control-label">Email</label>
           <div class="col-sm-8">
               <input type="text" class="form-control" name="email" id="email" placeholder="Email">
           </div>
       </div>
       <div class="form-group">
           <label for="name" class="col-sm-3 control-label">姓名</label>
           <div class="col-sm-8">
               <input type="text" class="form-control" name="name" id="name" placeholder="请输入姓名">
           </div>
       </div>
       <div class="form-group">
           <label for="telephone" class="col-sm-3 control-label">联系电话</label>
           <div class="col-sm-8">
               <input type="text" class="form-control" id="telephone" name="telephone">
           </div>
       </div>

       <div class="form-group">
           <label for="validateCode" class="col-sm-3 control-label">验证码</label>
           <div class="col-sm-4">
               <input type="text" class="form-control" id="validateCode" name="validateCode">
           </div>
           <div class="col-sm-2">
                <img id="loginform:vCode" src="${pageContext.request.contextPath }/validatecode.jsp"
               onclick="vCode()" />
           </div>
       </div>

       <div class="form-group">
           <div class="col-sm-offset-1 col-sm-10">
               <input class="btn btn-success btn-block" type="submit"  value="立即注册" />
           </div>
       </div>
</form>

本文地址:

 http://www.52xjava.cn/2018/01/15/validate-form-validation-plugin/

JavaMail 通过QQ邮箱发送邮件

您需要把依赖的 JavaMail mail.jarmail.zip 添加到项目

工具类

import java.security.GeneralSecurityException;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

import com.sun.mail.util.MailSSLSocketFactory;

public class MailUtils {

	/**
	 * 发送邮件
	 * @param email 收件人邮箱
	 * @param emailMsg 发送的内容
	 * @throws AddressException
	 * @throws MessagingException
	 * @throws GeneralSecurityException
	 */
	public static void sendMail(String email, String emailMsg)
			throws AddressException, MessagingException, GeneralSecurityException {
			
		// 1.创建一个程序与邮件服务器会话对象 Session
		Properties props = new Properties();
		props.setProperty("mail.transport.protocol", "SMTP");
		props.setProperty("mail.smtp.host", "smtp.qq.com");
		props.setProperty("mail.smtp.auth", "true");// 指定验证为true
		
		//  关于QQ邮箱,还要设置SSL加密,加上以下代码即可
		MailSSLSocketFactory sf = new MailSSLSocketFactory();
	        sf.setTrustAllHosts(true);
	        props.put("mail.smtp.ssl.enable", "true");
	        props.put("mail.smtp.ssl.socketFactory", sf);

		// 创建验证器
		Authenticator auth = new Authenticator() {
			public PasswordAuthentication getPasswordAuthentication() {
			//发信人的账号 密码
			return new PasswordAuthentication(gwk_87@qq.com, smtp的验证码);
			}
		};

		Session session = Session.getInstance(props, auth);

		// 2.创建一个Message,它相当于是邮件内容
		
		// 创建默认的 MimeMessage 对象
		Message message = new MimeMessage(session);
		
		// 设置发送者
		message.setFrom(new InternetAddress(gwk_87@qq.com)); 
		
		// 设置发送方式与接收者
		message.setRecipient(Message.RecipientType.TO, new InternetAddress(email)); 

		// 设置消息头
		message.setSubject("请激活邮件以完成注册");
		
		// 设置消息体 -- 普通文字
                // message.setText(emailMsg);
		
		//设置消息体 -- 发送 HTML 消息
		message.setContent("这是一封激活邮件,<br>"
				+ "<a href='#'>请访问点击,激活账号</a>", "text/html;charset=utf-8");

		// 3.创建 Transport用于将邮件发送
		Transport.send(message);
	}
}