蓝盟IT外包,徒手写Redis服务器(Godis  )

发布者:上海IT外包来源:http://www.lanmon.net点击数:1412

蓝盟IT小贴士,来喽!
您可能不需要自己实现Redis服务,但是您是否厌倦了每天编写添加、删除和更改的业务代码? 你注意到为了提高编程水平,从零开始写项目,想打开IDE,却无能为力吗?
制造车轮一定是提高编程能力的好方法。 建议使用Go从头开始写Redis服务器(Godis  )。 你可以从那里学习。
如何创建Go语言TCP服务器
设计并实现安全可靠的通信协议(redis协议)
如何使用Go语言开发高并发程序
分布式集群和分布式事务的设计与实现
熟悉链表、散列表、跳转表、时间轮等常用数据结构
不要担心内容太难,Go语言基础不行或者没有! 虽然示例代码是Go,但并不影响您了解Redis的原理、基本协议以及高性能的秘密。 并且作者为了照顾广大读者,优化了技术说明。 示例代码基于原始项目进行了简化,并每行进行了注释。 如果您是高级玩家,请直接访问项目以阅读源代码:
https://github.com/HDT3213/godis
从正文开始,一起拨开Redis的雾吧。
一、写TCP服务器
众所周知的Redis是一种C/S模型,它使用TCP协议进行通信。 接下来,我们从实现TCP服务端开始。 作为在服务端广泛使用的编程语言,Golang提供了非常简洁的TCP接口,因此实现起来非常方便。
:ok_hand:到目前为止已经用40行代码搞定了服务端。 启动上面的TCP服务后,在终端输入telnet  127.0.0.1 8000,就可以连接刚写的服务器了。 那个就照你发的信息回去
这个TCP服务器非常简单,主协调员调用accept函数来接收端口,接受新连接后,打开Goroutine进行处理。 这个简单的块IO模型类似于早期的Tomcat/Apache服务器。
块IO模型使用线程处理一个连接,并在未接收到新数据时在接收线程被阻止的情况下启动线程进行处理,直到数据被准备好。 由于要屏蔽IO模型,需要打开大量的线程,并经常进行上下文切换,因此效率很低。 另一方面,Redis使用的epoll技术(IO复用)在一个线程中处理了大量的连接,大大提高了吞吐量。 那么,我们的TCP服务器会比Redis慢很多吗?不,当然,Golang利用Goroutine调度开销远远小于线程调度开销的优点,实现了goroutine-per-connectIOn风格的非常简单的互联tcp库将epoll封装为阻止io,从而在享受epoll高性能的同时避免了本机epoll接口所需的复杂异步代码。
在作者的电脑上,Redis能够响应每秒10.6k个PING命令,但Godis  (完整代码)的吞吐量与9.2 kqps相差无几。 要了解更多有关Golang高性能:secret:密集版的信息,请搜索go  netpoller或go语言网络轮询关键字
此外,合格的TCP服务器不会在关闭时停止,而是需要完成对接收到的请求进行响应、释放TCP连接等所需的清理工作。 此功能通常称为优雅关闭或graceful  shutdown,它是一种优雅关闭步骤:
首先,关闭监听器并停止接受新连接
然后,逐一关闭所有存活的连接
因为优雅地关闭代码很多,所以这里有不完整的帖子。
二、透视Redis协议
解决通信后,下一步是弄清Redis的协议。 其实序列化协议的集合类似于JSON、协议缓冲器。 基础其实是基础知识。
Redis  2.0以后的通信统一为RESP协议(REdis  Serialization  Protocol  ),该协议不仅可以被有效地解析为程序,而且易于人类阅读和调试。
RESP是一种二进制安全文本协议,通过TCP协议运行。 RESP是逐行的,客户端和服务器发送的命令或数据始终使用\r\n(crlf  )作为换行符。
二进制安全性是指允许在不干扰协议的情况下显示任何字符。 例如,c语言字符串以\0结尾,不能在字符串中间包含\0。 另一方面,Go语言的string可以加入\0。 Go语言字符串是二进制的,是安全的,而c语言字符串是二进制的,是不安全的。
在RESP二进制安全中,密钥或值可以包含特殊字符,如\r或\n。 使用Redis存储二进制数据(如protobuf和msgpack  )时,二进制安全性尤为重要。RESP定义了五种格式。
简单字符串(Simple  String  ) )服务器用于返回“确定”等简单结果,二进制以外的安全性不允许换行
错误信息(Error  ) )服务器返回简单的错误信息,如“ERR  Invalid  Synatx”的非二进制安全性,不允许换行
整数(Integer  ):llen、scard等命令的返回值。 64位有符号整数
字符串(Bulk  String  ) :二进制安全字符串,如get命令的返回值
数组(Array,也称为Multi  Bulk  Strings  () Bulk  String数组,是客户端发送命令或对lrange等命令做出响应的格式
三、实现内存数据库
数据的接收和分析部分到此结束。 接下来是数据应该存在于哪里。
忽略持久化部分,基于内存的KV数据库Redis中的所有数据都必须存储在内存中的散列表中,该散列表是今天需要创建的最后一个组件。
与单线程的Redis不同,我们实现的Redis(Godis  )是并行工作的,所以我们必须考虑各种各样的并发安全问题。 有几种常见的并发安全散列表设计。
sync.map  :Golang官方提供的并发散列表适合阅读读写少的场景。 但是,在m.dirty升级之后,立即将m.read复制到新的m.dirty中。 如果数据量很大,拷贝操作会阻止所有的协作,存在很大的风险。
juc.ConcurrentHashMap  :Java的并发散列表是用分段锁实现的。 如果在扩展期间访问哈希表线程,rehash操作将变得更加容易,并且所有读写操作将在rehash结束之前被阻止。 由于缓存数据库中的键值对数量较多,对读写操作的响应时间也较高,因此不适合使用juc策略。
memcached  hashtable  :在后台线程上执行rehash操作时,主线程将确定要访问的哈希槽是否已rehash,然后选择old_hashtable和new_hashtable  该设计被称为渐进式rehash,具有rehash操作几乎不会阻塞主线程的读写的优点,是一种理想的方案。
但是,由于渐进式rehash的实现非常复杂,godis采用了Golang社区广泛使用的分段锁定策略(除了上面三个以外)。 也就是说,将key分散到一定数量的shard中,避免整体的rehash操作。 shard是受锁定保护的贴图,当shard进行渲染时,会阻止shard中的读写,但不会影响其他shard。
文/上海蓝盟  IT外包专家
IT外包
>
400-635-8089
立即
咨询
电话咨询
服务热线
400-635-8089
微信咨询
微信咨询
微信咨询
公众号
公众号
公众号
返回顶部