网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- 一般讲锁是多线程的锁,是在一个进程中的;
- 多个进程(JVM)在并发时也会产生问题,也要控制时序性;
- 可以采用分布式锁。使用Redis实现
sexNX
做乐观锁(Redis)
- 同步锁和数据库中的行锁、表锁都是悲观锁
- 悲观锁的性能是比较低的,响应性比较差
- 高性能、高响应(秒杀)采用乐观锁
- Redis可以实现乐观锁
watch + incr
什么是缓存
缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度很快。现在泛指存储在计算机上的原始数据的复制集,便于快速访问。在互联网技术中,缓存是系统快速响应的关键技术之一
大型网站中缓存的使用
单机架构LAMP(Linux+apache+MySQL+PHP)、JavaEE(SSM);访问量越大,响应力越差,用户体验越差
在大型网站中从浏览器到网络,再到应用服务器,再到数据库,通过在各个层面应用缓存技术,大大提升了系统性能和用户体验。
缓存分类
页面缓存
传统互联网:页面缓存和浏览器缓存
移动互联网:APP缓存
页面缓存
页面缓存:页面自身对某些元素或全部元素进行存储,并保存成文件。html5:Cookie、WebStorage(SessionStorage和LocalStorage)、WebSql、indexDB、Application、Cache等
开启步骤
1、设置manifest描述文件:CACHE MANIFEST #comment js/index.js img/bg.png
2、html关联manifest属性
浏览器缓存
当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
浏览器缓存
可分为强制缓存
和协商缓存
。
强制缓存
:直接使用浏览器的缓存数据
条件
:Cache-Control的max-age没有过期或者Expires的缓存时间没有过期
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="Expires" content="Mon, 20 Aug 2010 23:00:00 GMT" />
协商缓存
:服务器资源未修改,使用浏览器的缓存(304);反之,使用服务器资源(200)。
<meta http-equiv="cache-control" content="no-cache">
APP缓存
原生APP中把数据缓存在内存、文件或本地数据库(SQLite)中。比如图片文件。
网络端缓存
通过代理的方式响应客户端请求,对重复的请求返回缓存中的数据资源。
Web代理缓存
可以缓存原生服务器的静态资源,比如样式、图片等。常见的反向代理服务器比如大名鼎鼎的Nginx。
边缘缓存
边缘缓存中典型的商业化服务就是CDN了。CDN的全称是Content Delivery Network,即内容分发网络。CDN通过部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。现在一般的公有云服务商都提供CDN服务。
服务端缓存
服务器端缓存是整个缓存体系的核心。包括数据库级缓存、平台级缓存和应用级缓存。
数据库级缓存
数据库是用来存储和管理数据的。MySQL在Server层使用查询缓存机制。将查询后的数据缓存起来。K-V结构,Key:select语句的hash值,Value:查询结果。InnoDB存储引擎中的buffer-pool用于缓存InnoDB索引及数据块。
平台级缓存
平台级缓存指的是带有缓存特性的应用框架。比如:GuavaCache 、EhCache、OSCache等。部署在应用服务器上,也称为服务器本地缓存。
应用级缓存(重点)
具有缓存功能的中间件:Redis、Memcached、EVCache、Tair等。采用K-V形式存储。利用集群支持高可用、高性能、高并发、高扩展。
分布式缓存
缓存的优势、代价
使用缓存的优势
提升用户体验
用户体验(User Experience):用户在使用产品过程中建立起来的一种纯主观感受。缓存的使用可以提升系统的响应能力,大大提升了用户体验。
减轻服务器压力
客户端缓存、网络端缓存减轻应用服务器压力。服务端缓存减轻数据库服务器的压力。
提升系统性能
系统性能指标:响应时间、延迟时间、吞吐量、并发用户数和资源利用率等。
缓存技术可以:
缩短系统的响应时间
减少网络传输时间和应用延迟时间
提高系统的吞吐量
增加系统的并发用户数
提高了数据库资源的利用率
使用缓存的代价
额外的硬件支出
缓存是一种软件系统中以空间换时间的技术。需要额外的磁盘空间和内存空间来存储数据;搭建缓存服务器集群需要额外的服务器
采用云服务器的缓存服务就不用额外的服务器了;阿里云,百度云,提供缓存服务
高并发缓存失效
在高并发场景下会出现缓存失效(缓存穿透、缓存雪崩、缓存击穿)。造成瞬间数据库访问量增大,甚至崩溃
缓存与数据库数据同步
缓存与数据库无法做到数据的时时同步;Redis无法做到主从时时数据同步
缓存并发竞争
多个redis的客户端同时对一个key进行set值得时候由于执行顺序引起的并发问题
缓存读写模式
Cache Aside Pattern(常用)
Cache Aside Pattern
(旁路缓存),是最经典的缓存+数据库读写模式。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存呢?
缓存的值是一个结构:hash、list,更新数据需要遍历
2、懒加载,使用的时候才更新缓存
也可以采用异步的方式填充缓存
Read/Write Through Pattern
应用程序只操作缓存,缓存操作数据库。
Read-Through
(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入缓存。
Write-Through
(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。
该种模式需要提供数据库的handler,开发较为复杂。
Write Behind Caching Pattern(推荐)
应用程序只更新缓存。
缓存通过异步的方式将数据批量或合并后更新到DB中,不能时时同步,甚至会丢数据
缓存架构的设计思路
缓存的整体设计思路包括:
- 多层次
分布式缓存宕机,本地缓存还可以使用
- 数据类型
- 简单数据类型
Value是字符串或整数
Value的值比较大(大于100K)
只进行setter和getter
可采用Memcached
Memcached纯内存缓存,多线程 - 复杂数据类型
Value是hash、set、list、zset
需要存储关系,聚合,计算
可采用Redis
- 简单数据类型
- 要做集群
分布式缓存集群方案(Redis)
哨兵+主从
RedisCluster - 缓存的数据结构设计
- 1、与数据库表一致
数据库表和缓存是一一对应的
缓存的字段会比数据库表少一些
缓存的数据是经常访问的
用户表,商品表 - 2、与数据库表不一致
需要存储关系,聚合,计算等
比如某个用户的帖子、用户的评论。
以用户评论为例,DB结构如下:
如果要取出UID为1000的用户的评论,原始的表的数据结构显然是不行的。
我们应做如下设计:- key:UID+时间戳(精确到天) 评论一般以天为计算单位
- value:Redis的Hash类型。field为 id和content
- expire:设置为一天
- 1、与数据库表一致
案例: 设计拉钩首页缓存职位列表、热门职位
1、静态文件
2、职位列表
数据特点:固定数据,一次性读取
方案:
- 在服务器开启时一次性初始化到服务器本地缓存
- 采用Guava Cache,Guava Cache用于存储频繁使用的少量数据,支持高并发访问
- 也可以使用JDK的CurrentHashMap,需要自行实现
3、热门职位
数据特点:频繁变化,不必时时同步;但一定要有数据,不能为空
方案:
- 数据从服务层读取(dubbo),然后放到本地缓存中(Guava),如果出现超时或读取为空,则返回原
- 来本地缓存的数据。
注意:不同的客户端看到的数据有可能不一样
4、数据回填
从Dubbo中读取数据时,先读取Redis集群的缓存,如果缓存命中则直接返回。如果缓存不命中则返回本地缓存,不能直接读取数据库。用异步的形式从数据库刷入到缓存中。
5、热点策略
对于热点数据我们采用本地缓存策略,而不采用服务熔断策略,因为首页数据可以不准确,但不能不响应。
高并发脏读
先更新数据库,在更新缓存
先删除缓存,在更新数据库
先更新数据库,再删除缓存(推荐)
Redis底层结构和缓存原理
本章学习目标:
- 掌握Redis五种基本数据类型的用法和常见命令的使用
- 了解bitmap、geo、stream的使用
- 理解Redis底层数据结构(Hash、跳跃表、quicklist)
- 了解RedisDB和RedisObject
- 理解LRU算法
- 理解Redis缓存淘汰策略
- 能够较正确的应用Redis缓存淘汰策略
Redis数据类型和应用场景
Redis是一个Key-Value的存储系统,使用ANSI C语言编写。key的类型是字符串。value的数据类型有:常用的:string字符串类型、list列表类型、set集合类型、sortedset(zset)有序集合类型、hash类型。不常见的:bitmap位图类型、geo地理位置类型。Redis5.0新增一种:stream类型
注意:Redis中命令是忽略大小写,(set SET),key是不忽略大小写的 (NAME name)
Redis中key的设计
- 用:分割
- 把表名转换为key前缀, 比如: user:
- 第二段放置主键值
- 第三段放置列名
比如:用户表user, 转换为redis的key-value存储
username 的 key: user:9:username
{userid:9,username:zhangf}
email的key user:9:email
表示明确:看key知道意思
不易被覆盖
数据类型及命令手册
参考:https://blog.csdn.net/qq_36581961/article/details/113248387
Redis客户端访问
Java程序访问Redis
采用jedis API进行访问即可
关闭RedisServer端的防火墙
systemctl stop firewalld(默认)
systemctl disable firewalld.service(设置开启不启动)
新建maven项目后导入Jedis包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
测试类
@Test
public void testConn(){
//与Redis建立连接 IP+port
Jedis redis = new Jedis("192.168.127.128", 6379);
//在Redis中写字符串 key value
redis.set("jedis:name:1","jd-zhangfei");
//获得Redis中字符串的值
System.out.println(redis.get("jedis:name:1"));
//在Redis中写list
redis.lpush("jedis:list:1","1","2","3","4","5");
//获得list的长度
System.out.println(redis.llen("jedis:list:1"));
}
Spring访问Redis
新建项目,添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>
</dependencies>
添加Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:redis.properties</value>
</list>
</property>
</bean>
<!-- redis config -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="${redis.pool.maxActive}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="maxWait" value="${redis.pool.maxWait}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.server}"/>
<property name="port" value="${redis.port}"/>
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="KeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="ValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
</bean>
</beans>
添加properties文件
redis.pool.maxActive=100
redis.pool.maxIdle=50
redis.pool.maxWait=1000
redis.pool.testOnBorrow=true
redis.timeout=50000
redis.server=192.168.72.128
redis.port=6379
编写测试用例
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import java.io.Serializable;
@ContextConfiguration({ "classpath:redis.xml" })
public class RedisTest extends AbstractJUnit4SpringContextTests {
@Autowired
private RedisTemplate<Serializable, Serializable> rt;
@Test
public void testConn() {
rt.opsForValue().set("name","zhangfei");
System.out.println(rt.opsForValue().get("name"));
}
}
SpringBoot访问Redis
新建项目,导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加配置文件application.yml
spring:
redis:
host: 192.168.72.128
port: 6379
jedis:
pool:
min-idle: 0
max-idle: 8
max-active: 80
max-wait: 30000
timeout: 3000
添加配置类RedisConfig
package com.lagou.sbr.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
** 添加RedisController**
package com.lagou.sbr.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping(value = "/redis")
public class RedisController {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/put")
public String put(@RequestParam(required = true) String key,
@RequestParam(required = true) String value) {
//设置过期时间为20秒
redisTemplate.opsForValue().set(key,value,20, TimeUnit.SECONDS);
return "Success";
}
@GetMapping("/get")
public String get(@RequestParam(required = true) String key){
return (String) redisTemplate.opsForValue().get(key);
}
}
修改Application并运行
package com.lagou.sbr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
底层数据结构
参考(未更新完整):https://blog.csdn.net/qq_36581961/category_10691600.html
缓存过期和淘汰策略
Redis性能高:
官方数据
读:110000次/s
写:81000次/s
长期使用,key会不断增加,Redis作为缓存使用,物理内存也会满
内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降
maxmemory
不设置的场景
Redis的key是固定的,不会增加
Redis作为DB使用,保证数据的完整性,不能淘汰 , 可以做集群,横向扩展
缓存淘汰策略:禁止驱逐 (默认)
设置的场景
Redis是作为缓存使用,不断增加Key
maxmemory : 默认为0 不限制
问题:达到物理内存后性能急剧下架,甚至崩溃;内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降
设置多少?与业务有关
1个Redis实例,保证系统运行 1 G ,剩下的就都可以设置Redis;物理内存的3/4
slaver : 留出一定的内存
在redis.conf中
maxmemory 1024mb
命令: 获得maxmemory数
CONFIG GET maxmemory
设置maxmemory后,当趋近maxmemory时,通过缓存淘汰策略,从内存中删除对象
不设置maxmemory 无最大内存限制 maxmemory-policy noeviction (禁止驱逐) 不淘汰
设置maxmemory maxmemory-policy 要配置
expire数据结构
在Redis中可以使用expire命令设置一个键的存活时间(ttl: time to live),过了这段时间,该键就会自动被删除
expire的使用
expire命令的使用方法如下:expire key ttl(单位秒)
127.0.0.1:6379> expire name 2 #2秒失效
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name zhangfei
OK
127.0.0.1:6379> ttl name #永久有效
(integer) -1
127.0.0.1:6379> expire name 30 #30秒失效
(integer) 1
127.0.0.1:6379> ttl name #还有24秒失效
(integer) 24
127.0.0.1:6379> ttl name #失效
(integer) -2
expire原理
typedef struct redisDb {
dict *dict; -- key Value
dict *expires; -- key ttl
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
} redisDb;
上面的代码是Redis 中关于数据库的结构体定义,这个结构体定义中除了 id 以外都是指向字典的指针,其中我们只看 dict 和 expires
dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对,expires则用于维护一个 Redis 数据库中设置了失效时间的键(即key与失效时间的映射)
当我们使用 expire命令设置一个key的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的key是否存在,如果存在就将这个key和失效时间添加到 expires 这个字典表。当我们使用 setex命令向系统插入数据时,Redis 首先将 Key 和 Value 添加到 dict 这个字典表中,然后将 Key 和失效时间添加到 expires 这个字典表中。
简单地总结来说就是,设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。
删除策略
Redis的数据删除有定时删除
、惰性删除
和主动删除
三种方式。Redis目前采用惰性删除
+主动删除
的方式。
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
创建一个定时器,当key设置有过期时间 且过期时间到达 由定时器任务立即对键的删除
- 优点:节约内存 到时删除 快速释放不必要的内存
- 缺点:CPU压力很大 无论CPU此时的负载多高 均占用CPU 会影响redis服务器相应时间和指令吞吐量
总结:以时间换空间
惰性删除
数据到达过期时间 不做处理 等下次访问该数据时
- 如果过期 删除
- 如果未过期 返回数据
- 优点:节约CPU性能 发现必须删除才删除
- 缺点:内存压力大 出现长期占用内存数据
总结:用存储空间换处理器性能 以空间换时间
调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。
int expireIfNeeded(redisDb \*db, robj \*key) {
//获取主键的失效时间 get当前时间-创建时间>ttl
long long when = getExpire(db,key);
//假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0
if (when < 0) return 0;
//假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0
if (server.loading) return 0;
//如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键
//还未失效就直接返回0
if (mstime() <= when) return 0;
//如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失
//效的信息进行广播,最后将该主键从数据库中删除
server.stat_expiredkeys++;
propagateExpire(db,key);
return dbDelete(db,key);
}
定期删除(主动删除)
周期性轮询redis库中的时效性数据 采用随机抽取的策略 利用过期数据占比的方式控制删除频度
- 特点:CPU性能占用设置有峰值 检测频度可以自定义设置
- 特点:内存占用率不是很大 ,长期占用内存的冷数据会被持续清理
逐出算法
数据逐出的相关配置
- 最大可使用配置
![img](https://img-blog.csdnimg.cn/img_convert/ce8ae9a378e085490ad4d76744e01c5b.png)
![img](https://img-blog.csdnimg.cn/img_convert/f3915b7038619606448a17dd262ad7ff.png)
![img](https://img-blog.csdnimg.cn/img_convert/1ef72686b892fc87bea624b5445d3f29.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**
ess=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)
周期性轮询redis库中的时效性数据 采用随机抽取的策略 利用过期数据占比的方式控制删除频度
* 特点:CPU性能占用设置有峰值 检测频度可以自定义设置
* 特点:内存占用率不是很大 ,长期占用内存的冷数据会被持续清理
##### 逐出算法
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6ab1b6b5d8381dc2cb4e3846fb8eb79a.png)
**数据逐出的相关配置**
* 最大可使用配置
[外链图片转存中…(img-RcI3rrHs-1715544849134)]
[外链图片转存中…(img-5ei7bEgj-1715544849135)]
[外链图片转存中…(img-Rx9Z4t11-1715544849135)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新