- MySql中表的分类
实体表,维度表,事务型事实表,周期性事实表
其实最终可以把事务型事实表,周期性事实表统称实体表,实体表,维度表统称维度表
订单表(order_info)(周期型事实表)
订单详情表(order_detail)(事务型事实表)
商品表(实体表)
用户表(实体表)
商品一级分类表(维度表)
商品二级分类表(维度表)
商品三级分类表(维度表)
支付流水表(事务型实体表)
实体表,维度表统称维度表,每日全量或者每月(更长时间)全量
事务型事实表:每日增量
周期性事实表:拉链表
1NF:属性不可再分割(例如不能存在5台电脑的属性,坏处:表都没法用)
2NF:不能存在部分函数依赖(例如主键(学号+课名)-->成绩,姓名,但学号--》姓名,所以姓名部分依赖于主键(学号+课名),所以要去除,坏处:数据冗余)
3NF:不能存在传递函数依赖(学号--》宿舍种类--》价钱,坏处:数据冗余和增删异常)
Mysql关系模型:关系模型主要应用与OLTP系统中,为了保证数据的一致性以及避免冗余,所以大部分业务系统的表都是遵循第三范式的。
Hive 维度模型:维度模型主要应用于OLAP系统中,因为关系模型虽然冗余少,但是在大规模数据,跨表分析统计查询过程中,会造成多表关联,这会大大降低执行效率。所以HIVE把相关各种表整理成两种:事实表和维度表两种。所有维度表围绕着事实表进行解释。
雪花模型、星型模型和星座模型
(在维度建模的基础上又分为三种模型:星型模型、雪花模型、星座模型。)
星型模型(一级维度表),雪花(多级维度),星座模型(星型模型+多个事实表)
sqoop
导数据的原理是mapreduce,
import 把数据从关系型数据库 导到 数据仓库,自定义InputFormat,
export 把数据从数据仓库 导到 关系型数据库,自定义OutputFormat,
用sqoop从mysql中将八张表的数据导入数仓的ods原始数据层
全量无条件,增量按照创建时间,增量+变化按照创建时间或操作时间。
origin_data
sku_info商品表(每日导全量)
user_info用户表(每日导全量)
base_category1商品一级分类表(每日导全量)
base_category2商品二级分类表(每日导全量)
base_category3商品三级分类表(每日导全量)
order_detail订单详情表(每日导增量)
payment_info支付流水表(每日导增量)
order_info订单表(每日导增量+变化)
(八张表,表名,字段跟mysql完全相同)
从origin_data把数据导入到ods层,表名在原表名前加ods_
对ODS层数据进行判空过滤。对商品分类表进行维度退化(降维)。其他数据跟ods层一模一样
订单表 dwd_order_info
订单详情表 dwd_order_detail
用户表 dwd_user_info
支付流水表 dwd_payment_info
商品表 dwd_sku_info
其他表字段不变,唯独商品表,通过关联3张分类表,增加了
category2_id` string COMMENT '2id',
`category1_id` string COMMENT '3id',
`category3_name` string COMMENT '3',
`category2_name` string COMMENT '2',
`category1_name` string COMMENT '1',
小结:
- 维度退化要付出什么代价?或者说会造成什么样的需求处理不了?
如果被退化的维度,还有其他业务表使用,退化后处理起来就麻烦些。
还有如果要删除数据,对应的维度可能也会被永久删除。
- 想想在实际业务中还有那些维度表可以退化
城市的三级分类(省、市、县)等
从订单表 dwd_order_info 中获取 下单次数 和 下单总金额
从支付流水表 dwd_payment_info 中获取 支付次数 和 支付总金额
从事件日志评论表 dwd_comment_log 中获取评论次数
最终按照user_id聚合,获得明细,跟之前的mid_id聚合不同
从用户行为宽表中dws_user_action,根据统计日期分组,聚合,直接sum就可以了。
从日活跃数表 ads_uv_count 和 日新增设备数表 ads_new_mid_count 中取即可。
从用户行为宽表dws_user_action中取,下单人数(只要下单次数>0),支付人数(只要支付次数>0)
从日活跃数表 ads_uv_count 中取活跃人数,然后对应的相除就可以了。
需求:以月为单位统计,购买2次以上商品的用户
从用户购买商品明细宽表dws_sale_detail_daycount中,根据品牌id--sku_tm_id聚合,计算每个品牌购买的总次数,购买人数a=购买次数>=1,两次及以上购买人数b=购买次数>=2,三次及以上购买人数c=购买次数>=3,
单次复购率=b/a,多次复购率=c/a
宽表要3-5张,用户行为宽表,用户购买商品明细行为宽表,商品宽表,购物车宽表,物流宽表、登录注册、售后等。
- 为什么要建宽表
需求目标,把每个用户单日的行为聚合起来组成一张多列宽表,以便之后关联用户维度信息后进行,不同角度的统计分析。
订单表拉链表 dwd_order_info_his
`id` string COMMENT '订单编号',
`total_amount` decimal(10,2) COMMENT '订单金额',
`order_status` string COMMENT '订单状态',
`user_id` string COMMENT '用户id' ,
`payment_way` string COMMENT '支付方式',
`out_trade_no` string COMMENT '支付流水号',
`create_time` string COMMENT '创建时间',
`operate_time` string COMMENT '操作时间' ,
`start_date` string COMMENT '有效开始日期',
`end_date` string COMMENT '有效结束日期'
1)创建订单表拉链表,字段跟拉链表一样,只增加了有效开始日期和有效结束日期
初始日期,从订单变化表ods_order_info导入数据,且让有效开始时间=当前日期,有效结束日期=9999-99-99
(从mysql导入数仓的时候就只导了新增的和变化的数据ods_order_info,dwd_order_info跟ods_order_info基本一样,只多了一个id的判空处理)
2)建一张拉链临时表dwd_order_info_his_tmp,字段跟拉链表完全一致
3)新的拉链表中应该有这几部分数据,
(1)增加订单变化表dwd_order_info的全部数据
(2)更新旧的拉链表左关联订单变化表dwd_order_info,关联字段:订单id, where 过滤出end_date只等于9999-99-99的数据,如果旧的拉链表中的end_date不等于9999-99-99,说明已经是终态了,不需要再更新
如果dwd_order_info.id is null , 没关联上,说明数据状态没变,让end_date还等于旧的end_date
如果dwd_order_info.id is not null , 关联上了,说明数据状态变了,让end_date等于当前日期-1
把查询结果插入到拉链临时表中
4)把拉链临时表覆盖到旧的拉链表中
- 如果MR造成系统宕机。此时要控制Yarn同时运行的任务数,和每个任务申请的最大内存。调整参数:yarn.scheduler.maximum-allocation-mb(单个任务可申请的最多物理内存量,默认是8192MB)
- 如果写入文件过量造成NameNode宕机。那么调高Kafka的存储大小,控制从Kafka到HDFS的写入速度。高峰期的时候用Kafka进行缓存,高峰期过去数据同步会自动跟上。
Ganglia监控Flume发现尝试提交的次数大于最终成功的次数
- 增加Flume内存
- 增加Flume台数
Flume上传文件到HDFS时参数大量小文件?
调整hdfs.rollInterval、hdfs.rollSize、hdfs.rollCount这三个参数的值。
- Flume记录
- 日志有记录
- 短期没事
- 如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
- 如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。
在下一级消费者中去重。(redis、SparkStreaming)
Hive的metadata存储在MySql中(配置MySql的高可用(主从复制和读写分离和故障转移))
自定义UDF(extends UDF 实现evaluate方法) 解析公共字段
自定义UDTF(extends Genertic UDTF->实现三个方法init(指定返回值的名称和类型)、process(处理字段一进多出)、close方法) -> 更加灵活以及方便定义bug
Ads层数据用Sqoop往MySql中导入数据的时候,如果用了orc(Parquet)不能导入,需转化成text格式
Sqoop中导入导出Null存储一致性问题:
Hive中的Null在底层是以“\N”来存储,而MySQL中的Null在底层就是Null,为了保证数据两端的一致性。在导出数据时采用--input-null-string和--input-null-non-string两个参数。导入数据时采用--null-string和--null-non-string。
当Sqoop导出数据到MySql时,使用4个map怎么保证数据的一致性
因为在导出数据的过程中map任务可能会失败,可以使用—staging-table –clear-staging
sqoop export --connect jdbc:mysql://192.168.137.10:3306/user_behavior --username root --password 123456 --table app_cource_study_report --columns watch_video_cnt,complete_video_cnt,dt --fields-terminated-by "\t" --export-dir "/user/hive/warehouse/tmp.db/app_cource_study_analysis_${day}" --staging-table app_cource_study_report_tmp --clear-staging-table --input-null-string '\N'
任务执行成功首先在tmp临时表中,然后将tmp表中的数据复制到目标表中(这个时候可以使用事务,保证事务的一致性)
如何优雅的关闭SparkStreaming任务(将写好的代码打包,Spark-Submit)
Kill -9 xxx ?
开启另外一个线程每5秒监听HDFS上一个文件是否存在。如果检测到存在,调用ssc.stop()方法关闭SparkStreaming任务(当你要关闭任务时,可以创建你自定义监控的文件目录)
- Hadoop集群基准测试(HDFS的读写性能、MapReduce的计算能力测试)
- 一台服务器一般都有很多个硬盘插槽(插了几个插槽)
如果不配置datanode.data.dir多目录,每次插入一块新的硬盘都需要重启服务器
配置了即插即用
- Hdfs参数调优
Namenode有一个工作线程池,用来处理与datanode的心跳(报告自身的健康状况和文件恢复请求)和元数据请求 dfs.namenode.handler.count=20 * log2(Cluster Size)
4)编辑日志存储路径dfs.namenode.edits.dir设置与镜像文件存储路径 dfs.namenode.name.dir尽量分开,达到最低写入延迟(提高写入的吞吐量)
5)YARN参数调优yarn-site.xml
(1)服务器节点上YARN可使用的物理内存总量,默认是8192(MB)
(2)单个任务可申请的最多物理内存量,默认是8192(MB)。
6)HDFS和硬盘空闲控制在70%以下。
- Flume内存配置为4G(flume-env.sh修改)
- FileChannel优化
通过配置dataDirs指向多个路径,每个路径对应不同的硬盘,增大Flume吞吐量。
checkpointDir和backupCheckpointDir也尽量配置在不同硬盘对应的目录中,保证checkpoint坏掉后,可以快速使用backupCheckpointDir恢复数据
- Sink:HDFS Sink小文件处理
这三个参数配置写入HDFS后会产生小文件,hdfs.rollInterval、hdfs.rollSize、hdfs.rollCount
- Kafka的吞吐量测试(测试生产速度和消费速度)
- Kafka内存为6G(不能超过6G)
- Kafka数量确定:2 * 峰值生产速度(m/s)* 副本数 / 100 + 1 = ?
- Kafka中的数据量计算
每天数据总量100g(1亿条) 10000万/24/60/60 = 1150条/s
平均每秒钟:1150条
低谷每秒:400条
高峰每秒钟:1150 * 10 = 11000 条
每条日志大小: 1K左右
每秒多少数据量:20MB
- Kafka消息数据积压,Kafka消费能力不足怎么处理?
- 如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
- 如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。
Tez可以将多个有依赖的作业转换为一个作业,这样只需写一次HDFS,且中间节点较少,从而大大提升作业的计算性能。
- Sqoop导入导出Null存储一致性问题
- Sqoop数据导出一致性问题
–staging-table方式 --clear-staging
- Sqoop数据导出的时候一次执行多长时间
Sqoop任务5分钟-2个小时的都有。取决于数据量。
- 每天集群运行多少job?
- 每个任务的资源是如何分配的?
- 多个指标(200)*6=1200(1000-2000个job)
- 每天集群运行多少个task? 1000*(5-8)=5000多个
- 任务挂了怎么办?运行成功或者失败都会发邮件
Zip a.job b.job c.job job.zip 把压缩的zip包放到azkaban的web界面上提交(指定sechduler)
压缩采用Snappy,存储采用orc,压缩比是100g数据压缩完10g左右。
- 数据清洗
- 空值去除
- 过滤核心字段无意义的数据,比如订单表中订单id为null,支付表中支付id为空
- 对手机号、身份证号等敏感数据脱敏
- 对业务数据传过来的表进行维度退化和降维。
- 将用户行为宽表和业务表进行数据一致性处理
select case when a is null then b else a end as JZR,
...
from A
- 清洗的手段
Sql、mr、rdd、kettle、Python(项目中采用sql进行清除)
- 清洗掉多少数据算合理
1万条数据清洗掉1条。
- DWS层有3-5张宽表(处理100-200个指标 70%以上的需求)
具体宽表名称:用户行为宽表,用户购买商品明细行为宽表,商品宽表,购物车宽表,物流宽表、登录注册、售后等。
- 哪个宽表最宽?大概有多少个字段?
最宽的是用户行为宽表。大概有60-100个字段
- 具体用户行为宽表字段名称
评论、打赏、收藏、关注--商品、关注--人、点赞、分享、好价爆料、文章发布、活跃、签到、补签卡、幸运屋、礼品、金币、电商点击、gmv
CREATE TABLE `app_usr_interact`(
`stat_dt` date COMMENT '互动日期',
`user_id` string COMMENT '用户id',
`nickname` string COMMENT '用户昵称',
`register_date` string COMMENT '注册日期',
`register_from` string COMMENT '注册来源',
`remark` string COMMENT '细分渠道',
`province` string COMMENT '注册省份',
`pl_cnt` bigint COMMENT '评论次数',
`ds_cnt` bigint COMMENT '打赏次数',
`sc_add` bigint COMMENT '添加收藏',
`sc_cancel` bigint COMMENT '取消收藏',
`gzg_add` bigint COMMENT '关注商品',
`gzg_cancel` bigint COMMENT '取消关注商品',
`gzp_add` bigint COMMENT '关注人',
`gzp_cancel` bigint COMMENT '取消关注人',
`buzhi_cnt` bigint COMMENT '点不值次数',
`zhi_cnt` bigint COMMENT '点值次数',
`zan_cnt` bigint COMMENT '点赞次数',
`share_cnts` bigint COMMENT '分享次数',
`bl_cnt` bigint COMMENT '爆料数',
`fb_cnt` bigint COMMENT '好价发布数',
`online_cnt` bigint COMMENT '活跃次数',
`checkin_cnt` bigint COMMENT '签到次数',
`fix_checkin` bigint COMMENT '补签次数',
`house_point` bigint COMMENT '幸运屋金币抽奖次数',
`house_gold` bigint COMMENT '幸运屋积分抽奖次数',
`pack_cnt` bigint COMMENT '礼品兑换次数',
`gold_add` bigint COMMENT '获取金币',
`gold_cancel` bigint COMMENT '支出金币',
`surplus_gold` bigint COMMENT '剩余金币',
`event` bigint COMMENT '电商点击次数',
`gmv_amount` bigint COMMENT 'gmv',
`gmv_sales` bigint COMMENT '订单数')
PARTITIONED BY ( `dt` string)
- 商品详情 ----- 购物车 ----- 订单 ------ 付款的转换比率
5% 30% 70%
- 每天的GMV是多少,哪个商品卖的最好?每天下单量多少?
- 100万的日活每天大概有10万人购买,平均每人消费100元,一天的GMV在1000万
- 面膜,每天销售5000个
- 每天下单量在10万左右
- 离线指标
网站流量指标 独立访问数UV 页面访客数PV
流量质量指标类 跳出率 平均页面访问时长 人均页面访问数
- 购物车类指标
加入购物车次数 加入购物车买家次数 加入购物车商品数 购物车支付转化率
- 下单类指标
下单笔数 下单金额 下单买家数 浏览下单转化率
- 支付类指标
支付金额 支付买家数 支付商品数 浏览-支付买家转化率
下单-支付金额转化率 下单-支付买家数转换率
- 交易类指标
交易成功订单数 交易成功金额 交易成功买家数 交易成功商品数
交易失败订单数 交易失败订单金额 交易失败买家数
交易失败商品数 退款总订单量 退款金额 退款率
- 市场营销活动指标
新增访问人数 新增注册人数 广告投资回报率 UV订单转化率
- 风控类指标
买家评价数 买家上传图片数 买家评价率 买家好评率 买家差评率
物流平均配送时间
- 投诉类指标
发起投诉数 投诉率 撤销投诉(申诉数)
- 商品类指标
产品总数 SKU数 SPU数
上架商品SKU数 上架商品SPU数 上架商品数
日活跃用户,
月活跃用户,
各区域Top10商品统计,
季度商品品类点击率top10,
用户留存,
月APP的用户增长人数,
广告区域点击数top3,
活跃用户每天在线时长,
投诉人数占比,
沉默用户占比,
用户的新鲜度,
商品上架的sku数,
同种品类的交易额排名,
统计买家的评价率,
用户浏览时长,
统计下单的数量,
统计支付的数量,
统计退货的数量,
用户的(日活、月活、周活),
统计流失人数
日活,周活,月活,沉默用户占比,增长人数,活跃用户占比,在线时长统计,歌曲访问数,歌曲访问时长,各地区Top10歌曲统计 ,投诉人数占比,投诉回应时长,留存率,月留存率,转化率,GMV,复购vip率,vip人数,歌榜,挽回率,粉丝榜,打赏次数,打赏金额,发布歌曲榜单,歌曲热度榜单,歌手榜单,用户年龄组,vip年龄组占比,收藏数榜单,评论数
- 用户活跃数统计(日活,月活,周活)
- 某段时间的新增用户/活跃用户数
- 页面单跳转化率统计
- 活跃人数占比(占总用户比例)
- 在线时长统计(活跃用户每天在线时长)
- 统计本月的人均在线时长
- 订单产生效率(下单的次数与访问次数比)
- 页面访问时长(单个页面访问时长)
- 统计本季度付款订单
- 统计某广告的区城点击数top3
- 统计本月用户的流失人数
- 统计本月流失人数占用户人数的比例
- 统计本月APP的用户增长人数
- 统计本月的沉默用户
- 统计某时段的登录人数
- 统计本日用户登录的次数平均值
- 统计用户在某类型商品中的浏览深度(页面转跳率)
- 统计用户从下单开始到交易成功的平均时长
- Top10热门商品的统计
- 统计下单的数量
- 统计支付的数量
- 统计退货的数量
- 统计动销率(有销量的商品/在线销售的宝贝)
- 统计支付转化率
- 统计用户的消费频率
- 统计商品上架的SKU数
- 统计同种品类的交易额排名
- 统计按下单退款排序的top10的商品
- 统计本APP的投诉人数占用户人数的比例
- 用户收藏商品
最近连续3周活跃用户数:
最近7天连续3天活跃用户数:
基本一个项目建一个库,表格个数为初始的原始数据表格加上统计结果表格的总数。(一般70-100张表格)
每天0:30开始运行。
所有离线数据报表控制在8小时之内
大数据实时处理部分控制在5分钟之内。
常用的包括:textFile,rcFile,ORC,Parquet,一般企业里使用ORC或者Parquet,因为是列式存储,且压缩比非常高,所以相比于textFile,查询速度快,占用硬盘空间少
- 集群启动停止脚本(Hadoop、Flume、Kafka、Zookeeper)
- Sqoop和数仓之间的导入导出脚本
- 数仓层级之间的数据导入脚本。
Echarts、kibana、supesrset
- 公司有多少台测试服务器?
测试服务器一般三台
- 测试数据哪来的?
一部分自己写Java程序自己造,一部分从生产环境上取一部分。
- 如何保证写的sql正确性
需要造一些特定的测试数据,测试。
离线数据和实时数据分析的结果比较。
- 测试环境什么样?
测试环境的配置是生产的一半
- 测试之后如何上线?
上线的时候,将脚本打包,提交git。先发邮件抄送经理和总监,运维。通过之后跟运维一起上线。
- 先与产品讨论,看报表的各个数据从哪些埋点中取
- 将业务逻辑过程设计好,与产品确定后开始开发
- 开发出报表SQL脚本,并且跑几天的历史数据,观察结果
- 将报表放入调度任务中,第二天给产品看结果。
- 周期性将表结果导出或是导入后台数据库,生成可视化报表
刚入职第一个需求大概需要7天左右。
对业务熟悉后,平均一天一个需求。
影响时间的因素:开会讨论需求、表的权限申请、测试等
差不多一个月会迭代一次。就产品或我们提出优化需求,然后评估时间。每周我们都会开会做下周计划和本周总结。
有时候也会去预研一些新技术。
新需求比如埋点或是报表来了之后,需要设计做的方案,设计完成之后跟产品讨论,再开发。
数仓的任何步骤出现问题,需要查看问题,比如日活,月活下降等。
hashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现,以数组+链表的结构为例。
JDK1.8之前Put方法:
JDK1.8之后Put方法:
- newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
- newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
- newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
- newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。延迟3秒执行。
- 线程安全性不同
HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronize的,在多线程并发的情况下,可以直接使用HashTabl,但是使用HashMap时必须自己增加同步处理。
- 是否提供contains方法
HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。
- key和value是否允许null值
Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
- 数组初始化和扩容机制
HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
HashSet是采用hash表来实现的。其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为O(1)的方法。
TreeSet是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法。它还提供了一些方法来处理排序的set,如first(), last(), headSet(), tailSet()等等。
- String buffer和String build区别
- StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,
- 只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
- 在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低
- Final、Finally、Finalize
final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。
finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
== : 如果比较的是基本数据类型,那么比较的是变量的值
如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)
equals:如果没重写equals方法比较的是两个对象的地址值。
如果重写了equals方法后我们往往比较的是对象中的属性的内容
equals方法是从Object类中继承的,默认的实现就是使用==
缓存雪崩
缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
缓存正常从Redis中获取,示意图如下:
缓存失效瞬间示意图如下:
缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
以下简单介绍两种实现方式的伪代码:
(1)碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下:
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!
注意:加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!
(2)还有一个解决办法解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存,实例伪代码如下:
解释说明:
1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
关于缓存崩溃的解决方法,这里提出了三种方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间,还有一各被称为“二级缓存”的解决方法,有兴趣的读者可以自行研究。
缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴!
把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。
缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6种策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 定时去清理过期的缓存;
- 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
- 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
- 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
- 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
string |
字符串 |
list |
可以重复的集合 |
set |
不可以重复的集合 |
hash |
类似于Map<String,String> |
zset(sorted set) |
带分数的set |
- 持久化
- RDB持久化:
- 在指定的时间间隔内持久化
- 服务shutdown会自动持久化
- 输入bgsave也会持久化
- AOF : 以日志形式记录每个更新操作
Redis重新启动时读取这个文件,重新执行新建、修改数据的命令恢复数据。
保存策略:
推荐(并且也是默认)的措施为每秒持久化一次,这种策略可以兼顾速度和安全性。
缺点:
- 比起RDB占用更多的磁盘空间
- 恢复备份速度要慢
- 每次读写都同步的话,有一定的性能压力
- 存在个别Bug,造成恢复不能
选择策略:
官方推荐:
如果对数据不敏感,可以选单独用RDB;不建议单独用AOF,因为可能出现Bug;如果只是做纯内存缓存,可以都不用
所谓悲观锁:具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
简单来说:执行操作前假设当前的操作肯定(或有很大几率)会被打断(悲观)。基于这个假设,我们在做操作前就会把相关资源锁定,不允许自己执行期间有其他操作干扰。
悲观锁的并发性能差,但是能保证不会发生脏数据的可能性小一点。
执行操作前假设当前操作不会被打断(乐观)。基于这个假设,我们在做操作前不会锁定资源,万一发生了其他操作的干扰,那么本次操作将被放弃。Redis使用的就是乐观锁。
- redis是单线程的,为什么那么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 使用多路I/O复用模型,非阻塞IO
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
- MySql
- MyISAM与InnoDB的区别
对比项 |
MyISAM |
InnoDB |
外键 |
不支持 |
支持 |
事务 |
不支持 |
支持 |
行表锁 |
表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 |
行锁,操作时只锁某一行,不对其它行有影响, 适合高并发的操作 |
缓存 |
只缓存索引,不缓存真实数据 |
不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
数据结构:B+Tree
一般来说能够达到range就可以算是优化了
口诀(两个法则加6种索引失效的情况)
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写*;
不等空值还有OR,索引影响要注意;
VAR引号不可丢,SQL优化有诀窍。
- b-tree和b+tree的区别
- B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
- 在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。
- MySQL的事务
一、事务的基本要素(ACID)
- 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
二、事务的并发问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
三、MySQL事务隔离级别
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是
不可重复读(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 是
串行化(serializable) 否 否 否
name kecheng fenshu
张三 语文 81
张三 数学 75
李四 语文 76
李四 数学 90
王五 语文 81
王五 数学 100
王五 英语 90
A: select distinct name from table where name not in (select distinct name from table where fenshu<=80)
B:select name from table group by name having min(fenshu)>80
- 学生表
自动编号 学号 姓名 课程编号 课程名称 分数
1 2005001 张三 0001 数学 69
2 2005002 李四 0001 数学 89
3 2005001 张三 0001 数学 69
删除除了自动编号不同, 其他都相同的学生冗余信息
A: delete tablename where 自动编号 not in(select min(自动编号) from tablename group by学号, 姓名, 课程编号, 课程名称, 分数)
- 一个叫team的表,里面只有一个字段name,一共有4条纪录,分别是a,b,c,d,对应四个球队,现在四个球队进行比赛,用一条sql语句显示所有可能的比赛组合
select a.name, b.name
from team a, team b
where a.name < b.name
- 面试题:怎么把这样一个
year month amount
1991 1 1.1
1991 2 1.2
1991 3 1.3
1991 4 1.4
1992 1 2.1
1992 2 2.2
1992 3 2.3
1992 4 2.4
查成这样一个结果
year m1 m2 m3 m4
1991 1.1 1.2 1.3 1.4
1992 2.1 2.2 2.3 2.4
答案
select year,
(select amount from aaa m where month=1 and m.year=aaa.year) as m1,
(select amount from aaa m where month=2 and m.year=aaa.year) as m2,
(select amount from aaa m where month=3 and m.year=aaa.year) as m3,
(select amount from aaa m where month=4 and m.year=aaa.year) as m4
from aaa group by year
- 说明:复制表(只复制结构,源表名:a新表名:b)
SQL: select * into b from a where 1<>1 (where1=1,拷贝表结构和数据内容)
ORACLE:create table b
As
Select * from a where 1=2
[<>(不等于)(SQL Server Compact)
比较两个表达式。 当使用此运算符比较非空表达式时,如果左操作数不等于右操作数,则结果为 TRUE。 否则,结果为 FALSE。]
- 原表:
courseid coursename score
-------------------------------------
1 java 70
2 oracle 90
3 xml 40
4 jsp 30
5 servlet 80
-------------------------------------
为了便于阅读,查询此表后的结果显式如下(及格分数为60):
courseid coursename score mark
---------------------------------------------------
1 java 70 pass
2 oracle 90 pass
3 xml 40 fail
4 jsp 30 fail
5 servlet 80 pass
---------------------------------------------------
写出此查询语句
select courseid, coursename ,score ,if(score>=60, "pass","fail") as mark from course - 表名:购物信息
购物人 商品名称 数量
A 甲 2
B 乙 4
C 丙 1
A 丁 2
B 丙 5
……
给出所有购入商品为两种或两种以上的购物人记录
答:select * from 购物信息 where 购物人 in (select 购物人 from 购物信息 group by 购物人 having count(*) >= 2);
- info 表
date result
2005-05-09 win
2005-05-09 lose
2005-05-09 lose
2005-05-09 lose
2005-05-10 win
2005-05-10 lose
2005-05-10 lose
如果要生成下列结果, 该如何写sql语句?
win lose
2005-05-09 2 2
2005-05-10 1 2
答案:
(1) select date, sum(case when result = "win" then 1 else 0 end) as "win", sum(case when result = "lose" then 1 else 0 end) as "lose" from info group by date;
(2) select a.date, a.result as win, b.result as lose
from
(select date, count(result) as result from info where result = "win" group by date) as a
join
(select date, count(result) as result from info where result = "lose" group by date) as b
on a.date = b.date;
java虚拟机主要分为以下几个区:
- 方法区:
- 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
- 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
- 该区域是被线程共享的。
- 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
- 虚拟机栈:
- 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
- 虚拟机栈是线程私有的,它的生命周期与线程相同。
- 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
- 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
- 本地方法栈:
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
- 堆:
java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
- 程序计数器:
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。
Java类加载需要经历一下几个过程:
- 加载
加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:
- 通过一个类的全限定名获取该类的二进制流。
- 将该二进制流中的静态存储结构转化为方法去运行时数据结构。
- 在内存中生成该类的Class对象,作为该类的数据访问入口。
- 验证
验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:
- 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
- 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
- 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
- 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
- 准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
- 解析
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
- 初始化
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
- 引用计数法 应用于:微软的COM/ActionScrip3/Python等
- 如果对象没有被引用,就会被回收,缺点:需要维护一个引用计算器
- 复制算法 年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Copying)
- 效率高,缺点:需要内存容量大,比较耗内存
- 使用在占空间比较小、刷新次数多的新生区
- 标记清除 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
- 效率比较低,会差生碎片。
- 标记压缩 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
- 效率低速度慢,需要移动对象,但不会产生碎片。
- 标记清除压缩 标记清除-标记压缩的集合,多次GC后才Compact
- 使用于占空间大刷新次数少的养老区,是3 4的集合体
判断一个对象是否存活有两种方法:
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
- 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
- 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
内存分配:
- 栈区:栈分为java虚拟机栈和本地方法栈
- 堆区:堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区,主要放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。
- 方法区:被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)
- 程序计数器:当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。线程私有的。
回收策略以及Minor GC和Major GC:
- 对象优先在堆的Eden区分配。
- 大对象直接进入老年代。
- 长期存活的对象将直接进入老年代。
当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生GC的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。
- JUC
- Synchronized与Lock的区别
- Synchronized能实现的功能Lock都可以实现,而且Lock比Synchronized更好用,更灵活。
- Synchronized可以自动上锁和解锁;Lock需要手动上锁和解锁
- Runnable和Callable的区别
- Runnable接口中的方法没有返回值;Callable接口中的方法有返回值
- Runnable接口中的方法没有抛出异常;Callable接口中的方法抛出了异常
- Runnable接口中的落地方法是call方法;Callable接口中的落地方法是run方法
- 什么是分布式锁
当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。分布式锁可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存,如 Redis,通过set (key,value,nx,px,timeout)方法添加分布式锁。
- 什么是分布式事务
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。