场景知识
限流
漏桶和令牌桶都是常用的限流算法,用于控制系统的流量,防止系统被过度访问而崩溃。总的来说,漏桶算法适用于需要稳定处理请求的场景,而令牌桶算法适用于需要应对瞬时流量激增的场景。
固定窗口算法
劣势:临界时间点产生突发流量,统计数量不准确。
滑动窗口算法
滑动窗口算法把间隔时间划分成更小的粒度,当更小粒度的时间间隔过去后,把过去的间隔请求数减掉,再补充一个空的时间间隔。当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。但滑动窗口的时间间隔(小格子)多了,存储的空间也会增加。
漏桶
漏桶算法是一种固定容量的算法,类似于水桶,它可以以恒定的速率流出请求,无论输入速率有多快,最终输出速率都不会超过指定的限制。实现方式是,在服务端维护一个固定容量的队列(即漏桶),并以恒定速率处理请求。如果请求速率过快,请求将被加入到漏桶中,等待服务端处理。当漏桶已满时,新的请求将被丢弃,从而控制了流量。
令牌桶
原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。优点是可以应对瞬时流量激增的情况,因为它可以通过调整发放令牌的速率来控制请求的处理速度,但是实现相对漏桶算法更复杂一些。
使用Redis实现令牌桶算法:
- 首先判断当前令牌桶是否存在,可能是过期或刚启动
- 如果不存在就创建桶
- 存在就通过时间计算差计算当前剩余值减一放回,更新时间。
- 如果桶中剩余数量小于零,返回限流
鉴权JWT
JWT( JSON-WEB-TOKEN ) 是比较新的一种登录方式,他利用时间换空间的方式,服务端将用户的信息相关信息进行加密并返回到客户端,即签发了 一个”令牌”,在令牌的有效期内,客户端可以通过传递令牌的方式与服务端通信。
JWT 登录整体流程:
- 用户输入用户名密码进行登录
- 服务端验证用户名密码,成功后,将用户的相关信息(通常是 user_id)及一些附加信息通过 JWT 方式进行加密,并返回给客户端。
- 客户端可以用任意方式储存服务器返回的 JWT ,之后只需在每次请求时,将 JWT 通过某种方式传递给后端。
- 服务器收到请求后,获取并验证 JWT,从而获取用户的信息(通常是 user_id 及一些附加信息),即服务器不需要储存每个用户的状态(即 session), 只需要在每次请求时获取并解析 JWT,即可完成用户身份校验和用户基本信息的获取。
JWT默认的传递方式为:
1 | "headers": { |
特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。不加密的情况下,不要将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
- 后端每次接口请求都需要进行 JWT 的加解密,计算压力增大。
refreshToken就是用来在accessToken过期以后来重新获取accessToken。
Cookie+Session
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
cookie + session 是最传统的登录方式,利用浏览器默认行为,每次请求将登录后设置好的 cookie 发送给服务端, 服务端通过 cookie 中的信息( session_id),获取用户的登录信息。
整体流程如下:
- 用户输入用户名密码进行登录
- 服务端验证用户名密码,成功后,生成唯一的 session_id 储存起来(可以是内存、数据库等,通常使用 redis )。
- 通过设置 set-cookie,将 session_id 返回给前端并储存在浏览器 cookie 中。
- cookie 过期前,对该系统的每次请求都将会带上 cookie(浏览器默认行为),后端通过 cookie 中的信息,获取用户的 session_id 信息。 并在后端( redis )查询出对应用户的信息。
优点 :原理简单、实现方便
缺点:
- 服务器端需维护大量 session_id,有一定负担。(目前通常将 session_id 放在 redis中,也解决了服务器集群下 session_id 同步问题)
- 无法阻止跨站请求伪造CSRF 攻击。
- 这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
异步写库
实现异步写库可以帮助减轻数据库的压力,提高系统的吞吐量和响应速度。
为了解决这个问题,我把所有的更新、插入数据库的需求,放入一个独立的goroutine中,使用channel进行数据的异步传递,也避免了多个goroutine之间的竞争和锁的使用,使代码更加简洁易于维护。
数据一致性
采用先更新数据库再删除缓存的方案。
回写redis
先更新数据库再删除缓存,使用双检加锁机制锁住mysql,只让一个线程回写redis,完成数据一致性。
双检加锁
双检加锁就是在加锁后再次查询reids缓存,二次查询无数据才去查询mysql。
更新频繁的热点key
同一热点key保留2份,A有过期时间,B无
先查询A的,查询不到,则:
- 后端查询DB更新缓存
- 查询带后缀返回给调用方
延迟双删
先删除缓存在写数据库再延迟一段时间再次删除缓存。
假设A删除完redis缓存,然后更新mysql, 此时B读取数据,redis找不到,读mysql然后回写到redis中 A再次回写redis出现问题,因此在A第一次删除缓存时,延迟一段时间再次删除。
删除缓存失败
把要删除的缓存值或者要更新的数据库值暂存到消息队列中,从消息队列中读取这些值,然后再次进行删除或更新。
mysql有改动,立即同步redis
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis读Redis,一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。