如果业务进一步发展,通过"读写分离"、"分库分表"后,数据库的性能依然无法满足高并发读请求,此时就需要缓存出马了。
缓存的原理其实非常简单: 用"存取速度更快的空间"换取"存取速度更慢的时间"。
当然,天下没有免费的午餐,缓存自然也是有代价的。单位容量的内存比磁盘要昂的多。幸运的是,根据数据的局部性原理^2^ ,我们可以有如下策略:
- 只选择少量的"热数据"放入缓存
- 当缓存空间不够放置"热数据"时,根据策略替换掉缓存中的已有数据。
本节的主角是Memcached,一款高性能的内存缓存,性能可达每秒5万次^1^ 。
Memcached本身是不支持集群的,但可以通常可以部署多台服务。在访问时,可以根据key的哈希值取模进行分片,然后访问不同的Memcached结点。
集群搭建
由于Memcached是全内存的,所以无需创建Volume挂载点。
在这里,我们没有使用Deployment,而是使用了StatefulSet。
StatefulSet与Deployment基本相同,唯一的的区别是,前者认为所有副本是相互独立的,而后者认为所有副本是互为冗余的。
对于微服务的应用场景,每个节点都是完全相同的逻辑、连接完全相同的数据库、执行等同的操作,所以我们用的一直是Deployment。
而对于Memcached,我们会将不同数据分片到不同Memcached结点上,他们是相互独立而不是可替代的,所以我们采用了StatefulSet。
memcached-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: memcached
spec:
ports:
- port: 11211
selector:
app: memcached
clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: memcached
spec:
selector:
matchLabels:
app: memcached
serviceName: "memcached"
replicas: 2
template:
metadata:
labels:
app: memcached
spec:
restartPolicy: Always
hostname: memcached
containers:
- name: memcached-ct
image: memcached:1.5-alpine
ports:
- containerPort: 11211
args: ["memcached", "-m", "256"]
简单说明下:
- 我们声明了StatefulSet为Memcached,2个独立节点
- 限定了内存使用为256m
启动下:
kubectl apply -f memcached-service.yaml
然后我们登录一个额外的docker上,尝试ping一下,是可以的,说明启动成功:
ping memcached-0.memcached
PING memcached1 (172.17.0.8): 56 data bytes
64 bytes from 172.17.0.8: seq=0 ttl=64 time=1.014 ms
64 bytes from 172.17.0.8: seq=1 ttl=64 time=0.138 ms
64 bytes from 172.17.0.8: seq=2 ttl=64 time=0.134 ms
ping memcached-1.memcached
PING memcached2 (172.17.0.9): 56 data bytes
64 bytes from 172.17.0.9: seq=0 ttl=64 time=0.076 ms
64 bytes from 172.17.0.9: seq=1 ttl=64 time=0.123 ms
64 bytes from 172.17.0.9: seq=2 ttl=64 time=0.119 ms
注意上面对不同节点的DNS域名为"statefulName-x"."serviceName"
Memcached的配置看起来很简单,但是分片策略还需要进一步思考。
例如,前面提到了利用哈希值取模,可以实现Memcahced在客户端的分片。按照此策略,如果现在要增加一台机器到3台,计算取模的值将发生变化,缓存上的所有的数据都需要清空重新分片。
这种"推倒重来"的策略看起来简单,但可能会导致缓存击穿甚至造成线上故障。
想解决这类问题,可以采用一致性哈系,它可以尽可能地减少机器变动后,造成的数据重分布。
Memcached的日常运维比较简单,常见的操作就是清空全部缓存,可以通过nc指令来完成:
echo 'flush_all' | nc memcached1 11211
OK
提醒一下,线上执行清空操作要非常谨慎,若系统性能严重依赖缓存,那么清空操作往往会导致缓存击穿并造成系统故障。
小结
本节,我们使用StatefulSet,完成了Memcached集群的运维,并介绍了Memcahced集群运维中常见的问题。
^1^ . Memcached性能评测数据
^2^ . 分为空间局部性和时间局部性,可参考局部性原理浅析
下一节:前面已经提到,缓存是快速提升系统性能,缓解瓶颈的有效手段。
缓存的种类多种多样,小到CPU的缓存,大到静态生成的页面缓存。在本小节中,我们主要讨论在Spring Boot中整合如下两种缓存:
本地缓存: 在内存中开辟一小块空间,用于缓存,速度很快,但容量受限。我们采用Gruva中的缓存实现。
网络缓存: 同一微服务的不同节点间,通过网络共享,例如Memcached。