11.25
- 完成了项目的文件设置,以及前后端,数据库环境的配置
- redis的客户端jedis和pool的配置和使用,spring data redis的使用
- redis的自动和手动序列化和反序列化
- 实现手机号检验,登录检验功能,正则表达式,基于session
- 集群的session共享问题,利用redis解决
- 多个拦截器组合,刷新token的拦截所有请求,注册拦截指定操作,指定执行顺序
11.26
- 用redis实现商品商户缓存
- 缓存穿透:缓存空对象(可能短期不一致)、布隆过滤器(保证缓存和数据库至少有一个存在)
- 缓存雪崩:同一时段内大量的缓存key同时失效,或者redis服务宕机,导致大量请求到数据库
解决方法:给不同的key的TTL添加随机值,利用redis集群提高可用性,多级缓存,降级限流 - 缓存击穿:热点key问题,高并发访问并且缓存重建业务较复杂的key失效,数据库收到巨大冲击
解决方法:互斥锁,一致性,等待时间长;逻辑过期:创建新线程,旧线程无需等待,性能好,不保证一致性,额外内存 - 根据业务实现获取锁和解锁的代码,例如在redis中获取锁的代码是尝试赋值,解锁的代码是删除值
- 线程wait、sleep、join需要捕获InterruptedException e,抛出RuntimeException(e)
- 面向对象中,组合优于继承,多写一层组合属性
- 封装缓存模板
- 写一个处理查询的类,之中包含正常查询和解决缓存击穿、穿透问题的查询,再封装处理各种事件的模板
- 优惠券功能,全局id生成器,自增id有规律性太明显,受单表数据量限制等问题
解决方法:在分布式系统下用来生成全局唯一id,高可用,高性能,安全性。利用自增和拼接,1位符号位,31位时间戳,32位序列号
场景题:如果订单量太大,每天的订单继续拼接一个当天的日期字符串,保证数据容量足够; 把时间戳和序列号结合成最终的数字:利用位运算
全局唯一id生成策略:UUID,redis自增,雪花算法;redis:每天一个时间戳,时间戳+序列号 - 优惠券业务:先判断时间和库存的合理性,再扣减库存,写入数据库
额外问题:超卖问题, 加锁,悲观锁即每次数据操作都加锁,乐观锁只有更新时才判断数据是否被修改过
实现乐观锁:版本号法,数据修改之前先判断版本号是否相等,数据修改之后修改版本号
CAS法:用原变量代替版本,先比较是否变化再去修改数据,具体的判断条件需要根据业务确定 - 一人一单,创建订单前先根据用户id和优惠券id查询是否已经买过,但是多线程还是会出现问题
此时需要用悲观锁来执行,单独对用户id加锁,注意锁要放在事务外面,否则会因为事务没提交导致错误
在其他类内部调用方法时,需要加上AopContext.currentProxy()获取代理对象来实现增强,比如事务。需要添加依赖和注解 - 集群模式下由于多个JVM中的多个锁,仍然会导致线程出现问题,无法互斥
11.27
- 在集群模式下,需要利用锁监视器;分布式锁,满足分布式系统或集群模式下多进程可见并且互斥的锁
常用实现方式:mysql(本身的互斥锁),redis(setnx),zookeeper(节点的唯一性和有序性) - Redis的实现方式,获取锁:set lock thread1 NX EX TIME,释放锁:DEL KEY
- 包装类型到基本类型的类型互换,需要做一个自动拆箱,可能会出错,需要用equals方法来判断是否相等
- redis锁的极端情况,业务出现阻塞,锁超时释放,被其他线程占有,但是业务完成时会误删其他线程的锁
解决方法:获取锁时存入线程标识(UUID),释放锁时先获取锁中的线程标识,判断与当前线程是否一致,一致再释放
还需要满足判断和释放锁两个操作是原子性的,Lua脚本实现,在脚本中编写多条redis,一次性实现
需要新建一个redis脚本对象,设置脚本的位置和返回的数据类型,再把redis的操作改为执行脚本 - 利用setnx的问题,不可重入:同一个线程无法多次获取同一把锁;不可重试:获取锁失败一次就不可重试
超时释放:如果业务执行耗时太长,导致安全隐患;主从一致性: - Redisson,分布式锁的框架,引入依赖,配置客户端,设置ip和密码,直接使用
- Redisson可重入锁,设置一个锁计数器,如果重入,计数器加一,业务结束计数器减一,计数器不为0时重置锁的有效期,继续执行业务
- 分布式锁优化,可重入锁,用hash结构记录线程id和重入次数,可重试锁,利用信号量和pubsub实现等待,唤醒的重试机制
超时续约:利用watchdog,每隔一段时间,重置超时时间 - 分布式锁主从一致性问题:把原来的主从关系都变成独立的节点,再在其中设置主从关系;创建联锁
Redisson的multiLock,多个独立的redis节点,必须所有节点都获取重入锁,才算成功 - 秒杀步骤:判断购买资格(时间,库存),扣减库存(互斥锁),创建订单(互斥锁);优化思想:把读操作和写操作分离
把读操作的数据缓存到redis,优化时间,新建线程完成写操作
库存用string缓存库存,一人一单用set缓存用户id,如果抢购成功,将信息放进阻塞队列,创建订单用异步操作完成,用一个线程池执行阻塞队列的任务 - redis基于list结构模拟消息队列,双向队列实现消息进出,优点:使用另外的内存,不受JVM内存上限,数据持久化保证数据安全,消息有序性。 缺点:无法比卖你消息丢失,只支持单消费者
- redis基于PubSub的消息队列,发布订阅,可支持多个消费者;优点:支持多生产多消费;缺点:不支持数据持久化,无法避免消息丢失,消息堆积有上限,超出时数据丢失
- redis基于Stream的消息队列,优点:消息可回溯,一个消息可以被多个消费者读取,可以阻塞读取;缺点:会消息漏读
- 基于Stream的消息队列-消费者组,将多个消费者划分到一个组中,监听同一个队列;
优点:消息分流,加速处理,消息标示,确保不漏消息,消息确认,处理完成后发送ACK确认,至少被消费一次 - 基于Stream的消息队列实现异步秒杀,将信息加入消息队列,用一个线程来执行
- 点赞功能,点赞的数量存储在数据库,点赞的人员名单存储在redis缓存set,保证一人只能点赞一次;
点赞排行榜,实现展现最先点赞的前几名,利用redis消息队列 - 关注取关功能,数据库保存一个状态即可。共同关注功能,需要先把关注放到set中,用redis的set数据结构的求交集功能
- 关注推送,feed流,内容匹配用户;TimeLine模式:不做内容筛选,按照时间排序,内容多,有噪音;智能排序模式:根据兴趣推送
feed流实现方案:拉模式(读扩散),推模式(写扩散),推拉模式,生产消息之后直接推送给消费者。 - 关注推送功能,基于redis的list消息队列实现
- 滚动分页查询功能,利用redis的查询功能,指定上一次的最小时间戳和偏移量
- 附近商户功能,GEO数据结构,存储地理坐标,可以自动实现半径内的范围查询
- 用户签到,BitMap数据,二进制字符串实现统计一个月每天的签到情况
- 统计签到情况,连续签到天数:从最后一次签到开始往前找第一个未签到的数量;所有签到数:统计所有区域;从后往前遍历每一个:与1做与运算,能得到最后一个bit位;
- UV统计,独立访客量;PV统计,页面访问量;HyperLogLog概率算法,基于String,错误率极低,用于统计大量的数据