文章目录
1. Session 共享
1.0 cookie和session的工作流
在 Web 开发中,Cookie 和 Session 是非常常见的,尤其是在处理用户身份验证、会话管理等方面。
-
用户提交登录请求:
用户通过登录页面提交用户名和密码。 -
服务器验证用户信息:
服务器接收到登录请求后,验证用户提供的用户名和密码是否正确。 -
创建 Session:
如果验证通过,服务器生成一个唯一的 Session ID,并在服务器端创建一个 Session 来存储用户的相关信息(如用户 ID、用户名、权限等)。服务器将这个 Session ID 存储在 Session 对象中,并设置一个过期时间。
-
发送 Session ID 到客户端:
服务器通过响应头,在响应头中设置 Set-Cookie,Set-cookie中包含了 Session ID。 -
浏览器发送请求:
客户端收到响应后,如果服务器给了Set-cookie,当用户在登录后访问其他页面时,浏览器会自动将包含 Session ID 的 Cookie 附加到每个 HTTP 请求中。 -
服务器验证 Session:
服务器接收到请求后,会检查请求中的 Cookie,提取出 Session ID。
服务器使用这个 Session ID 查找对应的 Session,以确定该用户的身份,并获取相关的用户数据。
1.1 Cookie范围
-
Cookie存储在客户端且不可跨域。
-
种session的时候注意范围,可以在配置的cookie.domain进行设置
如果要共享cookie,可以种一个更高层的公共域名。 -
比如,前端localhost:3000访问,分配cookie;后4000访问,又重新分配cookie。设置成更高的公共域名localhost之后,4000、5000等再访问的时候,不用重新给分配cookie了。
server:
port: 8080
servlet:
context-path: /api
session:
cookie:
domain: localhost
1.2 为什么要共享?
-
为什么服务器A登陆后,请求发到服务器B,B不认识该用户?
原因:请求发到服务器A上,session(用户登录信息)存在A的内存中。
此时B的内存没有该用户的sessionID,因此不认识该用户。
解决方案:共享存储,把session存储到A和B都能读取到的中间件中,可以使用redis、mysql、文件服务器ceph等;而不是把数据放到单台服务器的内存中。
1.3 如何共享存储
核心思想:把数据放到同一个地方集中管理。
- Redis。用户信息读取 / 是否登录的判断及其频繁,Redis是基于内存的键值存储系统,读写性能很高,简单的数据单机qps能达到5w-10w。
- MySQL
- 文件服务器,如ceph
1.4 session共享实现
redis的云上装载参考:Java相关工具/插件的安装教程汇总
- 引入redis → 能够操作redis。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.4</version>
</dependency>
redis管理工具:quick redis,能看到存进来的session信息等。
- 引入spring-session 和 redis 的整合,使得自动将session存储到redis中。
<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.6.3</version>
</dependency>
- 修改spring-session存储配置,改application.yml的配置。
原来session只设置了timeout过期时间,现在要加一个store-type。
spring.session.store-type默认是none,表示存储在服务器
设置成:store-type:redis 表示从 redis 读写 session
Spring:
session:
timeout: 86400
store-type: redis
2. 缓存的实现
2.1 缓存分类
分布式缓存:
缓存只存在于单服务器上,会导致数据不一致。可以把缓存存在一台共享的服务器上,即为分布式缓存。
- redis(分布式缓存)
- memcached(分布式)
- Etcd(云原生架构的一个分布式存储,适合存储配置)建议学习,亮点之一
单机缓存:
- ehcache(单机,每个服务器只能读取自己的)
- 本地缓存(java 内存 存到 Map)
- caffeine(java 内存缓存,高性能)
- Google guava
2. 2 Redis 缓存实现
NoSQL 数据库
key - value 存储系统 (区别于关系型数据库,redis存储的是键值对)
2.1.1 Redis 的数据结构(高频考点)
基本数据类型:
- String 字符串类型:name: cat
- List 列表:names: [“cat”,“dog”,“dog”]
- Set 集合:names:[“cat”,“dog”](值不能重复)
- Hash 哈希:nameAge:{“dog":1,“cat”:2,“mouse”:3}
- Zset 集合:names:{dog - 3, cat - 5} 为值指定一个分数,适合做排行榜
高级类型:
- bloomfilter(布隆过滤器,主要从大量数据中快速过滤纸,如邮件黑名单拦截)
- geo(计算地理位置)
- hyperloglog(pv / uv)
- pub / sub(发布订阅,类似消息队列)
- BitMap(存储大量可以压缩的信息)
2.1.2 redisTemplate 自定义序列化
为了防止写入 Redis 的数据乱码、浪费空间等,可以自定义序列化器。
-
redisTemplate是什么?
-
redisTemplate如何自定义序列化?
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.RedisSerializer;
@Configuration
public class RedisTemplateConfig {
// https://space.bilibili.com/12890453/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
2.1.3 Java操作 Redis
① Spring data redis(推荐使用)
- Spring data 是一个通用的数据访问框架,定义了一组增删改查的接口。它能操作mysql、redis、jpa等。
- 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.4</version>
</dependency>
- 配置 reids 地址
Spring:
# redis配置
redis:
port: 6379
host: localhost / IP地址
password: 实际密码
database: 0
server:
② Jedis
- 独立于Spring 操作 Redis 的 Java 客户端
- Jedis 默认是线程不安全的,要配合 Jedis pool (连接池)使用
③ Lettuce
高阶的操作 Redis 的 Java 客户端
- springboot data redis 整合了 lettuce 的,看 springboot data redis 的依赖,引了 lettuce 的依赖
- 支持异步、连接池
- 连接池:复用连接
④ Redisson
分布式操作 Redis 的 Java 客户端,让你像在使用本地的集合一样操作 Redis (分布式 Redis 数据网格)
⑤ Jetcache
几种实现的对比【技术选型】
- 如果用的是 Spring,并且没有过多的定制化要求,可以用 Spring data redis 最方便。
- 如果用的不是 Spring,并且追求简单,没有过高的性能要求,可以用 Jedis + Jedis Pool
- 如果项目不是 Spring,并追求高性能、高定制化且自认为编程水平较高(hhhhh),可以用 Lettuce,支持异步、连接池
- 如果项目是分布式的,需要用到一些分布式的特性,比如分布式锁、分布式集合,推荐用 Redisson(可以简历写,亮点)
2.1.4 设计缓存的key
- 不同用户看到的数据不同,,不要和别的项目的key冲突,故设计一个带项目、模块的key
systemId:moduleId:func
例如”xuwu:user:recommed:userId - 重要: redis内存不能无限增加,一定要设置过期时间!!
2.1.5 缓存预热
问题引入:第一个用户访问(暂时还没有缓存的情况下)还是很慢,也能一定程度保护数据库。
分析优缺点:打开思路,从整个项目从0 - 1的链路上去分析
- 缓存预热的优点
- 解决问题引入中的问题,可以让用户始终访问很快
- 缓存预热的缺点
- 增加开发成本(要额外开发和设计)
- 预热的时机和时间如果错了,有可能缓存的数据不对或者是太老的数据。
- 需要占用额外空间
① 如何实现缓存预热:定时任务
- 定时任务:每隔一段时间执行一次
- 模拟触发(手动触发)
- 定时任务的方法
- Spring Scheduler(spring boot默认整合了) @Scheduler
- Quartz(独立于 Spring 存在的定时任务框架)
- XXL-Job 之类的分布式任务调度平台(界面 + sdk)【建议学习】
2.1.6 定时任务实现缓存预热
用定时任务,每天刷新所有用户的推荐列表
- 注意点:
- 缓存预热的意义:新增少,总用户多
- 缓存的空间不能太大,要预留其他缓存空间
- 缓存数据的周期(yupao项目每天一次)
- 实现定时任务
- 主类开启 @EnableScheduling
- 给要定时执行的方法添加 @Scheduled注解,指定 cron 表达式或执行频率。cron表达式有计算网站,不用自己去记。
2.7 小知识点
-
操作系统知识:取数据速度,硬盘最慢,从内存取会相对更快。Mysql是从硬盘读取数据,redis缓存是从内存读取数据。从内存的缓存中读数据,能解决更快查询的问题。
-
引入一个库时,先写测试类进行测试。