分布式缓存原理及Go实现 - 第一章
第一章:基于HTTP的内存缓存服务
缓存的概念:缓存(cache),指访问速度比一般的随机访问存储器(RAM)快的一种告诉存储器。通常不像系统主存那样使用DRAM(动态存储器)技术,而是使用更昂贵但是更快的SRAM(静态存储器)技术。
存储是非易失的,而缓存是允许丢失的。
RAM:随机存储器,也叫主存,可以快速的进行读写,但是断电后RAM不能保存数据。
ROM:只读存储器,工作过程中只能读出,不能像RAM一样快速的改写,ROM所存数据稳定,断电后数据仍能保存。
缓存服务放在其他服务的前端,只有当客户端需要的数据不存在与缓存中时,才去访问实际的服务。目前业界使用较多的缓存服务有Memcached和Redis等。他们都是内存内缓存,单结点最大的容量不能超过整个系统的内存。
分布式缓存集群的好处:单结点的扩展性,性价比,容错率都低于分布式缓存集群。
数据持久化:通俗的讲就是瞬时数据(比如内存中的数据,不能永久保存)持久化为持久数据。
第一章:基于HTTP的内存缓存服务
1.1 缓存服务的接口
利用 HTTP/REST 协议的 GET/PUT/DELETE 方法实现缓存的 Get/Set/Del 基本操作
Set操作用于将一对键值对设置进缓存服务器,通过HTTP的Put方法进行。
Get操作用于查询某个键并获取其值,通过HTTP的Get方法进行。
Del操作用于在缓存中删除某个键,通过HTTP的Delete方法进行。
1.2 Go语言实现
目录结构:
1 | server |
1.2.1 main包实现
1 | package main |
1.2.2 cache包实现
- Cache接口实现
1 | //首先定义了一个cache包接口: |
Go语言中,接口和声明是完全分开的,接口有其自己的类型interface,但是只定义接口是没用的,还需要实现接口。其中Stat的实现如下:
- Stat结构体的实现
1 | //Stat结构体的实现如下: |
- New函数实现
1 | //New函数用于创建并返回一个Cache接口 |
- inMemoryCache实现
1 | //这部分主要负责cache的set,get,del函数,以及获取缓存状态新建端口的函数的具体实现 |
Go语言的map支持多个goroutine同时读,但不支持多个goroutine同时写或者同时既读又写
相关的概念可以参考:
高并发架构系列:什么是分布式锁?Redis实现分布式锁详解 - 哔哩哔哩 (bilibili.com)
扩展内容:Go语言的互斥锁和读写锁:
Go 互斥锁(sync.Mutex)和 读写锁(sync.RWMutex) - kaichenkai - 博客园 (cnblogs.com)
什么时候需要用到锁?
当程序中就一个线程的时候,是不需要加锁的,但是通常实际的代码不会只是单线程,所以这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢?
- 多个线程在读相同的数据时
- 多个线程在写相同的数据时
同一个资源,有读又有写
互斥锁:互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问到共享资源(同一个时刻只有一个线程能够拿到锁)
在读多写少的环境中,可以优先使用读写锁(sync.RWMutex),它比互斥锁更加高效。sync 包中的 RWMutex 提供了读写互斥锁的封装
读写锁分为:读锁和写锁
- 如果设置了一个写锁,那么其它读的线程以及写的线程都拿不到锁,这个时候,与互斥锁的功能相同
- 如果设置了一个读锁,那么其它写的线程是拿不到锁的,但是其它读的线程是可以拿到锁
上面的inMemoryCache的例子用到了读写锁,也证明的了Go语言的map支持多个goroutine同时读,但是不支持多个goroutine同时写或者既读又写。
1.2.3 HTTP包的实现
- Server相关实现
1 | package http |
- cacheHandler的实现
1 | package http |
Go标准包中net/http的Handler接口定义如下:
1 | type Handler interface{ |
ServeHTTP方法解析URL获得key,并根据HTTP请求决定调用Cache.cache的哪种方法。这里用到了Go语言的多重内嵌;cacheHandler内嵌了Server结构体指针,Server内嵌了cache.Cache接口,于是cacheHandler就能直接访问cache.Cache的方法了。
3.statusHandler的实现
1 | package http |
1.3 功能测试
首先通过go mod init go-implement-your-cache-server-master
可以将项目文件夹设置为路径文件,这样在go-implement-your-cache-server-master
中会生成一个go.mod
文件。
注意在vscode
中使用文件的相对路径会有报错,将路径改为相对于go-implement-your-cache-server-master
的路径,如"go-implement-your-cache-server-master/chapter1/server/cache"
实验结果:
可以看到实现了从客户端到内存的增删查操作。
1.4 与Redis比较
1.4.1 Redis介绍
数据持久化:通俗的讲就是瞬时数据(比如内存中的数据,不能永久保存)持久化为持久数据。
Redis的两种磁盘持久化方案:RDB (Redis Database) 和 AOF (Append Only File);
RDB的持久化原理:会在特定的时间将内存的数据进行快照然后存入磁盘。当RDB开始工作时,Redis
服务会将自己分叉出一个持久化进程,持久化进程负责写入磁盘。S此时原服务进程的一切内存数据相当于被保存了一份快照,持久化进程和原服务进程共享同一批虚拟内存页。但是如果这段时间有Set
操作,就意味着原服务内存有一部分需要修改,这时候虚拟内存页会进行一个写时复制的操作,确保持久化进程的数据不被更新。
AOF的持久化原理:将服务端接到的所有写操作(Set 和 Del)计入磁盘的日志文件,重新开机时,Redis会重放日志文件来重新构建整个内存映像。
RDB和AOF相比性能更好,占用的磁盘更少,对Redis的性能影响更小,速度更快;但是在上次持久化到关机这段时间可能丢失数据,AOF可以保存到上次写数据。
1.4.2 redis-benchmark
介绍
redis-benchmark
是Redis
服务自带的性能测试工具。
下载和安装参考:windows下安装Redis并部署成服务 - 丁国丰 - 博客园 (cnblogs.com)
Releases · microsoftarchive/redis (github.com)
在上面的地址下载并解压安装包
./redis-server
启动Redis
然后通过redis-benchmark -c 1 -n 100000 -d 1000 -t set,get -r 100000
检测redis
的性能如下:
go get
命令:一键获取代码、编译并安装(因为网络的原因,这个过程经常会失败)
1.4.3 cache-benchmark
介绍
cache-benchmark
用来测试我们自己的cache的性能
go build
会执行源码,并输出可执行文件
注意在检测性能时必须先启动1.2中提到的./server
,这样cache-benchmark
才能找到本机的缓存服务,这样就可以检测自己的缓存的性能了。