bowenchen6

bowenchen6 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

bowenchen6 收藏了文章 · 2018-04-16

MongoDB入门之索引篇

原文链接: https://icoding.net/topic/12....

索引就像书的目录,如果查找某内容在没有目录的帮助下,只能全篇查找翻阅,这导致效率非常的低下;如果在借助目录情况下,就能很快的定位具体内容所在区域,效率会直线提高。

索引简介

首先打开命令行,输入mongo。默认mongodb会连接名为test的数据库。

➜  ~  mongo
MongoDB shell version: 2.4.9
connecting to: test
> show collections
> 

可以使用show collections/tables查看数据库为空。

然后在mongodb shell执行如下代码

> for(var i=0;i<100000;i++) {
... db.users.insert({username:'user'+i})
... }
> show collections
system.indexes
users
> 

再查看数据库发现多了system.indexesusers两个表,前者即所谓的索引,后者为新建的数据库表。
这样user表中即有了10万条数据。

> db.users.find()
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e4"), "username" : "user0" }
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e5"), "username" : "user1" }
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e6"), "username" : "user2" }
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e7"), "username" : "user3" }
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e8"), "username" : "user4" }
{ "_id" : ObjectId("5694d5da8fad9e319c5b43e9"), "username" : "user5" }

现在需要查找其中任意一条数据,比如

> db.users.find({username: 'user1234'})
{ "_id" : ObjectId("5694d5db8fad9e319c5b48b6"), "username" : "user1234" }

发现这条数据成功找到,但需要了解详细信息,需要加上explain方法

> db.users.find({username: 'user1234'}).explain()
{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 100000,
    "nscanned" : 100000,
    "nscannedObjectsAllPlans" : 100000,
    "nscannedAllPlans" : 100000,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 30,
    "indexBounds" : {
        
    },
    "server" : "root:27017"
}

参数很多,目前我们只关注其中的"nscanned" : 100000"millis" : 30这两项。
nscanned表示mongodb在完成这个查询过程中扫描的文档总数。可以发现,集合中的每个文档都被扫描了,并且总时间为30毫秒。
如果数据有1000万个,如果每次查询文档都遍历一遍。呃,时间也是相当可观。

对于此类查询,索引是一个非常好的解决方案。

> db.users.ensureIndex({"username": 1})

其中数字1-1表示索引的排序方向,一般都可以。
然后再查找user1234

> db.users.ensureIndex({"username": 1})
> db.users.find({username: 'user1234'}).explain()
{
    "cursor" : "BtreeCursor username_1",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 1,
    "nscannedAllPlans" : 1,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "username" : [
            [
                "user1234",
                "user1234"
            ]
        ]
    },
    "server" : "root:27017"
}

的确有点不可思议,查询在瞬间完成,因为通过索引只查找了一条数据,而不是100000条。

当然使用索引是也是有代价的:对于添加的每一条索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据发生变化时,不仅要更新文档,还要更新级集合上的所有索引。因此,mongodb限制每个集合最多有64个索引。通常,在一个特定的集合上,不应该拥有两个以上的索引。

小技巧

如果一个非常通用的查询,或者这个查询造成了性能瓶颈,那么在某字段(比如username)建立索引是非常好的选择。但只是给管理员用的查询(不太在意查询耗费时间),就不该对这个字段建立索引。

复合索引

索引的值是按一定顺序排列的,所以使用索引键对文档进行排序非常快。

db.users.find().sort({'age': 1, 'username': 1})

这里先根据age排序再根据username排序,所以username在这里发挥的作用并不大。为了优化这个排序,可能需要在age和username上建立索引。

db.users.ensureIndex({'age':1, 'username': 1})

这就建立了一个复合索引(建立在多个字段上的索引),如果查询条件包括多个键,这个索引就非常有用。

建立复合索引后,每个索引条目都包括一个age字段和一个username字段,并且指向文档在磁盘上的存储位置。
此时,age字段是严格升序排列的,如果age相等时再按照username升序排列。

查询方式

点查询(point query)

用于查询单个值(尽管包含这个值的文档可能有多个)

db.users.find({'age': 21}).sort({'username': -1})

因为我们已经建立好复合索引,一个age一个username,建立索引时使用的是升序排序(即数字1),当使用点查询查找{age:21},假设仍然是10万条数据。可能年龄是21的很多人,因此会找到不只一条数据。然后sort({'username': -1})会对这些数据进行逆序排序,本意是这样。但我们不要忘记建立索引时'username':1是升序(从小到大),如果想得到逆序只要对数据从最后一个索引开始,依次遍历即可得到想要的结果。

排序方向并不重要,mongodb可以从任意方向对索引进行遍历。

综上,复合索引在点查询这种情况非常高效,直接定位年龄,不需要对结果进行排序即可返回结果。

多值查询(multi-value-query)

db.users.find({'age': {"$gte": 21, "$lte": 30}})

查找多个值相匹配的文档。多值查询也可以理解为多个点查询
如上,要查找年龄介于21到30之间。monogdb会使用索引的中的第一个键"age"得到匹配的结果,而结果通常是按照索引顺序排列的。

db.users.find({'age': {"$gte": 21, "$lte": 30}}).sort({'username': 1})

与上一个类似,这次需要对结果排序。
在没有sort时,我们查询的结果首先是根据age等于21,age等于22..这样从小到大排序,当age等于21有多个时,在进行usernameA-Z(0-9)这样排序。所以,sort({'username': 1}),要将所有结果通过名字升序排列,这次不得不先在内存中进行排序,然后返回。效率不如上一个高。

当然,在文档非常少的情况,排序也花费不了多少时间。
如果结果集很大,比如超过32MB,MongoDB会拒绝对如此多的数据进行排序工作。

还有另外一种解决方案

也可以建立另外一个索引{'username': 1, 'age': 1}, 如果先对username建立索引,如果再sortusername,相当没有进行排序。但是需要在整个文档查找age等于21的帅哥美女,所以搜寻时间就长了。

  • 但哪个效率更高呢?
  • 如果建立多个索引,如何选择使用哪个呢?

效率高低是分情况的,如果在没有限制的情况下,不进行排序但需要搜索整个集合时间会远超过前者。但是在返回部分数据(比如limit(1000)),新的赢家就产生了。

>db.users.find({'age': {"$gte": 21, "$lte": 30}}).
sort({username': 1}).
limit(1000).
hint({'age': 1, 'username': 1})
explain()['millis']

2031ms

>db.users.find({'age': {"$gte": 21, "$lte": 30}}).
sort({username': 1}).
limit(1000).
hint({'username': 1, 'age': 1}).
explain()['millis']

181ms

其中可以使用hint指定要使用的索引。
所以这种方式还是很有优势的。比如一般场景下,我们不会把所有的数据都取出来,只是去查询最近的,所以这种效率也会更高。

索引类型

单键索引

最普通索引,如

db.users.ensureIndex({'username': 1})

唯一索引

可以确保集合的每个文档的指定键都有唯一值。

db.users.ensureIndex({'username': 1, unique: true})

如果插入2个相同都叫张三的数据,第二次插入的则会失败。_id即为唯一索引,并且不能删除。
这和使用mongoose框架很相似,比如在定义schema时,即可指定unique: true

company: { // 公司名称
    type: String,
    required: true,
    unique: true
}

多键索引

如果某个键的值在文档中是一个数组,那么这个索引就会被标记为多键索引
比如现在members文档中随便添加有3条数据:

> db.members.find()
{ "_id" : ObjectId("1"), "tags" : [  "ame",  "fear",  "big" ] }
{ "_id" : ObjectId("2"), "tags" : [  "ame",  "fear",  "big",  "chi" ] }
{ "_id" : ObjectId("3"), "tags" : [  "ame",  "jr",  "big",  "chi" ] }

当我查找tags='jr'数据时,db会查找所有文档,所以nscanned=3,并且返回一条,此时n=1

>db.members.find({tags: 'jr'}).explain()
{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 1,
    "nscanned" : 3,
}

然后建立索引

> db.members.ensureIndex({tags:1})

之后我们在对tags='jr'进行查找,此时nscanned=1,并且isMultiKey由原来的false变为true。所以可以说明,mongodb对数组做了多个键的索引,即把所有的数组元素都做了索引。

> db.members.find({tags: 'jr'}).explain()
{
    "cursor" : "BtreeCursor tags_1",
    "isMultiKey" : true,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
}
    

过期索引

是在一段时间后会过期的索引。索引过期后,相应的数据会被删除。适合存储一些在一段时间失效的数据比如用户的登录信息,存储的日志等。
和设置单键索引很类似,只是多个expireAfterSeconds参数,单位是

db.collectionName.ensureIndex({key: 1}, {expireAfterSeconds: 10})

首先我们先建立一下索引,数据会在30秒后删除

> db.members.ensureIndex({time:1}, {expireAfterSeconds: 30})

插入数据

> db.members.insert({time: new Date()})

查询

> db.members.find()

{ "_id" : ObjectId("4"), "time" : ISODate("2016-01-16T12:27:20.171Z") }

30秒后再次查询,数据则消失了。

存储的值必须是ISODate时间类型(比如new Date()),如果存储的非时间类型,则不会自动删除。
过期索引不能是复合索引。
删除的时间不精确,因为删除过程每60秒后台程序跑一次,而且删除也需要一些时间,存在误差。

稀疏索引

使用sparse可以创建稀疏索引和唯一索引

>db.users.ensureIndex({'email': 1}, {'unique': true, 'sparse': true})

下面来自官网的问候

Sparse Index with Unique Constraint(约束)

Consider a collection scores that contains the following documents:

{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }

You could create an index with a unique constraint and sparse filter on the score field using the following operation:

db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )

This index would permit the insertion of documents that had unique values for the score field or did not include a score field.
所以索引会允许不同score的文档或根本没有score这个字段的文档插入成功。

As such, given the existing documents in the scores collection, the index permits the following insert operations:
以下插入成功:

db.scores.insert( { "userid": "AAAAAAA", "score": 43 } )
db.scores.insert( { "userid": "BBBBBBB", "score": 34 } )
db.scores.insert( { "userid": "CCCCCCC" } )
db.scores.insert( { "userid": "DDDDDDD" } )

However, the index would not permit the addition of the following documents since documents already exists with score value of 82 and 90:

db.scores.insert( { "userid": "AAAAAAA", "score": 82 } )
db.scores.insert( { "userid": "BBBBBBB", "score": 90 } )

索引管理

system.indexes集合中包含了每个索引的详细信息

db.system.indexes.find()

创建索引

Mongo shell

  • ensureIndex()
  • createIndex()

example

db.users.ensureIndex({'username': 1})

后台创建索引,这样数据库再创建索引的同时,仍然能够处理读写请求,可以指定background选项。

db.test.ensureIndex({"username":1},{"background":true})

Schema

var animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } // field level
});

animalSchema.index({ name: 1, type: -1 }); // schema level

Schema中,官方不推荐在生成环境直接创建索引

When your application starts up, Mongoose automatically calls ensureIndex for each defined index in your schema. Mongoose will call ensureIndex for each index sequentially, and emit an 'index' event on the model when all the ensureIndex calls succeeded or when there was an error. While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact . Disable the behavior by setting the autoIndex option of your schema to false, or globally on the connection by setting the option config.autoIndex to false.

2.getIndexes()查看索引

db.collectionName.getIndexes()
db.users.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "ns" : "test.users",
        "name" : "_id_"
    },
    {
        "v" : 1,
        "key" : {
            "username" : 1
        },
        "ns" : "test.users",
        "name" : "username_1"
    }
]

其中v字段只在内部使用,用于标识索引版本。

3.dropIndex删除索引

> db.users.dropIndex("username_1")
{ "nIndexesWas" : 2, "ok" : 1 }

> db.users.dropIndex({"username":1})

https://iCoding.net

查看原文

bowenchen6 赞了文章 · 2017-12-20

PDO的bindParam与bindValue的区别

[来源于Stackoverflow: What is the difference between bindParam and bindValue?]
PDOStatement::bindParam 与 PDOStatement::bindValue()不同, 变量被以引用方式绑定到点位符上而且仅仅当调用PDOStatement::execute()时才会去计算具体被绑定变量在PDOStatement::execute()被调用时的值.

So, for example:

php<?php
$sex = 'male';
$s = $dbh->prepare('SELECT name FROM students WHERE sex = :sex');
$s->bindParam(':sex', $sex); // use bindParam to bind the variable
$sex = 'female';
$s->execute(); // 将执行 WHERE sex = 'female'

or

php<?php
$sex = 'male';
$s = $dbh->prepare('SELECT name FROM students WHERE sex = :sex');
$s->bindValue(':sex', $sex); // use bindValue to bind the variable's value
$sex = 'female';
$s->execute(); // 将执行 WHERE sex = 'male'
查看原文

赞 1 收藏 3 评论 1

bowenchen6 赞了文章 · 2017-11-10

Nginx 源码分析:ngx_pool_t

源代码路径

版本:1.8.0

src\core\Ngx_palloc.h
src\core\Ngx_palloc.c

主要作用分析

提供了一种机制,帮助进行资源管理(内存、文件)。可以类比C++中的RAII机制。

以内存管理为例,通常是手工进行malloc/free,这种做法的优点是灵活、高效,缺点是容易出错,造成内存泄漏以及各种莫名其妙的BUG。

因此,在C/C++中正确的管理内存一直是最棘手的问题。

C++中提供了RAII机制和智能指针来解决这个问题。Nginx采用C语言开发,实现了一套用来管理资源的机制。这就是nginx pool

数据结构

ngx_pool_data_t

typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

首先看一个示意图:
图片描述

last指针表示ngx_pool_data_t所管理的内存block中已使用的内存的末尾地址,这也是下次分配的起始地址。

end指针表示ngx_pool_data_t所管理的内存block的尾地址,该指针在分配此block时确定,用来标记last的边界值。

next用来指向下一个ngx_pool_data_t的内存block地址,用于形成block链表。
failed用来标记该block使用时分配失败次数。

ngx_pool_data_t的管理和使用

围绕核心数据结构ngx_pool_data_t, Nginx是如何管理和使用的呢?

本篇主要从以下几个方面说明:
1) 如何初始化ngx_pool_data_t链表;
2) 如何向链表增加ngx_pool_data_t节点。
3) 如何回收/销毁ngx_pool_data_t节点及链表

初始化ngx_pool_data_t链表

基本思路:

  1. 所谓初始化ngx_pool_data_t链表就是申请一段内存block,然后将该block指针void* p转成ngx_pool_data_t*类型的指针,这样,就可以利用ngx_pool_data_t*指针来管理这段block,而这个block就是ngx_pool_data_t链表的节点
  2. 当然要能够正确管理block还需要初始化ngx_pool_data_t的成员变量lastendnextfailed。这样,通过ngx_pool_data_t指针,可以获取管理ngx_pool_data_t链表节点的能力
  3. 但是现在我们还没有管理整个ngx_pool_data_t链表的能力,那么如何做呢?通过在内存block的起始部分添加lastendnextfailed等信息可以管理一段内存。相应地,通过在ngx_pool_data_t链表第一个节点添加管理链表的信息,就可以管理整个链表。

同时,由于链表第一个节点只是一个特殊的节点所以,负责管理节点的结构体ngx_pool_data_t应该是的负责管理链表结构体节点的子集

Nginx源码中这个管理链表的结构体就是:ngx_pool_s,其结构体定义为:

struct ngx_pool_s {
    ngx_pool_data_t       d;
    size_t                max;
    ngx_pool_t           *current;
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};

其中,dngx_pool_data_t用来管理节点本身,而其他变量则用来管理链表。

  • current用来指示链表中当前正在使用的节点;
  • chain用来在节点上挂接filter链表;
  • cleanup用来注册资源回收handler等;
  • large是与节点大内存小内存有关,这里暂不分析。

通过以上分析,可知,ngx_pool_s是管理整个ngx_pool_data_t链表,同时也能够管理所在的链表节点。

Nginx源码里,申请的block指针为void*统一转成ngx_pool_s*,这样,在链表第一个节点,通过ngx_pool_s*管理整个链表及节点本身,在其他节点,通过ngx_pool_s*管理节点本身。

根据以上思路,可以很容易明白Nginx源码里关于创建ngx_pool_data_t链表的代码

函数声明: Ngx_palloc.h

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);

说明:

输入要分配的ngx_pool_t节点block大小size,返回一个ngx_pool_t*的指针。该指针既用来管理链表,也用来管理节点block本身。
因此,后续链表的插入,删除都是通过操作该指针来完成,而使用链表第一个节点的block也是通过该指针来完成。

函数定义: Ngx_palloc.c

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    // 申请内存block,为提高效率,进行了对齐
    // 转成ngx_pool_t*指针
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
    if (p == NULL) {
        return NULL;
    }

    // 初始化ngx_pool_data_t用来管理节点block本身
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    // 为提高效率能使用的block最大值为NGX_MAX_ALLOC_FROM_POOL
    // 输入size最小值为sizeof(ngx_pool_t),输入小于该值会造成nginx崩溃
    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    // 初始时,链表current指向自身
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

向链表增加ngx_pool_data_t节点

基本思路:

上一小节中关于如何初始化ngx_pool_data_t链表的思路,可以基本套用到增加ngx_pool_data_t节点中来,只是在结构体ngx_pool_t成员变量初始化上有所区别。

直接上源码:

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    // 输入变量pool用来表示整个链表
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    // 计算链表第一个节点的block大小
    psize = (size_t) (pool->d.end - (u_char *) pool);
    // 申请与链表第一个节点大小相同的内存block
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
    // 转为ngx_pool_t*类型
    new = (ngx_pool_t *) m;
    // 初始化节点管理结构体ngx_pool_data_t
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
    // 为了效率,对ngx_pool_data_t的last变量进行对齐操作
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;
    // 将第一个链表节点用于操作链表的pool指针的current变量指向当前节点
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

当然,在使用时,Nginx并不是直接使用ngx_palloc_block来操作链表,而是使用ngx_palloc
原因在于,使用ngx_create_poolngx_palloc_block构造链表之后,我们得到的是多个连续的内存块组成的列表,这些内存块大小相同,其大小为调用ngx_create_pool传入的size值或NGX_MAX_ALLOC_FROM_POOL

而在实际使用时malloc的大小不一定等于这些内存块的大小,所以,需要提供一个接口,能够从ngx_pool_t链表中获取需要大小的内存。

这个函数就是ngx_palloc

基本思路:

如果要获取的内存大小大于ngx_pool_t节点的大小,那么属于创建大内存,调用ngx_palloc_large(pool, size)。这里我们暂不分析大内存的问题。

如果要获取的内存小于ngx_pool_t节点的大小,那么尝试从当前节点分配。

这里分两种情况:

  1. 当前节点剩余空间足够的情况,直接分配;
  2. 当前节点剩余空间不足的情况,创建一个新节点,并将新节点分配

P.S 这里的分配就是返回指针

函数声明:

void *ngx_palloc(ngx_pool_t *pool, size_t size);

函数定义:

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;
    // 获取内存小于链表节点内存的情况
    if (size <= pool->max) {

        p = pool->current;
        // 尝试从当前节点分配内存
        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

            if ((size_t) (p->d.end - m) >= size) {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);
        // 新建一个节点并分配
        return ngx_palloc_block(pool, size);
    }
    // 获取内存小于链表节点内存的情况
    return ngx_palloc_large(pool, size);
}

今天先到这里,后续再补充或另起一篇写。

查看原文

赞 3 收藏 10 评论 1

bowenchen6 赞了回答 · 2017-10-13

解决最好的Python web框架。

谁都知道没有最好的,要看情况。
很多情况下小巧优雅的web.py足够,个人喜欢tornado,如果构建的是web应用的话更推荐tornado,tornado类似web.py的威力加强版XD,是专为webapp而生的,异步非阻塞也是node.js的主要特性之一
如果是以内容为主的网站Django是比较优秀的选择,比如blog之类的,自带的后台、表单和ORM十分方便。缺点是太大了,什么都用django自家的而不用外头的好东西,而且模块间耦合比较严重。
新手学习来说web.py和django都是不错的选择,先试试django比较好
另外如果不用django的话表单可以用wtform,类似django数据库模块的有很多,sqlalchemy比较推荐但是学习曲线不是非常好看

关注 3 回答 16

bowenchen6 赞了回答 · 2017-05-24

如何保证数据库里的信息安全?

题主你这个问题问得好大... 从以下几个方面着手吧:

  1. 数据库权限:连数据库不要老用root,给web应用单独开帐号,最小化权限

  2. 数据库密码:不要弱密码,尽量搞个随机生成的16位以上的密码

  3. web服务器:同数据库,密码尽量复杂化,尽量不要直接用root帐号,单独开帐号,只有在必须需要的时候才用root

  4. web应用:

    1. 尽量选一个好点的DB库或ORM库,使用参数化的SQL查询,不要直接把参数拼在SQL里面,防止SQL注入

    2. 用户(包括后台管理用户)的密码信息不要直接存储,应加盐hash后再存储

    3. 如果可以,尽量使用HTTPS

    4. 渲染HTML的时候,尽量使用编码后再输出,防止XSS

    5. 非得输出一段用户控制的富文本HTML的时候,使用htmlpurifier这样的过滤器,防止XSS

    6. 遵循HTTP动词语义,增删改类操作使用POST/DELETE/PUT,并增加csrf_token,防止CSRF攻击

暂时想到这么多,web安全还有很多要注意的点,建议LZ直接请个安全顾问吧。

关注 5 回答 4

bowenchen6 关注了标签 · 2017-05-18

关注 496

bowenchen6 关注了标签 · 2017-05-18

tornado

Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被facebook收购以后框架以开源软件形式开放给大众。

关注 600

bowenchen6 关注了标签 · 2017-05-18

七牛云存储

七牛云由国内存储行业的领军人物之一许式伟于2011年创立,致力于提供最适合开发者的数据在线托管、传输加速以及云端处理的服务。

clipboard.png

七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化PaaS服务。围绕富媒体场景,七牛先后推出了对象存储,融合CDN加速,数据通用处理,内容反垃圾服务,以及直播云服务等。目前,七牛云已经在为 40多万家企业提供服务, 亲历互联网创新创业发展的同时,也深入理解传统企业转型过程中的云服务需求场景,推出了有针对性的一系列行业解决方案。

七牛团队

官方提示:

在这里,你将直接获得七牛云工程师们的帮助,并同像你一样的开发者交流。如果你在产品开发上遇到了什么困难,这儿将是最好的解决平台。

关注 820

bowenchen6 关注了标签 · 2017-05-18

linux

Linux是一种自由和开放源代码的类Unix计算机操作系统。目前存在着许多不同的Linux,但它们全都使用了Linux内核。Linux可安装在各种各样的计算机硬件设备,从手机、平板电脑、路由器和视频游戏控制台,到台式计算机,大型机和超级计算机。

Linux家族家谱图,很全很强大!! 图中可以清楚的看出各个Linux发行版的血缘关系。无水印原图:http://url.cn/5ONhQb

关注 66259

bowenchen6 关注了标签 · 2017-05-18

关注 959

认证与成就

  • 获得 4 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-05-19
个人主页被 160 人浏览