WEB系统中各参与者都可以进行缓存。数据库缓存,应用服务器缓存,Web服务器缓存,客户端浏览器缓存。
java缓存级别:
堆缓存
堆外缓存
磁盘缓存
分布式缓存
典型java服务器端缓存系统:
1、接入nginx负载均衡到应用nginx。
2、应用nginx读取本地缓存(Nginx Proxy Cache或者Local Redis缓存)。如果命中,则直接返回。后续步骤中也是类似不再赘述。
3、读取从redis服务器缓存。OpenResty的lua-resty-redis模块可以使Nginx具有了直接读取redis缓存的能力,不占用tomcat的线程。
3、回源到Tomcat集群。
4、Tomcat应用中,读取本地缓存。
6、回源到数据库集群。
7、将数据库返回的数据以异步方式写到主redis服务器。
有缓存时的查询流程:
应用服务器查询缓存。如果查到缓存服务器,则使用。如果没有查到缓存,则查询数据库。数据库返回结果后,应用服务器写缓存服务器,内容为key-value对。
缓存使用模式:
Cache-Aside:查不到缓存时,由业务代码回源SoR(source of record)
Cache-As-SoR:所有操作都是对cache进行,Cache进行SoR回源。
缓存淘汰or更新
淘汰缓存只是增加了一次cache miss。更新缓存虽然避免了cache miss,但是成本更高。所以通常淘汰缓存而不是更新。
数据库和缓存的一致性:
方式1:先更新数据库,然后同步淘汰缓存。优点:数据不一致性时间短。缺点:请求处理时间长。例如:使用mysql memcached UDF(用户自定义函数)和mysql触发器在更新mysql数据库的时候,触发淘汰cache。这样可以减少应用程序的逻辑。也可以保证事务的完整性。
方式2:先更新数据库,然后异步淘汰缓存。优点:请求处理时间短。缺点:数据不一致时间长。例如:可以使用canal,模拟mysql slave和master的同步机制,监控mysql binlog来触发redis缓存的淘汰。
清除缓存
基于时间清除缓存:TTL和TTI(Time to Idle)
缓存满的含义:缓存超过设置的存储空间或者超过设置的条目数。
缓存满了,使用LRU机制驱逐老的缓存。热点缓存,要多存储几份,也可以在应用系统中进行几秒钟的本地缓存,从而降低远程缓存系统的压力。
除了LRU还有FIFO和LFU(Least Frequently used)
缓存相关问题:
缓存穿透:查询不存在的id,每次都会访问DB。如果id不存在,则内容设置为null,并且设置缓存的失效时间。
缓存雪崩:缓存集中过期失效(比如双11活动在0点开始,1点缓存集中失效)。缓存失效时间加一个随机因子,防止同时失效。可以在缓存失效前,主动通知后台线程更新缓存。
缓存击穿:一个key非常热点(爆款产品),这个key失效瞬间,导致大量数据库请求。对一个key只允许一个线程查询数据库和写缓存,其他线程等待。
缓存预热:缓存预热就是系统上线后,提前将相关的热点数据加载到缓存系统。避免在用户请求的时候,再查询数据库。
Redis:
redis是数据结构服务器,所以重点就是它支持的数据结构。使用命令来操作数据结构。最常用的命令是set()和get()。
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库、高速缓存和消息队列。Key-Value中值(value)可以是字符串、列表、集合、有序集合、哈希、位图、hyperloglog(用于快速计算基数(一个数据集中不同元素的数目))、流等数据类型。内置Lua脚本、LRU机制、事务以及不同级别磁盘持久化功能。Redis支持master-slave模式的数据备份。可以通过Redis Sentinel(哨兵)提供高可用功能,发现主服务器崩溃后,自动选举新的主服务器,并且通知应用服务器。也可以通过Redis Cluster提供集群功能。
Redis使用ANSI C语言编写,提供多种语言的API,包括:c、C++、C#、php、java、python、go等语言。
Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
Redis支持 publish/subscribe模式,所以可以用作消息队列。
Redis 支持管道技术。允许服务端未响应时,客户端继续向服务端发送其它请求,并最终一次性读取所有服务端的响应。这提高了性能。
Twemproxy可以作为redis的代理。这样可以减少和后台缓存服务器的连接数。也可以用于实现redis集群。Twemproxy支持多种hash算法。支持失败节点自动删除。后端sharding(分片)逻辑对业务透明。
Redis集群也可以不使用Twemproxy,直接使用redis-cluster特性。Redis-Cluster采用无中心结构。健壮性更高。
数据按照hash slot存储分布在多个节点。
每个节点保存整个集群状态,每个节点(包括slave节点)都和其他所有节点连接。
每个主节点有若干个slave节点。实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升。这类似于简化版的OSPF DR选举。但是又不同,从节点发现主节点down了,从节点会广播request消息,要求成为主节点,其它从节点会如果同意则返回ack,如果选票过半,则当选。gossip协议用于P2P网络中,传播消息的方式类似于OSPF的flood机制。
可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
高可用性,部分节点不可用时,集群仍可用。
应用服务器可以访问任何一个主节点,如果数据不在这个主节点上,它会返回一个moved重定向消息,告诉应用服务器应该访问哪个主节点。可以使用JedisPool,它将cluster slots的结果保存在本地,分配请求。
主从同步:
Redis全量复制通常发生在slave初始化阶段。之后就是增量赋值。
全量复制时,主服务器收到从服务器的SYNC命令后会生成RDB(Redis DataBase)快照文件。同时在缓冲区中记录此后执行的命令。将RDB文件发送给slave之后,还要将缓冲区中的命令发送给slave。之后主服务器执行写命令时就会向从服务器发送相同的写命令。
这和ospf很类似。但是ospf显然更先进,它会比较双方的LSDB,只请求缺失的部分。
主从同步还可以使用AOF的方式,它的基本原理是从服务器回放主服务器执行过的命令。
redis cluster集群中通过消息来进行通信。消息共有以下5种。
meet消息:加入到集群中。
ping消息:检测目标节点是否处于在线状态。
pong消息:收到到meet消息或者ping消息后,会回复pong消息。另外,pong消息也可以用于主动更新,例如故障转移后,新的主节点会向集群中发送pong消息,用于告知它已经升级为主节点。
fail消息:fail消息用于通知某个节点掉线了。例如节点A认为节点B已下线(B一段时间内都没有回复pong消息),节点A会向集群中发送一条fail消息,接收到这条消息的节点会将节点B标记为疑似下线。当大于等于半数的节点认为B下线了,这个节点就被标记为下线。
Redis vs Memcache
有持久化需求、对数据结构要求高、对处理有高级要求的应用,选择redis。简单的key/value存储,选择memcache。Redis只支持linux。Memcache支持windows、linux。
Redis支持简单的事务,只是一次性按顺序执行多条命令。但是不支持回滚,即有命令出错,也会继续执行。Memcache不支持事务。
Redis是单线程的。Memcache是多线程的。
Redis没有自己的内存管理模块。Memcache有自己的内存管理模块,内存分配效率更高。