1. 大纲
仿牛客论坛讨论社区主要功能(侧重于后端,前端页面简化)
- 帖子展示(按时间/热度),帖子发布及敏感信息审核
- 登陆--邮箱验证码,修改头像/密码
- 帖子详情,回复、点赞
- 置顶、删除帖子的权限控制
- 发私信,查看私信
- 系统通知:评论/关注/点赞
- 全站搜索
- 一段时间内网站UV、活跃用户
技术架构
- Spring Boot
- Spring、Spring MVC、MyBatis(SSM)
- Redis、Kafka、Elasticsearch
- Spring Security、Spring Actuator
2. 开发环境
-
构建工具:Apache Maven:存放构件的位置(mirror 配置镜像仓库)
-
集成开发工具:IntelliJ IDEA
-
数据库:MySQL、Redis
-
应用服务器:Apache Tomcat
-
版本控制工具:Git
3. Spring Boot入门,开发社区首页
3.1 Spring Core:
- IoC
- Inversion of Control:控制反转,是一种面向对象编程的设计思想
- Dependency Injection:依赖注入,是IoC的实现方式
- IoC Container:IoC容器,是实现DI的关键,本质是一个工厂:ApplicationContext
- AOP
- 基本组件
- @SpringBootApplication:配置文件
- @ContextConfiguration:配置类
- @SpringBootConfiguration:Spring配置
- @SpringAutoConfiguration:自动扫描
- @ComponentScan:扫描本包和子包下Bean
- 四个可以让Bean被扫描的注释
- @Component:通用
- @Controller:处理请求组件
- @Service:业务组件
- @Repository:数据库访问组件
- 给Bean起名:@Component("name")
- Bean的操作
- @PostConstruct:在建造之前初始化
- @PreDestroy:在销毁之前
- @Bean:方法名就是Bean的名字
- @Autowired:容器给变量自动注入属性
- 综合流程
- Controller处理浏览器请求
- Controller调用业务组件Service
- Service调用dao访问数据库
3.2 Spring Data Access
- Transactions
- Spring MyBatis
3.3 Web Servlet
Spring MVC
- HTTP
- HyperText Transfer Protocol超文本传输协议
- 用于传输HTML等内容的应用层协议,规定了浏览器和服务器之间如何通信,以及通信时的数据格式
- mozilla
- Spring MVC
- 三层架构:表现层,业务层,数据访问层
- MVC:Model模型层,View视图层,Controller控制层(都在表现层)
- 核心组件:DispatcherServlet前端控制器,处理浏览器请求,根据注解路径,调用Controller。Controller把数据封装在Model中返回给前端控制器,再调用View Template生成HTML,最后响应给浏览器。
- Thymeleaf
- 模版引擎,以HTML文件为模版,生成动态HTML
- 常用语法:标准表达式,判断与循环,模版的布局等
3.4 Integration
- Scheduling
- AMQP
- Security
3.5 MyBatis入门
安装MySQL + 安装客户端MySQL workbench,连接数据库
MyBatis核心组件
- SqlSessionFactory:用于创建SqlSession的工厂类
- SqlSession:MyBatis的核心组件,用于向数据库执行SQL
- 主配置文件:XML配置文件,可以对MyBatis的底层行为做出详细的配置:DataSourceProperties + MyBatisProperties
- Mapper接口:就是DAO接口,在MyBatis中习惯性称为Mapper
- Mapper映射器:用于编写SQL,并将SQL和实体类映射的组件,采用XML,注解均可实现
前3项是核心,但已经被Spring Boot整合,不需要配置。SqlSeesion可以自动创建,主配置文件可以在application.properties中配置。主要开发Mapper
数据库中用户数据包括:
- id:唯一
- username:唯一
- password:经过加密后,存入数据库
- salt:防止用户密码过于简单,防止破解,在密码后拼接5位随机字符串,之后再加密
- type:0/1/2 用户类型:普通用户/管理员/版主
- status:0/1 激活/未激活
- activation_code:激活码
- header_url:头像访问路径 10.create_time
增删改查开发步骤:
-
在entity层中创建对象,并且属性与数据库一一对应,并提供get + set + toString(方便答应对象,查看数据) 方法。驼峰命名
-
访问数据库,在dao层中创建数据访问接口interface,只需要接口,不需要实现类。@Mapper注解,提供数据操作的方法
-
在mapper层中,为每一个方法创建XML配置文件,写出sql,Mybatis自动提供实现类。
id是方法的名字,resultType是查询后返回的类型,在框中写sql,其中#{}中写参数名称来接受对应参数
3.6 开发社区首页
目标:1. 开发社区首页,显示前10个帖子 2. 开发分页组件,分页显示所有帖子
开发步骤:
-
数据库建表 discuss_post:DDL(Data Definition Language)
-
开发dao层,在entity层中,新建实体类,用驼峰命名,对应标中字段,并生成get set toString方法
-
在dao层中,创建mapper接口。注意:@Param注解用于给参数起别名,如果只有一个参数,且在 <if> 中使用,则必须加别名
-
在service层中,新建service class,使用@Service注解,注入mapper。数据查询后的过滤,筛选等业务层面的处理,可以在service中完成。
-
在mapper层中,新建xml文件,插入sql。考虑特殊情况:
- 拉黑不显示:status !=2
- userId判断不是空<if>
- order by type, create_time
- limit
-
导入静态资源:css + img + js 和html文件
-
在controller层中,创建新的controller, 注入service(@Autowired,方法调用前,SpringMVC会自动实例化,所以thymeleaf中可以直接访问对象中的数据),并添加访问路径。把查询出的数据,装入到model中,并返回。
-
创建新的entity - page,封装分页相关信息
特殊判断:
- 当前页码 >= 1
- 显示上限 >= 1 && <= 100
- 获取总页数,起始页码,终止页码
-
修改html文件,展示数据库中的数据,并添加分页逻辑
3.7 项目调试
-
HTTP响应状态吗含义
-
服务端断点调试:ide
- 打断点,debug模式启动
- F8 向下执行一行
- F7 进入方法
- F9 程序执行到下一个断点,没有则直接执行完毕
-
客户端断点调试:网页检查
-
设计日志级别,并将日志输出到不同终端
logger日志等级
- debug
- info
- error
3.8 版本控制
-
Git简介
-
Git常用命令
- 提交代码
- 上传代码
-
IDEA集成Git
4. Spring Boot实践,开发社区登陆模版
4.1 发送邮件
- 邮箱设置
- 启用客户端SMTP服务:提供可靠且有效的电子邮件传输的协议,客户端和服务器建立TCP连接
- Spring Email
- 导入jar包
- 邮箱参数配置:application.properties配置邮箱地址/端口/用户名/密码/协议smtps
- 使用JavaMailSender发送邮件:创建工具类,定义方法,设置发件人/收件人/内容
- 模版引擎
- 使用Thhymeleaf发送HTML邮件:创建html文件,使用html格式写内容,好处是可以动态地修改内容,对于不同的用户,显示不同的用户名。利用TemplateEngine模版引擎,新建对象,给html文件传入参数
4.2 开发注册功能
- 访问注册页面
- 点击顶部区域内的链接,打开注册页面:
- 修改html文件,将首页中的注册按钮点击后的访问路径变成注册页面
- 提交注册数据
- 通过表单提交数据:html中提交的方式post,和提交的url地址
- 服务端验证账号是否已存在,邮箱是否已经注册(Service)
- 创建util工具类,生成随机字符串
- 创建util工具类,使用MD5加密,MD5对于同一字符串加密后的密码也一致,且只能加密不能解密。所以对于密码,随机生成5位字符串salt,拼接原密码后加密。
DigestUtils.md5DigestAsHex(key.getBytes()) - 判断用户名/密码/邮箱是否为空,空返回错误
- 在数据库中查询用户名/邮箱是否存在,存在返回错误。用户名和邮箱不能重复
- 注册用户,密码加salt后MD5加密后存入数据库
- 生成随机验证码
- 分配随机头像
- 把用户信息插入数据库
- 服务端发送激活邮件(Service)
- 使用thymeleaf生成动态html文件
th:text="${...}",加入url链接,链接到指定路径,动态地拼出用户的id(插入用户时,Mybatis自动生成id)和激活码 - 生成templateEngine生成html文件
- 调用mailClient发邮件,发送邮件后跳转主页
- 使用thymeleaf生成动态html文件
- Controller层:
- 接收前段页面传入的用户名,密码,邮箱,并调用Service层中的创建新用户的方法
- 激活注册账号
- 点击邮件中的链接,访问服务端的激活服务
- 用户点击链接后,会传入用户id和激活码(因为刚才的动态html文件中的url路径动态地拼出id和激活码)
- 在数据库中按用户名查询,对比库中的激活码和传入的激活码
- 判断,用户状态是否为已激活/激活码不正确
- 一致后,修改用户状态为已激活,并返回成功信息,跳转html
4.3 会话管理
- HTTP性质
- HTTP是简单的
- HTTP是可扩展的
- HTTP是无状态的,有会话的
- Cookie
-
是服务器发送到浏览器,并保存在浏览器端的一小块数据
-
浏览器下次访问该服务器时,会自动携带该数据,将其发送给服务器
-
解决HTTP请求之间无关联的问题,创建有状态的会话。使用cookies让每次请求共享相同的上下文信息,达成相同的状态。例子:购物车中添加东西,第二次带cookie可以获取第一次添加到购物车的东西
-
默认存到内存,关闭浏览器就失效,但是设置cookie生效时间后,会存入硬盘,有效期直到时间结束
- Session
-
是JavaEE的标准,用于在服务端记录客户端信息
-
数据存放在服务端更加安全,但也会增加服务端的内存压力
-
分布式部署session会有问题,客户端统一访问nginx,nginx分配给不同服务器。Session存在不同服务器中,不能识别
- 解决1粘性session:同一个ip分配给同一个服务器,但可能负载不均衡
- 解决2同步session:某一个服务器创建session后,同步给所有服务器,但影响服务器性能且服务器之间有耦合
- 解决3共享session:单独一台服务器存放session,但如果宕机,所有服务器收到影响
- 解决4不存服务器,存数据库,数据库集群,服务器在数据库中取session,但性能更慢
- 解决5存redis:推荐!
4.4 生成验证码
Kaptcha
- 导入jar包:可以生成随机字符和图片
- 编写Kaptcha配置类:实例化接口
- 生成随机字符,生成图片:设置图片大小,字符大小/颜色/长度
- 生成验证码,将验证码存入session,将图片输出给浏览器
- 修改html文件,设置动态路径,将验证码图片插入
- 检查输入的验证码与session中存放的是否一致
4.5 开发登陆,退出功能
- 访问登陆页面
- 点击顶部区域内的链接,打开登陆页面
- 登陆
- 验证账号、密码、验证码
- 成功时,生成登陆凭证,发放给客户端
- 失败时,跳转回登陆页
- 退出
- 将登陆凭证修改为失效状态
- 跳转至网站首页
- 开发过程:登陆
- 创建数据表login_ticket:包含id/user_id/ticket/status/expired。ticket是随机字符串,作为登陆凭证,发送给浏览器让浏览器保存cookie,再次登陆时,cookie携带ticket,在数据库查询后可以知道是那个用户在登录,且这个用户的登陆状态有没有过期
- Mapper层
- 插入数据:用户,ticket,状态,过期时间
- 通过ticket查询数据
- 修改用户状态-0代表正常登陆1代表退出,一般不直接删除数据
- Service层
- 验证用户登陆:验证账户不为空,密码不为空,账号激活状态,验证密码(前端输入密码+salt再次通过md5加密后的值,与user表中保存的值对比)
- 生成登陆凭证,随机生成字符串。生成过期时间,存入login_ticket表中
- 返回ticket的值
- Controller层
- 验证码是否正确,不管大小写。在contoller层判断,service不需要管。提高效率,不需要访问数据库
- 调用service层,检查账号密码以及过期时间。正确把ticket作为cookie发给客户端保存,并返回首页界面,错误返回登陆页面
- 修改html 文件
- 开发过程退出:传入ticket,在login_ticket表中查询ticket对应用户,将用户状态改为未登陆
4.6 显示登陆信息
- 拦截器示例
- 拦截器浏览器的请求,以很低的耦合度,批量解决共有业务
- 定义拦截器,实现HandlerInterceptor
- 配置拦截器,为它制定拦截、排除的路径
- 拦截器应用
-
在请求开始时查询登陆用户
-
在本次请求中持有用户数据
-
在模版视图上显示用户数据
-
在请求结束时清理用户数据
每次请求都需要走这个逻辑,template负责渲染用户信息,包括用户名和用户头像等。根据ticket找user,然后给template渲染用户信息
使用ThreadLocal作为容器,持有用户信息,代替session对象。好处:在同一线程中很方便的获取用户信息,不需要频繁的传递session对象
- HandlerInterceptor
- preHandle:在Controller之前执行
- postHandle:在Controller之后执行
- afterCompletion:在TemplateEngine之后执行
4.7 账号设置
- 上传文件
- 请求:必须是POST
- 表单:
enctype= "multipart/form-data" - Spring MVC:通过MultipartFIle处理上传文件
- 开发步骤
- 访问账号设置页面
- 上传头像
- 文件存入硬盘,设置存放路径。不需要存入数据库,所以不开发dao层mapper
- 判断文件格式,并给上传的文件生成随机文件名+原始后缀
- 数据库中更新当前用户的头像web路径,服务器存放路径中获取文件名,从而得到web路径
- 获取头像
4.8 检查登陆状态
- 使用拦截器
- 在方法前标注自定义注解
- 在请求最开始,判断是否登陆,看能否获取当前用户。没登陆屏蔽部分功能,如个人信息,个人收件箱等
- 拦截所有请求,只处理带有该注解的方法
- 自定义注解
- 常用的元注解:@Target/@Retention/@Document/@Inherited
- 如何读取注解:
Method.getDeclaredAnnotations()/Method.getAnnotation(Class<T> annotaionClass)
5. Spring Boot实践,开发社区核心功能
5.1 敏感词过滤
-
前缀树
- 名称:Trie/字典树/查找树
- 根结点没有字符,除了根结点,每个子节点只有一个字符
- 从根结点到叶子节点连起来是一个字符串,如果连起来是敏感词,则在叶子节点标记
- 同级子节点,字符唯一
- 特点:查找效率高,消耗内存大
- 字符串查询敏感词,需要3个指针
- 指针1:指向字典树的根节点,作为查询字典的指针
- 指针2:从左到右指向字符串,作为单词的起始位置,不回头
- 指针3:从指针2的位置开始,往后移动,作为单词的终止位置,每次指针2移动时,回头
- 建立StringBuilder,如果字符不是敏感词就直接加入,如果是则用*代替,指针2跳转,指针1回root
- 应用:字符串检索/词频统计/字符串排序
- 敏感词过滤器
- 定义前缀树
- 根据敏感词,初始化前缀树:注意要跳过奇奇怪怪的符号,只存汉字字符。
0x2E80-0x9FFF是东亚文字范围 - 编写过滤敏感词的方法
5.2 发布帖子
- AJAX
- Asynchronous JavaScript and XML
- 异步的JavaScript和XML,不是一门新技术,只是一个新术语
- 使用AJAX,网页能将增量更新呈现在页面上,而不需要刷新整个页面
- 虽然X代表XML,但目前JSON的使用比XML更普遍,JSON更好解析
- 示例
- 使用jQuery发送AJAX请求:在html中设置一个小button,每次点击时,发送异步请求POST,在不需要刷新整个页面的情况下,访问服务器,并获取数据
- 实践
- 采用AJAX请求,实现发布帖子的功能:过滤帖子敏感词,并插入数据库
5.3 帖子详情
- DiscussPostMapper
- DiscussPostService
- DiscussPostController
- index.html:在帖子标题上增加访问详情页面的链接
- discuss-detail.html:处理静态资源的访问路径/复用index.html的header区域/显示标题,作者,发布时间,帖子正文等内容
5.4 事务管理
- 事务:
2. 事务的隔离性
-
常见的并发异常
-
第一类丢失更新:某一个事务回滚,导致另一个事务已更新的数据丢失
-
第二类丢失更新:某一个事务提交,导致另一个事务已更新的数据丢失
-
脏读:某一个事务,读取了另外一个事务未提交的数据
-
不可重复读:某一个事务,对同一个数据前后读取的结果不一致
-
幻读:某一个事务,对同一个表前后查询到的行数不一致
-
-
常见的隔离级别
-
Read Uncommitted:读取未提交的数据
-
Read Committed:读取已提交的数据
-
Repeatable Read:可重复读
-
Serializable:串行化
-
- 实现机制
-
悲观锁(数据库默认)
- 共享锁(S锁):一个事务加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。能读不能改
- 排他锁(X锁):一个事务加了共享锁后,其他事务不能加共享锁,也不能加排他锁。不能读不能改
-
乐观锁(自定义)
- 版本号/时间戳等:在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)
- Spring事务管理
- 声明式事务
- 通过XML配置,声明某方法的事务特征
- 通过注解,声明某方法的事务特征
- 编程式事务
- 通过TransactionTemplate管理事务,并通过它执行数据库的操作
5.5 显示评论
- 数据层
- 根据实体查询一页评论数量:根据 entity_type 和 entity_id 查询,limit数量
- 创建comment表
- id
- 发帖user_id
- entity_type:1帖子/2评论
- entity_id:帖子/评论id
- target_id:指向哪个用户
- content
- status
- create_time
- 根据实体查询评论的数量:根据 entity_type 和 entity_id 查询,count
- 业务层
- 处理查询评论的业务
- 处理查询评论数量的业务
- 表现层
- 显示帖子详情数据时,同时显示该帖子所有的评论数据:简历hashmap,存放人和对应的评论
5.6 添加评论
- 数据层
- 增加评论数量
- 修改帖子的评论数量
- 业务层
- 处理添加评论的业务
- 先增加评论、再更新帖子的评论数量
- 表现层
- 处理添加评论数据的请求
- 设置添加评论的表单
5.7 私信列表
- 私信列表
- 查询当前用户的会话列表,每个会话只显示一条最新的私信
- 支持分页显示
- 私信详情
- 查询某个会话所包含的私信
- 创建数据表message
- 会话id
- from_id
- to_id
- content
- status
- create_time
- 支持分页显示
5.8 发送私信
- 发送私信
- 采用异步的方式发送私信
- 发送成功后刷新私信列表
- 设置已读
- 访问私信详情时,将显示的私信设置为已读状态
5.9 统一异常处理
表现层->业务层->数据层。会把异常给表现层,所有异常在表现层统一处理。控制台记录日志,前端页面重定向到专门的错误页面。
- @ControllerAdvice
- 用于修饰类,表示该类时Controller的全局配置类
- 在此类中,可以对Controller进行三种全局配置
- 异常处理方案
- 绑定数据方案
- 绑定参数方案
- @ExceptionHandler
- 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常
- @ModelAttribute
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数
4.@DataBinder
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器
5.10 统一记录日志
- 记录日志是系统需求,而不是业务需求。不要编码到业务方法里面。记录什么用户在什么时间访问了什么方法
解决方法:AOP,Aspect OrientedProgramming 面向切面编程。是对OOP(面向对象编程)的补充,可以进一步提高编程效率
横向封装系统需求的方法,使每个业务组件都可以调用,降低耦合
- AOP术语
- target:目标对象,业务组件处理需求的目标
- aspect:封装业务需求的对象,面对aspect编程
- weaving:把aspect织入target
- Pointcut:切点声明,声明要把aspect织入什么target的什么位置
- Advice:实现具体织入逻辑
- Joinpoint:目标对象可以织入代码的位置
- AOP实现
-
AspectJ
- AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法
- AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件
- 功能强大,但学习成本高,因为是一门新的语言
-
Spring AOP
-
Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器
-
Spring AOP在运行时通过代理(用一个对象代替原始对象,调用代理对象)的方式织入代码,只支持方法类型的连接点
- Spring AOP的代理1: JDK动态代理:Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用蛰虫方式,在接口的代理实例中织入代码
- Spring AOP的代理2:CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理实例。当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码
-
Spirng支持对AspectJ的集成
-
6. Redis,一站式高性能存储方案
6.1 Redis入门
- Redis是一款基于键值对的NoSQL数据库,它的值支持多种数据结构:字符串String,哈希hash,列表list,集合yset,有序集合sorted set 等
- Redis将所有的数据都存放在内存中,所以读写性能十分惊人。同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性
- Redis典型的应用场景包括:频繁访问缓存/排行榜/计数器/社交网络/消息队列等
6.2 Spring整合Redis
-
引入依赖:spring-boot-starter-data-redis
-
配置Redis
-
配置数据库参数
-
编写配置类,构造RedisTemplate
- 访问Redis:redis保证事务
reidsTemplate.execute(new SessionCallback)
- redisTemplate.opsForValue() -- string
- redisTemplate.opsForHash()
- redisTemplate.opsForList()
- redisTemplate.opsForSet()
- redisTemplate.opsForZset()
6.3 点赞
- 点赞:性能要求高,所以用Redis储存在内存中
- 支持对帖子、评论点赞
- 记录某个实体的赞(评论/帖子)
- key:like:entity:entityType:entityId
- value:set(userId)。记录点赞的人,数量用set.size()统计。只用Integer的话,不能知道是谁点赞了
- 异步请求
- 第一次点赞,第二次取消:查询key是否存在,存在删除,不存在添加
- 首页点赞数量
- 统计帖子点赞数量:
3.详情页点赞数量
- 统计点赞数量
- 显示点赞状态
6.4 我收到的赞
- 重构点赞功能
- 以用户为key,记录点赞数量:注意查询是否点过赞要放在事务之外,因为redis的事务不会立刻执行,事务过程所有命令都在队列中,事务提交再统一执行。所以查询在事务中的话,会导致不能及时得到结果
- key-increment,key-decrement
- 开发个人主页
- 以用户为key,查询点赞数量
6.5 关注和取关
- 需求
-
开发关注、取关功能
-
统计用户的关注数、粉丝数
- 查询关注的实体的数量
- 查询实体的粉丝的数量
- 查询当前用户是否已经关注该实体
- 查询关注数
- 查询粉丝数
- 查询是否已经关注
- 关键
- 如果A关注了B,则A是B的follower粉丝,B是A的followee目标
- 关注的目标可以是用户/帖子/题目等,在实现时将目标抽象为实体
6.6 关注列表和粉丝列表
-
业务层:直接访问redis
- 查询某个用户关注的人,支持分页
- 查询某个用户的粉丝,支持分页
-
表现层
- 处理“查询关注的人”、“查询粉丝”请求
- 编写“查询关注的人”、“查询粉丝”模版
6.7 优化登陆模块:验证码/登陆凭证/用户信息
- 使用Redis存储验证码
- 验证码需要频繁的访问与刷新,对性能要求较高
- 验证码不需要永久保存,通常在很短的时间后就失效
- 分布式部署时,存在Session共享的问题,Redis没有
- 验证码归属:生成id,保存cookie,设置生效时间
- 将验证码存入Redis
- 使用Redis存储登陆凭证
- 处理每次请求时,都要查询用户的登陆凭证,访问频率非常高
- 优先在缓存Redis中取值
- 取不到时,从MySQL数据库中取值,初始化缓存数据
- 数据变更时清除缓存数据
- 使用Redis缓存用户信息
- 处理每次请求时,都要查询用户信息,访问频率非常高
7. Kafka,构建TB级异步消息系统
7.1 阻塞队列
- 阻塞队列 Blocking Queue
- 解决线程通信的问题
- 阻塞方法:put/take
- 实现
- 点对点模式m:每个数据只被一个消费者消费
- 发布订阅模式:消息可以被多个不同消费者在不同时间读取(Kafka)
- 生产者消费者模式
- 生产者:产生数据的线程
- 消费者:使用数据的线程
- 解决生产者和消费者速度不一样的问题,不影响系统性能,避免CPU资源浪费
- 实现类
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue/SynchronousQueue/DelayQueue等
7.2 Kafka入门
- Kafka简介
- Kafka是一个分布式的流媒体平台
- 应用:消息系统/日志收集/用户行为追踪/流式处理
- Kafka特点
- 高吞吐量:TB级别
- 消息持久化:硬盘存储。硬盘读写速度取决于使用方法,硬盘顺序读写效率很高
- 高可靠性:分布式保证
- 高扩展性:可以加服务器
- Kafka术语
-
Broker:Kafka的服务器
-
Zookeeper:管理Kafka集群,Kafka内置。需要配置路径
-
Topic:发布订阅模式,存放发布者发布的消息
-
Partition:为了增强并发性能,把一个topic分为多个partition
-
Offset:消息在topic中存放的索引
-
Leader Replica:主副本,可以处理请求,如果挂了,会选择一个从副本作为主副本
-
Follower Replica:从副本,备份主副本数据
- 创建kafka topic
- 生产和消费消息
7.3 Spring整合Kafka
-
引入依赖: spring-kafka
-
配置Kafka:配置server,consumer
自动提交offset偏移位置
- 访问Kafka
- 生产者:
KafkaTemplate.send(topic,data) - 消费者:
@KafkaListener(topics = {"test"}),public void handleMessage(ConsumerRecord record) {}监听消息并获取
7.4 发送系统通知
- 触发事件
-
评论/点赞/关注操作频繁,为了提高性能,使用Kafka队列。异步方式
-
评论后,发布通知:Corntroller中添加操作
丢入队列,等待处理
-
点赞后,发布通知
-
关注后,发布通知
- 处理事件
- 封装事件对象
- 事件而不是消息,抽象成更高级更普遍的事件,类型可以是评论/点赞/关注
- 定义发送人和接收人,传输json,包括userid,entityid,entitytype,content
- 开发事件的生产者
- 开发事件的消费者
7.5 显示系统通知
把上一节存入数据库中的数据,显示在页面中
-
通知列表:显示评论/点赞/关注三种类型的通知
Controller:
-
通知详情:分页显示某一类主题包含的通知
-
未读消息:在页面头部显示所有未读消息的数量
8. Elasticsearch,分布式搜索引擎
8.1 Elasticsearch简介
- Elasticsearch简介
- 一个分布式的,Restful风格(设计风格,规定前后端http请求标准的描述)的搜索引擎
- 支持对各种类型的数据的检索
- 搜索速度快,可以提供实时的搜索服务
- 便于水平扩展(加服务器),每秒可以处理PB级海量数据
- Elasticsearch术语
- Elasticsearch实现搜索,需要复制一份数据库,所以可以把它看成特殊的数据库
- 索引:原来指数据库中的库,database。6.0指表table
- 类型:原来指数据库中的表,table。6.0以后废弃
- 文档:数据库中的表中的一行rowc,通常是json
- 字段:数据库中的表中的一列column,一个属性
- 集群:多个服务器组成
- 节点:集群中的每一个服务器
- 分片:一个索引可以拆分成多个分片,提高并发能力
- 副本:对分片的备份
- 分词
- Elasticsearch只支持英文分词
- 中文分词需要安装插件:GitHub :elasticsearch ik
- Postman: 模拟http请求
8.2 Spring整合Elasticsearch
-
引入依赖:spring-boot-starter-data-elasticsearch
-
配置:cluster-name/clustter-nodes
-
spring data elasticsearch
- ElasticsearchTemplate
- ElasticsearchRepository
- 常用操作
- save:保存一条数据
- saveAll:保存所有数据
- deleteById:删除一条
- withQuery:构建搜索条件
withQuery(QUeryBuilders.multiMatchQuery("关键词"),"title","content")
8.3 开发社区搜索功能
- 搜索服务
- 将帖子保存至Elasticsearch服务器
- 从Elasticsearch服务器删除帖子
- 从Elasticsearch服务器搜索帖子
- 发布事件
- 发布帖子时,将帖子异步的提交到Elasticsearch服务器
- 增加评论时,将评论异步的提交到Elasticsearch服务器
- 在消费组件中增加一个方法,消费帖子发布事件
- 显示结果
- 在Controller中处理搜索请求,在HTML上显示搜索结果
9. 项目进阶,构建安全高效的企业服务
9.1 Spring Security
-
简介:Spring Security是一个专注于为Java应用程序提供身份认证和授权的框架,它的强大之处在于可以轻松扩展以满足自定义的需求
-
特征
- 对身份认证和授权提供全面的、可扩展的支持
- 防止各种攻击,如会话固定攻击、点击劫持、csrf等
- 支持于Servlet API/Spring MVC等web技术集成
- 具体实现
- AuthenticationManager:认证的核心接口
- AuthenticationManagerBuilder:用于构建AuthenticationManager对象的工具
- ProviderManager:AuthenticationManager接口默认的实现类
- 自定义认证:AuthenticationProvider:ProviderManager持用一组AuthenticationProvider,每个AuthenticationProvider负责一种认证。这种是委托模式,ProviderManager将认证委托给AuthenticationProvider
- Authentication:用于封装认证信息的接口(这里是账号和密码),不同的实现类代表不同类型的认证信息
- support:当前的AuthenticationProvider支持那种类型的认证
- 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中
9.2 权限控制
-
登陆检查:之前采用拦截器实现了登陆检查,这是简单的权限管理方案,现在使用security代替
-
授权配置:对当前系统内包含的所有请求,分配访问权限(普通用户/版主/管理员)
-
认证方案:绕过security认证流程,采用系统原来的认证方案
-
CSRF配置:防止csrf攻击的基本原理,以及表单/AJAX相关的配置
- 提交表单时,网站窃取浏览器cookie中的ticket,假装身份发送请求
- 解决方法:security启用,每次服务器返回数据额外附加一个tooken,网站窃取不到,验证时需要ticket+cookie才可以
- 发送AJAX请求之前,将CSRF令牌设置到请求的消息头中
9.3 置顶/加精/删除
9.4 Redis高级数据类型
用于网站统计
- HyperLogLog
- 采用一种基数算法,用于完成独立总数的统计
- 占据空间小,无论统计多少个数据,只占12k的内存空间
- 不精确的统计算法,标准误差为0.81%
- 用于去重统计用户访问数量
- Bitmap
- 不是一种独立的数据结构,实际上就是字符串
- 支持按位存取数据,可以将其看成byte数组
- 适合存储大量连续数据的布尔值(true/false)
- 用于统计用户签到
9.5 网站数据统计
- UV(Unique Visitor)
- 独立访客,需要通过用户IP去重统计数据
- 每次访问都要统计
- 统计游客+登陆用户
- HyperLogLog,性能好,且存储空间小
- DAU(Daily Active User)
- 日活跃用户,需要通过用户IP去重统计数据
- 访问过一次,则认为其活跃
- 统计登陆用户
- Bitmap,性能好,且结果精确
9.6 任务执行和调度
- JDK线程池
- ExecutorService
- ScheduledExecutorService
- Spring线程池
- ThreadPoolTaskExecutor:普通线程池
- ThreadPoolTaskScheduler:线程执行定时任务
- 分布式定时任务
-
Spring Quartz
Nginx:浏览器任务发给Nginx, 负责服务器负载均衡
9.7 热帖排行
9.8 长图
9.9 将文件上传到云服务器
- 客户端上传
- 客户端将数据提交给云服务器,并等待其响应
- 用户上传头像时,将表单数据提交给云服务器
- 服务器直接传
- 应用服务器将数据直接提交给云服务器,并等待其响应
- 分享时,服务端将自动生成的图片,直接提交给云服务器
- 具体实现
-
使用七牛云服务器
9.10 优化网站的性能
加快响应速度有一个常见的方法就是加缓存
- 本地缓存
- 将数据缓存在应用服务器上,性能最好
- 常用工具:Encache/Guava/Caffeine
- 分布式缓存
- 将数据缓存在NoSQL数据库上,跨服务器
- 由于网络开销,效率略低于本地缓存··
- 常用工具:MemCache/Redis
- 多级缓存
- 一级缓存(本地缓存)> 二级缓存(分布式缓存) > DB
- 避免缓存雪崩(缓存失效,大量请求直达DB),提高系统的可用性
10. 项目发布和总结
8.1 单元测试
8.2 项目监控
8.3 项目部署
8.4 项目总结
- 介绍
一个基本功能完整的论坛项目。项目主要功能有:基于邮件激活的注册方式,基于 MD5 加密与加盐的密码存储方式,登陆功能加入了随机验证码的验证。实现登陆状态的检查、为游客和已登录用户展示不同界面与功能。实现不同用户的权限控制和网站数据统计(UV、DAU),管理员可以查看网站数据统计和网站监控信息。支持用户上传头像,实现发布帖子、评论帖子、热帖排行、发送私信与敏感词过滤等功能。实现了点赞关注与系统通知功能。支持全局搜索帖子信息的功能
2.核心功能:
- 发帖、评论、私信、转发;
- 点赞、关注、通知、搜索;
- 权限、统计、调度、监控;
3.核心技术:
- Spring Boot、SSM
- Redis、Kafka、ElasticSearch
- Spring Security、Quatz、Caffeine
- 核心功能实现
- 通过对登录用户颁发登录凭证,将登陆凭证存进 Redis 中来记录登录用户登录状态,使用拦截器进行登录状态检查,使用 Spring Security 实现权限控制,解决了 http 无状态带来的缺陷,保护需登录或权限才能使用的特定资源。
- 使用 ThreadLocal 在当前线程中存储用户数据,代替 session 的功能便于分布式部署。在拦截器的 preHandle 中存储用户数据并构建用户认证的结果存入 SecurityContext,在 postHandle 中将用户数据存入 Model,在 afterCompletion 中清理用户数据。
- 使用 Redis 的集合数据类型来解决踩赞、相互关注功能,采用事务管理,保证数据的正确,采用“先更新数据库,再删除缓存”策略保证数据库与缓存数据的一致性。 - 采用 Redis 存储验证码,解决性能问题和分布式部署时的验证码需求。采用 Redis 的 HyperLogLog 存储每日 UV、Bitmap 存储 DAU,实现网站数据统计的需求。
- 使用 Kafka 作为消息队列,在用户被点赞、评论、关注后以系统通知的方式推送给用户,用户发布或删除帖子后向 elasticsearch 同步,wk 生成长图后将长图上传至云服务器,对系统进行解耦、削峰。
- 使用 elasticsearch + ik 分词插件实现全局搜索功能,当用户发布、修改或删除帖子时,使用 Kafka 消息队列去异步将帖子给 elasticsearch 同步。
- 使用分布式定时任务 Quartz 定时计算帖子分数,来实现热帖排行的业务功能。
- 对频繁需要访问的数据,如用户信息、帖子总数、热帖的单页帖子列表,使用 Caffeine 本地缓存 + Redis 分布式缓存的多级缓存,提高服务器性能,实现系统的高可用
- 项目亮点:
- 项目构建在Spring Boot+SSM框架之上,并统一的进行了状态管理、事务管理、异常处理;
- 利用Redis实现了点赞和关注功能,单机可达5000TPS;
- 利用Kafka实现了异步的站内通知,单机可达7000TPS;
- 利用ElasticSearch实现了全文搜索功能,可准确匹配搜索结果,并高亮显示关键词;
- 利用Caffeine+Redis实现了两级缓存,并优化了热门帖子的访问,单机可达8000QPS。
- 利用Spring Security实现了权限控制,实现了多重角色、URL级别的权限管理;
- 利用HyperLogLog、Bitmap分别实现了UV、DAU的统计功能,100万用户数据只需*M内存空间;
- 利用Quartz实现了任务调度功能,并实现了定时计算帖子分数、定时清理垃圾文件等功能;
- 利用Actuator对应用的Bean、缓存、日志、路径等多个维度进行了监控,并通过自定义的端点对数据库连接进行了监控。
8.5 面试题
- MySQL
- 存储引擎
- 事务
- 锁
- 索引
- Reids
- 数据类型
- 过期策略
- 淘汰策略
- 缓存穿透
- 缓存击穿
- 缓存雪崩
- 分布式锁
- Spring
- Spring IOC
- Spring AOP
- Spring MVC