mongodb 操作经验总结(其三)

mongodb 操作经验总结:
1,使用优点:
1),不需要像关系型数据库把产品信息打散到不同的表之中,查询还是使用join连接或者拼接成复杂的sql语句,完全可以把产品信息放置在一起
2),实用高效的二进制BSON作为数据存储,更快的遍历速度,比json 提供更多的内置结构
3),内置聚合工具,可以通过 MAPReduce等方式进行复杂的并行,统计计算
2,可以使用 pymongo操作mongodb
1)聚合方式:
1),管道聚合,相当于 linux(管道操作符),将当前的输出结果作为下一个命令的参数
2),MAPReduce MAP-Reduce计算模型,也即是可以进行计算,不过计算语法是 javascript
3),单一的聚合方式,count,distinct,group
3,mongodb 有着自己的 ODM框架(对象文档映射)框架,有点像 SQLALchemy(mysql ORM框架)
1),安装 mongoengine
pip install mongoengine
2),常见使用方式
# coding=utf-8
import os
import uuid
import magic
import urllib
from datetime import datetime
import cropresize2
import short_url
from PIL import Image
from flask import abort, request
from werkzeug.utils import cached_property
from mongoengine import (
    Document as BaseDocument, connect, ValidationError, DoesNotExist,
    QuerySet, MultipleObjectsReturned, IntField, DateTimeField, StringField,
    SequenceField)

from mimes import IMAGE_MIMES, AUDIO_MIMES, VIDEO_MIMES
from utils import get_file_md5, get_file_path

connect('r', host='localhost', port=27017)


class BaseQuerySet(QuerySet):
    # 使用自定义的查询集
    def get_or_404(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except (MultipleObjectsReturned, DoesNotExist, ValidationError):
            abort(404)


class Document(BaseDocument):
    # 使用自定义查询集
    meta = {'abstract': True,
            'queryset_class': BaseQuerySet}


class PasteFile(Document):
    id = SequenceField(primary_key=True)
    filename = StringField(max_length=5000, null=False)
    filehash = StringField(max_length=128, null=False, unique=True)
    filemd5 = StringField(max_length=128, null=False, unique=True)
    uploadtime = DateTimeField(null=False)
    mimetype = StringField(max_length=128, null=False)
    size = IntField(null=False)
    meta = {'collection': 'paste_file'}  # 自定义集合的名字

    def __init__(self, filename='', mimetype='application/octet-stream',
                 size=0, filehash=None, filemd5=None, *args, **kwargs):
        # 初始化父类的__init__方法
        super(PasteFile, self).__init__(filename=filename, mimetype=mimetype,
                                        size=size, filehash=filehash,
                                        filemd5=filemd5, *args, **kwargs)
        self.uploadtime = datetime.now()
        self.mimetype = mimetype
        self.size = int(size)
        self.filehash = filehash if filehash else self._hash_filename(filename)
        self.filename = filename if filename else self.filehash
        self.filemd5 = filemd5

    @staticmethod
    def _hash_filename(filename):
        _, _, suffix = filename.rpartition('.')
        return '%s.%s' % (uuid.uuid4().hex, suffix)

    @cached_property
    def symlink(self):
        return short_url.encode_url(self.id)

    @classmethod
    def get_by_symlink(cls, symlink, code=404):
        id = short_url.decode_url(symlink)
        return cls.objects.get_or_404(id=id)

    @classmethod
    def get_by_filehash(cls, filehash, code=404):
        return cls.objects.get_or_404(filehash=filehash)

    @classmethod
    def get_by_md5(cls, filemd5):
        rs = cls.objects(filemd5=filemd5)
        return rs[0] if rs else None

    @classmethod
    def create_by_upload_file(cls, uploaded_file):
        rst = cls(uploaded_file.filename, uploaded_file.mimetype, 0)
        uploaded_file.save(rst.path)
        with open(rst.path) as f:
            filemd5 = get_file_md5(f)
            uploaded_file = cls.get_by_md5(filemd5)
            if uploaded_file:
                os.remove(rst.path)
                return uploaded_file
        filestat = os.stat(rst.path)
        rst.size = filestat.st_size
        rst.filemd5 = filemd5
        return rst

    @classmethod
    def create_by_old_paste(cls, filehash):
        filepath = get_file_path(filehash)
        mimetype = magic.from_file(filepath, mime=True)
        filestat = os.stat(filepath)
        size = filestat.st_size

        rst = cls(filehash, mimetype, size, filehash=filehash)
        return rst

    @property
    def path(self):
        return get_file_path(self.filehash)

    def get_url(self, subtype, is_symlink=False):
        hash_or_link = self.symlink if is_symlink else self.filehash
        return 'http://{host}/{subtype}/{hash_or_link}'.format(
            subtype=subtype, host=request.host, hash_or_link=hash_or_link)

    @property
    def url_i(self):
        return self.get_url('i')

    @property
    def url_p(self):
        return self.get_url('p')

    @property
    def url_s(self):
        return self.get_url('s', is_symlink=True)

    @property
    def url_d(self):
        return self.get_url('d')

    @property
    def image_size(self):
        if self.is_image:
            im = Image.open(self.path)
            return im.size
        return (0, 0)

    @property
    def quoteurl(self):
        return urllib.quote(self.url_i)

    @classmethod
    def rsize(cls, old_paste, weight, height):
        assert old_paste.is_image, TypeError('Unsupported Image Type.')

        img = cropresize2.crop_resize(
            Image.open(old_paste.path), (int(weight), int(height)))

        rst = cls(old_paste.filename, old_paste.mimetype, 0)
        img.save(rst.path)
        filestat = os.stat(rst.path)
        rst.size = filestat.st_size
        return rst

    @property
    def is_image(self):
        return self.mimetype in IMAGE_MIMES

    @property
    def is_audio(self):
        return self.mimetype in AUDIO_MIMES

    @property
    def is_video(self):
        return self.mimetype in VIDEO_MIMES

    @property
    def is_pdf(self):
        return self.mimetype == 'application/pdf'

    @property
    def type(self):
        for t in ('image', 'pdf', 'video', 'audio'):
            if getattr(self, 'is_' + t):
                return t
        return 'binary'


#使用案例
# 使用自定义的查询集合
Document.objects.get_or_404(id=12)
Document(id=12, filename='test').save()
from mongoengine.queryset.visitor import Q
# 查找id为12, 且filename为 tst 的记录
result = Document.objects(Q(id=12) & Q(filemame='tst'))
Document.objects.sum('id') # 以id 字段进行聚合
View Code
4,正确使用索引:
查找查询时间长的语句:
1),每次查询在var/log/mongodb/mongod.log 都会有一个记录
2),将慢查询记录到 system.profile集合之中.慢查询指的是记录响应时间超过阀值的语句
3),每次在集合 system.profile 进行分析,比如可以查询花费时间 TOP100 的数据
为时间长的语句增加联合索引:
1),创建联合索引是按照查询条件的顺序
2),排序的字段一般放在索引之后,但是设计范围查询有可能提前
3),能过滤数量多的字段放置在前面
4),一般时间到 1ms左右是可接受的
5,mongodb常见的两种高可用方案(也就是复制)
1),主从复制方案:
2),副本集
1),与主从复制相似,增加了故障转移
2),故障转移:主服务器由于某些原因下线,从节点可以提升为主节点
3),角色类型:
1,主节点,处理客户端读写请求
2,从节点,可以处理客户端的读请求,多个,每个都保存着主服务器的数据副本,可升为主节点
3,仲裁节点,用来选举主节点
4,隐藏节点,用来数据备份
常见的副本集架构:
1,一个主节点,两个从节点(或者一个从,一个仲裁),主节点读写,从节点只读,小规模
2,主节点只写,从节点读,有一个备份节点.读写进行分离,异步分布式架构会将备份的从节点在一个数据中心(大项目)
6,分片方案:
1,分片是用来解决吞吐量的问题:
1),mongodb 支持自动分片,它是将一个集合根据片键分成多个块(chunk),然后将各个块对应到各个服务器之中,分发是按照块分发
2),关于片键的选择:
1,不要按照自增的字段作为片键,比如_id ,它会一直向最后一个服务器之中添加数据
2,使用随机数高的,基数大字段进行分片,这样避免一个服务器承受太大的压力
3,可以使用多个属性最为片键,一般找不到随机数高片键的时候使用
4,使用_id的哈希值,多个文档查询,需要遍历不同的分片
django 项目缓存使用 mongodb
# -*-coding:utf-8 -*-
"mongodb cache backend"

import time, json, base64, os, random, sys
from datetime import datetime, timedelta
from django.conf import settings
from django.core import serializers
from django.core.exceptions import SuspiciousOperation
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
from django.utils.hashcompat import md5_constructor
from pymongo.objectid import ObjectId
from django.conf import settings
from pymongo import Connection
import pymongo.errors

try:
    import cPickle as pickle
except ImportError:
    import pickle


class CacheClass(BaseCache):
    def __init__(self, server, params):
        BaseCache.__init__(self, params)
        self._connection = Connection(server.split(':')[0], int(server.split(':')[1]))
        self.db = self._connection[params['db']] if self._connection else None
        self._cache = self.db.caches

    def get(self, key, default=None):
        val = self._cache.find_one({
            '_id': key,
            'expire_date': {'$gt': time.time()}})
        if val is None:
            return default
        # return  pickle.loads(str(val['cache_data']))
        return json.loads(str(val['cache_data']))

    def _set(self, key, value, timeout=0):
        timeout += int(time.time())

        obj = {
            '_id': key,
            # 'cache_data': unicode(pickle.dumps(value)),
            'cache_data': json.dumps(value),
            'expire_date': timeout
        }
        try:
            return self._cache.save(obj, safe=True)
        except pymongo.errors.OperationFailure, e:
            raise

    # 保存单记录缓存
    def pset(self, key, value, timeout=0):
        timeout += int(time.time())

        obj = {
            '_id': key,
            'cache_data': unicode(pickle.dumps(value)),
            'expire_date': timeout
        }
        try:
            return self._cache.save(obj, safe=True)
        except pymongo.errors.OperationFailure, e:
            raise

    # 取出单记录缓存
    def pget(self, key, default=None):
        val = self._cache.find_one({
            '_id': key,
            'expire_date': {'$gt': time.time()}})
        if val is None:
            return default
        return pickle.loads(str(val['cache_data']))

    # 序列化以后存入缓存
    def serset(self, key, value, timeout=0):
        data = serializers.serialize('python', value)
        return self._set(key, data, timeout)

    def add(self, key, value, timeout=0):
        return self._set(key, value, timeout)

    def set(self, key, value, timeout=0):
        return self._set(key, value, timeout)

    def delete(self, key):
        self._cache.remove({'_id': key})

    def close(self, **kwargs):
        self._connection.disconnect()

    def clear(self):
        self.db.drop_collection('caches')
View Code

 

posted @ 2019-01-04 12:31  十七楼的羊  阅读(483)  评论(0编辑  收藏  举报