给定一系列的随机证书,记录下低位连续零位的最大长度k,通过k值可以估算出随机数的数量。
在Redis的HyperLogLog实现中用到了16384个桶,即2^14,每个桶maxbits需要6个bits来存储,最大可以表示maxbits=63,总占用内存就是2^14*6/8=12k字节。
布隆过滤器可以理解为一个不绝对精确的set结构,当你使用它的contains方法判断某个对象是否存在时,它可能会误判。
每个布隆过滤器对应到Redis的数据结构里面就是一个大型的位数组和几个不一样的无偏hash函数,即能把元素的hash算得比较均匀。
向布隆过滤器中添加key时,会使用多个hash函数对key进行hash算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个hash函数都会算出一个位置,然后再把这几个位置都设置为1就完成了add操作。
查询key是否存在时,只需要把hash的几个位置算出来,看看这几个位置是否都为1,只要有一个0就说明不存在。
可以利用Redis的地理位置GEO模块实现摩拜单车附近的Mobike类似的功能。
地图元素的位置数据使用二维的维度表示,经度(-180,180],纬度范围(-90,90]。
当两个元素距离不是很远的时候可以直接使用勾股定理算得距离。
但是如果要计算[附近的人],给定一个元素的坐标,计算附近的其他元素,你不可能通过遍历来计算所有的元素和目标元素的距离然后排序。一般的方法都是通过矩形区域来限定元素数量,然后对区域内元素进行全量距离计算并排序。
为了满足高性能矩形区域算法,数据表需要在经纬度坐标加上双向符合索引(x,y)。但是在高并发场合这并不是一个好办法。
GeoHash算法将二维的经纬度映射到一维的整数,所有元素都挂载到一条线,距离靠近的二维坐标,映射到一维后的点之间的距离也会更接近,只需要在一维的线上获取附近的点就行。
映射算法:
把地球看成一个二维平面,划分成一些列正方形方格,将地图元素放置于唯一的方格中,方格越小越精确。然后对这些方格进行整数编码,越靠近的方格编码越接近。一个编码方案就是切蛋糕法。比如用两刀均匀切分成四个小正方形,这四个小正方形可以分别标记为00,01,10,11四个二进制整数。然后对每一个小正方形继续二刀法切割,这时候更小的正方形就可以用4bit的二进制数表示,继续切下去,二进制数越来越长,精度也越来越高。
GeoHash会继续对整数做一次base32编码变成字符串。在Redis中,经纬度使用52位的整数进行编码,放进zset里面,通过zset的score排序就可以得到坐标附近的其他元素,通过将score还原成坐标值就可以得到元素的原始坐标。
scan相比keys具有以下特点:
scan指令返回的游标就是第一维数组的位置索引,将这个位置索引称为槽(slot)。如果不考虑字典的扩容缩绒,直接按数组下标挨个遍历就行了。limit参数就表示要遍历的槽位数,之所以返回的结果可能多可能少,是因为不是所有的槽位上都会挂载链表,有些槽可能是空的,还有些槽上挂接的链表上的元素可能会有多个。每一次遍历都会将limit数量的槽位上挂接的所有链表元素进行模式匹配过滤后,一次性返回给客户端。
Redis是单线程程序!
速度快是因为所有的数据都在内存中,所有的运算都是内存级别的运算。
当调用套接字读写方法时,默认是阻塞的,比如read方法要传递进去一个参数n,表示读取这么多字节后在返回,如果没有读够线程就会卡在哪里,直到新的数据来或者链接关闭了,read方法才可以返回,线程才能继续处理。而write方法一般来说不会阻塞,除非内核为套接字分配的写缓冲区已经满了,write方法就会阻塞,直到缓存区中有空闲空间挪出来。
Redis的作者认为数据库系统的瓶颈一般不在于网络流量,而是数据库自身内部逻辑处理上。所以即使Redis使用了浪费流量的文本协议,依然可以取得极高的访问性能。Redis将所有的数据都放在内存,用一个单线程对外提供服务,单个节点在跑慢一个CPU核心的情况下达到了10w/s的超高QPS。
RESP(Redis Serialization Protocol):是一种直观的文本协议,优势在于实现异常简单,解析性能极好。
用来保证突然宕机时内存中数据丢失问题。
有两种机制,一种是快照,一种是AOF日志。
实际上Redis管道本身不是Redis服务器直接提供的技术,这个技术本质上是由客户端提供的,跟服务器没有什么直接关系。
当我们使用客户端对Redis进行一次操作时,客户端将请求传送给服务器,服务器处理完后,在将相应回复给客户端,这需要花费一个网络数据包来回的时间。
如果连续执行多条指令,那么就会花费多个网络包来回的时间。
对于客户端代码层面,客户端经历了写-读-写-读
四个操作完成了两条指令。
如果我们调整读写顺序,变成写-写-读-读
,同样可以完成指令,但是只会花费一次网络来回。
这就是管道操作的本质,服务器根本没有任何区别对待。
Redis的事务使用非常简单,不同于关系数据库,无需理解那么多复杂的事务模型就可以直接使用。
所有指令在exec之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到exec指令,才开始执行整个事务队列,执行完毕后返回所有指令的运行结果。由于Redis的单线程特性,指令自带原子性。
Redis的消息队列不支持消息的多播机制。
消息多播允许生产者生产一次消息,中间件负责将消息复制到多个消息队列,每个消息队列由相应的消费组进行消费。它是分布式系统常用的一种解耦方式,用于将多个消费组的逻辑进行拆分。支持了消息多播,多个消费组的逻辑就可以放到不同的子系统中。
如果是普通的消息队列,就得将多个不同的消费组逻辑串接起来放在一个子系统中进行连续消费。
Redis单独使用了一个模块来支持消息多播,即PubSub,PublisherSubscriber,发布者订阅模型。
客户端发起订阅命令后,Redis会立即给予一个反馈消息通知订阅成功。因为有网络传输延迟,在subscribe命令发出后,需要休眠一会,再通过get_message才能拿到反馈消息。客户端接下来执行发布命令,发布了一条消息。同样因为延迟,在publish命令发出后,需要休眠一会才能拿到发布的消息。如果没有消息会返回空,所以它不是阻塞的。
Redis作者为了优化数据结构的内存占用,也苦心孤诣增加了很多优化点,即使是以牺牲代码可读性为代价,但是这是非常值得的。
Redis如果使用32bit进行编译,内部所有数据结构所使用的指针空间占用会减少一半,如果使用内存不超过4G,可以考虑用32bit进行编译。
如果Redis内部管理的集合数据结构很小,它会使用紧凑存储形式压缩存储。这就好比HashMap本来是二维结构,但是如果内部元素比较少,使用二维结构反而浪费空间,还不如使用一维数组进行存储。
Redis并不总是可以将空闲内存立即归还给操作系统
如果当前Redis内存有10G,当你删除了1GB的key后,再去观察内存,你会发现内存变化不会太大,原因是操作系统回收内存是以页为单位,如果这个页上只要有一个key还在使用,那么它就不能被回收。Redis虽然删除了1GB的key,但是这些key分散到了很多页面中,每个页面都还有其他key存在,这就导致了内存不会立即被回收。
不过如果你执行flushdb,然后在观察内存会发现内存确实被回收了。原因是所有的key都干掉了,大部分之前使用的页面都完全干净了,会立即被操作系统回收。
Redis虽然无法保证立即回收已经删除的key内存,但是它会重用那些尚未回收的空闲内存。就好比电影院虽然人走了,但是作为还在,下一波观众来了直接坐就行;而操作系统回收内存就好比把座位都给搬走了。
Redis为了保持自身结构的简单性,将内存分配细节用第三方库实现。目前Redis默认使用jemalloc库。
有了主从,当master挂掉的时候,运维让从库过来接管,服务就可以继续。
即,网络分区(网络断开)发生时,一致性和可用性两难全。
Redis的主从数据是异步同步的,所以分布式的Redis系统不满足一致性要求。当客户端在Redis的主节点修改了数据后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以满足可用性。
Redis保证最终一致性,当断开的网络恢复后,从节点会努力追赶主节点,从而保持一致。
Redis同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地内存buffer中,然后异步将buffer中的指令同步到从节点。
但是内存buffer有限,容量满了后会覆盖前面的内容。那么如果断线时间太长可能会导致没同步的指令已经被覆盖,所以需要用到更复杂的同步机制–快照同步。
首先在主库上进行一次bgsave将当期内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续增量同步。