数据库MySQL/Redis & TCP & SpringBean知识整理

1、MySQL binlog、redo、undo日志对比:

innodb引擎中的redo/undo log与mysql binlog是完全不同的日志,它们主要有以下几个区别:

  • a)层次不同。redo/undo log是innodb层维护的,而binlog是mysql server层维护的,跟采用何种引擎没有关系,记录的是所有引擎的更新操作的日志记录。innodb的redo/undo log更详细的说明可以参见姜承尧的《mysql技术内幕-innodb存储引擎》一书中相关章节。

  • b)记录内容不同。redo/undo日志记录的是每个页的修改情况,属于物理日志+逻辑日志结合的方式(redo log物理到页,页内采用逻辑日志,undo log采用的是逻辑日志),目的是保证数据的一致性。binlog记录的都是事务操作内容,比如一条语句DELETE FROM TABLE WHERE i > 1之类的,不管采用的是什么引擎,当然格式是二进制的,要解析日志内容可以用这个命令mysqlbinlog -vv BINLOG

  • c)记录时机不同。redo/undo日志在事务执行过程中会不断的写入;而binlog仅仅在事务提交后才写入到日志,之前描述有误,binlog是在事务最终commit前写入的,多谢anti-semicolon 指出。当然,binlog什么时候刷新到磁盘跟参数sync_binlog相关。

显然,我们执行SELECT等不涉及数据更新的语句是不会记binlog的,而涉及到数据更新则会记录。要注意的是,对支持事务的引擎如innodb而言,必须要提交了事务才会记录binlog。

binlog刷新到磁盘的时机跟sync_binlog参数相关,如果设置为0,则表示MySQL不控制binlog的刷新,由文件系统去控制它缓存的刷新,而如果设置为不为0的值则表示每sync_binlog次事务,MySQL调用文件系统的刷新操作刷新binlog到磁盘中。设为1是最安全的,在系统故障时最多丢失一个事务的更新,但是会对性能有所影响,一般情况下会设置为100或者0,牺牲一定的一致性来获取更好的性能。

通过命令SHOW MASTER LOGS可以看到当前的binlog数目。如下面就是我机器上的mysql的binlog情况,第一列是binlog文件名,第二列是binlog文件大小。可以通过设置expire_logs_days来指定binlog保留时间,要手动清理binlog可以通过指定binlog名字或者指定保留的日期,命令分别是:purge master logs to BINLOGNAME;purge master logs before DATE;

------------------------------

摘自:

binlog、redo、undo日志对比:http://www.php.cn/mysql-tutorials-361643.html

2、ArrayList、LinkedList、HashMap初始及扩容

ArrayList:初始为空,add是初始容量为10,空间不足时(如,添加第11个元素时)扩容,扩容时扩容为1.5倍

LinkedList:无初始,add即可

HashMap:初始大小16,承载因子0.75,扩容时扩容为2倍

3、Redis底层怎么实现?

Redis是什么,优点有哪些?

是什么?

Redis是一个K-V的非关系型数据库(NoSQL)。常见的NoSQL数据库有:K-V数据库如Redis、Memcached,列式数据库如大数据组件HBase,文档数据库如mogoDB。Redis应用广泛,尤其是被作为缓存使用。

好处?

(1)读写性能高--100000次/s以上的读速度,80000次/s以上的写速度;

(2)K-V,value支持的数据类型很多:字符串(String),队列(List),哈希(Hash),集合(Sets),有序集合(Sorted Sets)5种不同的数据类型。

(3)原子性,Redis的所有操作都是单线程原子性的。

(4)特性丰富--支持订阅-发布模式,通知、设置key过期等特性。

(5)在Redis3.0 版本引入了Redis集群,可用于分布式部署。

Redis数据类型及底层实现方式

Redis是由C语言编写的。Redis支持5种数据类型,以K-V形式进行存储,K是String类型的,V支持5种不同的数据类型,分别是:string,list,hash,set,sorted set,每一种数据结构都有其特定的应用场景。从内部实现的角度来看是如何更好的实现这些数据类型。Redis底层数据结构有以下数据类型:简单动态字符串(SDS),链表,字典,跳跃表,整数集合,压缩列表,对象。接下来,就探讨一下Redis是怎么通过这些数据结构来实现value的5种类型的。

1、简单动态字符串(simple dynamic string -- SDS)

定义:

/* 

 * 保存字符串对象的结构 

 */ 

struct sdshdr { 

     

    // buf 中已占用空间的长度 

    int len; 

 

    // buf 中剩余可用空间的长度 

    int free; 

 

    // 数据空间 

    char buf[]; 

用途:

实现字符串类型,还用作AOF持久化时的缓冲区。

好处:

获取字符串长度(O(1))

防止缓冲区溢出

减少扩展或收缩字符串带来的内存重分配次数

二进制安全

2、链表

定义:

typedef struct listNode{

      struct listNode *prev;

      struct listNode * next;

      void * value; 

}



typedef struct list{

    //表头节点

    listNode  * head;

    //表尾节点

    listNode  * tail;

    //链表长度

    unsigned long len;

    //节点值复制函数

    void *(*dup) (void *ptr);

    //节点值释放函数

    void (*free) (void *ptr);

    //节点值对比函数

    int (*match)(void *ptr, void *key);

}

用途:

作为List的底层实现。

好处:

链表结构的特点是可以快速的在表头和表尾插入和删除元素,但查找复杂度高,是列表的底层实现之一,也因此列表没有提供判断某一元素是否在列表中的借口,因为在链表中查找复杂度高。

3、字典

定义:

字典,又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对的抽象数据结构。 

在字典中,一个键(key)可以和一个值(value)进行关联,字典中的每个键都是独一无二的。

typedef struct dict {

    // 类型特定函数

    dictType *type;

    // 私有数据

    void *privedata;

    // 哈希表

    dictht  ht[2];

    // rehash 索引

    in trehashidx;



}



typedef struct dictht {

   //哈希表数组

   dictEntry **table;

   //哈希表大小

   unsigned long size;



   //哈希表大小掩码,用于计算索引值

   unsigned long sizemask;

   //该哈希表已有节点的数量

   unsigned long used;

}



typeof struct dictEntry{

   //键

   void *key;

   //值

   union{

      void *val;

      uint64_tu64;

      int64_ts64;

   }

   struct dictEntry *next;



}

我们存入里面的key 并不是直接的字符串,而是一个hash 值,通过hash 算法,将字符串转换成对应的hash 值,然后在dictEntry 中找到对应的位置。

 这时候我们会发现一个问题,如果出现hash 值相同的情况怎么办?Redis 采用了链地址法来解决hash冲突。这与hashmap的实现类似

解决hash冲突:采用链地址法来实现。

扩充Rehash:随着对哈希表的不断操作,哈希表保存的键值对会逐渐的发生改变,为了让哈希表的负载因子维持在一个合理的范围之内,我们需要对哈希表的大小进行相应的扩展或者压缩,这时候,我们可以通过 rehash(重新散列)操作来完成。其实现方式和hashmap略有不同,因为dict有两个hash表dictht,所以它是通过这两个dictht互相进行转移的(dictht  ht[2]的原因)。

Rehash操作渐进式,rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的。采用渐进式rehash 的好处在于它采取分而治之的方式,避免了集中式rehash 带来的庞大计算量。

详情参考:https://blog.csdn.net/u013679744/article/details/79195563

用途:

Redis本身的K-V存储就是利用字典这种数据结构的,另外value类型的哈希表也是通过这个实现的。

4、跳跃表

定义:

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成。

 Redis 的跳跃表 主要由两部分组成:zskiplist(链表)和zskiplistNode (节点)

typedef struct zskiplistNode{

   //层

     struct zskiplistLevel{

     //前进指针

        struct zskiplistNode *forward;

    //跨度

        unsigned int span;

    } level[];

  //后退指针

    struct zskiplistNode *backward;

  //分值

    double score;

  //成员对象

    robj *obj;

}

1、层:level 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针。level数组的每个元素都包含:前进指针:用于指向表尾方向的前进指针,跨度:用于记录两个节点之间的距离

2、后退指针:用于从表尾向表头方向访问节点

3、分值和成员:跳跃表中的所有节点都按分值从小到大排序(按照这个进行排序的,也就是平衡二叉树(搜索树的)的节点大小)。成员对象指向一个字符串,这个字符串对象保存着一个SDS值(实际存储的值)

typedef struct zskiplist {

     //表头节点和表尾节点

     structz skiplistNode *header,*tail;

     //表中节点数量

     unsigned long length;

     //表中层数最大的节点的层数

     int level;



}zskiplist;

整体结构:

从结构图中我们可以清晰的看到,header,tail分别指向跳跃表的头结点和尾节点。level 用于记录最大的层数,length 用于记录我们的节点数量。

跳跃表是有序集合的底层实现之一

主要有zskiplist 和zskiplistNode两个结构组成

   每个跳跃表节点的层高都是1至32之间的随机数

   在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的对象必须是唯一的

   节点按照分值的大小从大到小排序,如果分值相同,则按成员对象大小排序

用途:

一个是实现有序集合键(sorted Sets),另外一个是在集群节点中用作内部数据结构。

其实跳表主要是来替代平衡二叉树的,比起平衡树来说,跳表的实现要简单直观的多。

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成。

5、整数集合

整数集合是集合建(sets)的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。

typedef struct intset{

    //编码方式

    uint32_t enconding;

   // 集合包含的元素数量

    uint32_t length;

    //保存元素的数组   

    int8_t contents[];



} 

整数集合是集合建的底层实现之一.

整数集合的底层实现为数组,这个数组以有序,无重复的范式保存集合元素,在有需要时,程序会根据新添加的元素类型改变这个数组的类型.

6、压缩列表

压缩列表是列表键(list)和哈希键(hash)的底层实现之一。当一个列表键只有少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,那么Redis 就会使用压缩列表来做列表键的底层实现。

  •   1、zlbytes:用于记录整个压缩列表占用的内存字节数
  •   2、zltail:记录要列表尾节点距离压缩列表的起始地址有多少字节
  •   3、zllen:记录了压缩列表包含的节点数量。
  •   4、entryX:要说列表包含的各个节点
  •   5、zlend:用于标记压缩列表的末端

作用:

压缩列表是一种为了节约内存而开发的顺序型数据结构

压缩列表被用作列表键和哈希键的底层实现之一

压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值

添加新节点到压缩列表,可能会引发连锁更新操作。

参考:

Redis基础及底层实现:https://blog.csdn.net/u013679744/article/details/79195563

Redis内存淘汰、主备复制:https://blog.csdn.net/u013679744/article/details/79203933

Redis持久化数据方式对比:https://www.cnblogs.com/xiaoxi/p/7065328.html

Redis各种数据类型应用场景:https://www.cnblogs.com/xiaoxi/p/7007695.html

4、Redis高可用

主备复制:

Redis的同步策略是:主从刚刚连接的时候,进行全量同步;全量同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

全量复制图示:

Redis哨兵:

Redis-Sentinel也就是哨兵机制,是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。
它的主要功能有以下几点

  • 不时地监控redis是否按照预期良好地运行;
  • 如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);
  • 能够进行自动切换(进行主备切换)。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。

Sentinel支持集群
很显然,只使用单个sentinel进程来监控redis集群是不可靠的,当sentinel进程宕掉后(sentinel本身也有单点问题,single-point-of-failure)整个集群系统将无法按照预期的方式运行。所以有必要将sentinel集群,这样有几个好处:

  • 即使有一些sentinel进程宕掉了,依然可以进行redis集群的主备切换;
  • 如果只有一个sentinel进程,如果这个进程运行出错,或者是网络堵塞,那么将无法实现redis集群的主备切换(单点问题);
  • 如果有多个sentinel,redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。

摘自:https://blog.csdn.net/u013679744/article/details/79203933 

5、HTTP相关

GET/POST区别:

GET和POST还有一个重大区别,简单的说:

GET产生一个TCP数据包;POST产生两个TCP数据包。

长的说:

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

外表表现区别:

你轻轻松松的给出了一个“标准答案”:

  • GET参数通过URL传递,POST放在Request body中。
  • GET在浏览器回退时是无害的,而POST会再次提交请求。
  • GET产生的URL地址可以被Bookmark,而POST不可以。
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  • GET请求只能进行url编码,而POST支持多种编码方式。
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  • GET请求在URL中传送的参数是有长度限制的,而POST么有。
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

摘自:https://www.cnblogs.com/huaxingtianxia/p/5895236.html

参考:https://blog.csdn.net/zangdaiyang1991/article/details/84642797

TCP/IP:

https://blog.csdn.net/zangdaiyang1991/article/details/84642797

TCP三次握手与四次挥手:

摘自:https://blog.csdn.net/qzcsu/article/details/72861891

滑动窗口:https://blog.csdn.net/m0_37962600/article/details/79951780

6、hashmap为什么线程不安全,及高并发建议

1、hash冲突时,两个线程同时修改链表或者红黑树的结构,会出现数据覆盖等现象

会出现的问题:

  1. 数据丢失

  2. 数据重复

  3. 死循环(Java8不存在了)

2、加载因子0.75,扩容的代价比较大,

需要重新计算hash,旧桶数组中的某个桶的外挂单链表是通过头插法插入新桶数组中的,并且原链表中的Entry结点并不一定仍然在新桶数组的同一链表

扩容机制:https://blog.csdn.net/u014532901/article/details/78936283

参考:http://blog.sina.com.cn/s/blog_5bba80460102wd48.html

           http://www.importnew.com/21429.html

           https://blog.csdn.net/wangyiyungw/article/details/82455196

7、Spring bean的生命周期

参考:

Spring bean的生命周期:https://www.cnblogs.com/zrtqsk/p/3735273.html

                                         https://www.cnblogs.com/redcool/p/6397398.html

猜你喜欢

转载自blog.csdn.net/zangdaiyang1991/article/details/89893147