2019知识点总结
2022-02-08 20:31:47 0 举报
AI智能生成
登录查看完整内容
2019知识点总结
作者其他创作
大纲/内容
mysql架构
sql优化
性能优化
索引结构
索引失效情况分析
表设计
null值相关问题
https://blog.csdn.net/weixin_39786341/article/details/111276441
存储引擎对比
mysql
tomcat
mycat
shardingjdbc
分库分表
查找
排序
算法
运行时内存结构
类加载
垃圾回收算法
垃圾收集器
jvm调优
jvm线上故障排查
jvm
dubbo
Dubbox
Spring Cloud
gRPC
thrift
Hessian
rpc框架
长链接短链接https://www.cnblogs.com/gotodsp/p/6366163.html
http
select:查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回;fd_set 是一个位集合,大小是在编译内核时的常量,默认大小为 1024特点:连接数限制,fd_set 可表示的 fd 数量太小了;线性扫描:判断 fd 是否就绪,需要遍历一边 fd_set;数据复制:用户空间和内核空间,复制连接就绪状态信息poll:解决了连接数限制:poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组解决 fd 数量过小的问题数据复制:用户空间和内核空间,复制连接就绪状态信息epoll: event 事件驱动事件机制:避免线性扫描为每个 fd,注册一个监听事件fd 变更为就绪时,将 fd 添加到就绪链表fd 数量:无限制(OS 级别的限制,单个进程能打开多少个 fd)select,poll,epoll:I/O多路复用的机制;I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。监视多个文件描述符但select,poll,epoll本质上都是同步I/O:用户进程负责读写(从内核空间拷贝到用户空间),读写过程中,用户进程是阻塞的;异步 IO,无需用户进程负责读写,异步IO,会负责从内核空间拷贝到用户空间;
select/poll 和 epoll 比较https://www.cnblogs.com/520playboy/p/12743367.html
面试题汇总https://mp.weixin.qq.com/s?__biz=MzIyNDU2ODA4OQ==&mid=2247485351&idx=2&sn=214225ab4345f4d9c562900cb42a52ba&chksm=e80db1d1df7a38c741137246bf020a5f8970f74cd03530ccc4cb2258c1ced68e66e600e9e059&scene=21#wechat_redirect
所谓开闭也是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。
可以提高软件系统的可复用性、可维护性
开闭原则是面向对象设计中最基础的原则,它知道我们如何构建一个稳定、灵活的系统
java课程降价
java课程涨价
java课程
大数据课程
人工智能课程
课程类(抽象类)
案例:课程类定义了基本的---课程名称get方法,价格get方法,idget方法。当我们想要降价java课程时就使用java课程降价类,想涨java课程价格时,就使用java涨价的实体类,实现开闭原则
public interface ICourse {Integer getId();String getName();Double getPrice();}
代码
开闭原则
抽象不应该依赖细节,细节应该依赖抽象
通过依赖倒置可以减少代码的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。并能降低修改程序所造成的风险。
public class Tom {public void studyJavaCourse(){System.out.println(\"Tom 在学习 Java 的课程\");}public void studyPythonCourse(){System.out.println(\"Tom 在学习 Python 的课程\");}}
public static void main(String[] args) {Tom tom = new Tom();tom.studyJavaCourse();tom.studyPythonCourse();}
原始设计(当继续增加学习项目的代码时候,需要修改原有代码,代码结构发生变化)
定义一个接口public interface ICourse {void study();}
多个类实现对应接口public class JavaCourse implements ICourse {@Overridepublic void study() {System.out.println(\"Tom 在学习 Java 课程\");}}
多个类实现对应接口public class PythonCourse implements ICourse {@Overridepublic void study() {System.out.println(\"Tom 在学习 Python 课程\");}}
public class Tom {public void study(ICourse course){course.study();}}
public static void main(String[] args) {Tom tom = new Tom();tom.study(new JavaCourse());tom.study(new PythonCourse());}
升级tom类public class Tom {private ICourse course;public Tom(ICourse course){this.course = course;}public void study(){course.study();}}
继续改造tom类public class Tom {private ICourse course;public void setCourse(ICourse course) {this.course = course;}public void study(){course.study();}}
最终main类public static void main(String[] args) {Tom tom = new Tom();tom.setCourse(new JavaCourse());tom.study();tom.setCourse(new PythonCourse());tom.study();}
先抽象后细节的设计
案例分析
以抽象为基准比以细节为基准构建的架构要稳定的多。拿到需求后要面向接口编程,先顶层再细节的来设计代码结构
切记:
依赖倒置原则
(Simple Responsibility Pinciple,SRP)不要存在多于一个导致类变更的原因
假设我们有一个 Class 负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个 Class 存在两个导致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个 Class 来实现,进行解耦。后期需求变更维护互不影响。这样的设计,可以降低类的复杂度,提高类的可 读 性 , 提 高 系 统 的 可 维 护 性 , 降 低 变 更 引 起 的 风 险 。
总 体 来 说 就 是 一 个Class/Interface/Method 只负责一项职责
public class Course {public void study(String courseName){if(\"直播课\".equals(courseName)){System.out.println(courseName + \"不能快进\");}else{System.out.println(courseName + \"可以反复回看\");}}}
public static void main(String[] args) {Course course = new Course();course.study(\"直播课\");course.study(\"录播课\");}
原始代码
需求变更:假如,现在要对课程进行加密,那么直播课和录播课的加密逻辑都不一样,必须要修改代码。而修改代码逻辑势必会相互影响容易造成不可控的风险
public class LiveCourse {public void study(String courseName){System.out.println(courseName + \"可以反复回看\");}}
public class ReplayCourse {public void study(String courseName){System.out.println(courseName + \"不能快进\");}}
public static void main(String[] args) {LiveCourse liveCourse = new LiveCourse();liveCourse.study(\"直播课\");ReplayCourse replayCourse = new ReplayCourse();replayCourse.study(\"录播课\");}
升级代码
业务继续发展,课程要做权限。没有付费的学员可以获取课程基本信息,已经付费的学员可以获得视频流,即学习权限
public interface ICourseInfo {String getCourseName();byte[] getCourseVideo();}
public interface ICourseManager {void studyCourse();void refundCourse();}
再次改造
public interface ICourse {//获得基本信息String getCourseName();//获得视频流byte[] getCourseVideo();//学习课程void studyCourse();//退款void refundCourse();}
再次升级代码
类层面设计
private void modifyUserName(String userName){userName = \"Tom\";}private void modifyAddress(String address){address = \"Changsha\";}
修改代码
方法层面设计
代码实例,还是用课程举例,我们的课程有直播课和录播课。直播课不能快进和快退,录播可以可以任意的反复观看,功能职责不一样
单一职责原则
根据这个原则,指导我们在设计接口时需要注意以下几点:1、一个类对一个类的依赖应该建立在最小的接口之上。2.建立单一接口,不要设计庞大臃肿接口。3.尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)
public interface IAnimal {void eat();void fly();void swim();}
public class Bird implements IAnimal {@Overridepublic void eat() {}@Overridepublic void fly() {}@Overridepublic void swim() {}}
public class Dog implements IAnimal {@Overridepublic void eat() {}@Overridepublic void fly() {}@Overridepublic void swim() {}}
旧版
问题及修改方案:Bird 的 swim()方法可能只能空着,Dog 的 fly()方法显然不可能的。这时候,我们针对不同动物行为来设计不同的接口,分别设计 IEatAnimal,IFlyAnimal 和ISwimAnimal 接口
public interface IEatAnimal {void eat();}
public interface IFlyAnimal {void fly();}
public interface ISwimAnimal {void swim();}
新版
接口隔离原则
(Law of Demeter LoD 一个对象应该保持与其他对象最少的了解,又叫最少知道原则(Law of Demeter LoD),尽量降低类与类之间的耦合。
迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类
public class Course {}
public class TeamLeader {public void checkNumberOfCourses(List<Course> courseList){System.out.println(\"目前已发布的课程数量是:\"+courseList.size());}}
public class Boss {public void commandCheckNumber(TeamLeader teamLeader){//模拟 Boss 一页一页往下翻页,TeamLeader 实时统计List<Course> courseList = new ArrayList<Course>();for (int i= 0; i < 20 ;i ++){courseList.add(new Course());}teamLeader.checkNumberOfCourses(courseList);}}
public static void main(String[] args) {Boss boss = new Boss();TeamLeader teamLeader = new TeamLeader();boss.commandCheckNumber(teamLeader);}
问题:boss和课程类有关联
public class TeamLeader {public void checkNumberOfCourses(){List<Course> courseList = new ArrayList<Course>();for(int i = 0 ;i < 20;i++){courseList.add(new Course());}System.out.println(\"目前已发布的课程数量是:\"+courseList.size());}}
public class Boss {public void commandCheckNumber(TeamLeader teamLeader){teamLeader.checkNumberOfCourses();}}
这样boss只需要知道techer就够了,不用自己关心课程的问题
迪米特法则
为一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变
注意:子类可以扩展父类的功能,但不能改变父类原有的功能1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。2、子类中可以增加自己特有的方法。3、当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。4、当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
优点:1、约束继承泛滥,开闭原则的一种体现。2、加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。
public class Rectangle {private long height;private long width;@Overridepublic long getWidth() {return width;}@Overridepublic long getLength() {return length;}public void setLength(long length) {this.length = length;}public void setWidth(long width) {this.width = width;}}
public class Square extends Rectangle {private long length;public long getLength() {return length;}public void setLength(long length) {this.length = length;}@Overridepublic long getWidth() {return getLength();}@Overridepublic long getHeight() {return getLength();}@Overridepublic void setHeight(long height) {setLength(height);}@Overridepublic void setWidth(long width) {setLength(width);}}
public static void resize(Rectangle rectangle){while (rectangle.getWidth() >= rectangle.getHeight()){rectangle.setHeight(rectangle.getHeight() + 1);System.out.println(\"width:\"+rectangle.getWidth() + \
public static void main(String[] args) {Rectangle rectangle = new Rectangle();rectangle.setWidth(20);rectangle.setHeight(10);resize(rectangle);}
把长方形 Rectangle 替换成它的子类正方形 Square,修改测试代码public static void main(String[] args) {Square square = new Square();square.setLength(10);resize(square);}这时候我们运行的时候就出现了死循环,违背了里氏替换原则,将父类替换为子类后,程序运行结果没有达到预期。因此,我们的代码设计是存在一定风险的。里氏替换原则只存在父类与子类之间,约束继承泛滥
问题
抽象四边形 Quadrangle 接口:public interface Quadrangle {long getWidth();long getHeight();}
修改长方形 Rectangle 类:public class Rectangle implements Quadrangle {private long height;private long width;@Overridepublic long getWidth() {return width;}public long getHeight() {return height;}public void setHeight(long height) {this.height = height;}public void setWidth(long width) {this.width = width;}}
修改正方形类 Square 类:public class Square implements Quadrangle {private long length;public long getLength() {return length;}public void setLength(long length) {this.length = length;}@Overridepublic long getWidth() {return length;}@Overridepublic long getHeight() {return length;}}
此时,如果我们把 resize()方法的参数换成四边形 Quadrangle 类,方法内部就会报错。因为正方形 Square 已经没有了 setWidth()和 setHeight()方法了。因此,为了约束继承泛滥,resize()的方法参数只能用 Rectangle 长方形。
里氏替换原则
public class DBConnection {public String getConnection(){return \"MySQL 数据库连接\";}}
public class ProductDao{private DBConnection dbConnection;public void setDbConnection(DBConnection dbConnection) {this.dbConnection = dbConnection;}public void addProduct(){String conn = dbConnection.getConnection();System.out.println(\"使用\"+conn+\"增加产品\");}}
是一种非常典型的合成复用原则应用场景。但是,目前的设计来说,DBConnection还不是一种抽象,不便于系统扩展。目前的系统支持 MySQL 数据库连接,假设业务发生变化,数据库操作层要支持 Oracle 数据库。当然,我们可以在 DBConnection 中增加对Oracle 数据库支持的方法。但是违背了开闭原则
将 DBConnection 修改为 abstractpublic abstract class DBConnection {public abstract String getConnection();}
public class MySQLConnection extends DBConnection {@Overridepublic String getConnection() {return \"MySQL 数据库连接\";}}
public class OracleConnection extends DBConnection {@Overridepublic String getConnection() {return \"Oracle 数据库连接\";}}
合成复用原则
软件设计原则
工厂方法
抽象工厂
构建者
原型
单例
创建型
适配器
桥接
组合
装饰器
门面
享元
代理
结构型
行为型
设计模式
学习设计原则,学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构
设计原则总结
hashmap
arraylist
linkedhashmap
java集合框架源码
springboot启动流程
ioc流程
di流程
aop流程
属性注入
能解决的循环依赖
构造注入
子主题
不能解决的循环依赖
https://www.jianshu.com/p/84fc65f2764b
为什么用三级缓存解决循环依赖
三级缓存https://www.jianshu.com/p/84fc65f2764bhttps://blog.csdn.net/csdnlijingran/article/details/86617958
spring源码
架构
mybatis源码
一个请求过来的整个流程
tomcat源码
源码分析
常用命令:set、get、decr、incr、mget 等。
注意:Redis 规定了字符串的长度不得超过 512 MB。
string
常用命令:list类型常用的命令有:lpush、rpush、lpop、rpop、lrange等。
list(链表)
扩容和收缩的过程:1、如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。2、重新利用哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。3、所有键值对都迁徙完毕后,释放原哈希表的内存空间。在redis中执行扩容和收缩的规则是:服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF (持久化)命令,并且负载因子大于等于1。服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF (持久化)命令,并且负载因子大于等于5。负载因子 = 哈希表已保存节点数量 / 哈希表大小。
渐进式rehash的优缺点:优点是把rehash操作分散到每一个字典操作和定时函数上,避免了一次性集中式rehash带来的服务器压力。缺点是在rehash期间需要使用两个hash表,占用内存稍大。
常用命令:hash类型的常用命令有:hget、hset、hgetall 等。
zipList:压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。 压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。压缩列表数据结构:①、previous_entry_ength:记录压缩列表前一个字节的长度。previous_entry_ength的长度可能是1个字节或者是5个字节,如果上一个节点的长度小于254,则该节点只需要一个字节就可以表示前一个节点的长度了,如果前一个节点的长度大于等于254,则previous length的第一个字节为254,后面用四个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费。 ②、encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。 ③、content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。
哈希对象的编码有两种,分别是:ziplist、hashtable。当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用ziplist(压缩列表)存储;否则使用 hashtable 存储。Redis中的hashtable跟Java中的HashMap类似,都是通过\"数组+链表\"的实现方式解决部分的哈希冲突。直接看源码定义。typedf struct dict{ dictType *type;//类型特定函数,包括一些自定义函数,这些函数使得key和value能够存储 void *private;//私有数据 dictht ht[2];//两张hash表 int rehashidx;//rehash索引,字典没有进行rehash时,此值为-1 unsigned long iterators; //正在迭代的迭代器数量}dict;typedef struct dictht{ //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 //总是等于 size-1 unsigned long sizemask; //该哈希表已有节点的数量 unsigned long used; }dictht;typedf struct dictEntry{ void *key;//键 union{ void val; unit64_t u64; int64_t s64; double d; }v;//值 struct dictEntry *next;//指向下一个节点的指针}dictEntry;
hash(字典)
encoding:分别是INTSET_ENC_INT16、INSET_ENC_INT32、INSET_ENC_INT64,代表着整数值的取值范围。Redis会根据添加进来的元素的大小,选择不同的类型进行存储,可以尽可能地节省内存空间。
length:记录集合有多少个元素,这样获取元素个数的时间复杂度就是O(1)
contents:存储数据的数组,数组按照从小到大有序排列,不包含任何重复项。
数据类型升级的优缺点:升级的优点在于,根据存储的数据大小选择合适的编码方式,节省了内存。缺点在于,升级会消耗系统资源。而且升级是不可逆的,也就是一旦对数组进行升级,编码就会一直保持升级后的状态。
常用的命:set数据类型常用的命令有:sadd、spop、smembers、sunion等等。
应用场景:Redis为set类型提供了求交集,并集,差集的操作,可以非常方便地实现譬如共同关注、共同爱好、共同好友等功能。
set类型的特点很简单,无序,不重复,跟Java的HashSet类似。它的编码有两种,分别是intset和hashtable。如果value可以转成整数值,并且长度不超过512的话就使用intset存储,否则采用hashtable。hashtable在前面讲hash类型时已经讲过,这里的set集合采用的hashtable几乎一样,只是哈希表的value都是NULL。这个不难理解,比如用Java中的HashMap实现一个HashSet,我们只用HashMap的key就是了。typedef struct intset{ uint32_t encoding;//编码方式 uint32_t length;//集合包含的元素数量 int8_t contents[];//保存元素的数组}intset;
set(集合)
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质: 1、由很多层结构组成; 2、每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点; 3、最底层的链表包含了所有的元素; 4、如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集); 5、链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
zset是Redis中比较有特色的数据类型,它和set一样是不可重复的,区别在于多了score值,用来代表排序的权重。也就是当你需要一个有序的,不可重复的集合列表时,就可以考虑使用这种数据类型。zset的编码有两种,分别是:ziplist、skiplist。当zset的长度小于 128,并且所有元素的长度都小于 64 字节时,使用ziplist存储;否则使用 skiplist 存储。
为什么要设计成这样呢:好处在于查询的时候,可以减少时间复杂度,如果是一个链表,我们要插入并且保持有序的话,那就要从头结点开始遍历,遍历到合适的位置然后插入,如果这样性能肯定是不理想的。所以问题的关键在于能不能像使用二分查找一样定位到插入的点,答案就是使用跳跃表。比如我们要插入38,那么查找的过程就是这样。首先从L4层,查询87,需要查询1次。然后到L3层,查询到在->24->87之间,需要查询2次。然后到L2层,查询->48,需要查询1次。然后到L1层,查询->37->48,查询2次。确定在37->48之间是插入点。有没有发现经过L4,L3,L2层的查询后已经跳过了很多节点,当到了L1层遍历时已经把范围缩小了很多。这就是跳跃表的优势。这种方式有点类似于二分查找,所以他的时间复杂度为O(logN)。其实生活中也有这种例子,类似于快递填写的地址是省->市->区->镇->街,当快递公司在送快递时就根据地址层层缩小范围,最终锁定在一个很小的区域去搜索,提高了效率。
zet常用的命令有:zadd、zrange、zrem、zcard等
应用场景:zset的特点非常适合应用于开发排行榜的功能,比如三天阅读排行榜,游戏排行榜等等。
zset(有序集合)
数据类型及底层结构https://zhuanlan.zhihu.com/p/344918922
1)典型的一些慢命令,如:save持久化数据化;keys匹配所有的键;hgetall,smembers等大集合的全量操作; 2)使用del命令删除一个非常大的集合键,这一点经常被大家忽略;只是删除一个键为什么会慢呢?原因就在于集合键在删除的时候,需要释放每一个元素的内存空间,想想要是集合键包含1000w个元素呢? 目前对于集合键的删除,redis提供了异步删除方式,主线程中只是断开了数据库与该键的引用关系,真正的删除动作通过队列异步交由另外的子线程处理。对应的,异步删除需要使用新的删除命令unlink。另外,时间事件循环中也会周期性删除过期键,这里的删除也可以采用异步删除方式,不过需要配置lazyfree-lazy-expire=yes。 3)bgsave持久化,虽说是fork子进程执行持久化操作,但有时fork系统调用同样会比较耗时,从而阻塞主线程执行命令请求; 4)aof持久化,aof写入是需要磁盘的,如果此时磁盘的负载较高(比如其他进程占用,或者redis进程同时在执行bgsave),同样会阻塞aof的写入,从而影响命令的执行; 5)时间事件循环中的周期性删除过期键,在遇到大量键集中过期时,删除过期键同样会比较耗时;另外,如果配置lazyfree-lazy-expire=no,删除大集合键时同样会阻塞该过程;虽说redis保证了周期性删除过期键不会耗费太多时间,根据配置hz计算,比如hz=10时,说明时间事件循环每秒执行10次,限制周期性删除过期键耗时不超过cpu时间的25%,那么执行时间应该不超过1000毫秒 * 25% / 10次 = 25毫秒;显然周期性删除过期键的耗时还是有可能超过redis慢查询门限的。 6)快命令被其他慢命令请求阻塞,一来如果是这样前面的慢命令请求也应该有慢查询报警,二来这时候redis server统计的当前命令执行还是非常快的,不会有慢查询日志,只是从客户端角度看来,该请求执行时间较长。
Redis慢查询
Redis 单节点虽然有通过 RDB 和 AOF 持久化机制能将数据持久化到硬盘上,但数据是存储在一台服务器上的,如果服务器出现硬盘故障等问题,会导致数据不可用,而且读写无法分离,读写都在同一台服务器上,请求量大时会出现 I/O 瓶颈。为了避免单点故障 和 读写不分离,Redis 提供了复制(replication)功能实现 master 数据库中的数据更新后,会自动将更新的数据同步到其他 slave 数据库上。Redis 主从结构特点:一个 master 可以有多个 slave 节点;slave 节点可以有 slave 节点,从节点是级联结构。主从模式优缺点优点: 主从结构具有读写分离,提高效率、数据备份,提供多个副本等优点。不足: 最大的不足就是主从模式不具备自动容错和恢复功能,主节点故障,集群则无法进行工作,可用性比较低,从节点升主节点需要人工手动干预。普通的主从模式,当主数据库崩溃时,需要手动切换从数据库成为主数据库:在从数据库中使用 SLAVE NO ONE 命令将从数据库提升成主数据继续服务。启动之前崩溃的主数据库,然后使用 SLAVEOF 命令将其设置成新的主数据库的从数据库,即可同步数据。
主从模式(redis2.8版本之前的模式)
哨兵模式是从 Redis 的 2.6 版本开始提供的,但是当时这个版本的模式是不稳定的,直到 Redis 的 2.8 版本以后,这个哨兵模式才稳定下来。哨兵模式核心还是主从复制,只不过在相对于主从模式在主节点宕机导致不可写的情况下,多了一个竞选机制:从所有的从节点竞选出新的主节点。竞选机制的实现,是依赖于在系统中启动一个 sentinel 进程。哨兵本身也有单点故障的问题,所以在一个一主多从的 Redis 系统中,可以使用多个哨兵进行监控,哨兵不仅会监控主数据库和从数据库,哨兵之间也会相互监控。每一个哨兵都是一个独立的进程,作为进程,它会独立运行。
哨兵模式的作用:监控所有服务器是否正常运行:通过发送命令返回监控服务器的运行状态,处理监控主服务器、从服务器外,哨兵之间也相互监控。故障切换:当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换 master。同时那台有问题的旧主也会变为新主的从,也就是说当旧的主即使恢复时,并不会恢复原来的主身份,而是作为新主的一个从。
哨兵实现原理:哨兵在启动进程时,会读取配置文件的内容,通过如下的配置找出需要监控的主数据库:sentinel monitor master-name ip port quorum# master-name 是主数据库的名字# ip 和 port 是当前主数据库地址和端口号# quorum 表示在执行故障切换操作前,需要多少哨兵节点同意。这里之所以只需要连接主节点,是因为通过主节点的 info 命令,获取从节点信息,从而和从节点也建立连接,同时也能通过主节点的 info 信息知道新增从节点的信息。一个哨兵节点可以监控多个主节点,但是并不提倡这么做,因为当哨兵节点崩溃时,同时有多个集群切换会发生故障。哨兵启动后,会与主数据库建立两条连接。订阅主数据库 _sentinel_:hello 频道以获取同样监控该数据库的哨兵节点信息定期向主数据库发送 info 命令,获取主数据库本身的信息。跟主数据库建立连接后会定时执行以下三个操作:每隔 10s 向 master 和 slave 发送 info 命令。作用是获取当前数据库信息,比如发现新增从节点时,会建立连接,并加入到监控列表中,当主从数据库的角色发生变化进行信息更新。每隔 2s 向主数据里和从数据库的 _sentinel_:hello 频道发送自己的信息。作用是将自己的监控数据和哨兵分享。每个哨兵会订阅数据库的_sentinel:hello 频道,当其他哨兵收到消息后,会判断该哨兵是不是新的哨兵,如果是则将其加入哨兵列表,并建立连接。每隔 1s 向所有主从节点和所有哨兵节点发送 ping 命令,作用是监控节点是否存活。
主观下线和客观下线:哨兵节点发送 ping 命令时,当超过一定时间(down-after-millisecond)后,如果节点未回复,则哨兵认为主观下线。主观下线表示当前哨兵认为该节点已经下线,如果该节点为主数据库,哨兵会进一步判断是够需要对其进行故障切换,这时候就要发送命令(SENTINEL is-master-down-by-addr)询问其他哨兵节点是否认为该主节点是主观下线,当达到指定数量(quorum)时,哨兵就会认为是客观下线。当主节点客观下线时就需要进行主从切换,主从切换的步骤为:选出领头哨兵。领头哨兵所有的 slave 选出优先级最高的从数据库。优先级可以通过 slave-priority 选项设置。如果优先级相同,则从复制的命令偏移量越大(即复制同步数据越多,数据越新),越优先。如果以上条件都一样,则选择 run ID 较小的从数据库。选出一个从数据库后,哨兵发送 slave no one 命令升级为主数据库,并发送slaveof 命令将其他从节点的主数据库设置为新的主数据库。
哨兵模式的优缺点:优点:哨兵模式是基于主从模式的,解决可主从模式中master故障不可以自动切换故障的问题。缺点:是一种中心化的集群实现方案:始终只有一个 Redis 主机来接收和处理写请求,写操作受单机瓶颈影响。集群里所有节点保存的都是全量数据,浪费内存空间,没有真正实现分布式存储。数据量过大时,主从同步严重影响 master 的性能。Redis 主机宕机后,哨兵模式正在投票选举的情况之外,因为投票选举结束之前,谁也不知道主机和从机是谁,此时 Redis 也会开启保护机制,禁止写操作,直到选举出了新的 Redis 主机。主从模式或哨兵模式每个节点存储的数据都是全量的数据,数据量过大时,就需要对存储的数据进行分片后存储到多个 Redis 实例上。此时就要用到 Redis Sharding 技术。
哨兵sentinel模式(redis2.8及之后的模式)
客户端分片的优缺点:优点:客户端 sharding 技术使用 hash 一致性算法分片的好处是所有的逻辑都是可控的,不依赖于第三方分布式中间件。服务端的 Redis 实例彼此独立,相互无关联,每个 Redis 实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强。开发人员清楚怎么实现分片、路由的规则,不用担心踩坑。缺点:这是一种静态的分片方案,需要增加或者减少 Redis 实例的数量,需要手工调整分片的程序。运维成本比较高,集群的数据出了任何问题都需要运维人员和开发人员一起合作,减缓了解决问题的速度,增加了跨部门沟通的成本。在不同的客户端程序中,维护相同的路由分片逻辑成本巨大。比如:java 项目、PHP 项目里共用一套 Redis 集群,路由分片逻辑分别需要写两套一样的逻辑,以后维护也是两套。客户端分片有一个最大的问题就是,服务端 Redis 实例群拓扑结构有变化时,每个客户端都需要更新调整。如果能把客户端分片模块单独拎出来,形成一个单独的模块(中间件),作为客户端 和 服务端连接的桥梁就能解决这个问题了,此时代理分片就出现了。
客户端分片
Redis 代理分片用得最多的就是 Twemproxy,由 Twitter 开源的 Redis 代理,其基本原理是:通过中间件的形式,Redis 客户端把请求发送到 Twemproxy,Twemproxy 根据路由规则发送到正确的 Redis 实例,最后 Twemproxy 把结果汇集返回给客户端。Twemproxy 通过引入一个代理层,将多个 Redis 实例进行统一管理,使 Redis 客户端只需要在 Twemproxy 上进行操作,而不需要关心后面有多少个 Redis 实例,从而实现了 Redis 集群。
codis
代理分片
分片
出现原因:Redis 的哨兵模式虽然已经可以实现高可用,读写分离 ,但是存在几个方面的不足:哨兵模式下每台 Redis 服务器都存储相同的数据,很浪费内存空间;数据量太大,主从同步时严重影响了 master 性能。哨兵模式是中心化的集群实现方案,每个从机和主机的耦合度很高,master 宕机到 slave 选举 master 恢复期间服务不可用。哨兵模式始终只有一个 Redis 主机来接收和处理写请求,写操作还是受单机瓶颈影响,没有实现真正的分布式架构。
Redis 在 3.0 上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。cluster 模式为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS 不受限于单机,可受益于分布式集群高扩展性。Redis Cluster 是一种服务器 Sharding 技术(分片和路由都是在服务端实现),采用多主多从,每一个分区都是由一个 Redis 主机和多个从机组成,片区和片区之间是相互平行的。Redis Cluster 集群采用了 P2P 的模式,完全去中心化。
Redis Cluster 集群特点:集群完全去中心化,采用多主多从;所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。客户端与 Redis 节点直连,不需要中间代理层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。每一个分区都是由一个 Redis 主机和多个从机组成,分片和分片之间是相互平行的。每一个 master 节点负责维护一部分槽,以及槽所映射的键值数据;集群中每个节点都有全量的槽信息,通过槽每个 node 都知道具体数据存储到哪个 node 上。
注意:Redis cluster 主要是针对海量数据 + 高并发 + 高可用的场景,海量数据,如果你的数据量很大,那么建议就用 Redis cluster,数据量不是很大时,使用 sentinel 就够了。Redis cluster 的性能和高可用性均优于哨兵模式。Redis Cluster 采用虚拟哈希槽分区而非一致性 hash 算法,预先分配一些卡槽,所有的键根据哈希函数映射到这些槽内,每一个分区内的 master 节点负责维护一部分槽以及槽所映射的键值数据。
redis cluster模式(redis3.0版本之后)
1. cluster模式优点:客户端(Jedis)直连redis节点,性能会更好缺点: 1. 客户端(Jedis)直连redis节点,意味着客户端就需要保存集群所有节点信息,当集群比较大100-200个master节点时,这个数据量会比较大 2. 当集群规模比较大,100-200个master节点时,算上从节点,要到200-400个节点,互相ping pong的健康检查网络开销会非常大2. proxy模式优点: 1. 客户端(Jedis)直连有限的proxy节点,会比较轻量和简单 2. 集群规模理论上可以非常大,因为proxy对外隐藏了集群规模缺点: 1. 多了一层proxy访问,性能会有影响 2. 需要第三方的proxy实现,集群水平扩容时proxy要想平滑动态更新集群配置,需要开发工具支持3. 方案 1. 采用 twemproxy做为代理,去zk获取集群配置 2. 集群通过sentinel保证高可用4. 水平扩容 1. 开发工具,伪装成集群的slave节点,从而拿到RDB文件和增量更新数据,路由到新的集群(此处的路由算法要保证和twemproxy实际实现的一致) 2. 当开发工具评估两个集群基本一致(实时更新数据较小、偏移量追平等因素综合考虑),更新新的集群配置到zk 3. zk会动态通知twemproxy,由于twemproxy的路由算法跟4.1步骤相同,基本就可以保证平滑迁移
Proxy模式对比cluster
部署模式
当从节点发现自己的主节点变为fail状态时,便尝试进行failover,以期成为新的主节点。由于挂掉的主节点可能会有多个从节点,从而存在多个从节点竞争成为主节点的过程,其过程如下:1.从节点发现自己的主节点变为fail。2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息。3.其他节点收到该信息,只有主节点响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack。4.尝试failover的从节点收集其他主节点返回的FAILOVER_AUTH_ACK。5.从节点收到超过半数主节点的ack后变成新主节点(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)6.从节点广播pong消息通知其他集群节点,从节点并不是在主节点一进入fail状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待fail状态在集群中传播,从节点如果立即尝试选举,其它主节点尚未意识到fail状态,可能会拒绝投票。延迟计算公式:DELAY = 500ms + random(0~500ms)+SALVE_RANK*1000msSALVE_RANK表示此从节点从主节点复制数据的总量的rank。rank越小代表已复制的数据越新。这种方式下,持有最新数据的从节点将会首先发起选举。
redismaster选举机制
当一个主节点服务器被某哨兵视为下线状态后,该哨兵会与其他哨兵协商选出哨兵的leader进行故障转移工作。每个发现主节点下线的哨兵都可以要求其他哨兵选自己为哨兵的leader,选举是先到先得。每个哨兵每次选举都会自增选举周期,每个周期中只会选择一个哨兵作为的leader。如果所有超过一半的哨兵选举某哨兵作为leader。之后该哨兵进行故障转移操作,在存活的从节点中选举出新的主节点,这个选举过程跟集群的主节点选举很类似。哨兵集群哪怕只有一个哨兵节点,在主节点下线时也能正常选举出新的主节点,当然那唯一一个哨兵节点就作为leader选举新的主节点。不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个主节点类似。
哨兵leader选举流程
Redis分布式锁的底层原理:加锁机制 某个客户端要加锁。如果该客户端面对的是一个Redis Cluster集群,它首先会根据hash节点选择一台机器,这里注意,仅仅只是选择一台机器。紧接着就会发送一段lua脚本到redis上,lua脚本如下所示:使用lua脚本,可以把一大堆业务逻辑通过封装在lua脚本发送给redis,保证这段赋值业务逻辑执行的原子性。在这段脚本中,这里KEYS[1]代表的是你加锁的那个key,比如说:RLock lock = redisson.getLock(“myLock”);这里你自己设置了加锁的那个锁key就是“myLock”。 ARGV[1]代表的就是锁key的默认生存时间,默认30秒。ARGV[2]代表的是加锁的客户端的ID,类似于下面这样:8743c9c0-0795-4907-87fd-6c719a6b4586:1。 脚本的意思大概是:第一段if判断语句,就是用“exists myLock”命令判断一下,如果你要加锁的那个key不存在,就可以进行加锁。加锁就是用“hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1”命令。通过这个命令设置一个hash数据结构,这个命令执行后,会出现一个类似下面的数据结构:上述就代表“8743c9c0-0795-4907-87fd-6c719a6b4586:1”这个客户端对“myLock”这个锁key完成了加锁。接着会执行“pexpire myLock 30000”命令,设置myLock这个锁key的生存时间是30秒。好了,到此为止,ok,加锁完成了。锁互斥机制 如果这个时候客户端B来尝试加锁,执行了同样的一段lua脚本。第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在。接着第二个if判断,判断myLock锁key的hash数据结构中,是否包含客户端B的ID,但明显没有,那么客户端B会获取到pttl myLock返回的一个数字,代表myLock这个锁key的剩余生存时间。此时客户端B会进入一个while循环,不听的尝试加锁。watch dog自动延期机制 客户端A加锁的锁key默认生存时间只有30秒,如果超过了30秒,客户端A还想一直持有这把锁,怎么办?其实只要客户端A一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间。可重入加锁机制 客户端A已经持有锁了,然后可重入加锁,如下代码所示:这个时候lua脚本是这样执行的:第一个if判断不成立,“exists myLock”会显示锁key已经存在了。第二个if判断会成立,因为myLock的hash数据结构中包含的那个ID,就是客户端A的ID,此时就会执行可重入加锁的逻辑,它会用“incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1 ”这个命令对客户端A的加锁次数,累加1,此时myLock的数据结构变成下面这样:即myLock的hash数据结构中的那个客户端ID,就对应着加锁的次数。释放锁机制 执行lock.unlock(),就可以释放分布式锁。释放逻辑是:每次对myLock数据结构中的那个加锁次数减1,如果加锁次数为0了,说明客户端已经不再持有锁了,此时就会用“del MyLock”命令,从redis里删除了这个key。然后另外的客户端B就可以尝试完成加锁了。
Redis分布式锁的缺点:如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例,但是这个过程中如果发送redis master宕机,主备切换,redis slave变为了redis master。 这就会导致客户端B来尝试加锁的时候,在新的redis master上完成了加锁,而客户端A也以为自己成功加了锁,此时就会导致多个客户端对一个分布式锁完成了加锁。这时就会导致各种脏数据的产生。 所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。
Redisson方式https://zhuanlan.zhihu.com/p/150602212
package com.xiaoju.manhattan.financing.base.redis;import com.google.common.collect.Lists;import com.xiaoju.manhattan.financing.base.log.FinLogger;import com.xiaoju.manhattan.financing.base.util.Inspections;import java.util.UUID;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;import org.apache.commons.codec.digest.DigestUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.data.redis.connection.RedisStringCommands.SetOption;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.RedisScript;import org.springframework.data.redis.core.types.Expiration;/** * 基于redis的简单的分布式锁,可重入。 * * 在下面描述的极端情况下,在释放锁以后,锁还会存在 lifeSeconds 秒。 * * 假设主线程名称为\"主\",过期时间刷新线程名称为\"刷\
原理:加锁设置key对应的value,加上失效时间返回1则加锁成功解锁先判断是否为指定key,相等则删除;加锁期间若业务代码超时,可以使用线程池自动刷新机制,刷新加锁时间
使用SETNX完成同步锁的流程及事项如下:使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间释放锁,使用DEL命令将锁数据删除
setnex方式
分布式锁
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下: - 从服务器连接主服务器,发送SYNC命令; - 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; - 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; - 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; - 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; - 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。
全量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
增量同步
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
Redis主从同步策略
如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。Redis主从复制的配置十分简单,它可以使从服务器是主服务器的完全拷贝。需要清除Redis主从复制的几点重要内容:1)Redis使用异步复制。但从Redis 2.8开始,从服务器会周期性的应答从复制流中处理的数据量。2)一个主服务器可以有多个从服务器。3)从服务器也可以接受其他从服务器的连接。除了多个从服务器连接到一个主服务器之外,多个从服务器也可以连接到一个从服务器上,形成一个 图状结构。4)Redis主从复制不阻塞主服务器端。也就是说当若干个从服务器在进行初始同步时,主服务器仍然可以处理请求。5)主从复制也不阻塞从服务器端。当从服务器进行初始同步时,它使用旧版本的数据来应对查询请求,假设你在redis.conf配置文件是这么配置的。 否则的话,你可以配置当复制流关闭时让从服务器给客户端返回一个错误。但是,当初始同步完成后,需要删除旧的数据集和加载新的数据集,在 这个短暂的时间内,从服务器会阻塞连接进来的请求。6)主从复制可以用来增强扩展性,使用多个从服务器来处理只读的请求(比如,繁重的排序操作可以放到从服务器去做),也可以简单的用来做数据冗余。7)使用主从复制可以为主服务器免除把数据写入磁盘的消耗:在主服务器的redis.conf文件中配置“避免保存”(注释掉所有“保存“命令),然后连接一个配 置为“进行保存”的从服务器即可。但是这个配置要确保主服务器不会自动重启(要获得更多信息请阅读下一段)
注意点
1)采用异步复制;2)一个主redis可以含有多个从redis;3)每个从redis可以接收来自其他从redis服务器的连接;4)主从复制对于主redis服务器来说是非阻塞的,这意味着当从服务器在进行主从复制同步过程中,主redis仍然可以处理外界的访问请求;5)主从复制对于从redis服务器来说也是非阻塞的,这意味着,即使从redis在进行主从复制过程中也可以接受外界的查询请求,只不过这时候从redis返回的是以前老的数据, 如果你不想这样,那么在启动redis时,可以在配置文件中进行设置,那么从redis在复制同步过程中来自外界的查询请求都会返回错误给客户端;(虽然说主从复制过程中 对于从redis是非阻塞的,但是当从redis从主redis同步过来最新的数据后还需要将新数据加载到内存中,在加载到内存的过程中是阻塞的,在这段时间内的请求将会被阻, 但是即使对于大数据集,加载到内存的时间也是比较多的);6)主从复制提高了redis服务的扩展性,避免单个redis服务器的读写访问压力过大的问题,同时也可以给为数据备份及冗余提供一种解决方案;7)为了编码主redis服务器写磁盘压力带来的开销,可以配置让主redis不在将数据持久化到磁盘,而是通过连接让一个配置的从redis服务器及时的将相关数据持久化到磁盘, 不过这样会存在一个问题,就是主redis服务器一旦重启,因为主redis服务器数据为空,这时候通过主从同步可能导致从redis服务器上的数据也被清空;
主从复制的一些特点:
全量同步:master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件,与此同时,服务器会缓存所有接收到的来自客户端的写命令(包含增、删、改),当后台保存进程处理完毕后,会将该rdb文件传递给slave服务器,而slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存,在此之后master服务器会将在此期间缓存的命令通过redis传输协议发送给slave服务器,然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性。 部分同步:从redis 2.8版本以前,并不支持部分同步,当主从服务器之间的连接断掉之后,master服务器和slave服务器之间都是进行全量数据同步,但是从redis 2.8开始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始融入了部分同步的概念。部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了一份同步日志和同步标识,每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。当主从连接断掉之后,slave服务器隔断时间(默认1s)主动尝试和master服务器进行连接,如果从服务器携带的偏移量标识还在master服务器上的同步备份日志中,那么就从slave发送的偏移量开始继续上次的同步操作,如果slave发送的偏移量已经不再master的同步备份日志中(可能由于主从之间断掉的时间比较长或者在断掉的短暂时间内master服务器接收到大量的写操作),则必须进行一次全量更新。在部分同步过程中,master会将本地记录的同步备份日志中记录的指令依次发送给slave服务器从而达到数据一致。
Redis大概主从同步是怎么实现的?
1)在上面的全量同步过程中,master会将数据保存在rdb文件中然后发送给slave服务器,但是如果master上的磁盘空间有效怎么办呢?那么此时全部同步对于master来说将是一份十分有压力的操作了。此时可以通过无盘复制来达到目的,由master直接开启一个socket将rdb文件发送给slave服务器。(无盘复制一般应用在磁盘空间有限但是网络状态良好的情况下) 2)主从复制结构,一般slave服务器不能进行写操作,但是这不是死的,之所以这样是为了更容易的保证主和各个从之间数据的一致性,如果slave服务器上数据进行了修改,那么要保证所有主从服务器都能一致,可能在结构上和处理逻辑上更为负责。不过你也可以通过配置文件让从服务器支持写操作。(不过所带来的影响还得自己承担哦。。。) 3)主从服务器之间会定期进行通话,但是如果master上设置了密码,那么如果不给slave设置密码就会导致slave不能跟master进行任何操作,所以如果你的master服务器上有密码,那么也给slave相应的设置一下密码吧(通过设置配置文件中的masterauth); 4)关于slave服务器上过期键的处理,由master服务器负责键的过期删除处理,然后将相关删除命令已数据同步的方式同步给slave服务器,slave服务器根据删除命令删除本地的key。
主从同步中需要注意几个问题
主从复制https://www.cnblogs.com/daofaziran/p/10978628.html
2)AOF方式默认情况下Redis没有开启AOF(append only file)方式的持久化,可以在redis.conf中通过appendonly参数开启:appendonly yes在启动时Redis会逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,载入的速度相较RDB会慢一些 开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改:appendfilename appendonly.aof配置redis自动重写AOF文件的条件 auto-aof-rewrite-percentage 100 # 当目前的AOF文件大小超过上一次重写时的AOF文件大小的百分之多少时会再次进行重写,如果之前没有重写过,则以启动时的AOF文件大小为依据 auto-aof-rewrite-min-size 64mb # 允许重写的最小AOF文件大小配置写入AOF文件后,要求系统刷新硬盘缓存的机制 # appendfsync always # 每次执行写入都会执行同步,最安全也最慢appendfsync everysec # 每秒执行一次同步操作# appendfsync no # 不主动进行同步操作,而是完全交由操作系统来做(即每30秒一次),最快也最不安全 Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。此时重新启动Redis后Redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少
aof
RDB方式(默认)RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的所有数据进行快照并存储在硬盘上。进行快照的条件可以由用户在配置文件中自定义,由两个参数构成:时间和改动的键的个数。当在指定的时间内被更改的键的个数大于指定的数值时就会进行快照。RDB是redis默认采用的持久化方式,在配置文件中已经预置了3个条件:save 900 1 #900秒内有至少1个键被更改则进行快照save 300 10 #300秒内有至少10个键被更改则进行快照save 60 10000 #60秒内有至少10000个键被更改则进行快照 可以存在多个条件,条件之间是\"或\"的关系,只要满足其中一个条件,就会进行快照。 如果想要禁用自动快照,只需要将所有的save参数删除即可。Redis默认会将快照文件存储在当前目录(可CONFIG GET dir来查看)的dump.rdb文件中,可以通过配置dir和dbfilename两个参数分别指定快照文件的存储路径和文件名
Redis实现快照的过程- Redis使用fork函数复制一份当前进程(父进程)的副本(子进程);- 父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件;- 当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。- 在执行fork的时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令 ),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。 Redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。这使得我们可以通过定时备份RDB文件来实 现Redis数据库备份。RDB文件是经过压缩(可以配置rdbcompression参数以禁用压缩节省CPU占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。 除了自动快照,还可以手动发送SAVE或BGSAVE命令让Redis执行快照,两个命令的区别在于,前者是由主进程进行快照操作,会阻塞住其他请求,后者会通过fork子进程进行快照操作。 Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将一个记录一千万个字符串类型键、大小为1GB的快照文件载入到内 存中需要花费20~30秒钟。 通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
rdb(默认)
数据持久化方式及数据恢复
> - allkeys-lru: 所有的key都用LRU进行淘汰。> - volatile-lru: LRU策略淘汰已经设置过过期时间的键。> - allkeys-random:随机淘汰使用的。> - key volatile-random:随机淘汰已设置过过期时间的key> - volatile-ttl:只回收设置了过期时间的key
缓存淘汰策略
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
Redis线程模型
redis面试题https://www.cnblogs.com/javazhiyin/p/13839357.html
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。解决方案缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
缓存雪崩
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。解决方案接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。Bitmap:典型的就是哈希表缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。布隆过滤器(推荐)就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
隆过滤器
缓存穿透
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。解决方案设置热点数据永远不过期。加互斥锁,互斥锁
缓存击穿
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!解决方案直接写个缓存刷新页面,上线时手工操作一下;数据量不大,可以在项目启动的时候自动进行加载;定时刷新缓存;
缓存预热
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
缓存降级
热点数据,缓存才有价值对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
热点数据和冷数据
缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。解决方案对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
缓存热点key
缓存异常
一致性(Consistency):读操作总是能读取到之前完成的写操作结果,系统每时每刻每个节点上的同一份数据都是一致可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)分区可容忍性(Partition tolerance):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition);分区可容忍性:系统节点无法通信,系统依旧可以运行可用性和分区容忍性区别:可用性是指节点故障,如主、从redis宕机,剩下的从redis依旧可以提供服务,该系统有可用性分区容忍性是指节点间无法通信,如主从redis无法通信,系统依旧可用Redis是保证AP不保证C:系统要不管何时系统都能够响应读写请求
cap模型属于哪个?
redis
volatile
synchronized
aqs
线程
线程池
jmmjava内存模型
并发
https://www.cnblogs.com/kx33389/p/11182082.html
https://blog.csdn.net/qq_34668848/article/details/105611546
在kafka中,topic只是存储消息的一个逻辑的概念,他并没有实际的文件存在磁盘上,可以认为是某一类型的消息的集合。所有发送到kafka上的消息都一个类型,这个类型就是他的topic。在物理上来说,不同的topic的消息是分开存储的。同时,一个topic可以有多个producer和多个consumer。
topic
partition
(1)我们创建一个有三个分区的topic先,创建语句如下:./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic quanTest(2)partition是以文件的形式存储在文件系统里面的。
kafka中topic和partition的存储
1.首先消息是我们kafka最基本的数据单元,每一个消息都是由key–>value组成的,在发送消息时,我们可以指定key,我们的生产者producer会根据key和partition的规则来确认这一个消息应该分发到哪一个partition里面。2.我们消息分发到partition的默认的规则:当我们的key不为空的时候,我们的分区数为key的哈希值取模运算值。当我们的key为空的时候,他会随机发送,但是这个随机数是在这个参数”metadata.max.age.ms”的时间范围内随机选择一个,这个metadata.max.age.ms的值每十分钟会刷新一次。3.当然,我们也可以自己定义消息分发到partition的规则
消息的分发到partition的原理
1.简要说明:在真实的生产环境中,我们都是有多个partition的,使用多个partition的好处在于能有效的对broker上面的数据进行分片,减少io性能问题。同时为了提高消费能力,我们会有多个consumer对数据进行消费。在多个consumer,多个partition的消费策略有时什么?我们有分组group的概念,我们知道相同topic下的同一个partition只能被一个consumer消费。组内的所有consumer订阅这个topic下的所有的消息。2.对于consumer消费端而言他的分区分配策略是:(1)默认是范围分区:Range:他会先对所有的分区按照序号进行排序,所有的consumer按字母进行排序,然后将partitions的个数除于消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。弊端是:当有多个topic是这样的情况的时候,会出现某一个consumer的压力会很大。(2)第二种就是轮询分区策略(RoundRobin strategy)轮询分区策略就是先把所有的partition和所有的consumer都列出来,然后进行hashCode,根据hash值排序,然后根据轮询算法,把partition分配给consumer消费。如果所有consumer实例的订阅是相同的,那么partition会均匀分布。 使用轮询分区策略必须满足两个条件(1)每个主题的消费者实例具有相同数量的流(2) 每个消费者订阅的主题必须是相同的3.partition与partition的数量关系:(1)消费者数量多于partition的数量的时候,会有消费者消费不到数据的情况(2)消费者数量少于partition的数量的时候,会有消费者消费多个partition(3)一般partition是consumer的整数倍
消息的消费原理
简要的说明:kafka consumer的rebalance机制规定了同一个group下的consumer如何达成一致来消费订阅各个分区的消息,具体的策略是范围策略,或者轮询策略。1.什么时候会触发kafka consumer的rebalance(1) 同一个consumer group内新增了消费者(2)消费者离开当前所属的consumer group,比如主动停机或者宕机(3)topic新增或者减少了分区2.如何管理rebalance,以及整个过程是怎么样的?(1)首先我们会确定的一个coordinate角色,当启动第一个consumer的时候我们就会确定为coordinate,之后所有的consumer都会与这个coordinate保持通信。而我们的coordinate就是对consumer group进行管理。(2)如何确定coordinate:消费者向kafka集群中的任意一个broker发送一个GroupCoordinatorRequest请求,服务端会返回一个负载最小的broker节点的id,并将该broker设置为coordinator(3)接着,我们会进行第一阶段joinGroup(选举leader)的过程:所有的消费者都会向consumer发送joinGroup的请求,当所有的consumer都发送了请求之后,我们的coordinate就会在选举出一个consumer来作为leader,而且会把订阅消息,组成员信息反馈回去。 简要的文字说明:所有的consumer都来了的时候,coordinate会选出一个leader(4)接着再继续,我们会进入第二阶段Synchronizing Group State(同步leader的分区分配方案),这个过程简单来说就是leader把分区分配方案发送给coordinate,然后,coordinate再把这个分区发送给各个consumer。 简要说明:第二阶段就是把leader的分区分配方案,发送到coordinate,然后通过coordinate同步到组内的所有的consumer
kafka consumer的rebalance机制
(1)什么是offset:每一个消息进入partition的时候,他都会有一个偏移量,就是offset,他代表这个消息在这个partition上面的位置,他只保证在同一个partition上面是有顺序的。(2)我们是如何确保同一个分组consumer不会去重复消费同一个消息?我们通过一个叫consumer_offset_*的topic来保存已经消费的各个组的信息,commit之后,他就会保存在对应的consumer_offset分区里面,consumer_offset默认有50个分区,具体是落在哪一个分区使用过:Math.abs(“groupid”.hashCode())%50来计算得到的。
offset
1)我们的kafka的消息都是日志消息(log),他不是直接存放在磁盘上面的,而是存放在对应的topic_partitionNum目录下面2)在多个broker和多个partition,他是这样分配的:总结:他就是partition按照broker顺序一个一个轮着来分配下去3)我们写入的方式是顺序写入
消息的存储
1)我们的kafka是零拷贝的。他大概的过程是磁盘上的数据,读到内存里面,内存里面的数据再通过socket发送到消费者通过“零拷贝”技术,可以去掉这些没必要的数据复制操作,同时也会减少上下文切换次数传统的是磁盘-内核空间-用户空间--socketbuffer-网卡buffer零拷贝是磁盘-内核空间-网卡buffer只需要一次拷贝就行,允许操作系统将数据直接从页缓存发送到网络上
消息的性能问题
https://zhuanlan.zhihu.com/p/944122661)Producer :消息生产者,就是向kafka broker发消息的客户端;2)Consumer :消息消费者,向kafka broker取消息的客户端;3)Consumer Group (CG):消费者组,由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。4)Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。5)Topic :可以理解为一个队列,生产者和消费者面向的都是一个topic;6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列;7)Replica:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。8)leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。9)follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的follower。
架构https://blog.csdn.net/Computer_hello/article/details/109549542
详细介绍及Spring集成https://blog.csdn.net/james_xiaojun/article/details/103380297
分区包含java代码https://blog.csdn.net/Computer_hello/article/details/109549542
1、日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如Hadoop、Hbase、Solr等2、消息系统:解耦和生产者和消费者、缓存消息等3、用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到Hadoop、数据仓库中做离线分析和挖掘4、运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告5、流式处理:比如spark streaming和storm6、事件源
1、Kafka的用途有哪些?使用场景如何?
2、 Kafka中的ISR、AR又代表什么?ISR的伸缩又指什么
3 Kafka中的HW、LEO、LSO、LW等分别代表什么?
1、一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。2、写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
4、Kafka中是怎么体现消息顺序性的?
5、Kafka中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?
6、Kafka生产者客户端的整体结构是什么样子的?
消费者消费后没有commit offset(程序崩溃/强行kill/消费耗时/自动提交偏移情况下unscrible)
有哪些情形会造成重复消费?
先提交offset,后消费,有可能造成数据的重复
那些情景下会造成消息漏消费?
面试题https://blog.csdn.net/qq_34668848/article/details/105611546
kafaka
架构图
1)producer发送消息的负载均衡:默认会轮询向Topic的所有queue发送消息,以达到消息平均落到不同的queue上;而由于queue可以落在不同的broker上,就可以发到不同broker上(当然也可以指定发送到某个特定的queue上)
pruducer
2)consumer订阅消息的负载均衡:假设有5个队列,两个消费者,则第一个消费者消费3个队列,第二个则消费2个队列,以达到平均消费的效果。而需要注意的是,当consumer的数量大于队列的数量的话,根据rocketMq的机制,多出来的队列不会去消费数据,因此建议consumer的数量小于或者等于queue的数量,避免不必要的浪费
consumer
负载均衡
01.为什么要用RocketMq?吞吐量高:单机吞吐量可达十万级可用性高:分布式架构消息可靠性高:经过参数优化配置,消息可以做到0丢失功能支持完善:MQ功能较为完善,还是分布式的,扩展性好支持10亿级别的消息堆积:不会因为堆积导致性能下降源码是java:方便我们查看源码了解它的每个环节的实现逻辑,并针对不同的业务场景进行扩展可靠性高:天生为金融互联网领域而生,对于要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况
05.rocketmq如何保证高可用性?1)集群化部署NameServer。Broker集群会将所有的broker基本信息、topic信息以及两者之间的映射关系,轮询存储在每个NameServer中(也就是说每个NameServer存储的信息完全一样)。因此,NameServer集群化,不会因为其中的一两台服务器挂掉,而影响整个架构的消息发送与接收;2)集群化部署多broker。producer发送消息到broker的master,若当前的master挂掉,则会自动切换到其他的mastercosumer默认会访问broker的master节点获取消息,那么master节点挂了之后,该怎么办呢?它就会自动切换到同一个broker组的slave节点进行消费那么你肯定会想到会有这样一个问题:consumer要是直接消费slave节点,那master在宕机前没有来得及把消息同步到slave节点,那这个时候,不就会出现消费者不就取不到消息的情况了?这样,就引出了下一个措施,来保证消息的高可用性3)设置同步复制前面已经提到,消息发送到broker的master节点上,master需要将消息复制到slave节点上,rocketmq提供两种复制方式:同步复制和异步复制异步复制,就是消息发送到master节点,只要master写成功,就直接向客户端返回成功,后续再异步写入slave节点同步复制,就是等master和slave都成功写入内存之后,才会向客户端返回成功那么,要保证高可用性,就需要将复制方式配置成同步复制,这样即使master节点挂了,slave上也有当前master的所有备份数据,那么不仅保证消费者消费到的消息是完整的,并且当master节点恢复之后,也容易恢复消息数据在master的配置文件中直接配置brokerRole:SYNC_MASTER即可
06.rocketmq的工作流程是怎样的?RocketMq的工作流程如下:1)首先启动NameServer。NameServer启动后监听端口,等待Broker、Producer以及Consumer连上来2)启动Broker。启动之后,会跟所有的NameServer建立并保持一个长连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系3)创建Topic。创建时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic4)Producer发送消息。启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic所在的Broker;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送5)Consumer消费消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,进行消息的消费
07.RocketMq使用哪种方式消费消息,pull还是push?RocketMq提供两种方式:pull和push进行消息的消费而RocketMq的push方式,本质上也是采用pull的方式进行实现的。也就是说这两种方式本质上都是采用consumer轮询从broker拉取消息的push方式里,consumer把轮询过程封装了一层,并注册了MessageListener监听器。当轮询取到消息后,便唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉好像消息是被推送过来的其实想想,消息统一都发到了broker,而broker又不会主动去push消息,那么消息肯定都是需要消费者主动去拉的喽~
08.RocketMq如何负载均衡?1)producer发送消息的负载均衡:默认会轮询向Topic的所有queue发送消息,以达到消息平均落到不同的queue上;而由于queue可以落在不同的broker上,就可以发到不同broker上(当然也可以指定发送到某个特定的queue上)2)consumer订阅消息的负载均衡:假设有5个队列,两个消费者,则第一个消费者消费3个队列,第二个则消费2个队列,以达到平均消费的效果。而需要注意的是,当consumer的数量大于队列的数量的话,根据rocketMq的机制,多出来的队列不会去消费数据,因此建议consumer的数量小于或者等于queue的数量,避免不必要的浪费
09.RocketMq的存储机制了解吗?RocketMq采用文件系统进行消息的存储,相对于ActiveMq采用关系型数据库进行存储的方式就更直接,性能更高了RocketMq与Kafka在写消息与发送消息上,继续沿用了Kafka的这两个方面:顺序写和零拷贝1)顺序写我们知道,操作系统每次从磁盘读写数据的时候,都需要找到数据在磁盘上的地址,再进行读写。而如果是机械硬盘,寻址需要的时间往往会比较长而一般来说,如果把数据存储在内存上面,少了寻址的过程,性能会好很多;但Kafka 的数据存储在磁盘上面,依然性能很好,这是为什么呢?这是因为,rocketmq采用的是顺序写,直接追加数据到末尾。实际上,磁盘顺序写的性能极高,在磁盘个数一定,转数一定的情况下,基本和内存速度一致因此,磁盘的顺序写这一机制,极大地保证了Kafka本身的性能2)零拷贝比如:读取文件,再用socket发送出去这一过程buffer = File.readSocket.send(buffer)传统方式实现:先读取、再发送,实际会经过以下四次复制1、将磁盘文件,读取到操作系统内核缓冲区Read Buffer2、将内核缓冲区的数据,复制到应用程序缓冲区Application Buffer3、将应用程序缓冲区Application Buffer中的数据,复制到socket网络发送缓冲区4、将Socket buffer的数据,复制到网卡,由网卡进行网络传输传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的重新思考传统IO方式,会注意到在读取磁盘文件后,不需要做其他处理,直接用网络发送出去的这种场景下,第二次和第三次数据的复制过程,不仅没有任何帮助,反而带来了巨大的开销。那么这里使用了零拷贝,也就是说,直接由内核缓冲区Read Buffer将数据复制到网卡,省去第二步和第三步的复制。那么采用零拷贝的方式发送消息,必定会大大减少读取的开销,使得RocketMq读取消息的性能有一个质的提升此外,还需要再提一点,零拷贝技术采用了MappedByteBuffer内存映射技术,采用这种技术有一些限制,其中有一条就是传输的文件不能超过2G,这也就是为什么RocketMq的存储消息的文件CommitLog的大小规定为1G的原因小结:RocketMq采用文件系统存储消息,并采用顺序写写入消息,使用零拷贝发送消息,极大得保证了RocketMq的性能
10.RocketMq的存储结构是怎样的?如图所示,消息生产者发送消息到broker,都是会按照顺序存储在CommitLog文件中,每个commitLog文件的大小为1GCommitLog-存储所有的消息元数据,包括Topic、QueueId以及messageCosumerQueue-消费逻辑队列:存储消息在CommitLog的offsetIndexFile-索引文件:存储消息的key和时间戳等信息,使得RocketMq可以采用key和时间区间来查询消息也就是说,rocketMq将消息均存储在CommitLog中,并分别提供了CosumerQueue和IndexFile两个索引,来快速检索消息
12.RocketMq性能比较高的原因?就是前面在文件存储机制中所提到的:RocketMq采用文件系统存储消息,采用顺序写的方式写入消息,使用零拷贝发送消息,这三者的结合极大地保证了RocketMq的性能
https://www.wwwbuild.net/huangtalkit/23928.html
面试题
rocketmq
消费者消费单个topic
消费者拉去数量控制offset设置
消费者拉取消息的时间间隔设置
代码层面可以使用多线程解决速度问题
拉取消息的大小设置
kafaka消息积压解决方案
1.实时/消费任务挂掉比如,我们写的实时应用因为某种原因挂掉了,并且这个任务没有被监控程序监控发现通知相关负责人,负责人又没有写自动拉起任务的脚本进行重启。那么在我们重新启动这个实时应用进行消费之前,这段时间的消息就会被滞后处理,如果数据量很大,可就不是简单重启应用直接消费就能解决的。解决方案:a.任务重新启动后直接消费最新的消息,对于\"滞后\"的历史数据采用离线程序进行\"补漏\"。此外,建议将任务纳入监控体系,当任务出现问题时,及时通知相关负责人处理。当然任务重启脚本也是要有的,还要求实时框架异常处理能力要强,避免数据不规范导致的不能重新拉起任务。b.任务启动从上次提交offset处开始消费处理如果积压的数据量很大,需要增加任务的处理能力,比如增加资源,让任务能尽可能的快速消费处理,并赶上消费最新的消息
2.Kafka分区数设置的不合理(太少)和消费者\"消费能力\"不足Kafka单分区生产消息的速度qps通常很高,如果消费者因为某些原因(比如受业务逻辑复杂度影响,消费时间会有所不同),就会出现消费滞后的情况。此外,Kafka分区数是Kafka并行度调优的最小单元,如果Kafka分区数设置的太少,会影响Kafka consumer消费的吞吐量。解决方案:如果数据量很大,合理的增加Kafka分区数是关键。如果利用的是Spark流和Kafka direct approach方式,也可以对KafkaRDD进行repartition重分区,增加并行度处理。
3.Kafka消息的key不均匀,导致分区间数据不均衡在使用Kafka producer消息时,可以为消息指定key,但是要求key要均匀,否则会出现Kafka分区间数据不均衡。解决方案:可以在Kafka producer处,给key加随机后缀,使其均衡。
消息积压问题原因及解决方案
多个topic读取消息如何保证顺序性
1、我们可以对比下kafka和rocketMq在协调节点选择上的差异,kafka通过zookeeper来进行协调,而rocketMq通过自身的namesrv进行协调。 2、kafka在具备选举功能,在Kafka里面,Master/Slave的选举,有2步:第1步,先通过ZK在所有机器中,选举出一个KafkaController;第2步,再由这个Controller,决定每个partition的Master是谁,Slave是谁。因为有了选举功能,所以kafka某个partition的master挂了,该partition对应的某个slave会升级为主对外提供服务。 3、rocketMQ不具备选举,Master/Slave的角色也是固定的。当一个Master挂了之后,你可以写到其他Master上,但不能让一个Slave切换成Master。那么rocketMq是如何实现高可用的呢,其实很简单,rocketMq的所有broker节点的角色都是一样,上面分配的topic和对应的queue的数量也是一样的,Mq只能保证当一个broker挂了,把原本写到这个broker的请求迁移到其他broker上面,而并不是这个broker对应的slave升级为主。 4、rocketMq在协调节点的设计上显得更加轻量,用了另外一种方式解决高可用的问题,思路也是可以借鉴的。作者:晴天哥_王志链接:https://www.jianshu.com/p/c474ca9f9430来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于吞吐量1、首先说明下面的几张图片来自于互联网共享,也就是我后面参考文章里面的列出的文章。2、kafka在消息存储过程中会根据topic和partition的数量创建物理文件,也就是说我们创建一个topic并指定了3个partition,那么就会有3个物理文件目录,也就说说partition的数量和对应的物理文件是一一对应的。3、rocketMq在消息存储方式就一个物流问题,也就说传说中的commitLog,rocketMq的queue的数量其实是在consumeQueue里面体现的,在真正存储消息的commitLog其实就只有一个物理文件。4、kafka的多文件并发写入 VS rocketMq的单文件写入,性能差异kafka完胜可想而知。5、kafka的大量文件存储会导致一个问题,也就说在partition特别多的时候,磁盘的访问会发生很大的瓶颈,毕竟单个文件看着是append操作,但是多个文件之间必然会导致磁盘的寻道。作者:晴天哥_王志链接:https://www.jianshu.com/p/c474ca9f9430来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
https://blog.csdn.net/damacheng/article/details/42846549数据可靠性RocketMQ支持异步实时刷盘,同步刷盘,同步Replication,异步ReplicationKafka使用异步刷盘方式,异步Replication总结:RocketMQ的同步刷盘在单机可靠性上比Kafka更高,不会因为操作系统Crash,导致数据丢失。 同时同步Replication也比Kafka异步Replication更可靠,数据完全无单点。另外Kafka的Replication以topic为单位,支持主机宕机,备机自动切换,但是这里有个问题,由于是异步Replication,那么切换后会有数据丢失,同时Leader如果重启后,会与已经存在的Leader产生数据冲突。开源版本的RocketMQ不支持Master宕机,Slave自动切换为Master,阿里云版本的RocketMQ支持自动切换特性。性能对比Kafka单机写入TPS约在百万条/秒,消息大小10个字节RocketMQ单机写入TPS单实例约7万条/秒,单机部署3个Broker,可以跑到最高12万条/秒,消息大小10个字节总结:Kafka的TPS跑到单机百万,主要是由于Producer端将多个小消息合并,批量发向Broker。RocketMQ为什么没有这么做?Producer通常使用Java语言,缓存过多消息,GC是个很严重的问题Producer调用发送消息接口,消息未发送到Broker,向业务返回成功,此时Producer宕机,会导致消息丢失,业务出错Producer通常为分布式系统,且每台机器都是多线程发送,我们认为线上的系统单个Producer每秒产生的数据量有限,不可能上万。缓存的功能完全可以由上层业务完成。单机支持的队列数Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长RocketMQ单机支持最高5万个队列,Load不会发生明显变化队列多有什么好处?单机可以创建更多Topic,因为每个Topic都是由一批队列组成Consumer的集群规模和队列数成正比,队列越多,Consumer集群可以越大消息投递实时性Kafka使用短轮询方式,实时性取决于轮询间隔时间RocketMQ使用长轮询,同Push方式实时性一致,消息的投递延时通常在几个毫秒。消费失败重试Kafka消费失败不支持重试RocketMQ消费失败支持定时重试,每次重试间隔时间顺延总结:例如充值类应用,当前时刻调用运营商网关,充值失败,可能是对方压力过多,稍后在调用就会成功,如支付宝到银行扣款也是类似需求。这里的重试需要可靠的重试,即失败重试的消息不因为Consumer宕机导致丢失。严格的消息顺序Kafka支持消息顺序,但是一台Broker宕机后,就会产生消息乱序RocketMQ支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序Mysql Binlog分发需要严格的消息顺序定时消息Kafka不支持定时消息RocketMQ支持两类定时消息开源版本RocketMQ仅支持定时Level阿里云ONS支持定时Level,以及指定的毫秒级别的延时时间分布式事务消息Kafka不支持分布式事务消息阿里云ONS支持分布式定时消息,未来开源版本的RocketMQ也有计划支持分布式事务消息消息查询Kafka不支持消息查询RocketMQ支持根据Message Id查询消息,也支持根据消息内容查询消息(发送消息时指定一个Message Key,任意字符串,例如指定为订单Id)总结:消息查询对于定位消息丢失问题非常有帮助,例如某个订单处理失败,是消息没收到还是收到处理出错了。消息回溯Kafka理论上可以按照Offset来回溯消息RocketMQ支持按照时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息总结:典型业务场景如consumer做订单分析,但是由于程序逻辑或者依赖的系统发生故障等原因,导致今天消费的消息全部无效,需要重新从昨天零点开始消费,那么以时间为起点的消息重放功能对于业务非常有帮助。消费并行度Kafka的消费并行度依赖Topic配置的分区数,如分区数为10,那么最多10台机器来并行消费(每台机器只能开启一个线程),或者一台机器消费(10个线程并行消费)。即消费并行度和分区数一致。RocketMQ消费并行度分两种情况顺序消费方式并行度同Kafka完全一致乱序方式并行度取决于Consumer的线程数,如Topic配置10个队列,10台机器消费,每台机器100个线程,那么并行度为1000。消息轨迹Kafka不支持消息轨迹阿里云ONS支持消息轨迹开发语言友好性Kafka采用Scala编写RocketMQ采用Java语言编写Broker端消息过滤Kafka不支持Broker端的消息过滤RocketMQ支持两种Broker端消息过滤方式根据Message Tag来过滤,相当于子topic概念向服务器上传一段Java代码,可以对消息做任意形式的过滤,甚至可以做Message Body的过滤拆分。消息堆积能力理论上Kafka要比RocketMQ的堆积能力更强,不过RocketMQ单机也可以支持亿级的消息堆积能力,我们认为这个堆积能力已经完全可以满足业务需求。开源社区活跃度Kafka社区更新较慢RocketMQ的github社区有250个个人、公司用户登记了联系方式,QQ群超过1000人。商业支持Kafka原开发团队成立新公司,目前暂没有相关产品看到RocketMQ在阿里云上已经开放公测近半年,目前以云服务形式免费供大家商用,并向用户承诺99.99%的可靠性,同时彻底解决了用户自己搭建MQ产品的运维复杂性问题成熟度Kafka在日志领域比较成熟RocketMQ在阿里集团内部有大量的应用在使用,每天都产生海量的消息,并且顺利支持了多次天猫双十一海量消息考验,是数据削峰填谷的利器。
mq对比
mq
选举机制
1、leader 接受到消息请求后,将消息赋予给一个全局唯一的64位自增id,叫:zxid,通过zxid的代销比较即可以实现因果有序的这个特征2、leader 为每个follower 准备了一个FIFO队列(通过TCP协议来实现,以实现了全局有序这个特点)将带有zxid的消息作为一个提案(proposal)分发给所有的follower3、当follower接受到proposal,先把proposal写到磁盘,写入成功以后再向leader恢复一个ack4、当leader 接受到合法数量(超过半数节点)的 ack,leader 就会向这些follower发送commit命令,同时会在本地执行该消息5、当follower接受到消息的commit命令以后,就会提交该消息————————————————版权声明:本文为CSDN博主「菜鸟编程98K」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_39938758/article/details/105754198
leader-follower数据同步
zookeeper
限流算法
限流
知识点总结
0 条评论
回复 删除
下一页