流利的表达项目的完整业务
项目介绍:
在线社交是互联网时代的产物,已成为互联网用户的基础需求之一。移动互联网自2003年起快速发展,促使在线社交逐渐从PC端转移至移动端。探花交友项目定位于陌生人交友市场。涵盖附近的人,即时通信,左滑右滑,测试灵魂等功能
功能介绍:
注册登录:
用户通过手机验证码进行登录,如果是第一次登录就需要完善用户信息,用户信息分为两部分一个是基本资料填写,标准CURD存储,另一部分为头像上传,需要对图片进行人脸识别校验,防止用户上传非人脸的图片作为头像。**应用的技术是百度智能云的人脸识别功能。**完成流程,则完成登录。
交友:
交友是探花项目的核心功能之一,用户可以记进行查看好友,添加好友,搜索好友等操作。
圈子:
圈子功能分为两部分,一部分为推荐的圈子(非好友部分),一部分是好友的圈子展示
1、推荐频道为根据问卷调查以及喜好推荐相互用户动态;
2、显示内容为用户头像、用户昵称、用户性别、用户年龄、用户标签和用户的动态内容;
3、图片最多不超过6张或发布一个小视频;
4、动态下方显示发布时间距离当前时间长度;
5、动态下方显示距离:发布动态的地点与当前本地距离
6、显示用户浏览量
7、显示用户点赞数、评论数、转发数
即时通信:
即时通信是社交不可缺少的一环,消息包含通知类消息和好友消息
小视频:
类似抖音(快手),用户可以发小视频,评论等。用户可以上传小视频,也可以查看小视频,并且进行点在,评论等功能。内部基于大数据算法根据行为特征进行不同的视频推荐列表
我的:
我的动态,我的关注数,通用设置等等
技术架构
采用MongoDB geo实现地理位置查询
采用RocketMQ作为消息中间件
采用MongoDB作为海量消息存储
采用环信服务实现即时通讯
采用FastDFS分布式文件系统存储小视频数据
采用Apache Dubbo作为微服务技术架构技术
采用springBoot + Mybatis实现系统主架构
采用redis集群实现缓存的高可用
前后端分离开发思想
通过nginx+Tomcat的方式有效的进行解耦,并且前后端分离为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端:浏览器、车载终端、安卓、IOS等)打下坚实基础
专业的人做专业的事。前后端分离之后,前段人员可以专注于UI界面的设计开发,后端人员则可以专注于业务逻辑开发。
1、客户端app与服务器交互的数据格式?
http请求进行json的数据传递
2、如何规范前后端接口和数据格式?
通过接口(API)文档管理
前后端分离
1、前后端分离开发基于HTTP和JSON交互
2、通过接口文档(API)定义规范
3、后端按照文档定义请求即响应数据
4、前段按照文档发送请求解析数据
JWT
JSON Web Token(JWT)是一个非常轻巧的范式、这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
JWT结构
三部分:头部、载荷、签名
头部
最基本信息,例如其类型以及签名所用的算法等。
这也可以表示成一个JSON对象。{“typ”:“JWT”,“alg”:“HS256”}。
载荷
自定义信息(自己的信息:用户名、id、权限,不要放敏感的内容),token的信息(token的id,当前token过期时间)
签名
算法(base64 进行编码)(base64(头).base64(载荷))
base64算法是公开的,谁都可以解码,所以不应该存放敏感信息
作用
做无状态登录(服务端不存储登录状态)。
登录,生成JWT,存储到客户端
客户端每次请求都需要携带token
服务端验证token,获得用户的信息
退出登录,删除token
@mysql存储引擎
一、MySQL有多少种存储引擎?
在MySQL5之后,支持的存储引擎有十多个,但是我们常用的就那么几种,而且,默认支持的也是 InnoDB。
通过命令:show engines \g,我们可以查看到当前数据库可以支持的存储引擎有哪些。MySQL默认支持了9种,其中,有3种是我们最常见的。如下图:
二、你们项目中使用MySQL的搜索引擎是哪个?为什么要用这个?
我们使用的是 InnoDB。
1、InnoDB
InnoDB是默认的数据库存储引擎,主要特点有:
a、可以自动增长列,方法是:auto_increment。
**b、支持事务。**默认的事务隔离级别是可重复读,通过MVCC(并发版本控制)来实现。
c、使用的锁粒度为行级锁,可以支持更高的并发。
**d、支持外键约束;**外键约束其实降低了表的查询速度,但是增加了表之间的耦合度。
e、配合一些热备工具可以支持在线热备份。
f、在 InnoDB 中存着缓冲管理,通过缓冲池,将索引和数据全部缓存起来,加快查询的速度;
**g、对于 InnoDB 类型的表,其数据的物理组织形式是聚簇表。**所有的数据按照主键来组织,数据和索引放在一块,都位于B+树的叶子节点上。
当然,InnoDB 的存储表和索引也有下面两种形式:
**(1)使用共享表空间存储:**所有的表和索引存放在同一个表空间中。
**(2)使用多表空间存储:**表结构放在frm文件,数据和索引放在IBD文件中。分区表的话,每个分区对应单独的IBD文件,分区表的定义可以查看我的其他文章。使用分区表的好处在于提升查询效率。
对于InnoDB来说,最大的特点在于支持事务。但是这是以损失效率来换取的。
2、MyISAM
使用这个存储引擎,每个 MyISAM 在磁盘上存储形成3个文件:
a、frm文件:存储表的定义数据;
b、MYD文件:存放表具体记录的数据;
c、MYI文件:存储索引;
frm 和 MYI 可以存放在不同的目录下。MYI 文件用来存储索引,但仅保存记录所在页的指针,索引的结构是B+树结构。
下面这张图就是MYI文件保存的机制:
从这张图可以发现,这个存储引擎通过MYI的B+树结构来查找记录页,再根据记录页查找记录。并且支持全文索引、B树索引和数据压缩。
支持数据的类型也有三种:
(1)静态固定长度表
这种方式的优点在于存储速度非常快,容易发生缓存,而且表发生损坏后也容易修复。缺点是占空间。这也是默认的存储格式。
(2)动态可变长表
优点是节省空间,但是一旦出错恢复起来比较麻烦。
(3)压缩表
上面说到支持数据压缩,说明肯定也支持这个格式。在数据文件发生错误时候,可以使用check table工具来检查,而且还可以使用repair table工具来恢复。
有一个重要的特点那就是不支持事务,但是这也意味着他的存储速度更快,如果你的读写操作允许有错误数据的话,只是追求速度,可以选择这个存储引擎。
3、Memory
将数据存在内存,为了提高数据的访问速度,每一个表实际上和一个磁盘文件关联。文件是frm。
**(1)支持的数据类型有限制,**比如:不支持TEXT和BLOB类型,对于字符串类型的数据,只支持固定长度的行,VARCHAR会被自动存储为CHAR类型;
**(2)支持的锁粒度为表级锁。**所以,在访问量比较大时,表级锁会成为MEMORY存储引擎的瓶颈;
(3)由于数据是存放在内存中,一旦服务器出现故障,数据都会丢失;
(4)查询的时候,如果有用到临时表,而且临时表中有BLOB,TEXT类型的字段,那么这个临时表就会转化为MyISAM类型的表,性能会急剧降低;
(5)默认使用hash索引。
(6)如果一个内部表很大,会转化为磁盘表。
在这里只是给出3个常见的存储引擎。使用哪一种引擎需要灵活选择,一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能
SQL优化
where语句中:这些操作会导致查询放弃索引而是用全局扫描:
使用!=, > , < ,避免where语句中加or关键字查询多个,
使用 "num is null"判断(解决: 修改默认空值为0,这样就变成了 num = 0)
使用%进行模糊查询,
使用in和not in 产生的范围查询 (解决: 使用exists 代替in)
使用参数: where num = @num参数未知,因而无所作为索引进入扫面项
(解决: 可改为强制使用索引,with index(索引名) where num = @num)
对字段使用表达式形式: num/2 = 100(解决: 修改成 num = 100 *2)
索引操作:
建立索引, 索引所在字段不应大量重复(性别字段就不适合作为索引),且数据有特点,最好是数字字段
控制所以索引数量(一个表的索引最好不要超过6个)
尽量避免更新clustered索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源
使用复合索引作为条件,name应该使用第一个字段作为条件才能保证正确使用索引(最左原则),多个字段的顺序也应该与索引保持一致
其他操作:
使用varchar 代替 char
不要使用select * from,返回具体字段
使用表变量代替临时表
避免频繁创建和删除临时表,减少资源消耗
临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如, 当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,较好使 用导出表。
在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大, 为了缓和系统表的资源,应先 create table,然后 insert。
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除, 先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1 万行,那么就应该考虑改写。
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
与临时表一样,游标并不是不可使用。对小型数据集使用FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONEINPROC 消息。
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
尽量避免大事务操作,提高系统并发能力。
ssm
spring
四个注解 @Component @Controller @Service @Repository 指定的id
@Autowired 自动装配 只要容器中有对象 就可以注入对象
@Qualifier 装配的过程中 指明id属性
1.根据类型注入
2.根据name名称(id)注入
3.根据成员变量的名称注入
@Scope(“singleton”) 单例 @Scope(“prototype”) 原型
@Value() 给成员变量赋值 支持spring el
如果要使用注解: 必须有配置 扫描配置
<conext:component-scan base-package=“com.itheima”> 扫描父包即可
@ComponentScan(basepackages= “com.itheima”) 扫描注解
@Configuration 加载类上 表示此类为配置类 等效配置文件
@Bean(“名称”) 加载方法上的, 将方法的返回值放入容器中
@Import(类名.class) 引入其他的配置类
@PropertySource(“classpath:配置文件的名称”) 引入properties配置
引入spring-test的jar包 整合junit的jar
@Runwith(SpringJUnit4ClassRunner.class) 将程序运行在spring的junit的环境上
等效: new ClassPathXmlApplicationContext()
@ContextConfiguration(localtions= “” , classes=类名.class) 加载配置文件 或者 加载配置类
***docker
概述
- Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。
- 容器是完全使用沙箱机制,相互隔离
- 容器性能开销极低。
常用命令
# 启动服务
systemctl start docker
# 停止服务
systemctl stop docker
# 重启服务
systemctl restart docker
# 查看服务的状态
systemctl status docker
# 设置开机自启动
systemctl enable docker
镜像相关命令
# 查看本地镜像
docker images
# 搜索镜像仓库,推荐:https://hub.docker.com/
docker search 镜像名称
# 下载(拉取)镜像,镜像名称格式为 名称:版本号
docker pull 镜像名称
# 删除镜像(慎重)
docker rmi 镜像名称
docker rmi 镜像名称:版本号
# 停止所有容器运行 慎用 运维程序不允许使用 开发阶段可以使用
docker stop $(docker ps -a -q)
# 删除所有的容器服务 慎用 运维程序不允许使用 开发阶段可以使用
docker rm $(docker ps -a -q)
容器相关命令
# 查看本地容器
docker ps # 能查看正在运行
docker ps -a # 能查看所有的容器(运行的和停止的)
# 创建一个新的容器并运行(交互式) =号可省略
docker run -it --name=容器名 镜像名称 /bin/bash
# 启动redis 没用
docker run -it --name redis1 redis:5.0
# 启动容器
docker start 容器名称
# 停止容器
docker stop 容器名称
# 删除容器
docker rm 容器名称
# 创建一个新的容器并运行(守护式) =号可省略
docker run -id --name=容器名 镜像名称
#启动redis
docker run -id --name redis1 redis:5.0
# 进入容器内部
docker exec -it 容器名称 /bin/bash
# 查看容器信息
docker inspect 容器名称
参数说明:
- -i:保持容器运行。通常与 -t 同时使用。加入it这两个参数后,容器创建后自动进入容器中,退出容器后,容器自动关闭。
- -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用。
- -d:以守护进程(后台)模式运行容器。创建一个容器在后台运行,需要使用docker exec 进入容器。退出后,容器不会关闭。
- -it 创建的容器一般称为交互式容器,-id 创建的容器一般称为守护式容器
- –name:为创建的容器命名。
zookeeper
概述:
ZooKeeper是一个分布式服务协调框架,主要用来解决分布式应用中的一些数据管理问题,如:统一命名服务、状态同步服务、应用配置项的管理等等。
zookeeper数据结构
zookeeper的数据节点可以视为树状(或者目录)结构,树种的各节点成为znode(即zookeeper node),一个znode可以有多个子节点。
znode节点
znode兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又可以像目录一样挂载子目录。
znode特点:可以向目录一样存储子节点, 亦可以像文件一样存储数据信息。
一个znode大体上分为三部分:
- data:节点的数据
- children:节点的子节点
- stat:(元信息)节点的状态,用来描述节点的创建、修改记录等
# 在zookeeper shell中使用get命令查看指定路径节点的data、stat信息:
[zk: localhost:2181(CONNECTED) 5] get /itcast/bj
bjdata
cZxid = 0x17
ctime = Sun Oct 11 17:24:37 CST 2020
mZxid = 0x17
mtime = Sun Oct 11 17:24:37 CST 2020
pZxid = 0x1b
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 1
# cZxid:数据节点创建时的事务 ID
# ctime:数据节点创建时的时间
# mZxid:数据节点最后一次更新时的事务 ID
# mtime:数据节点最后一次更新时的时间
# pZxid:数据节点的子节点最后一次被修改时的事务 ID
# cversion:子节点的更改次数 只针对于子节点
# dataVersion:节点数据的更改次数
# aclVersion: 权限的更改次数
# ephemeralOwner: 持久节点还是临时节点
# dataLength:数据内容的长度
# numChildren:数据节点当前的子节点个数
最多
# cZxid:数据节点创建时的事务ID 用来唯一标识
# dataVersion:节点数据的更改次数
节点类型
对于Zookeeper中的节点,有两种分类方式,一种是按照节点是否持久化,一种是按照节点是否有顺序进行分类。
持久节点:客户端执行删除操作才会被删除。
临时节点:可以主动清理,且每个临时节点虽然都会绑定一个客户端,但是他对所有客户端还是可见的。另外,zookeeper的临时节点不允许有子节点。
持久化无序节点 :节点创建后会一直存在zookeeper服务器上,直到主动删除
持久化有序节点 :在持久化无序节点的基础上,为每个节点都会为它的一级子节点维护一个顺序
临时无序节点 : 临时节点的生命周期和客户端的会话保持一致,当客户端会话失效,该节点自动清理
临时有序节点 : 在临时节点上多了一个顺序性特性
**zook的使用场景
ID生成器
通过在zookeeper里创顺序节点,很容易创建一个全剧唯一的名字,即生成一个全局唯一的ID
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oe9wVQHd-1640137764811)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210215231511204.png)]
配置中心
配置中心使用的是zookeeper的watch机制
* 需求:
在分布式应用系统中,会有很多的类似配置文件,他们会散落在各个服务中心,比如数据库和密码,如果后期要求改的话就需要更改跟多配置文件,不符合开发规范
* 方案:
可以把所有的配置都放在一个配置中心,然后各个服务分别去监听配置中心,一旦里面的内容发生变化,立即获取变化内容,然后更新本地配置即可。
* 实现:
通过zookeeper的watch机制。
所有服务需要监听zookeeper的配置节点,当配置信息发生变化之后,zookeeper会将变化信息推送给服务,服务也可以主动拉去节点数据。
* 说明:
在这里zookeeper采用了推拉模式相结合的做法。
push可以保证第一时间拿到更新配置,基本可以做到实时更新,但是push存在问题,如果网络波动,导致某一次的push没有成功,就会导致服务器失去这一次的配置信息。
pull可以保证一定可以拉取到数据。pull一般采用定时拉取得方式,即使某一次网络问题,拉取数据失败,在下一次定时器也可以拉取到数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vvbaQhja-1640137764812)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210215234704816.png)]
集群选主:
集群选主使用的是zookeeper的临时节点
* 需求:
集群中,需要区分主从节点,从别从事不同的工作(主节点负责数据写操作,从节点负责读取数据)
* 实现:
使用zookeeper的临时节点
* 步骤:
1.所有参与选主的主机都去zookeeper上共同创建一个临时节点,那么最终只会有一个客户端请求能够创建成功。
2。成功创建临时节点的客户端所在的机器就成了主节点,其他没有成功创建该节点的客户端就成了从节点。
3.所有的从节点都会在主节点上注册一个子节点变更的watcher,用于监控当前主节点是否存活,一旦主节点挂了,其他客户当重新进行竞选。
分布式锁:
分布式锁使用的是zookeeper的临时有序节点
* 需求:
在分布式系统中,很容出现多台主机操作同一资源的情况, 比如两台主机同时往一个文件中追加写入文本,
如果不去做任何的控制,很有可能出现一个写入操作被另一个写入操作覆盖掉的状况
* 方案:
此时我们可以来一把锁,哪个主机获取到了这把锁,就执行写入,另一台主机等待;直到写入操作执行完毕,另一台主机再去获得锁,然后写入
这把锁就称为分布式锁, 也就是说:分布式锁是控制分布式系统之间同步访问共享资源的一种方式
* 实现:
使用Zookeeper的临时有序节点
1. 所有需要执行操作的主机都去Zookeeper上创建一个临时有序节点
2. 然后获取到Zookeeper上创建出来的这些节点进行一个从小到大的排序
3. 判断自己创建的节点是不是最小的,如果是,自己就获取到了锁; 如果不是,则对最小的节点注册一个监听
4. 如果自己获取到了锁,就去执行相应的操作,当执行完毕之后,连接断开,节点消失,锁就被释放了
5. 如果自己没有获取到锁,就等待,一直监听节点消失,锁释放后,再重新执行抢夺锁的操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GRRiHniZ-1640137764813)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210216013948359.png)]
zookeeper拓展
zookeeper常见命令
Dubbo
1、结构:
register/producer/consumer/Monitor
register:注册中心,服务启动时会向注册中心注册服务地址。注册中心相当于一个目录目录,仅在服务启动时在其中完成服务的注册于查找功能。注册中心不完成转发请求,压力较小,官方推荐使用zookeeper为dubbo的注册中心。
producer/container:服务提供方和服务运行容器,service 服务代码 核心功能
consumer:服务的消费者,controller,消费者代码
Monitor:监控中心,统计服务的调用次数和服务时间的监控中心(基本不用)
2、执行过程
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推
送变更数据给消费者。 - 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,
如果调用失败,再选另一台调用。 - 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计
数据到监控中心。
3、配置与使用细节
包扫描
<!--服务提供者和服务消费者都需要配置包扫描,作用是扫描指定包(包括子包)下的类中的注解: @Service dubbo @Refrence-->
<dubbo:annotation package="com.itheima.dubbo" />
协议
<!--
在服务提供者一方配置,可以指定使用的协议名称和端口号。
其中RPC支持的协议有:dubbo、rmi、hessian、http、webservice、rest、redis等。
-->
<dubbo:protocol name="dubbo" port="20880"/>
启动时检查
Dubbo在启动时会检查服务提供者所提供的服务是否可用,默认为true
通过配置服务消费者的统一规则来关闭服务检查
效果是: 启动消费者的时候 如果不调用 不注入远程对象
不建议配置: 必须有提供才启动消费方
<dubbo:consumer check="false"/>
1.保证zookpeer存在
2.保证服务提供方启动
3.保证消费方启动
超时和重试
Dubbo在通信时,由于网络或服务端不可靠,会导致调用过程中出现不确定的阻塞状态(超时)
为了避免超时导致客户端线程挂起处于一致等待状态,必须设置超时时间和请求重试,默认为1S超时和重试2次数
通过配置服务消费者的统一规则来设置超时时间
<!--配置在消费者端一方: 默认的超时时间是1s ,默认的重试次数是2次-->
<dubbo:consumer check="false" timeout="50000" retries="0" />
在运行时,我们可以针对某个特定的业务,增加超时时长,比如说3秒内返回,都不会报错~~~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yljhDkNX-1640137764814)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210209185455711.png)]
RocketMQ—数据统计;
**RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。**RocketMQ由阿里巴巴开源。
1、结构-四部分组成:
nameSever/broker/producer/consumer
nameServe: 相当于注册中心,broker的地址以及生产者获得地址都通过nameSever,每个NameServer节点互相之间是独立的,没有任何信息交互
broker:消息中间件:进行消息的投递(向其写入消息,从其读取消息)
producer:产生消息
consumer:接收消息
2、执行流程:
broker启动时向nameSever注册自己的地址并定时发送心跳,Producer启动时会到nameSever上去Topic所属的broker具体地址,根据地质向broker发送消息.消费者按照Topic和消费者组获得消息
集群的目的(集群方式):为了消除单点故障,增加可靠性或增大吞吐量,可以在多台机器上部署多个nameserver和broker,并且为每个broker部署1个或多个slave
发送心跳:在BrokeController的初始化方法initialize中初始化了心跳管理线程池heartbeatExecutor;然后在registerProcessor对线程池任务进行了填充。
3、springboot整合RocketMQ
java使用:
注入依赖:
生产者和消费者端都需要注入依赖
<!--RocketMQ相关-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.6.0</version>
</dependency>
配置yml文件
#配置生产者
rocketmq:
name-server: 192.168.136.160:9876
producer:
group: produerDemo
#配置消费者
#服务器端口 可以省略
server:
port: 9091
#配置nameserver
rocketmq:
name-server: 192.168.136.160:9876
发送普通消息:
生产者端
完成环境搭建之后,注入RocketMQTemplate类对象,直接调用方法即可发送消息:
/**
* 生产者发送消息
* 1.注入对象:RocketMQTepmlate
* 2.调用对象方法完成消息发送
* 不同消息类型,调用的方法不一样
*/
@Autowired
private RocketMQTemplate template;
//发送普通消息\]
@Test
public void testSendBaseMessage() {
//参数一:消息主题,参数二:消息数据(java类型:一般传递String)
template.convertAndSend("baseTopic143","别睡啦,醒醒吧马上吃饭啦~~");
}
消费者端:
类上加@RockerMQMessageListener注解,注解内部配置属性值,配置主题以及消费者组
同时,类要实现RocketMQLitener<>接口;重载onMessage方法,其中的参数就是最新接收到的消息
/**
* 消息监听器:
* 1、实现RocketMQListener接口,并且提供泛型(消息类型)
* 2、实现内部的onMessage方法
* 当监听器自动获取到信息时,调用onMessage方法完成业务处理(使用消息)
* 3、监听器需要交给容器管理
* 4、需要在监听器类上通过注解@RocketMQMessageListener注解配置主题和消费者组
*
*/
@Component
@RocketMQMessageListener(
topic = "baseTopic143", //主题
consumerGroup = "baseGroup" //消费者组
)
public class BaseMessageListener implements RocketMQListener<String> {
//使用消息 (参数:最新消息)
public void onMessage(String message) {
System.out.println("获取到最新消息:"+message);
}
}
***发顺序消息:
消息有序指的是可以按照消息的发送顺序来消费。RocketMQ是通过将“相同ID的消息发送到同一个队列,而一个队列的消息只由一个消费者处理“来实现顺序消息 。
如何保证顺序
在MQ的模型中,顺序需要由3个阶段去保障:
1.消息被发送时保持顺序
2.消息被存储时保持和发送的顺序一致
3.消息被消费时保持和存储的顺序一致
发送时保持顺序意味着对于有顺序要求的消息,用户应该在同一个线程中采用同步的方式发送。存储保持和发送的顺序一致则要求在同一线程中被发送出来的消息A和B,存储时在空间上A一定在B之前。而消费保持和存储一致则要求消息A、B到达Consumer之后必须按照先A后B的顺序被处理。
生产者
producer端保持顺序消息唯一要做的事情就是将消息路由到特定的队列,在RocketMQ中,通过MessageQueueSelector来实现分区的选择。
需要自己编写消息选择队列的代码,这里我们用 orderid 对 队列总数 取模:long index = orderId % mqs.size();
/**
* 发送顺序消息
* 保证发送时消息的顺序
* 保证对于同一个订单的所有消息,通过一个队列处理
*/
@Test
public void testSendOrderlyMessage() {
List<Order> orders = Order.buildOrders(); //发送消息的顺序
for (Order order : orders) {
//保证对于同一个订单的所有消息,通过一个队列处理
// * 选择唯一标志,通过id进行筛选
// * 筛选的算法:通过id 与 队列长度 取模
//1、筛选队列的算法
template.setMessageQueueSelector(new MessageQueueSelector() {
//实现筛选算法 (list:所有队列,message消息,key:唯一标志)
public MessageQueue select(List<MessageQueue> list, Message message, Object key) {
Long id = Long.valueOf((String)key);
int index = (int) (id / list.size());
return list.get(index); //目标队列
}
});
//2、发送
//主题,消息内容,唯一标志
template.syncSendOrderly("orderlyTopic143",order.toString() ,order.getId().toString());
}
}
消费者
消费者端不用做额外的配置,正常读取就行
发送延时消息:
某些特定场景之下消息要延时发送,测试用到延时消息功能
例如:订单倒计时付费
生产者:
//发送延迟消息:内部会有误差(误差 10秒之内)
@Test
public void testSendDelayMessage() {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
//参数一:消息主题,参数二:消息数据,参数三:延迟的时间
template.syncSend("delayTopic143","延迟消息:"+time,10000);
}
消费者:
消费者端不用做任何配置,直接,覆盖onMessage方法即可.
发送广播消息:
RocketMQ中只有两种消息模式,集群和广播
- 集群模式:消费者组内只有一个获取消息
- 广播模式:消费者组内所有的消费者都可以收到消息。
想要实现广播功能,只需要修改消费者的 消息类型选项,即:
messageModel = MessageModel.BROADCASTING
package cn.itcast.consumer.listener;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/**
* 消息监听器:
* 1、实现RocketMQListener接口,并且提供泛型(消息类型)
* 2、实现内部的onMessage方法
* 当监听器自动获取到信息时,调用onMessage方法完成业务处理(使用消息)
* 3、监听器需要交给容器管理
* 4、需要在监听器类上通过注解@RocketMQMessageListener注解配置主题和消费者组
*
*/
@Component
@RocketMQMessageListener(
topic = "broadCast143", //主题
consumerGroup = "broadCastGroup143", //消费者组
messageModel = MessageModel.BROADCASTING //广播消息(组内多个消费者同时获取)
)
public class BroadCastMessageListener implements RocketMQListener<String> {
//使用消息 (参数:最新消息)
public void onMessage(String message) {
System.out.println("获取到最新消息:"+message);
}
}
4、附属内容–能说的越多越好
具有以下特点:
- 能够保证严格的消息顺序
- 提供针对消息的过滤功能
- 提供丰富的消息拉取模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力前
RocketMQ的特性
消费模式
- **集群模式:**在默认情况下,就是集群消费,此时消息发出去后将只有一个消费者能获取消息。
- 广播模式:一条消息被多个Consumer消费。消息会发给Consume Group中的每一个消费者进行消费。
消息顺序
消息的顺序指的是消息消费时,能按照发送的顺序来消费。
RocketMQ是通过将“相同ID的消息发送到同一个队列,而一个队列的消息只由一个消费者处理“来实现顺序消息
消息重复
消息重复的原因
消息领域有一个对消息投递的QoS(服务质量)定义,分为:最多一次(At most once)、至少一次(At least once)、仅一次( Exactly once)。
MQ产品都声称自己做到了At least once。既然是至少一次,就有可能发生消息重复。
有很多原因导致,比如:网络原因闪断,ACK返回失败等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者
不同的消息队列发送的确认信息形式不同:RocketMQ返回一个CONSUME_SUCCESS成功标志,RabbitMQ是发送一个ACK确认消息
消息去重
1)、去重原则:使用业务端逻辑保持幂等性
- 对张三的1号订单扣款
- 在操作之前判断(是否已经扣过款项)
幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用,数据库的结果都是唯一的,不可变的。
2)、只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样,需要业务端来实现。
去重策略:保证每条消息都有唯一编号(比如唯一流水号),且保证消息处理成功与去重表的日志同时出现。
RocketMQ的应用场景
削峰填谷
比如如秒杀等大型活动时会带来较高的流量脉冲,如果没做相应的保护,将导致系统超负荷甚至崩溃。如果因限制太过导致请求大量失败而影响用户体验,可以利用MQ 超高性能的消息处理能力来解决。
异步解耦
通过上、下游业务系统的松耦合设计,比如:交易系统的下游子系统(如积分等)出现不可用甚至宕机,都不会影响到核心交易系统的正常运转。
顺序消息
与FIFO原理类似,MQ提供的顺序消息即保证消息的先进先出,可以应用于交易系统中的订单创建、支付、退款等流程。
分布式事务消息
比如阿里的交易系统、支付红包等场景需要确保数据的最终一致性,需要引入 MQ 的分布式事务,既实现了系统之间的解耦,又可以保证最终的数据一致性
@HashMap
1、基本概念
基本特点:
基于哈希表的Map接口实现,双列结构,允许空值,有且只能有一个键值为控制,不保证顺序
1.8前后版本对比
1.8之前由数组+链表组成,数组为主题,链表为了解决**哈希冲突(两个对象调用hashCode方法计算的值相同导致计算的数组索引相同)**问题
1.8之后引入红黑树,阈值为8,如果链表长度大于8.并且数组主体大于64,将链表转化为红黑树。原因:红黑树需要左旋右旋,效率问题。数组长度默认16,链表长度达到8,先进性数组扩容,扩容到32,直到数组长度大于64,且同时存在了8个链表,就会自动转换成红黑树。
哈希碰撞:
hash值相同,调用equals方法,返回值true,key相等,value覆盖,false:那么继续向下(当前数组节点下有链表)和其他的key进行比较,如果都不相等,则添加一个节点node存储数据
扩容:
数组长度翻倍扩容,将原来的值复制过来,重新计算哈希值(因为长度变了,哈希值也会变,重新计算,重新存储)存储数据。
扩容时机:首次扩容长度达到12 或 链表长度达到8且数组长度超过64
2、面试常问1:哈希表底层采用何种算法计算哈希值,还有哪些算法计算到哈希值?
底层采用的key的hashCode()方法得值结合数组长度进行无符号右移( > > >)、按位异或(^)、按位与(&)计算出索引。还可以采用:平方去中发、取余法、伪随机数法。
3、HashMap继承关系
HashMap 继承 AbstractMap,实现了Cloneable(表示可以克隆。创建并返回一个HashMap副本)、Serializable接口;AbstractMap继承Map类、
4、数组长度为什么是2的n次幂
数组长度不是2的n次幂,计算(按位与计算)出的索引特别容易相同,极其容易发生哈希碰撞,导致其他数组空间空白,链表或者红黑树过长,效率过低。
5、负载因子(加载因子):loadFactor
初始长度16,默认加载因子0.75,当数组中存储数据内容达到长度*加载因子时,就进行扩容
加载因子默认值不建议修改,官方计算出来比较合适的,加载因子越靠近1,查询效率越低,加载因子越靠近0,数组利用率越低。
加载因子自定义过大产生的问题:数据过密(数组中基本存满了),哈希碰撞的机会变多,更容易产生长度过长的链表,导致查询效率过低;
加载因子自定义过小产生的问题:数据过稀疏,没放几个数据就进行扩容,空间资源上的浪费,且扩容后需要重新计算哈希值,重新存储数据,时间资源的浪费。
6、为什么链表达到8才进行转化红黑树:
根据空间和时间复杂度的一个权衡
根据统计学中的泊松分布,桶长度达到8 的几率是很小的
红黑树节点内存是普通节点的2倍,链表长度大于8时装换成红黑树节点,红黑树节点数量小于6时就会变回普通链表节点
桶:数组上的每一个元素,如果形成了链表,这个元素就可以称为桶
7、HashMap的几种遍历方法
1、直接分别遍历key和value
Set keys = map.keySet():返回key数组
Collection values = map.values():返回value数组
2、迭代器方式
Set<Map.entry<Object,Object>> entries = map.entrySet();
for(Itereator<Map.entry<Object,Object>> it = entries.itertor();it.hasNext;){
Map.entry<Object,Object> entry = it.next();
entry.getKey();
entry.getValue();
}
3、keySet方式–不建议使用,效率低
Set keys = map.keySet():返回key数组
Object value = map.get(keys[index]);
4、使用forEach
map.forEach((key, value) -> System.out.println(key + “—” + value));
@多线程基础
线程的四种创建方式及特点
1、继承Thread类:没有返回值,直接.start就启动run方法了
2、实现Runable接口:没有返回值,直接.start就启动run方法了
3、实现Callable接口:有返回值,可以抛出异常并支持泛型
4、利用线程池的方式:
线程池介绍:
一个装线程的池子
好处
1、降低资源消耗:通过重复利用已经存在的线程,降低线程创建和销毁时的资源消耗
2、提高响应速度:当任务到达时,不用等待线程创建,可以直接运行
3、提高线程管理:线程的创建和销毁统一进行管理,不会过多消耗系统资源
线程池种类
一、newCachedThreadPool
大小不限制的可缓存线程池
二、newFixedThreadPool
创建一个固定数量大小的线程池:适用于任务数量比较固定并且任务时间比较长的任务
三、newSingleThreadExecutor
创建一个单线程的线程池:适用于需要保证顺序执行的任务
四、newScheduledThreadPool
一种可以无线扩大的线程池,支持定时和周期任务
线程池的参数-简介
1、线程池的核心线程数:最小存在的线程数量
2、线程池的最大线程数
3、线程的存活时间:线程池的现有线程数大于核心线程数,线程的空闲时间如果超过设定时间,那么这个线程就会被销毁,直到当前线程数不大于核心线程数
4、任务队列:用于保存等待执行的任务
5、线程工厂:用于创建新的线程
6、线程饱和策略:当线程池和线程队列都满了,再加入进来的任务执行那种策略。
当一个任务进来,判断线程池的当前数量是否大于核心线程数量,如果小于直接创建一个新的线程来执行任务,如果大于就判断任务队列时候已经满了,如果没满则添加任务到任务队列,如果满了则判断当前线程数量是否大于当前线程池容量,如果小于则创建一个新的线程出来,如果大于则执行饱和策略(比如拒绝策略)
线程池的参数-详解
一、corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
二、maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
三、keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
四、unit 空闲线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
事务
本地事务
方法上加注解 @Transactional(rollbackFor = Exception.class),这是本地事务,分布式事务中只能回滚本地事务
CAP理论:(一致性,可用性.分区容错)
zookeeper:CP 强一致
eureka:AP短时间容错,但最终还是要一致
分布式事务
比较消耗资源,看情况,不需要的就不用使用
@GlobalTransactional(rollbackFor = Exception.class) 分布式事务注解,需要引入分布式事务支持,seata,配置数据库连接池为德鲁伊(其中有些配置),不再使用默认的连接池(号称最快连接池H开头)
java中的四中引用
1.强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象
2.软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
3.弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象
4。虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。