1.锁:
锁的定义:用于对共享资源的控制
Java的锁有两种,lock接口和synchronized关键字。
synchronized:方法、代码块,如果方法是非静态的,取得的锁是对象。如果作用的是类、静态方法,取得的锁是类,该类所有对象共用一把锁
lock:sync的缺点:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程,无法知道是否成功获取到锁
lock(),unlock(),trylock(time)在超时时间内获取到锁返回true,lockInterruptibly()可中断锁
锁的分类
要不要同步锁住资源:乐观锁,悲观锁
乐观锁:比较乐观,认为自己在处理操作的时候,不会有其它线程来干扰,所以并不会锁住操作对象
在更新的时候,去对比我修改期间的数据有没有被改变过,如没有,就正常的修改数据,在mysql表里添加一个verson字段,每次更新记录都是加一,在更新时,如果version对不上,表示在此期间数据被修改过了。
如果数据和我一开始拿到的不一样了,说明其他人在这段时间内改过,会选择放弃,报错,重试等策略
适合并发写入少,读取多的情况,提高读取性能
悲观锁:认为如果我不锁住这个资源,别人就会来争抢,就会造成数据结果错误,所以它会锁住操作对象,Java中悲观锁的实现就是syn和Lock相关类多线程是否能共享一把锁:共享锁,独占锁
独占锁;写锁。ReentrantReadWriteLock.WriteLock
共享锁:读锁。ReentrantReadWriteLock.ReadLock多线程竞争时,是否要排队:公平锁,非公平锁。
new Reentrant(true)
在项目中的应用:
Lock,有一个从其他系统同步卫星传感器数据的功能,由客户在页面点击按钮发起,这是一个全局的功能,由于执行时间较长,因此使用了ReenTrantLock的tryLock方法,设置超时时间,如果获取到锁就执行同步数据功能,如果没有获取到锁说明同步数据功能正在执行,返回给客户信息。这样可以防止客户重复发起,及时给客户反馈。(此处用的是单机应用,如果是分布式,需要用到分布式锁)
synchronized,开发netty网络接口时,为了channel的可复用,设置了channel连接池,有两个大小为10的数组,一个数组存储channel,一个数组存储channel对应的锁,一个channel对应一把锁,在使用channel时,随机从数组中取出channel,如果channel已建立,返回channel,如果还未建立,则创建一个。在创建时,用synchronized,给channel对应的锁对象添加锁,然后再次判断channel是否已建立,如果还没建立,则创建channel。这里用到了单例模式的双重检测。
还有就是netty的网络io(包括接收和发送数据)是异步的,所以每次发送数据需要新建一个回调对象,用于接收返回数据,然后执行对象的wait和notify方法,在执行这两个方法前,需要用synchronized给回调对象加锁。
2. mysql隔离级别:
先说定义
事务的定义:事务就是要保证一组数据库操作,要么全部成功,要么全部失败。
数据库事务隔离级别定义:主要作用是实现事务工作期间,数据库操作读的隔离特性,所谓读的操作就是将数据页可以调取到内存;然后可以读取数据页中相应数据行的能力,并且不同事务之间的数据页读操作相互隔离;可以简单理解为:一个事务在对数据页中数据行做更新操作时,在没有更新提交前,另一个事务此时是不能读取数据页中数据行内容的;
- 读未提交:可以读取到事务未提交的数据,隔离性差,会出现脏读(当前内存读),不可重复读,幻读问题
脏读:事务B的更新数据,没有提交,被事务A读取,但是事务B回滚了,更新数据全部还原,也就是说事务A刚刚读到的数据并没有存在于数据库中。从宏观来看,就是事务A读出了一条不存在的数据 - 读已提交:可以读取到事务已提交的数据,隔离性一般,不会出现脏读问题,但是会出现不可重复读,幻读问题;
不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致 - 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。可以防止脏读(当前内存读),防止不可重复读问题,防止会出现的幻读问题,但是并发能力较差。在可重复读隔离级别下,事务会对读取的数据行进行快照,保证在同一个事务内多次读取同一行数据时的一致性。
- 可串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。隔离性好,并发性差。
3. MySQL索引
说一下MySQL的索引
先说定义
索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
索引类型
根据是否包含重复值:创建索引时,可以规定索引是否可包含重复值。如果不包含,则索引应该创建为 PRIMARY KEY 或 UNIQUE 索引。对于单列唯一性索引,这保证单列不包含重复的值。对于多列唯一性索引,保证多个值的组合不重复。
根据叶子节点的内容,索引类型分为主键索引和非主键索引。主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引。非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引。
主键索引和普通索引查询的区别
如果通过普通索引查询,先查询到主键值,再根据主键值查询主键的索引树,这个过程称为回表。所以在应用中尽量使用主键查询。
1.普通索引index :加速查找
2.唯一索引
主键索引:primary key :加速查找+约束(不为空且唯一)
唯一索引:unique:加速查找+约束 (唯一)
3.联合索引
-primary key(id,name):联合主键索引
-unique(id,name):联合唯一索引
-index(id,name):联合普通索引
与b树的区别,为什么MySQL索引选择B+树
①B+树非叶子节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)。
②B+树叶子节点首尾连接形成有序链表,大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起,则无法区间查找。
③B+树更适合外部存储。由于只有叶子结点存储数据,非叶子节点只存储键和指针,每个节点能索引的范围更大更精确
索引的使用
1.覆盖索引
一个索引包含了满足查询结果的数据就叫做覆盖索引。优点是不需要回表等操作。 如何实现?在编写sql时 索引列+主键 包含 SELECT 到 FROM之间查询的列。
- 最左匹配原则
在说最左匹配原则之前要先了解联合索引的结构和执行过程:
https://blog.csdn.net/weixin_44440311/article/details/130495246
索引下推
https://mp.weixin.qq.com/s?__biz=MzU3Mjk2NDc3Ng==&mid=2247483828&idx=1&sn=c21c6720a83240c2b899ed81ef57b912&chksm=fcc9ab73cbbe2265ac05ec67cca251bba661e995a00ad2acf2fabb69471bdc56987cdcf5b5e9&scene=27
定义:以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。
举例:比如:索引 abc_index:(a,b,c),只会在 where 条件中带有 (a)、(a,b)、(a,b,c) 的三种类型的查询中使用。其实这里说的有一点歧义,其实当 where 条件只有 (a,c) 时也会走,但是只走a字段索引,不会走 c 字段。
失效情况:1、查询条件中,缺失优先级最高的索引 “a”
当 where b = 6300 and c = ‘JJJ疾风剑豪’ 这种没有以 a 为条件来检索时;B+树就不知道第一步该查哪个节点,从而需要去全表扫描了(即不走索引)
当 where a =1 and c =“JJJ疾风剑豪” 这样的数据来检索时;B+ 树可以用 a 来指定第一步搜索方向,但由于下一个字段 b 的缺失,所以只能把 a = 1 的数据主键ID都找到,通过查到的主键ID回表查询相关行,再去匹配 c = ‘JJJ疾风剑豪’ 的数据了,当然,这至少把 a = 1 的数据筛选出来了,总比直接全表扫描好多了。
这就是MySQL非常重要的原则,即索引的最左匹配原则。
https://www.jb51.net/article/270219.htm
4. 线程池参数:
int corePoolSize, // 线程池的核心线程数量
int maximumPoolSize, // 线程池的最大线程数
long keepAliveTime, // 当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit, // 存活时间的时间单位
BlockingQueue<Runnable> workQueue, // 任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory, // 线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler // 拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。
maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
workQueue:新任务来的时候会先判断当前运行(正在执行任务的线程)的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于 corePoolSize时,超过corePoolSize的线程如果空闲时间超过keepAliveTime,线程将被终止。
jdk中提供了四种拒绝策略(policy:政策,策略):
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法。如果线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。(abort:放弃,丢弃)
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。(discard:丢弃)
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
5.Redis分布式锁
为什么需要分布式锁?
与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享资源产生数据问题,通常会使用一把锁来互斥以保证共享资源的正确性。
单机锁的使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。
想要实现分布式锁,必须借助一个外部系统,所有进程都去这个系统上申请加锁。而这个外部系统,必须要实现互斥能力,即两个请求同时进来,只会给一个进程加锁成功,另一个失败。这个外部系统可以是数据库,也可以是Redis或Zookeeper,但为了追求性能,我们通常会选择使用Redis或Zookeeper来做。
Redis本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁。而且 Redis 的读写性能高,可以应对高并发的锁操作场景。
Redis实现分布式锁的思路是这样的:
1.Redis可以使用键值对保存锁变量,实现互斥功能的方法是使用SETNX命令,SET IF NOT EXIST,即如果key不存在,才会设置它的值,否则什么也不做。
基本逻辑是:
// 加锁
SETNX lock_key 1
// 业务逻辑
DO THINGS
// 释放锁
DEL lock_key
问题:死锁。
以上实现存在一个很大的问题,当客户端1拿到锁后,如果发生下面的场景,就会造成死锁。
1)程序处理业务逻辑异常,没及时释放锁
2)进程挂了,没机会释放锁
以上情况会导致已经获得锁的客户端一直占用锁,其他客户端永远无法获取到锁
如何避免死锁?
2.在申请锁时,给锁设置一个过期时间,假设操作共享资源的时间不会超过10s,那么加锁时,给这个key设置10s过期即可。
SETNX lock_key 1
EXPIRE lock_key 10
问题:加锁、设置过期时间是2条命令,有可能只执行了第一条,第二条却执行失败
SETNX执行成功后,由于(1)网络异常,EXPIRE执行失败、(2)Redis异常宕机,EXPIRE没有机会执行、(3)客户端异常崩溃,EXPIRE没有机会执行
总之这两条命令如果不能保证是原子操作,就有潜在的风险导致过期时间设置失败,依旧有可能发生死锁问题。幸好在Redis 2.6.12之后,Redis扩展了SET命令的参数,可以在SET的同时指定EXPIRE时间,这条操作是原子的,例如以下命令是设置锁的过期时间为10秒
SET lock_key 1 EX 10 NX
这样解决了死锁的问题,但是还会出现两个问题:
(1)锁过期
(2)释放了别人的锁
客户端1加锁成功,开始操作共享资源
客户端1操作共享资源耗时太久,超过了锁的过期时间,锁失效(锁被自动释放)
客户端2加锁成功,开始操作共享资源
客户端1操作共享资源完成,在finally块中手动释放锁,但此时它释放的是客户端2的锁。
第1个问题是评估操作共享资源的时间不准确导致的,如果只是一味增大过期时间,只能缓解问题降低出现问题的概率,依旧无法彻底解决问题。
第2个问题是释放了别人的锁,原因在于释放锁时并没有检查这把锁的归属,这样解锁不严谨。
锁被别人释放了如何解决。第一步在加锁时,设置一个唯一标识,例如线程ID作为value。第二步在释放锁时,要先判断这把锁是否归自己持有,只有是自己的才能释放它。
这里释放锁使用的是GET + DEL两条命令,这时又会遇到原子性问题了。
客户端1执行GET,判断锁是自己的,刚好锁过期。
客户端2执行了SET命令,强制获取到锁(虽然发生概念很低,但要严谨考虑锁的安全性)
客户端1执行DEL,却释放了客户端2的锁
怎样原子执行两条命令呢。把以上逻辑写成Lua脚本,让Redis执行。因为Redis处理每个请求是单线程执行的,在执行一个Lua脚本时其它请求必须等待,直到这个Lua脚本处理完成,这样一来GET+DEL之间就不会有其他命令执行了。
4.这样一路优先下来,整个加锁、解锁流程就更严谨了,先小结一下,基于Redis实现的分布式锁,一个严谨的流程如下:
(1)加锁时要设置过期时间,添加唯一标识。SET lock_key unique_value EX expire_time NX
(2)操作共享资源
(3)释放锁:Lua脚本,先GET判断锁是否归属自己,再DEL释放锁
如何确定锁过期的时间。
加锁时,先设置一个预估的过期时间,然后开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间。
这个方案可以用Redission库来实现,客户端一旦加锁成功,就会启动一个watch dog看门狗线程,它是一个后台线程,会每隔一段时间(这段时间的长度与设置的锁的过期时间有关)检查一下,如果检查时客户端还持有锁key(也就是说还在操作共享资源),那么就会延长锁key的生存时间。
6 Redis的基本数据类型
Redis的数据类型
Redis的基本数据类型有5种,分别是string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合)
01 字符串 String
是 Redis 最简单的数据结构,可以存储字符串、整数或者浮点数。最常见的应用场景就是对象缓存,例如缓存用户对象,key是"userInfo"+#{用户ID},value是用户信息对象的JSON字符串。
基本操作有
127.0.0.1:6379> set name hzy # 设置
OK
127.0.0.1:6379> get name # 获取
"hzy"
127.0.0.1:6379> exists name # 判断是否存在
(integer) 1
127.0.0.1:6379> del name # 删除
(integer) 1
批量操作:
127.0.0.1:6379> mset name1 xiaoming name2 xiaohong # 批量设置
OK
127.0.0.1:6379> mget name1 name2 # 批量获取
1) "xiaoming"
2) "xiaohong"
计数操作:
如果 value 值是一个整数,我们可以对它进行自增长操作。
127.0.0.1:6379> incr likenum # 自增1
(integer) 1
127.0.0.1:6379> get likenum
"1"
127.0.0.1:6379> decr likenum # 减1
(integer) 0
127.0.0.1:6379> get number
"0"
过期操作:
127.0.0.1:6379> expire name 60 # 设置超时时间
(integer) 1
127.0.0.1:6379> setex name 60 value # 等价于 setex + expire
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 11
Redis String的使用场景:
(1)缓存:把一个对象转成json字符串,然后放到redis里缓存
(2)计数器:像博客文章的阅读量、评论数、点赞数等等
(3)分布式系统生成自增长ID
02 List
Redis 的列表相当于 Java 语言里面的 LinkedList。
LinkedList优点:插入性能高,不管是从末尾插入还是中间插入
LinkedList缺点:随机读性能差,例如LinkedList.get(10),这种操作,性能就很低,因为他需要遍历这个链表,从头开始遍历这个链表,直到找到index = 10的这个元素为止。
Redis List 数据类型如何实现队列?
右边进左边出
127.0.0.1:6379> rpush apps qq # 将元素插入到列表的尾部(最右边)
(integer) 1
127.0.0.1:6379> rpush apps wechat taobao # 将多个元素插入到列表的尾部(最右边)
(integer) 3
127.0.0.1:6379> lpop apps # 移除并返回列表的第一个元素(最左边)
"qq"
127.0.0.1:6379> lrange apps 0 1 # 返回列表中指定区间内的元素,0表示第一个,1表示第二个,-1表示最后一个
1) "wechat"
2) "taobao"
127.0.0.1:6379> lrange apps 0 -1 # -1表示倒数第一
1) "wechat"
2) "taobao"
Redis List 数据类型如何实现栈?
先进先出,右边进右边出
127.0.0.1:6379> rpush apps qq wechat taobao
(integer) 3
127.0.0.1:6379> rpop apps # 移除列表的最后一个元素,返回值为移除的元素
"taobao"
Redis List 使用场景
异步队列
任务轮询(RPOPLPUSH)
文章列表(lrange key 0 9)
03 Hash
Redis的Hash结构相当于Java语言的HashMap,内部实现结构上与JDK1.7的HashMap一致,底层通过数组+链表实现。
127.0.0.1:6379> hmset userInfo name "hzy" age "24" sex "1"
OK
127.0.0.1:6379> hexists userInfo name # 相当于HashMap的containsKey()
(integer) 1
127.0.0.1:6379> hget userInfo name # 相当于HashMap的get()
"hzy"
127.0.0.1:6379> hget userInfo age
"24"
127.0.0.1:6379> hgetall userInfo # 数据量大时,谨慎使用!获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "hzy"
3) "age"
4) "24"
5) "sex"
6) "1"
127.0.0.1:6379> hkeys userInfo # 数据量大时,谨慎使用!获取 key 列表
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> hvals userInfo # 获取 value 列表
1) "hzy"
2) "24"
3) "1"
127.0.0.1:6379> hset userInfo name "test" # 修改某个字段对应的值
127.0.0.1:6379> hget userInfo name
"test"
Redis Hash 使用场景
记录博客中某个博主的主页访问量、博主的姓名、联系方式、住址
04 Set
Redis的set集合相当于Java的HashSet,是一个集合,里面的元素是无序的,他里面的元素不能重复的。
127.0.0.1:6379> sadd apps wechat qq # 添加元素
(integer) 2
127.0.0.1:6379> sadd apps qq # 重复
(integer) 0
127.0.0.1:6379> smembers apps # 注意:查询顺序和插入的并不一致,因为 set 是无序的
1) "qq"
2) "wechat"
127.0.0.1:6379> scard apps # 获取长度
(integer) 2
127.0.0.1:6379> sismember apps qq # 谨慎使用!检查某个元素是否存在set中,只能接收单个元素
(integer) 1
127.0.0.1:6379> sadd apps2 wechat qq
(integer) 2
127.0.0.1:6379> sinterstore apps apps apps2 # 获取 apps 和 apps 的交集并存放在 apps3 中
(integer) 1
127.0.0.1:6379> smembers app3
1) "qq"
2) "wechat"
注意: 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收
Redis Set 使用场景
微博抽奖:如果数据量不是特别大的时候,可以使用spop(移除并返回集合中的一个随机元素)或srandmember(返回集合中一个或多个随机数)
QQ标签:一个用户多个便签
共同关注(交集)sinter key1 key2 …求多个集合的交集
共同好友(交集)
05 sorted set
有序集合,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。使得它类似于Java的TreeSet和HashMap的结合体。
Sorted set 常用命令
127.0.0.1:6379> zadd apps 3.0 qq # 添加元素到 sorted set 中,3.0是score的值
(integer) 1
127.0.0.1:6379> zadd apps 2.0 wechat 1.0 aliyun # 一次添加多个元素
(integer) 2
127.0.0.1:6379> zcard apps # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore apps wechat # 查看某个 value 的权重
"2.0"
127.0.0.1:6379> zrange apps 0 -1 # 通过索引区间返回有序集合指定区间内的成员,0 -1 表示输出所有元素
1) "aliyun"
2) "wechat"
3) "qq"
127.0.0.1:6379> zrange apps 0 1 # 通过索引区间返回有序集合指定区间内的成员
1) "aliyun"
2) "wechat"
127.0.0.1:6379> zrevrange apps 0 1 # 相当于zrange的反转
1) "qq"
2) "wechat"
注意: sorted set 中最后一个 value 被移除后,数据结构自动删除,内存被回收
Sorted set 使用场景
排行榜
订单支付超时(下单时插入,member为订单号,score为订单超时时间戳,然后写个定时任务每隔一段时间执行zrange)
https://www.modb.pro/db/99437
7 Redis的持久化
Redis是一个基于内存的数据库,它的数据是存放在内存中,内存有个问题就是关闭服务或者断电会丢失。Redis的数据也支持写到硬盘中,这个过程就叫做持久化。
RDB(Redis DataBase) :就是在指定的时间间隔内,定时的将 redis 存储的数据生成Snapshot快照并存储到磁盘上;
AOF(Append Of File) :将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
01 RDB(Redis DataBase):redis备份默认方式
同时允许使用两种方式: 其实 RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
可以选择关闭持久化: 如果你没有数据持久化的需求,也完全可以关闭 RDB 和 AOF 方式,这样的话,redis 将变成一个纯内存数据库,就像 memcache 一样。
触发RDB持久化的方式有两种,一是自动备份,需配置备份规则,redis.conf中配置自动备份的规则,save的格式: save 秒钟 写操作次数
方式2:手动执行命令备份(save | bgsave)
有2个命令可以触发备份。
save:save时只管保存,其他不管,全部阻塞,手动保存,不建议使用。
bgsave:redis会在后台异步进行快照操作,快照同时还可以响应客户端情况。
5、RDB优缺点
优势:
适合大规模数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
基于二进制存储的,恢复速度快
劣势:
Fork的时候,内存中的数据会被克隆一份,大致2倍的膨胀,需要考虑
虽然Redis在fork的时候使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down的话,就会丢失最后一次快照后所有修改
02 AOF
流程:
客户端的请求写命令会被append追加到AOF缓冲区内
AOF缓冲区会根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中
AOF文件大小超过重写策略或手动重写时,会对AOF文件进行重写(rewrite),压缩AOF文件容量
redis服务器重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的
5、AOF优缺点
优势:
备份机制更稳健,丢失数据概率更低
可读的日志文本,通过操作AOF文件,可以处理误操作
劣势:
比RDB占用更多的磁盘空间
恢复备份速度要慢
每次读写都同步的话,有一定的性能压力
存在个别bug,造成不能恢复
03 Redis 4.0 混合持久化
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
将 rdb 文件的内容和增量的 AOF 日志文件存在一起。
这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
优点:
混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF的优点,有减低了大量数据丢失的风险。
缺点:
AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。
8 Java 流
数据源:
数据源data source,提供数据的原始媒介。常见的数据源有:数据库、文件、其他程 序、内存、网络连接、lo 设备。 数据源分为:源设备、目标设备。
源设备:为程序提供数据,一般对应输入流。
目标设备:程序数据的目的地,一般对应输出流。
流的概念:
流是一个抽象、动态的概念,是一连串连续动态的数据集合。 对于输入流而言,数据源就像水箱,流(stream)就像水管中流动着的水流,程序就是我们最终的用户。我们通过流(AStream)将数据源(Source)中的数据(information)输送到程序(Program)中。对于输出流而言,目标数据源就是目的地(dest),我们通过流(A Stream)将程序(Program)中的数据(information)输送到目的数据源(dest)中。
01 按流的方向分类:
输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)。 输出流:数据流向是程序到目的地(以 OutPutStream、Writer结尾的流)。
02 按处理的数据单元分类:
字节流:以字节为单位获取数据,命名上以Stream结屋的流一般是字节流 。如FilelnputStream、FileOutputStream。
字符流:以字符为单位获取数据,命名上以Reader/Writer结屋的流一般是字符流,如 FileReader、FileWriter
03 按处理对象不同分类:
节点流:可以直接从数据源或目的地读写数据,如 FilelnputStream、FileReaderDatalnputStream等。
处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如 BufferedInputStream、BufferedReader等。
字节流和字符流的区别与联系
区别:
- 数据类型:字节流以字节为单位进行读写,而字符流以字符为单位进行读写。字节流可以处理任意类型的数据,包括文本、图像、音频等,而字符流主要用于处理文本数据。
- 编码方式:字节流是以字节的形式直接读写数据,不关心数据的具体编码方式。而字符流是以字符的形式读写数据,会根据指定的字符编码将字符转换为字节进行处理。
- 处理效率:字节流的处理效率通常比字符流高,因为字节流直接操作底层的字节数据,不需要进行字符编码的转换。
- 使用场景:字节流适用于处理二进制数据,如文件的复制、网络传输等。字符流适用于处理文本数据,如文件的读写、文本的处理等。
联系: - 继承关系:字节流和字符流都是抽象类InputStream和OutputStream的子类,以及Reader和Writer的子类。
- 使用方式:字节流和字符流都提供了类似的读写方法,如read()和write()方法。
- 转换:可以通过InputStreamReader和OutputStreamWriter类将字节流转换为字符流,以便处理文本数据。
- 字符流是建立在字节流的基础上的。在字符流中,使用了字符编码来处理字符数据,而字符编码又是通过字节流来实现的。因此,字符流可以看作是字节流的高级封装,提供了更方便的字符处理功能。
原文链接:https://blog.csdn.net/weixin_42594143/article/details/133947547
文件字符流
缓冲字节流
缓冲字符流
字节数组流
数据流
对象流
ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。 需要序列化的对象,需要实现接口:iava.io.Serializable对象序列化的作用有如下两种: 持久化:把对象的字节序列永久地保存到硬盘上 通常存放在一 个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。 网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
https://blog.csdn.net/weixin_74469493/article/details/131152841