分布式缓存:

项目中缓存是如何使用的?
为什么要使用缓存?
主要俩用途,高性能、高并发
高性能:处理在大量复杂查询数据库比较耗时的结果,将查询到的数据缓存到redis中,后面请求都使用缓存中的数据即可,大量提升用户体验
image.png
高并发:在高并发的场景下mysql 支持不了那么大的qps,可以把很多数据放在缓存中,少量放在mysq中
image.png
缓存使用不当会造成什么结果?(常见的缓存问题)

  • 缓存与数据库双写不一致
  • 缓存雪崩
  • 缓存穿透
  • 缓存并发竞争

redis线程模型,为啥单线程还能有很高的效率?

纯内存操作、核心是基于非阻塞的IO多路复用机制epoll、单线程可以避免上限文切换问题
线程模型:单线程 nio同步非阻塞 线程模型
Reactor线程负责多路分离套接字,accept新连接,并分派请求到handler。Redis使用单Reactor单线程的模型

image.png

image.png

redis 与 memcached 主要区别

  1. redis 的 数据类型更多,支持的功能更多
  2. redis支持原生集群cluster模式,memcached不支持,需要客户端实现往集群分片中写入数据

redis都有哪些数据类型?分别在哪些场景下使用比较合适?

  1. string:key-value
  2. hash: 类似map一种结构、可以将结构化数据放在缓存里,每次读写缓存可以操作hash里的某个字段

    1. key=order:12345678
    2. value = {
    3. "name":haha,
    4. "city":北京
    5. }
  3. list:有序列表

可以存储一些列表形式的数据、例如粉丝列表、文章评论列表 等

  1. list = [zhangsan,lisi,wangwu,zhaoliu,zhengqi]
  2. 通过lrange命令可以实现对粉丝的高性能分页查询
  3. 也可以做类似微博下拉不断分页的东西
  4. 也可以搞个简单的消息队列,头进 尾出
  1. set:无序集合、自动去重

对于某些数据进行全局去重,需要扔进redis 的set里面
也可以基于set 使用交集、并集、差集的操作,可以将两个人的粉丝好友整一个交集、查看共同好友

  1. sorted set:排序set、根据塞进去的分数进行排序 ,可以获取排名等

例如排行榜:

  1. zadd board score username
  2. zrevrange board 0 99 获取前100名用户
  3. zrank borad username 可以查询到用户排名
  4. zadd board 85 li1
  5. zadd board 86 li2
  6. zadd board 87 li3
  7. zadd board 88 li4

redis 过期策略,内存淘汰机制,手写LRU

  1. 过期策略:
    • 定期删除+过期删除
  2. 内存淘汰机制:
  3. 手写LRU:Least Recently Used

实现原理:一个双向链表+字典 (保证插入的有序(或说是借助列表来记录插入的顺序))
实现方式1:通过collections.OrderedDict类来实现,首先要说明的是OrderedDict是在普通字典的方法保证了插入的有序,同时要强调的是这个类还有一个特殊的方法popitem(Last=False),当Last参数为False时,说明其是以队列先进先出方式弹出第一个插入字典的键值对,而当Last参数为True时,则是以堆栈方式弹出键值对。
实现方式2:借助于普通dict和list来实现,其实就是自己来实现一个OrdereDict,保证插入的有序(或说是借助列表来记录插入的顺序)

  1. from collections import OrderedDict
  2. class LRUCache(OrderedDict):
  3. def __init__(self, size):
  4. self.size = size
  5. self.cache = OrderedDict()
  6. def get(self, key):
  7. if key in self.cache:
  8. value = self.cache.pop(key)
  9. self.cache[key] = value
  10. return value
  11. else:
  12. return None
  13. def set(self, key, value):
  14. if key in self.cache:
  15. self.cache.pop(key)
  16. self.cache[key] = value
  17. elif self.size == len(self.cache):
  18. self.cache.popitem(last=False) # if last = false, FIFO order pop
  19. self.cache[key] = value
  20. else:
  21. self.cache[key] = value
  22. lru_test = LRUCache(5)
  23. lru_test.set("ha1",11)
  24. lru_test.set("ha2",12)
  25. lru_test.set("ha3",13)
  26. lru_test.set("ha4",14)
  27. lru_test.set("ha5",15)
  28. print(lru_test.cache.items())
  29. lru_test.get("ha2")
  30. print(lru_test.cache.items())
  31. lru_test.set("ha6",16)
  32. print(lru_test.cache.items())
  33. odict_items([('ha1', 11), ('ha2', 12), ('ha3', 13), ('ha4', 14), ('ha5', 15)])
  34. odict_items([('ha1', 11), ('ha3', 13), ('ha4', 14), ('ha5', 15), ('ha2', 12)])
  35. odict_items([('ha3', 13), ('ha4', 14), ('ha5', 15), ('ha2', 12), ('ha6', 16)])
  1. # 基于普通dict和list实现
  2. class LRUCache:
  3. def __init__(self, size=5):
  4. self.size = size
  5. self.cache = {}
  6. self.key = []
  7. def get(self, key):
  8. if key in self.cache: # 获取k-v,如果key存在,则将key放到前面,并返回v
  9. self.key.remove(key)
  10. self.key.insert(0, key)
  11. return self.cache[key]
  12. else:
  13. return None
  14. def set(self, key, value):
  15. if key in self.cache: # key 在缓存中,更新k-v,删除list中的该key,并插入list的头部
  16. self.cache.pop(key)
  17. self.cache[key] = value
  18. self.key.remove(key)
  19. self.key.insert(0, key)
  20. elif len(self.cache) == self.size: # 如果当前缓存容量 等于 给定容量,则删除old_key,然后插入新k-v
  21. old_key = self.key.pop() # 删除list最后一个key
  22. self.cache.pop(old_key) # 删除字典中得key
  23. self.key.insert(0, key)
  24. self.cache[key] = value
  25. else: # key不存在,则直接存放
  26. self.cache[key] = value
  27. self.key.insert(0, key)
  28. lru_test = LRUCache(5)
  29. lru_test.set("ha1", 11)
  30. lru_test.set("ha2", 12)
  31. lru_test.set("ha3", 13)
  32. lru_test.set("ha4", 14)
  33. lru_test.set("ha5", 15)
  34. print(lru_test.key)
  35. lru_test.get("ha2")
  36. print(lru_test.key)
  37. lru_test.set("ha6", 16)
  38. print(lru_test.key)
  39. ['ha5', 'ha4', 'ha3', 'ha2', 'ha1']
  40. ['ha2', 'ha5', 'ha4', 'ha3', 'ha1']
  41. ['ha6', 'ha2', 'ha5', 'ha4', 'ha3']

redis如何通过读写分离承载读请求qps超过10W+?

redis单机高并发请求也就达到几万,当10w+打到单机上肯定会挂掉的
解决方法:主从架构读写分离,对缓存一般都是用来支持读高并发的,写比较少
主从架构:一主多从,主写从读,主将数据同步复制到从节点,所有读请求全部走从节点,写请求写master
每台从机器可以承载几万,多个从机器就可以承载几十万,可以水平扩容,增加机器就可以应对更高的并发
主从架构->读写分离-> 支撑10W+读qps请求

redis replication 复制以及master持久化对主从架构的安全意义?

master一定要开持久化rdb和aof,否则一旦宕机,所有数据清空丢失

redis 主从复制原理


断点续传

master里面有一个backlog ,master和slave里面都有保存一个复制的offset,如果没有记录offset则就传一遍全量,有的话,就从上一次的offset处进行开始同步

无磁盘化复制

就是master向slave同步数据的时候,要不要写rdb文件,默认是无磁盘化的,就是写在内存中的,直接发送给slave,也可以打开,就是先写rdb再发给slave

过期key处理

slave是没有key过期自动处理机制的,依靠master的key过期后发送给slave对应的删除命令

redis 高可用:

使用 哨兵机制 建立主从同步集群从切换 实现redis 高可用
sentinal 哨兵
哨兵是redis集群架构中非常重要的组件

  1. 集群监控:负责监控redis maser和slave进程是否正常工作
  2. 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
  3. 故障转移:如果master node 挂掉了,会自动转移到slave node上
  4. 配置中心,如果故障转移发生了,通知client客户端新的master地址