服务负载较重时常用的两项技术,这两项技术都是非常强大的工具因而广为流行。

1.使用Memcached

       Memcached意为“内存缓存守护进程”。Memcached将安装它的服务器上的空闲RAM与一个很大的近期最少使用的缓存结合使用,但Memcached缺乏认证以及安全管制,这代表应该将Memcached服务器放置在防火墙后。如果想在Python中使用Memcached需在终端运行:

pip install python3-memcached

       服务器可以丢弃Memcached中的数据。Memcached实际上将重复计算花销较高的结果记录下来,以此来加速操作。下面的代码将展示Memcached的基本模式:在运行一个花销很大的整数平方操作前,代码首先会检查Memcached中是否已经保存了之前的计算过的答案。如果缓存中已经存在相应的答案,那么就不需要进行重复的计算,就能将答案返回。

#!/usr/bin/python
#coding:utf-8
import memcache,random,time,timeit

def compute_square(mc,n):
    value = mc.get('sq:%d' % n)
    if value is None:
        time.sleep(0.001)
        value = n * n
        mc.set('sq:%d' % n,value)
    return value

def main():
    mc = memcache.Client(['127.0.0.1:11211'])

    def make_request():

        compute_square(mc,random.randint(0,5000))

    print('Ten successive runs:')
    for i in range(1,11):
        print(' %.2fs' % timeit.timeit(make_request,number=2000),end='')
    print()

if __name__ == '__main__':
    main()

测试结果:

怎么通过队列来解决缓存穿透java 缓存和队列_消息队列


       可以看到程序刚开始计算某个数的平方时,RAM(随机存取存储器)缓存中并没有存储过该整数的平方,因此必须进行计算程序运行时间较长。但是随着程序的运行,就会开始不断遇到一些相同的整数进行,此时缓存中已经存储了一些整数的平方,所以加快了运行速度。但是当Memcached存满或所有可能输入都已经计算过之后,速度就不在变化了。

2.散列与分区

分区

#!/usr/bin/python
#coding:utf-8

import hashlib

def alpha_shard(word):

    if word[0] < 'g':
        return 'server0'
    if word[0] < 'n':
        return 'server1'
    if word[0] < 't':
        return 'server2'
    else:
        return 'server3'

def hash_shard(word):

    return 'server%d' % (hash(word) % 4)

def md5_shard(word):

    data = word.encode('utf-8')
    return 'server%d' % (hashlib.md5(data).digest()[-1] % 4)

if __name__ == '__main__':
    words = open('words.txt').read().split()
    for function in alpha_shard,hash_shard,md5_shard:
        d = {'server0':0,'server1':0,'server2':0,'server3':0}
        for word in words:
            d[function(word.lower())] += 1
        print(function.__name__[:-6])
        for key,value in sorted(d.items()):
            print("{} {} {:.2}".format(key,value,value / len(words)))
        print()

       第一个算法是将字母表分为4个大致平均的部分,并根据单词的首字母来分配键;另外两个算法使用了散列值。

怎么通过队列来解决缓存穿透java 缓存和队列_消息队列_02


       可以看到使用散列值进行查找的算法运行速度很快。

3.消息队列

“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

在python中使用消息队列

       下面的代码将使用蒙特卡洛方法(使用随机数或更常见的伪随机数来解决很多计算问题的方法)计算圆周率。下面是消息传递的拓扑:

怎么通过队列来解决缓存穿透java 缓存和队列_字符串_03


       bitsource生成的表示坐标轴的二进制字符串进行监听,always_yes监听模块只接受以00开始的字符串,然后直接生成结果Y,并推送给tally模块。judge模块会请求pythagoras模块计算两个整数坐标值的平方和,然后判断对应的点是否在第一象限的四分之一圆里,并根据结果奖T或F推送到输出队列中。

       tally模块接受由每个随机串生成的T或F,通过计算T的数量与T和F总数的比值,就能估算出圆周率。

代码:

#!/usr/bin/python
#coding:utf-8
import random, threading, time, zmq
B = 32  #每个随机整数的精度位数

def ones_and_zeros(digits):
    """以至少'd'个二进制数字表示'n',不带特殊前缀。lstrip() 方法用于截掉字符串左边的空格或指定字符。
     zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0。getrandbits(n)以长整型形
     式返回n个随机位(二进制数)"""
    return bin(random.getrandbits(digits)).lstrip('0b').zfill(digits)

def bitsource(zcontext, url):
    """在单位平方中生成随机点。"""
    zsock = zcontext.socket(zmq.PUB)
    zsock.bind(url)
    while True:
        zsock.send_string(ones_and_zeros(B * 2))
        time.sleep(0.01)

def always_yes(zcontext, in_url, out_url):
    """左下象限的坐标在单位圆内。"""
    isock = zcontext.socket(zmq.SUB)
    isock.connect(in_url)
    isock.setsockopt(zmq.SUBSCRIBE, b'00')
    osock = zcontext.socket(zmq.PUSH)
    osock.connect(out_url)
    while True:
        isock.recv_string()
        osock.send_string('Y')

def judge(zcontext, in_url, pythagoras_url, out_url):
    """确定每个输入坐标是否在单位圆内。"""
    isock = zcontext.socket(zmq.SUB)
    isock.connect(in_url)
    for prefix in b'01', b'10', b'11':
        isock.setsockopt(zmq.SUBSCRIBE, prefix)
    psock = zcontext.socket(zmq.REQ)
    psock.connect(pythagoras_url)
    osock = zcontext.socket(zmq.PUSH)
    osock.connect(out_url)
    unit = 2 ** (B * 2)
    while True:
        bits = isock.recv_string()
        n, m = int(bits[::2], 2), int(bits[1::2], 2)
        psock.send_json((n, m))
        sumsquares = psock.recv_json()
        osock.send_string('Y' if sumsquares < unit else 'N')

def pythagoras(zcontext, url):
    """返回数字序列的平方和"""
    zsock = zcontext.socket(zmq.REP)
    zsock.bind(url)
    while True:
        numbers = zsock.recv_json()
        zsock.send_json(sum(n * n for n in numbers))

def tally(zcontext, url):
    """计算单位圆内有多少点,然后打印p/q"""
    zsock = zcontext.socket(zmq.PULL)
    zsock.bind(url)
    p = q = 0
    while True:
        decision = zsock.recv_string()
        q += 1
        if decision == 'Y':
            p += 4
        print(decision, p / q)

#args中保存的是没有利用的所有多余参数,保存方式为元组
def start_thread(function, *args):
    thread = threading.Thread(target=function, args=args)
    thread.daemon = True  #你可以很容易地控制整个程序
    thread.start()

def main(zcontext):
    pubsub = 'tcp://127.0.0.1:6700'
    reqrep = 'tcp://127.0.0.1:6701'
    pushpull = 'tcp://127.0.0.1:6702'
    start_thread(bitsource, zcontext, pubsub)
    start_thread(always_yes, zcontext, pubsub, pushpull)
    start_thread(judge, zcontext, pubsub, reqrep, pushpull)
    start_thread(pythagoras, zcontext, reqrep)
    start_thread(tally, zcontext, pushpull)
    time.sleep(30)

if __name__ == '__main__':
    main(zmq.Context())

        PS:消息队列支持多种拓扑结构有管道,发布者-订阅者,请求-响应,示例代码采用发布者-订阅者这种模式。