redis缓存优化、分布式id、qps、tps、分布式锁

缓存优化

缓存更新策略

缓存更新策略,redis是缓存数据库,数据存在内存中;随着所占内存越来越大,内存不够用了,就需要更新策略,剔除一部分数据

LRU/LFU/FIFO算法剔除:例如maxmemory-policy(到了最大内存,对应的应对策略)

LRU  -Least Recently Used,没有被使用时间最长的
LFU  -Least Frequenty User,一定时间段内使用次数最少的
FIFO -First In First Out,先进先出
LIRS (Low Inter-reference Recency Set)是一个页替换算法,相比于LRU(Least Recently Used)和很多其他的替换算法,LIRS具有较高的性能。这是通过使用两次访问同一页之间的距离(本距离指中间被访问了多少非重复块)作为一种尺度去动态地访问页排序,从而去做一个替换的替换

缓存穿透、击穿、雪崩

缓存穿透

描述

缓存穿透是指缓存和数据库中都没有的数据,而用户不断的发起请求,如发起id为"-1"的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  3. 通过布隆过滤器实现:通过布隆过滤器避免缓存穿透;在mysql中有的记录,id放到布隆过滤器中,在查询数据库之前,先判断一下布隆过滤器中没有该id号,如果没有,说明数据库中也没有,不进行数据库查询,直接返回错误,如果布隆过滤器中有,再进行数据库查询
  4. 使用频率限制:ip

缓存击穿

描述

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增加,造成过大压力

解决方案

设置热点数据永不过期

缓存雪崩

描述

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
  3. 设置热点数据永远不过期

分布式id

订单号,如何生成的

项目集群化部署,不同机器上生成永不重复的id号

在分布式系统中,能够生成全局唯一的id,它就称之为分布式id(消息id,订单id号...)

分布式的id应该具备的特点(了解)

全局唯一性:不能出现重复的id号,既然是唯一标识,这是最基本的要求

趋势递增:在mysql的innodb引擎中使用的是聚集索引,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。

单调递增:保证下一个id一定大于上一个id,例如事务版本号、im增量消息、排序等特殊需求

信息安全:如果id是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定url即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要id无规则、不规则

具备以下几点要求

  1. 平均延迟和tp999延迟都要尽可能低(tp90就是满足百分之九十的网络请求所需要的最低耗时。tp99就是满足百分之99的网络请求所需要的最低耗时。同理tp999就是满足千分之九百九十九的网络请求所需要的最低耗时)。
  2. 可用性5个9(99.999%)
  3. 高qps:高的并发量

如何生成

  1. uuid:简单,没有递增趋势,存在重复的概率(订单号就是使用uuid);uuid重复的问题?没有遇到过,分布式id的生成方案:雪花算法
  2. 数据库自增:简单,性能低,有递增趋势
  3. redis生成id:单线程架构,incr自增数据,时间戳+自增;性能,趋势自增,性能高
  4. snowflake(雪花算法)方案[美团(Leaf)];这种方案大致来说是一种以划分命名空间(uuid也算,由于比较常见,所以单独分析)来生成id的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflask中的64-bit;qps约为409.6w/s
雪花算法优点:
  毫秒数在高位,自增序列在低位,整个id都是趋势递增的。
  不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成id的性能也是非常高的。
  可以根据自身业务特性分配bit位,非常灵活
  
缺点:
  强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态

python如何实现雪花算法

# pysnowflake  第三方模块
https://www.cnblogs.com/Blogwj123/p/16614304.html

补充:qps、tps

参考博客:https://www.cnblogs.com/liuqingzheng/p/16207660.html

qps

每秒查询率

tps

transactions per second,是每秒处理的事务数,包括一条消息入和一条消息出,加上一次用户数据库访问

一个事务是指一个客户端向服务器发送请求然后服务器做出反应的过程。客户端在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数

TPS的过程包括:客户端请求服务端、服务端内部处理、服务端返回客户端

例如:访问一个index页面会请求服务器3次,包括一次html,一次css,一次js,那么访问这一个页面就会产生一个t,产生三个q

并发量

系统同时处理的请求或者事务数,可以直接理解为:系统同时处理的请求数量

QPS=并发量/平均响应时间
并发量=QPS*平均响应时间

例如当前系统QPS为1w,每个请求的响应时间都是2s,那么并发量就是2w

吞吐量

吞吐量是指系统在单位时间内处理请求的数量,tps、qps都是吞吐量的常用量化指标

系统吞吐量要素

  • 一个系统的吞吐量(承压能力)与request(请求)对cpu的消耗,外部接口,io等等紧密相关
  • 单个request对cpu消耗越高,外部系统接口,io影响速度越慢,系统吞吐能力越低,反之越高。

PV

PV(Page View):页面访问量,即页面浏览器或点击量,用户每次刷新即被计算一次。可以统计服务一天的访问日志得到。

UV

UV(Unique Visitor):独立访客,统计一天内访问某站点的用户数。可以统计服务一天的访问日志并根据用户的唯一标识去重得到。

DAU(日活)

DAU(Daily Active User):日活跃用户数量。常用于反应网站、app、网游的运营情况

DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似

MAU(月活)

MAU(Month Active User):月活跃用户数量,指网站、app等去重后的月活跃用户数量

系统服务端性能影响的因素

衡量服务性能的指标,主要有两个:

QPS(Query Per Second):每秒请求数

响应时间(Response Time RT),它可以理解为服务器处理响应的耗时。

正常情况下,响应的时间越短,QPS则越高

在单线程的情况下,是呈线性关系。但也不是无限增长,RT总会有极限值

多线程时,总QPS=(1000ms/响应时间)* 线程数

分布式锁

参考:https://github.com/SPSCommerce/redlock-py

在分布式系统中,多个系统中的多条线程操作同一个变量,会出现错乱,需要加锁,锁要跨机器,不能使用普通互斥锁,这个锁称之为分布式锁

分布式锁要具备的条件

  1. 在分布式的系统环境中,一个方法在同一时间只能被一个机器的一个线程执行
  2. 高可用的获取锁和释放锁
  3. 高性能的获取锁和释放锁
  4. 具备可重入特性
  5. 具备锁失效机制,防止死锁
  6. 具备非阻塞锁特性,即没有获取到锁直接返回获取锁失败

实现分布式锁的方案

基于数据库实现分布式锁

for_update的悲观锁,性能低,一般不用它

基于缓存(Redis等)实现分布式锁(主流使用的,性能高)

python实现redis分布式锁

https://github.com/SPSCommerce/redlock-py

安装

pip3 install redlock-py

-安装二: 使用源码包安装
  	- 下载源码:源码中有 setup.py
    python3 setup.py install 
-安装方式三:使用whl文件安装
  	pip3 install  xx.whl

具体代码

from redlock import Redlock
import time
dlm = Redlock([{"host": "localhost", "port": 6379, "db": 0}, ])

# 获得锁
my_lock=dlm.lock("my_resource_name",1000)

# 业务逻辑代码
print('xunfei')
time.sleep(20)

# 释放锁
dlm.unlock(my_lock)

这个代码可以放在任意的节点上,使用的是分布式锁,某个节点获取到锁后,别的节点获取不到,操作数据,释放锁后,别的节点的线程才能操作数据

redis实现分布式锁(自己实现)

实现思想(setnx)

  1. 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的uuid,通过在此释放锁的时候进行判断
  2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁
  3. 释放锁的时候,通过uuid判断是不是该锁,若是该锁,则执行delete进行锁释放。
import redis
import uuid
import time

from threading import Thread,get_ident

# 连接redis
redis_client = redis.Redis(host="localhost",
                           port=6379,
                           # password=password,
                           db=10)


# 获取一个锁
# lock_name:锁定名称
# acquire_time: 客户端等待获取锁的时间
# time_out: 锁的超时时间
def acquire_lock(lock_name, acquire_time=10, time_out=10):
    """获取一个分布式锁"""
    identifier = str(uuid.uuid4())
    end = time.time() + acquire_time
    lock = "string:lock:" + lock_name
    while time.time() < end:
        if redis_client.setnx(lock, identifier):
            # 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
            redis_client.expire(lock, time_out)
            return identifier
        elif not redis_client.ttl(lock):
            redis_client.expire(lock, time_out)
        time.sleep(0.001)
    return False


# 释放一个锁
def release_lock(lock_name, identifier):
    """通用的锁释放函数"""
    lock = "string:lock:" + lock_name
    pip = redis_client.pipeline(True)
    while True:
        try:
            pip.watch(lock)
            lock_value = redis_client.get(lock)
            if not lock_value:
                return True

            if lock_value.decode() == identifier:
                pip.multi()
                pip.delete(lock)
                pip.execute()
                return True
            pip.unwatch()
            break
        except redis.excetions.WacthcError:
            pass
    return False


def seckill():
    identifier = acquire_lock('resource')
    print(get_ident(), "获得了锁")
    release_lock('resource', identifier)


if __name__ == '__main__':
    for i in range(50):
        t = Thread(target=seckill)
        t.start()


posted @ 2023-01-10 21:06  荀飞  阅读(555)  评论(0编辑  收藏  举报