REDIS Redis (Remote Dictionary Server)是一个流行的开源内存 提供高级键值抽象的键值存储。 Redis 是单线程的,它只处理一个命令 客户端在进程的主线程中一次。 不同于传统的KV 键是简单数据类型(通常是字符串)的系统,键 在 Redis 中可以用作复杂的数据类型,例如哈希、列表、 集和排序集。 此外,Redis 支持复杂的原子 对这些数据类型的操作(例如,从 一个列表,将具有给定分数的新值插入排序集等)。 Redis 抽象和高摄取速度已被证明特别重要 对于许多延迟敏感的任务很有用。 因此,Redis 已获得广泛采用,并被越来越多的人使用 生产环境中的公司.
Redis 支持高可用性和持久性。 高可用性 是通过将数据从主节点复制到 从节点并同步它们。 当一个主进程失败时,它的 对应的从属进程已准备好接管后续进程 称为故障转移。 持久性可以通过以下任一方式配置 以下两个选项:
使用时间点快照文件 称为 RDB(Redis 数据库)
使用名为AOF(仅附加文件)。
注意这三种机制(AOF重写、RDB 快照和复制)依赖 fork 获取 进程内存的时间点快照并将其序列化 (而主进程继续为客户端命令提供服务)
项目简介 最近在做工业自动化(IIoT)项目,涉及到很多场景需要对一系列设备进行监控和信号处理。 该类场景对实时处理能力,系统稳定性,高可用性,容灾能力等等要求非常高。 其中几个核心的需求:
设备数据不能丢失 实时告警(毫秒级延迟) 设备数据必须优先在边缘节点处理,边缘节点的物理服务器只有两台 每个边缘节点接入的设备上行数据量大概6万点/秒,数据包小于1K 中心需要汇聚和分析所有边缘节点的设备数据 技术现状 实时数据处理 实时数据处理是一个自 1990 年代以来一直在研究的问题 。 产生的数据量增加了,加上越来越复杂的软件解决方案 开发,需要满足这些需求出现了流式应用程序,例如 欺诈检测、网络监控和电子交易依赖于实时数据处理 确保所提供的服务被认为是正确和可靠的。 绝大多数现代应用程序使用某种数据库管理系统 处理数据。当应用程序收集或生成数据时,它会被存储和索引 它可以在以后由应用程序查询。但是,对于具有更严格 的实时数据处理的要求,这不是一个合适的选项,而是流 处理开始发挥作用的地方。流处理在接收数据时直接处理数据。实时流 处理应用程序通常具有必须满足的某些关键要求。有的 输入和处理后的数据输出之间的延迟是实现实时的关键特征 应用。更传统的批处理方法需要以这样的方式收集数据 称为批次,其中处理只能在每个批次的最终数据块完成后开始 到达的。对于实时用例,这导致的延迟是不可接受的,因为这些实时的延迟 流应用程序最好在毫秒内。
LevelDB 是一基于内存+SSD的键值存储引擎。
内存存储最新的更改和热数据(缓存) SSD持久化所有数据 LevelDB经常使用代理+主从模式构建集群。 与REDIS协议保持兼容,可以被REDIS客户端访问。
内存结构 LevelDB在内存维护两个跳表:MemTable, 一个只读,一个可写。
序列号(Sequence number)是全局自增的,每次的修改序列号都会加1。 每个主键保存多个版本的键值。LevelDB用serial number标识键值对 的版本。最大的serial number代表最新的键值对。
键值对有两种更新操作:
Put = 1 Delete = 0 当可写的MemTable的大小超过阈值,会把所有的键值转移到只读的MemTable。 同时会创建一个新的可写的MemTable。只读的MemTable会被一个线程异步的 写入SSD。读操作会先查询可写的MemTable,然后查询只读的MemTable,最后 去查询SSD.
对可写的MemTable支持多线程操作,所以需要并发控制。
只读的MemTable存在时间很短,被创建后就会把异步写入SSD,然后清空。 当可写的MemTable增长很快,只读的MemTable会很快被填满。如果只读 的MemTable还没有别完全写入SSD,写入线程就会被阻塞。
内存结构操作日志 最近的MemTable的写入操作都会有一个对应的日志文件记录。 日志文件也有两份,对应两个跳表。
SSD数据结构 SST LevelDB在SSD上存储了许多SST(Sorted String Table)文件, 每个文件对应一个级别,每个级别有多个SST文件。 SST文件的大小是相同的,不同的是每个级别文件数目不同。 主键在每个SST文件都是有序的。 级别0的文件和其他级别的文件有一个明显的区别:其他级别的SST 文件之间的主键不会重叠,但级别0的SST文件之间的主键可能重叠, 因为级别0的SST文件之间从MemTable直接转存过来。
为了防止在级别0的SST文件进行键值读取的读放大,LevelDB默认 级别0有4个文件。
MANIFEST 所有文件的主键范围,级别和其他的元信息存储在MANIFEST文件。 MANIFEST有版本号,通过文件名后缀标识,例如MANIFEST-000031。 每次打开数据库,一个新的版本号的MANIFEST就好被创建,旧版本 的文件就会被删除。
CURRENT CURRENT文件内容记录了当前版本的MANIFEST文件名字。LevelDB首先读 CURRENT文件内容知道合法的MANIFEST文件。
LOCK LevelDB数据库目录不允许被多个进程同时访问,当一个进程打开数据库, 一个排他的文件锁就创建。
log 日志文件,记录操作日志,例如次要整理和主要整理日志
数据整理 从只读MemTable到0级的SST文件的转移叫次级整理。从SST文件向 更低级转移交主要整理。
文件合并
跳表介绍 Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.
跳表是一种可以用来代替平衡树的数据结构,跳表使用概率平衡而不是严格执行的平衡, 因此,与等效树的等效算法相比,跳表中插入和删除的算法要简单得多,并且速度要快得多。
跳表是由包含多个级别的链表组成,最低级别的链表存储了所有的主键并且按序链接
java跳表 https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListMap.html
跳表结构 多条链构成,是关键字升序排列的数据结构; 包含多个级别,一个head引用指向最高的级别,最低(底部)的级别,包含所有的key; 每一个级别都是其更低级别的子集,并且是有序的; 如果关键字key在级别level=i中出现,则level<=i的链表中都会包含该关键字key; ConcurrentSkipListMap和TreeMap类似,它们虽然都是有序的哈希表。但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的
跳表分为许多层(level),每一层都可以看作是数据的索引,这些索引的意义就是加快跳表查找数据速度。每一层的数据都是有序的,上一层数据是下一层数据的子集,并且第一层(level 1)包含了全部的数据;层次越高,跳跃性越大,包含的数据越少。跳表包含一个表头,它查找数据时,是从上往下,从左往右进行查找。
ConcurrentSkipListMap优点:
在4年前加入GLODON的时候,当时我们正在从传统的CS,单体的中心数据库,私有数据中心 向分布式,混合云架构演进。我们现在构建,部署,运营分布式关系/图像/键值数据库, 分布式检索,基于HDFS和对象存储的大数据分析平台。
在这个过程中,打心里认为学到的最重要的一个简单概念:日志。有时候我们叫它预写日志, 提交日志或事务日志。日志伴随着计算机出现就一直存在,是分布式数据系统和实时应用 架构的核心。
你不会完全理解数据库,NoSQL,键值存储,复制,paxos共识,hadoop,版本控制等任务 软件系统,除非完全理解日志。然而大部分程序员其实对日志并不熟悉。本贴将带你一步步 了解日志,包括什么是日志,如何有效的使用日志,通过日志来构建数据集成,实时数据处理 等业务系统。
第一个部分:什么是日志 日志可能是最简单的存储抽象。它是一个只可追加的,完全按照时间顺序记录的数据序列。 新的记录追加到日志存储的末尾。读取的时候从左向右处理。每一条记录条目分配了一个 唯一的日志序列号。
日志的存储次序定义了时间的概念,因为左边的日志条目始终比右边的日志存储的时间更早。 日志条目唯一序列号可以看做日志条目时间戳。把这个顺序定义为时间咋一看觉得奇怪,但 这个属性很方便的和特定的物理时钟隔离开来。这个属性很快证明是分布式系统所必须的。
日志条目的内容和格式不是本贴讨论内容要重点关心的。而且我们也不能无限的追加记录, 因为存储空间的限制。我们晚点开会谈到这一点。
因此,日志和文件,数据表没有根本不同。文件是字节数组,数据表是记录数组,日志只是 一个特定的类型的文件或数据表:日志数组按时间排序。
到此,你可能奇怪,我们有啥必要讨论如此简单的东西?只可追加的日志序列到底和数据系统 在哪些方面有关联呢?答案是日志有一个特殊的目的:记录什么时间发生了什么事。对分布式 系统来说,在很多方面,这都是核心问题。
但在我们继续讨论之前,让我先澄清一个让人困惑的事情。每一个程序员应该对另一种日志定义 非常熟悉并经常使用:非结构化的错误日志或跟踪日志,这些日志由应用程序利用syslog或 log4j写到本地文件系统。这类日志我们叫做应用日志。应用日志是我要描述的日志的退化形式。 他俩最大的区别是应用日志主要是用来运维开发人员读取使用,而我描述的数据日志主用来被 程序访问。
数据库日志 我不知道数据库日志的真正起源,可能就像二分查找一样,因为太过于简单,发明者都没有意识 到这是一个发明。它最早在IBM的System R中使用,用作各种数据结构和索引在崩溃的后同步。 为了保证数据的原子性和持久性,数据库把要修改的记录在提交前先写入日志。日志记录发生了什么 ,是每个数据表或索引表的历史投影。因为日志被立即写入存储盘,被作为系统崩溃发生后进行 系统恢复有效的数据来源。
慢慢的,日志的使用范围从ACID的实现细节扩展到数据库间的数据复制机制。实践证明,这些 本地数据库数据的变更记录正是要保持远程的数据库副本同步所必需的。Oracle, MySQL和 PostgreSQL都有日志传送协议,在从库上做数据库复制。
分布式系统日志 日志解决的两个问题:变更顺序和数据分发,在分布式数据系统中更加重要。对数据变更顺序的 达成一致是这些系统设计的核心问题。
以日志为中心作为分布式系统的解决思路,源于一个简单的观察结果,我称之为状态机复制规则:
如果两个相同的,”确定性”的程序从相同的状态启动,以相同的顺便获取 相同的输入,他俩会产生相同的输出并以相同的状态结束
“确定性”是指程序处理不依赖于时间,不会因为任何其他的带外的输入影响其处理结果。那些 输出结果依赖线程的执行顺序,或者API调用*gettimeofday*,或其他不可重复的调用的程序 都是非”确定性”。
机器里面的数据是程序的状态的表示,任务处理完后,无论是数据留在才内存,还是在磁盘。
以相同的顺序产生的相同输入这一点提醒我们:这就是日志上场了。这是一个直觉概念: “确定性”的程序会从相同的日志产生相同的结果。
分布式应用看来一个相当直观思路就是:实现一个分布式一致性的日志,作为输入分发到多个 机器上处理相同的任务的程序。日志的目的就是排除输入流产生的所有非确定性输出,保证 每个处理输入流的程序副本保持在相同的状态。
如果你理解了这一点,关于这个规则就没有多深奥或多复杂啦,它或多或少的等于说:确实性的 处理过程是确定性的。然而,我认为它只是分布式系统设计更通用的设计工具之一。
以日志为中心的分布式设计的一个亮点,以时间戳为索引的日志成为程序副本的状态的时钟。 你可以用一个数字来描述一个程序副本:该副本处理的最近日志条目的时间戳。时间戳和日志 一起唯一的快照了副本的完整状态。
不同应用组的人描述日志的使用可能有所不同,数据库组的人经常区分物理日志和逻辑日志, 物理日志记录每行改变的内容,逻辑日志记录导致改变的SQL命令(CRUD)。
分布式系统文献经常把处理和复制划分为两大类别:状态机模式和主备模式。状态机模式 经常指双活,对输入请求做日志记录,每一个副本处理每一个请求。主备模式和状态机模式 有稍微的差别,主备模式为推举一个主节点,主节点会按顺便处理请求,并把处理结果的 状态作为日志同步到从节点。
变更日志101: 参考 [1] What every software engineer should know about real-time data’s unifying abstraction
在windows上搭建Leveldb的开发环境
Leveldb使用了checkstyle
https://github.com/wubigo/leveldb/blob/master/src/checkstyle/checks.xml
<module name="RegexpMultiline"> <property name="format" value="\r"/> <property name="message" value="Line contains carriage return"/> </module> 使用git默认设置拣出代码的时候,换行设置编译的时候会报错如下
[ERROR] src\main\java\org\iq80\leveldb\WriteOptions.java:[6] (regexp) RegexpMultiline: Line contains carriage return [ERROR] src\main\java\org\iq80\leveldb\WriteOptions.java:[7] (regexp) RegexpMultiline: Line contains carriage return 解决办法之一是调整git换行设置
git换行设置(line endings) 换行设置的3个选项:
Checkout Windows-style, commit Unix-style Git will convert LF to CRLF when checking out text files. When committing text files, CRLF will be converted to LF. For cross-platform projects, this is the recommended setting on Windows (“core.
LETTUCE VS JEDIS While Jedis is easy to use and supports a vast number of Redis features, it is not thread safe and needs connection pooling to work in a multi-threaded environment. Connection pooling comes at the cost of a physical connection per Jedis instance which increases the number of Redis connections.
Lettuce, on the other hand, is built on netty (https://netty.io/) and connection instances can be shared across multiple threads.
系统配置 /etc/sysctl.conf
vm.swappiness = 1 vm.overcommit_memory = 1 改变数据目录 sudo install -o redis -g redis -d /mnt/redis-data > config get dir 1) "dir" 2) "/mnt/redis-data" /lib/systemd/system/redis-server.service
[Service] ReadWriteDirectories=-/mnt/redis-data pidfile NOT FOUND FROM SYSTEMD /etc/redis/redis.conf
pidfile /var/run/redis/redis-server.pid /lib/systemd/system/redis-server.service
PIDFile=/run/redis/redis-server.pid 删除消费组 XGROUP DESTROY STREAM:TEST STRRAM:TEST:GROUP Redis latency problems troubleshooting Make sure you are not running slow commands that are blocking the server. Use the Redis Slow Log feature to check this.