- MySQL的执行过程
1. 索引是在 执行引擎 中发挥的作用
2. MySQL查询缓存(内存中)的效率是最高的,所以Redis应运而生!
Redis、Memcached、MySQL
- 内存管理机制
- Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存切割成特定长度的块,存储相应长度的key-value数据记录,以完全解决内存碎片化问题;使用空闲列表(free-list)判断存储状态,比如现有内存块为88 bytes、112 bytes、144 bytes,当有一条100 bytes的数据时,会选择112 bytes的块存储,所以会有12 bytes的碎片;
- Redis使用现场申请内存的方式来存储数据,并且很少使用 free-list 等方式来优化内存分配,会在一定程度上存在内存碎片;
- 主要在于CPU的内存是连续的,所以容易出现内存碎片;
- Memcached和Redis的机制,类似JVM对象的分类:空闲列表、直接内存分配(指针碰撞)
- 数据持久化
- Memcached不支持内存数据的持久化操作,所有的数据以 in-memory 的形式存储;
- redis支持两种数据持久化方案,RDB、AOF
- RDB:全量数据备份,备份的是数据
- AOF:增量支持化备份,备份的是指令,如set key value
- 数据过期机制
- Memcached在删除失效主键时也是采用消极方法,内部不会监视主键是否失效,而是在通过 Get 访问主键时才会检查是否已经失效;
- Redis采用定时、定期等多种方式检测失效主键,减少内存泄漏;
- 数据类型:Memcached支持单一数据类型,而Redis支持多达5种数据类型;
- Redis与MySQL相比
- 优点:没有Scheme(创建表、字段类型)约束,数据结构变更相对容易,抗压能力强,性能极高,可以达到每秒10万人次访问;
- 缺点:没有索引和外键,缺少int/date等基本数据类型,多条件查询集合内联和连接间接实现,开发效率低,可维护性不佳;
- 二者通常搭配使用,Redis也可以作为mybatis的二级缓存,而一级缓存则是SqlSession、进程缓存,单次链接有效;
Redis
- centOS-7安装redis-4.0.6
yum install wget //安装wget命令
wget http://download.redis.io/releases/redis-4.0.6.tar.gz //下载redis-4.0.5
yum install gcc //安装gcc环境
mv redis-4.0.6.tar.gz /usr/local/
tar -zxvf redis-4.0.6.tar.gz
cd redis-4.0.6
make MALLOC=libc // 编译
cd src && make install //安装
./redis-server // 启动redis,测试 - 三种启动redis的方式
- 直接启动:执行 redis-server 命令,进程级别,Ctrl+C关闭进程,redis也随之关闭
- 指定配置文件,后台启动:修改redis.conf中的 daemonize 为 yes
redis-server redis.conf //指定配置文件的方式启动
ps -ef | grep redis // 查看redis的进程信息
kill -9 8341 // 根据进程号杀死redis - 开机启动redis
- 启动脚本在 redis 的utils目录下:redis_init_script
- Linux配置开机自启动:/etc/init.d
mkdir /etc/redis
cp redis.conf /etc/redis/6379.conf // 拷贝 redis.conf 到 /etc/redis/6379.conf
cp utils/redis_init_script /etc/init.d/redisd // 通常以 d 结尾的表示后台自启动服务 - 修改启动脚本的级别
#!/bin/sh # # chkconfig: 2345 90 10
- 执行脚本:chkconfig redisd on
- 重启系统,redis服务已经启动:ps -ef | grep redis
- 启动/关闭redis服务:service redisd start/stop
- SSH连接,则需要安装SSH服务,SSH存在的意义就是模拟操作Linux
- 检查是否安装了:yum list installed | grep openssh-server
- centOS-7系统上默认已经安装了openssh-server,对应的安装命令:yum install openssh-server
- 配置:vi /etc/ssh/sshd_config
Port 22
ListenAddress 0.0.0.0
ListenAddress ::
PermiRootLogin yes
PasswordAuthentication yes - 开启sshd服务:service sshd start
- 查看IP地址:ip addr
- window系统上可以使用Xshell工具连接SSH:ssh root@192.168.103.230 --> 回车键 --> 输入root的密码
- 让sshd开机自启动:systemctl enable sshd.service
- Redis支持的5种数据结构:string、list、set、sortset、hash
- string:字符串数据,最常用的一种数据类型,普通的key-value存储都可以归为此类;
- set、get
- mget:批量获取多个key的值
- incr、incrby:incr对key的值做++,incrby加指定的值,incrby key offset
- desr、desrby:desr对key的值做--,desrby减指定的值
- setnx:set if not exist,只有key不存在时才会设置,如果key已存在,则返回0
- setex:设置key的值为string类型,并指定有效期,单位是秒,setex key value seconds
getrange:获取value的子串,getrange key startIndex endIndex
mset:批量设置多个key的值,返回0 表示没有任何值被设置;
msetnx:同mset,不存在则设置,不会覆盖原有的key-value
getset:设置key的值,并返回旧值
append:给value追加字符串,并返回新字符串的长度 - redis string的存储格式(C语言)
struct key {
int len; //buf种存储的字符串长度
int free; //buf种空闲空间的长度
char buf[]; //buf用于存储的字符串内容
}
type 数据类型 int/string
- hash:一个string类型的field和value之间的映射表
- redis的Hash数据类型的key(hash表名)对应的value,内部存储结构是一个HashMap,Map<String, Map<String, String>> --> Map<Key, Map<field, value>
- Hash特别适合存储对象:相对于将对象的每个属性存储为string类型,将整个对象存储在Hash类型中会占用更少的内存;
- 存储的成员较少时,数据存储为zipmap,当成员数量增大时会自动转为真正的HashMap,此时的encoding为ht
hset key field value
hset student name Jack
hset student age 30
hset student sex 男
hmset key field1 value1 field2 value2 field3 value3
hget key field
hset student name // Jack
hmget key field1 field2 field3
hgetall key
hgetall student
hdel key field // 删除某个field
hlen key //field的数量
- list:有序、可重复,以栈的结构存储,先进后出
lpush/rpush key value1 value2 value3:向头/尾部添加元素
lrange key [startIndex] [endIndex]:获取一段value
lrange key 0 -1 //获取所有的value
lpop/rpop key:从头/尾部删除一个元素,并返回被删除的元素
llen key:元素个数
lindex key [index]:根据索引index获取对应的元素
lrem key [count] value:根据指定的元素名称,移除 count 个此元素 - Set:不允许重复,无序,HashMap结构存储,Map<String, Map<String, null> --> Map<Key, Map<value, null>
sadd key value1 value2 value3:添加成员
smembers key:获取所有成员
sdiff key1 key2:求两个集合的差集,key1中有,但key2中没有
suion key1 key2:求并集
sinter key1 key2:求交集 - sortSet:在set的基础上增加控制顺序的score,应用场景如排行榜
zadd key score1 value1 score2 value2 score3 value3
zadd myzset 1 AA 3 BB 2 CC
zrange:根据索引范围获取value
zrange myzset 0 -1 //获取所有的value
zrange myzset 0 -1 withscores //获取所有的value及其score
zrem key value1 value2 value3:删除元素
zrangebyscore //根据score的范围获取value
zrangebyscore myzset 1 6
zrangebyscore myzset 1 6 withscores
zrank key value:查看value的排名,从 0 开始,score最小的value,排名为 0
zcard key:元素个数- sortset也是HashMap结构存储的,另外还加了一层跳跃表
- 跳跃表相当于双向链表,在其基础上添加了前往比当前元素大的跳转连接,分为若干层,性能与树结构差不多
- 发布订阅:
publisher -> channel -> subscriber1、subscriber2、subscriber3- 消息发布者把消息发送到管道,由管道负责把消息准确发送给每个订阅者;
- 类似信息管道,用来进行系统之间的消息解耦,降低失败的风险;
PUBLISH channel message // 向channel中发送message
SUBSCRIBE channel // 订阅channel
UNSUBSCRIBE channel1 channe2 // 取消订阅,如果不指定,则取消所有订阅 - 与MQ相比,redis的发布订阅功能比较薄弱、无后台功能,但比较轻量级;而MQ可以持久化消息,但消息的可靠性较差;
事务
传统关系型数据库的事务
- 目的:
- 为数据库操作序列(一组SQL命令)提供一个从失败中恢复到正常的方法,即使数据库处在异常状态下,仍能保持一致性;
- 当并发访问数据库时,可以做到隔离,彼此的操作互不干扰;
- 四大特性:ACID
- 原子性(Atomicity):事务作为一个整体被执行,要么全部被执行,要么都不执行;
- 一致性(Consistency):保证数据库从一个一致状态转变为另一个一致状态,一致状态指数据库中的数据应满足完整性约束;
- 隔离性(Isolation):多个事务并发执行时,相互不应该干扰;
- 持久性(Durability):已提交的事务对数据库的修改应该永久保存在数据库;
- 事务隔离机制:read uncommintted、read committed、repeatable read、serializable
select @@tx_isolation; // 查看当前会话的事务隔离级别
select @@global.tx_isolation; // 查看系统隔离级别- 在旧版本的MySQL系统中,tx_isolation是transaction_isolation的别名,新版已经弃用了!
select @@transaction_isolation;
select @@global.transaction_isolation; - 设置隔离级别
set session transaction isolation level read committed; //设置当前会话的隔离级别
set global transaction isolation level read committed; //设置系统的隔离级别
- 在旧版本的MySQL系统中,tx_isolation是transaction_isolation的别名,新版已经弃用了!
- innodb:MySQL的默认引擎
- innodb实现repeatable read的方式:MVCC,Multiversion Concurrency Control 多版本并发控制
2. DATA_TRX_ID:一条记录的一个事务ID,每处理一个事务,ID值自动加1
3. DATA_ROLL_PTR:存储UNDO LOG的指针,undo log用于记录事务执行命令的记录;
4. DELETE_BIT:标识这条记录是否被删除,不是真正的删除,只有事务被commit之后才会真删除;
Redis事务
multi // 开启事务
······ // 命令入队
EXEC // 执行事务
DISCARD // 取消事务,清空命令队列
WATCH // 监视key
Redis与ACID
原子性:单个Redis命令的执行是原子性的,但Redis事务上没有任何机制维持原子性,所以Redis事务并不是原子性;
-
一致性:比如A向B转账100,当B账户加了100时,A账户一定减了100,反之亦然,绝不会出现中间状态!
- 入队错误:在命令入队过程中,如果客户端向服务器发送了错误的命令,如参数出错,那么服务器将向客户端返回一个出错信息,并将客户端的事务状态设置为 REDIS_DIRTY_EXEC
- 执行错误:如果命令在事务执行过程中发生错误,如对一个不同类型的key执行了错误的操作,那么Redis只会将错误包含在事务的结果中,不会引起事务中断或整个失败,不会影响已执行命令的结果,也不会影响后面要执行的事务命令,所以它对一致性也没有影响;
隔离性:WATCH命令用于在事务开始之前监视任何数量的key,事务执行过程中,如果任意一个被监视的key被其他客户端更改了,那么整个事务不再执行,直接返回失败,所以对隔离性没有影响;
持久性:事务不过是用队列包裹的一组Redis命令,并没有提供任何额外的持久性功能,所以事务的持久性由Redis所使用的持久化模式决定;
-
redis在分布式集群环境下的session共享
- SpringBoot的依赖:spring-session-data-redis
- @EnableRedisHttpSession:开启Redis缓存Session,属性maxInactiveIntervalInSeconds 指定缓存时间
request.getSession().setAttribute(key, value):设置session
request.getSession().getAttribute(key):获取session
request.getSession().getId():获取session ID,首次建立会话并设置Session时,会自动生成Session ID - Spring Session会保存两个Session ID,以防止其中一个过期
-
MySQL数据库表的设计
- 更小的通常更好,控制字节长度
- 使用合适的数据类型,如tinyint只占8个位,char存储定长数据时比varchar更节省空间,因为varchar需要额外存储数据长度,如32位的UUID可以用char(32)
- 尽量避免NULL,而选择 NOT NULL、DEFAULT ''
NULL的列会让索引统计和值比较更复杂,可为NULL的列会占用更多的磁盘空间,MySQL处理起来也更复杂
-
MySQL索引设计
- 选择唯一性索引:索引的值是唯一的,可以更快速的查询,保证物理上的唯一
- 为经常需要排序、分组和联合操作的字段建立索引,排序会浪费很多时间
- 常作为查询条件的字段建立索引
- 数据少的地方不必建立索引
-
SQL语句的优化:explain查看执行计划
- 能用 between 就不用 in
- 能用 distinct 就不用 group by
- 避免数据强转
-
缓存带来的回报
- 高速读写
CPU L1/L2/L3 Catch、Linux page Cache加速硬盘读写、浏览器缓存、Ehcache缓存数据库结果 - 降低后端负载
后端服务器通过前端缓存降低负载,通过Redis降低MySQL负载
- 高速读写
-
缓存带来的代价
- 数据不一致性:缓存层和数据层有时间窗口不一致,和更新策略有关;
- 代码维护成本增加:原本只需要读写数据库就能实现的功能,在加入了缓存之后就要去维护缓存的数据;
- 堆内缓存可能带来内存溢出的风险,影响用户进程,如ehCache、loadingCache
本机内存分类:堆、JVM栈、方法区、本地方法栈、程序计数器 - 堆内缓存与远程服务器(如Redis)缓存
堆内缓存一般性能更好,远程缓存则需要套接字传输;
用户级别的数据尽量采用远程缓存;
大数据量尽量采用远程缓存,服务节点化原则;
缓存雪崩
- 缓存集中在一段时间内失败,发生大量缓存穿透,所有查询都落在数据库上,造成缓存雪崩;
- 由于原有缓存失效,在新缓存未到期间,所有原本应该访问缓存的请求都去查询数据库了,从而对数据库CPU和内存造成巨大压力,严重的会导致数据库宕机;
解决方案
- 加锁排队:multex互斥锁解决
Redis的 SETNX 去 set 一个 mutex key,当操作返回成功时,再进行load db的操作,并回设缓存;否则就重试整个get缓存的方法; - 数据预热
系统上线后,将相关的缓存数据直接加载到缓存系统,从而避免在用户请求的时候,先查数据库、再将数据设置到缓存;
可以通过缓存reload机制,预先去更新缓存,在即将发生大并发访问前,手动触发加载,缓存不同的key - 双层缓存策略
C1为原始缓存,C2为拷贝缓存,C1失效时,可以访问C2,C1缓存失效时间设置为短期,C2设置为长期 - 定时更新缓存策略
时效性要求不高的缓存,容器启动初始化加载,采用定时任务更新缓存 - 设置不同的过期时间,让缓存失效时间点尽量均匀
缓存穿透
- 用户查询数据,如果在数据库中没有,缓存中自然也不会有,这就导致每次用户查询时,在缓存中查不到,就要去数据库中再查一遍,然后返回空!也就是进行两次无用的查询;
- 每次用户请求,都会绕过缓存,直接查询数据库,这就是缓存穿透;
解决方案
- 缓存空值
如果一个查询返回的数据为空,不管数据是否存在,我们都把这个空结果进行缓存,但它的过期时间会很短,最长不超过5min
第二次查询缓存时,就可以获取到一个值了,而不会继续查询数据库; - 采用布隆过滤器 Bloom Filter
将所有可能存在的数据 哈希 到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免对底层存储系统造成压力;
布隆过滤器占用内存很小,bit存储,性能特别高