缓存机制说明
问题1: 为什么用户直接操作数据库效率低?
链接创建/销毁需要大量的时间 JDBC/数据库链接池
问题2: 为什么创建链接耗时?
使用TCP协议 3次握手机制 4次挥手操作
说明: 缓存机制实质是为了降低用户访问物理设备的频次,减少用户的交互的时间,提高数据响应的能力.
一、实现缓存机制要素
1.数据结构应该采用什么类型? 采用K-V结构
2.内存特点断电即擦除如何防止? 将内存数据进行持久化操作(存到磁盘中)
3.内存容量有限,如何只保存热点数据? LRU算法/LFU算法/TTL算法/随机算法
4.为了与硬件交互性强,所以采用C语言的方式进行开发
二、Redis介绍
官网:https://redis.io/
中文网:http://www.redis.cn/
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库(非关系型数据库)、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps,hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
数据: 读: 11.2万次/秒
写: 8.6万次/秒
平均 10万次/秒
三、安装redis
1)上传安装包
安装包版本5.0.4(centos7目前对redis6.x.x的兼容性不好)
2)解压安装包并改名
tar -zxvf redis-5.0.4.tar.gz
mv redis-5.0.4 redis
3)跳入到redis根目录(cd redis)中执行 make make install命令
(1)make 执行效果
(2)make install效果
4)说明:redis的启动依赖核心配置文件redis.conf
修改redis配置文件:(注释部分的wxz为本人名字缩写,请忽略!!!)
(1)将IP绑定注释
(2)关闭保护模式
(3)开启后台启动
四、Redis入门命令
1.启动命令: redis-server redis.conf
2.进入客户端: redis-cli -p 6379
3.关闭redis: redis-cli -p 6379 shutdown
五、String类型的命令
命令 | 说明 | 案例 |
---|---|---|
set | 添加key-value | set username admin |
get | 根据key获取数据 | get username |
strlen | 根据key获取值的长度 | strlen key |
exists | 判断key是否存在 | exists name 返回1存在 0不存在 |
del | 删除redis中的key | del key |
Keys | 用于查询符合条件的key | keys 星 查询redis中全部的key,keys n?me 使用占位符获取数据,keys nam* 获取nam开头的数据 |
mset | 赋值多个key-value | mset key1 value1 key2 value2 key3 value3 |
mget | 获取多个key的值 | mget key1 key2 |
append | 对某个key的值进行追加 | append key value |
type | 检查某个key的类型 | type key |
select | 切换redis数据库 | select 0-15 redis中共有16个数据库 |
flushdb | 清空单个数据库 | flushdb |
flushall | 清空全部数据库 | flushall |
incr | 自动加1 | incr key |
decr | 自动减1 | decr key |
incrby | 指定数值添加 | incrby key 10 |
decrby | 指定数值减 | decrby key 10 |
expire | 指定key的生效时间 单位秒 | expire key 20 key20秒后失效 |
pexpire | 指定key的失效时间 单位毫秒 | pexpire key 2000key 2000毫秒后失效 |
ttl | 检查key的剩余存活时间 | ttl key -2数据不存在 -1该数据永不超时 |
persist | 撤销key的失效时间 | persist key |
六、Redis入门
1)导入jar包
<!--spring整合redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
2)编辑测试案例
setnx 如果数据存在 不做任何操作,如果数据不存在则赋值。
setex 设置超时时间时,要保证操作的原子性。要么同时成功,要么同时失败
//如果数据存在 不做任何操作,如果数据不存在则赋值
@Test
public void test02(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll();
//jedis.set("redis", "AAA");
jedis.setnx("redis","BBB");
System.out.println(jedis.get("redis"));
}
//添加一个数据,并且设定超时时间 10秒
//设定超时时间应该注意
// 原子性: 要么同时成功/要么同时失败.
@Test
public void test03() throws InterruptedException {
Jedis jedis = new Jedis("192.168.126.129", 6379);
//jedis.set("a","设定超时时间");
//jedis.expire("a", 10);
jedis.setex("a", 10, "设定超时时间");
Thread.sleep(2000);
System.out.println("剩余时间" + jedis.ttl("a"));
}
/**
* 1.如果数据存在,则不允许修改 反之允许修改
* 2.同时为数据设定超时时间. 10秒
* 3.上述操作需要满足原子性要求
* XX: 存在才可以操作
* NX: 不存在才可以操作
* PX: 毫秒
* EX: 秒
*/
@Test
public void test04(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
SetParams setParams = new SetParams();
setParams.nx().ex(10);
jedis.set("b", "xxxx",setParams);
System.out.println(jedis.get("b"));
}
七、hash类型的命令
说明:可以用散列类型保存对象和属性值
应用场景:一般在工作中存储的都是基于一个业务对象的数据
例子:User对象{id:2,name:小明,age:19}
命令 | 说明 | 案例 |
---|---|---|
hset | 为对象添加数据 | hset key field value |
hget | 获取对象的属性值 | hget key field |
hexists | 判断对象的属性是否存在 | HEXISTS key field 1表示存在 0表示不存在 |
hdel | 删除hash中的属性 | hdel user field [field ...] |
hgetall | 获取hash全部元素和值 | HGETALL key |
hkyes | 获取hash中的所有字段 | HKEYS key |
hlen | 获取hash中所有属性的数量 | hlen key |
hmget | 获取hash里面指定字段的值 | hmget key field [field ...] |
hmset | 为hash的多个字段设定值 | hmset key field value [field value ...] |
hsetnx | 设置hash的一个字段,只有当这个字段不存在时有效 | HSETNX key field value |
hstrlen | 获取hash中指定key的值的长度 | HSTRLEN key field |
hvals | 获取hash的所有值 | HVALS user |
举例:
一个订单下来,包含用户信息,订单信息,物流信息...
hset orderId user "用户信息"
hset orderId order "订单信息"
hset orderId shipping "物流信息"
八、list类型的命令
说明:Redis中的List集合是双端循环链表,分别可以从左右两个方向插入数据.
List集合可以当做队列使用,也可以当做栈使用
队列: 存入数据的方向和获取数据的方向相反
栈: 存入数据的方向和获取数据的方向相同
命令 | 说明 | 案例 |
---|---|---|
lpush | 从队列的左边入队一个或多个元素 | LPUSH key value [value ...] |
rpush | 从队列的右边入队一个或多个元素 | RPUSH key value [value ...] |
lpop | 从队列的左端出队一个元素 | LPOP key |
rpop | 从队列的右端出队一个元素 | RPOP key |
lpushx | 当队列存在时从队列的左侧入队一个元素 | LPUSHX key value |
rpushx | 当队列存在时从队列的右侧入队一个元素 | RPUSHx key value |
lrange | 从列表中获取指定返回的元素 | LRANGE key start stop Lrange key 0 -1 获取全部队列的数据 |
lrem | 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:•count > 0: 从头往尾移除值为 value 的元素。• count < 0: 从尾往头移除值为 value 的元素。•count = 0: 移除所有值为 value 的元素。 | LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。 |
Lset | 设置 index 位置的list元素的值为 value | LSET key index value |
关于秒杀业务
redis没有线程并发安全性问题:单进程,单线程问题
悲观锁:同步代码块
乐观锁:数据库控制(加版本号,成功购买了,版本号加1)
若update执行结果为0,表示没有更新数据库,即没有抢到商品。
悲观锁,乐观锁都不好,都增加数据库的压力。故可以用redis来实现秒杀。
九、redis事务命令
redis中有事务控制。但是该事务控制是通过队列的方式实现的。
说明:redis中操作可以添加事务的支持.一项任务可以由多个redis命令完成,如果有一个命令失败导致入库失败时.需要实现事务回滚.
命令 | 说明 | 案例 |
---|---|---|
multi | 标记一个事务开始 | 127.0.0.1:6379> MULTI OK |
exec | 执行所有multi之后发的命令 | 127.0.0.1:6379> EXEC OK |
discard | 丢弃所有multi之后发的命令 |
十、Redis 持久化策略说明(默认开启的就是rdb模式)(redis.conf)
1.需求说明
如果redis服务器宕机/或者断电 则可能会导致整个内存数据丢失. 为了防止该现象的发生,redis内部引入持久化机制.
持久化: 将内存数据按照特定的规则保存到磁盘中.
2.RDB模式
2.1 RDB模式说明
1) RDB模式是Redis默认持久化机制.
2) RDB模式记录的是内存数据的快照,持久化效率更高. (只会保留当前最新数据)
3) RDB模式是定期持久化. 也可以手动操作. save(同步) 用户操作可能阻塞 /bgsave 后端运 行(异步)登录redis客户端后执行这两条命令(redis-cli)
4).由于是定期备份,所以可能导致数据丢失.
2.2.2 RDB模式配置项(dump.rdb)
Redis 重启会通过加载dump.rdb文件恢复数据。
1).持久化策略
save 900 1 900秒 1次更新,则持久化一次
save 300 10 300秒 10次更新 则持久化一次
save 60 10000 60秒 10000更新 则持久化一次
save 1 1 效率极低. 不要这样配置 压测之后进行适当的调整.
2).持久化文件名称
3).持久化文件位置
2.3 AOF模式
2.3.1 AOF模式说明(appendonly.aof)
1).默认条件下AOF模式处于关闭状态,如果需要开启,则手动配置(appendonly yes)
2).AOF模式记录用户的操作的过程,可以实现实时的持久化操作, 持久化文件相对较大维护不易.
3).如果同时开启了 RDB与AOF模式,则默认以AOF模式为准.但是用户可以手动执行save命令,此时就会生成rdb文件。
4).AOF模式是一种异步的操作,不会影响程序的正常使用
2.3.2 AOF模式配置
1).开启AOF配置
2).日志信息展现
3).AOF模式持久化策略
appendfsync always 用户执行一步操作,则持久化一次
appendfsync everysec 每秒持久化一次 性能略低于RDB模式
appendfsync no 不主动持久化
2.3.3 持久化方式如何选择
1.如果允许少量的数据丢失 首选RDB模式 快
2.如果不允许数据丢失, 首选AOF模式.
3.工作中一般如何选择 主机 选用RDB模式 从机 选用AOF模式.
2.3.4 关于持久化面试题
公司新来了个漂亮的实习生. 去生产环境下,误操作了flushall命令,问你作为主管 如何处理?
解决方案:
前提:持久化策略配的是AOF,如果是RDB则无法补救。
1.进入AOF文件中,修改文件信息.,将fluashAll命令删除
2.重启redis即可。
2.4 Redis 内存策略
2.4.1 需求介绍
Redis中的数据,保存到内存中. 但是内存资源相对较少.如何保证其中的数据都是热点数据呢
2.4.2 内存优化-LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的数据置换算法,选择最近最久未使用的数据予以淘汰。该算法赋予每个数据一个访问字段,用来记录一个数据自上次被访问以来所经历的时间 t,当须淘汰一个数据时,选择现有数据中其 t 值最大的,即最近最少使用的数据予以淘汰。
维度: 时间T
2.4.3 内存优化-LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 使用次数
2.4.4 内存优化-Random算法
2.4.5 内存优化-TTL算法
根据剩余的存活时间,排序,根据时间少的删除数据.
2.4.6 修改内存优化策略
volatile-lru 在设定超时时间的数据中采用LRU算法
allkeys-lru 在所有数据中采用LRU算法
volatile-lfu 在设定了超时时间的数据采用LFU机制进行计算
allkeys-lfu 在所有数据中采用LFU算法
volatile-random 设定超时时间的随机
allkeys-random 所有数据的随机算法
volatile-ttl 在设定了超时时间的数据中,采用TTL算法
noeviction 默认规则 如果内存满了,则不做任何操作 直接报错返回.
2.4.7 设定内存优化策略
十一、Redis分片机制
1 分片机制说明
说明:由于电商网站中的数据量一般很大,如果只是使用单个redis节点进行数据存储,则不能完成任务.所以需要准备多台Redis公共实现内存数据的扩容.
2 准备3台redis
2.1 编辑配置文件
1).准备文件目录
2).修改端口号
2.2 启动多台redis
命令:
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf
Redis分片案例测试(操作ShardedJedis对象、JedisShardInfo)
3 一致性Hash算法
3.1 算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
3.2 原理说明
常识:
1.常见hash函数8位16进制数. (2^4)8 = 2^32 种可能性
2.如果对相同的数据进行hash计算 值必然相同.
3.如果值相同.,则数据不一定相同 hash碰撞.
3.3 特性-平衡性(均衡性)
①平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。
说明: 通过虚拟节点实现数据的平衡
.3.4 特性-单调性
②单调性是指在新增或者删减节点时,不影响系统正常运行 [4] 。
特点: 如果实现数据迁移时,应该尽可能保证原有的数据不变.
3.3.5 特性-分散性
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4] 。
解释: 鸡蛋不要放到一个篮子里.
十二、Redis哨兵机制(一主两从)
1.1 分片机制存在的问题
如果redis分片机制其中有一台redis宕机,则整个Redis分片将不能正常的使用. 分片机制没有高可用的效果.
1.2 哨兵机制说明
1.2.1 配置哨兵的前提
说明:实现redis数据同步(主从数据同步),是实现哨兵配置的前提条件.
1.2.2 复制文件
1.2.3删除多余的持久化文件
删除完成之后,依次启动redis.如图
1.2.4 实现主从挂载
主机: 6379
从机: 6380/6381
命令:
- 检查节点的状态 info replication
2.主从挂载命令 - 主从检查
1.3 哨兵工作原理
步骤:
1).当哨兵启动时,会动态的监控主机,之后利用PING-PONG 心跳检测机制,检查主机是否正常.
2).哨兵链接主机之后,获取相关的主从的服务信息. 方便以后选举.
3).当哨兵发现主机宕机,之后采用随机算法 选择新的主机. 之后其他节点当新主机的从.
1.4 哨兵配置
1.4.1 复制文件
1.4.2 修改哨兵配置文件
1).关闭保护模式
!
2).开启后端运行
3).监控主机
目前就一个哨兵,所以1票就生效
哨兵设置为奇数个
4).设定哨兵选举的时间(当主机宕机后,多久进行选举)
1.4.3 哨兵高可用测试
前提:
由于之前搭建好redis主从,故3台redis都是启动状态。
1).启动哨兵
命令:
redis-sentinel sentinel.conf
2).哨兵高可用测试
第一步: 检查redis主机的状态.
第二步: 关闭redis主机.
第三步: 等待10秒 检查从机是否切换为主机.
此时配置文件(sentinel.conf)中也会自动发生改变(随机选举):
第四步: 重启主机,检查是否为新主机的从.
1.4.4 哨兵入门案例
/**
* 实现redis哨兵测试
* mymaster 配置文件(sentinel.conf)中写的就是mymaster
* 哨兵只连接主机,不连接从机
*/
@Test
public void testSentinel(){
//1.链接哨兵的集合
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.126.129:26379");
JedisSentinelPool pool = new JedisSentinelPool("mymaster",sentinels);
Jedis jedis = pool.getResource();
jedis.set("aaa", "aaaaaaa");
System.out.println(jedis.get("aaa"));
spring整合:
1.把池交给Spring管理。
@Bean
public JedisSentinelPool jedisSentinelPool(){
//1.设定连接池大小
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(5); //设定最小的空闲数量
poolConfig.setMaxIdle(10); //设定最大空闲数量
poolConfig.setMaxTotal(100); //最大链接数
//2.链接哨兵的集合
Set<String> sentinels = new HashSet<>();
sentinels.add(sentinel);
return new JedisSentinelPool("mymaster",sentinels,poolConfig);
}
2.注入这个池对象,并获取资源
@AutoWired
private JedisSentinelPool sentinelPool ;
//从池中, 动态获取数据
Jedis jedis = sentinelPool.getResource();
jedis.close(); //用完之后,关闭(还)链接;
分片哨兵总结
1.分片主要实现了内存扩容, 没有高可用的效果.
2.哨兵主要实现了高可用效果, 没有实现内存数据的扩容. 哨兵本身没有高可用的效果.
如何优化: 内存扩容,节点实现高可用 redis集群实现.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。