# 项目介绍

《网约车整体架构图》

# 需求

乘客端原型。

司机端原型。

boss UI。

# 项目特点

后排座,车载大屏。

车机是公司自己的技术。

残障人士、孕妇、儿童 标签。

司机:是后台录入的。标签属性。

乘客叫车的时候:从哪到哪,儿童,xxxx标签。


# 业务层

为什么要拆分业务层,避免微服务间 的交叉调用,也便于 组装新功能。

api-driver,

api-passenger,

api-boss,

api-listen-order,


《乘客端整体设计》

# 接口文档 YAPI

# 项目第三方

《项目第三方接口图》

短信服务:腾讯->阿里->华信。都支持,接口在service-sms中。(改写完成)

语音服务:在司机和乘客之间,阿里隐私号服务,隐藏号码。并有录音功能,录音文件存OSS,然后oss地址 放mysql。

文件服务:app,h5,将文件上传至OSS,然后将oss中文件的url,存到mysql中。(司机驾照,行驶证,实名认证的资料。),减轻服务端压力。

地图服务:百度:http://lbsyun.baidu.com/index.php?title=open/netcar,

​ 高德(https://lbs.amap.com/smart/mobility)

​ 高德司乘同显,100(300忘了)万,一年。

手机消息:极光。两种消息类型,通知和透传。最开始派单也是用极光,vip能保证99.9999%的质量。后来我们自 己改造成netty。

支付:微信,支付宝。我们统一了微信和支付宝的流程。让app端调用时方便。

航旅纵横:接送机,查航班。

飞猪上运营:在 杭州一条旅游线路上。

# 项目中常问的点

介绍业务(时序图)

做完抗疫项目,去美国面试抗疫猿,躺尸没必要说,直接说钟南山的方案。

全民宅=CRUD,钟南山:分布锁,李兰娟:分布式事务,张文宏:消息队列,缓存等。

接口设计,保证安全。

分布式锁,抢单环节

分布式事务,各个设计多个服务调用的环节。

支付: 通一微信和支付宝。

派单:看代码。

听单:极光,可以用listener。

# 接口安全设计

# 安全问题及解决方案

  1. 数据在网络中传输,中间会经历无数路由器,而每个路由器都可以抓包。比如网约车查询用户信息中,有用户身份证,余额等信息。或者订单中用户的行程记录。

    用fiddler演示一下:
    打开fiddler。
    浏览器访问:http://localhost:9100/api-driver/test/hello
    查看fiddler中:Inspectors下 Headers。
    
    1
    2
    3
    4
  2. 为防止被窃取需要加密,有对称加密和非对称加密。

    《加密》

    看图,知道两者区别。

    对称加密:两个密钥一样。(安全隐患,密钥会被泄露)
    
    非对称加密:密钥不一样。非对称更安全,性能低。
    (RSA 在com.online.taxi.common.util.RSAEncrypt,)
    如果黑客拿到密文,也没啥用,因为密钥他不知道。
    
    加密(对称加密DES,AES,非对称加密RSA),
    
    加解密:https://www.sojson.com/encrypt.html,可以测试我们java加解密正确否,和外部调试。
    md5  sha1自己看过。sha1在散列中。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    对称和非对称加密应用的典型场景:https。

    https得知道,面试会问

    《https原理》

    传输内容用对称加密,证书验证阶段用非对称加密
    
    为什么数据传输是用对称加密?
    首先,非对称加密的加解密效率是非常低的,而 HTTP 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。
    另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。
    
    1
    2
    3
    4
    5

    《中间人攻击》,如果证书不是CA颁发的。会发生中间人攻击。

    为什么需要 CA 认证机构颁发证书? 在win控制面板中搜索证书,就会有。
    本地负责证书的认证。
    
    HTTP 协议被认为不安全是因为传输过程容易被监听者勾线监听、伪造服务器,而 HTTPS 协议主要解决的便是网络传输的安全性问题。
    
    首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的“中间人攻击”问题。
    
    证书也可以自己颁发。自己生成得证书一般都提示风险。
    
    1
    2
    3
    4
    5
    6
    7
    8

    解决办法:https

  3. 再退一步,数据密文被窃取后,黑客拿着原来的密文,继续访问。怎么办?

    过期时间
    
    1

    加密内容是:(有效信息+时间戳),时间的有效期,看自己业务情况。越小越好。(没有绝对的安全)

    解密后:截取掉后13位。分成: 有效信息+时间戳。判断时间戳是否在1分钟内。

  4. 如果被人拿到内容,别人在进行访问,也还是可以的。如何处理?

    建立密文黑名单,如果此密文被用过了,则不能用了。降低了性能,换来了安全。

    用set做个例子。生产环境,应该放到redis的set中。

  5. 别人篡改数据。用签名,利用散列加密,区块链中也有应用,相同的值,生成得hash相同,不同的值hash有可能相同,概率极低,反正知道hash值,无法推断出 原值。加上一个sign=xxx(也成校验位),加盐(盐不在网络中传输)。

    sign=md5((参数=参数值&参数=参数值+盐))

    md5 结果是固定长度字符串,不可逆。但是现在已经能被碰撞,有网站专门收集。被面过在西小口。

    Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
    MD5与SHA1都是Hash算法,MD5摘要是128位的,SHA1摘要是160位的,MD5比SHA1快,SHA1比MD5强度高。
    
    1
    2
  6. 网页端。token(服务端存储,客户端存储)。此方案存在的问题:

    每个接口,都需要带token,对后台业务开发侵入性太大。用拦截器,统一处理。

    对前端:用cookie和session。

    jwt看下面:

  7. 攻防此消彼长。

  8. 总结:

1、用https保证通道安全。去申请证书。
2、为了减少敏感信息暴露,用token代替用户名和密码等,无token用户不能使用服务。
3、请求中携带:参数,timestamp,token,sign(md5(参数字母排序+加盐)) 
4、timestamp 有有效期,如果被人篡改,超时无效。
5、签名,签名的黑名单。防止重复请求。

1
2
3
4
5
6

# JWT(JSON Web Token)

两个原因:

  1. 由于token,需要和用户做对应,会增加服务端存储负担。所以有了无状态的jwt。
  2. 集群中要进行session共享。需要将session放到一个公共地方去,比如db。如果db挂了。咋整。

JWT是一种基于JSON的令牌安全验证(在某些特定的场合可以替代Session或者Cookie)

JWT组成:

1.头部信息(header)
	作用:指定该JWT使用的签名
	{
    	“alg”: “HS256”,// 签名算法
    	“typ”: “JWT” //token类型	
	}
	 将上面的json,用Base64URL 算法转成字符串,即为header。
2.消息体,也就是负载,java中用Claims(playload)
{
"iss" (issuer):签发人
"exp" (expiration time):过期时间
"sub" (subject):主题,一般用用户id
"aud" (audience):受众
"nbf" (Not Before):生效时间
"iat" (Issued At):签发时间
"jti" (JWT ID):编号
}
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
	作用:JWT的请求数据,
3.签名( signature)
Signature 部分是对前两部分的签名,防止数据篡改。
需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)

最终:把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
header.payload.signature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

demo:

JwtUtil

一般将jwt值放在:header中的Authorization中。设备码(或者ip,有时候我们换个wifi,银行客户端都提示网络环境改变,需要重新登录)也放header中。智远一户通。

主题中,用户id和设备唯一码,防止token被别人拿走用。这样设备码不一样。

https保证设备外,网络中安全;设备码保证设备中安全,如果你把设备给别人了,那没办法了。

# 不好的接口

接口文档不好。

出入参数风格各异:-,驼峰,

异常提示不友好:系统出错,相当于没说。

参数模型随意升级。

对代码不尊重,经常出错。(写好代码,其实是做个人品牌,以后让人找你合作)

# 方案(工具+标准)

单纯从技术上来说,不说行政上的。(乘法口诀的a)。

开评审会,作用不大,大家都不动脑子,10小时的会,看电影。

工具:yapi。

程序员能力:分析,总结,归纳,抽象 的能力。

# 出入参数

应用添加
path:/app/resource
method:post
请求参数:
{
	"enAppName":"英文应用名称",
	"chAppName":"中文应用名称",
	"operator":"操作人",
	"dataSourceType":"1",
	
}
返回参数:
{
	"code":0,
	"message":"消息内容""data":{}
}

应用修改
path:/app/resource
method:put
请求参数:
{
	"appId":"uuid",//新增不填,修改必填
	"enAppName":"英文应用名称",
	"chAppName":"中文应用名称",
	"operator":"操作人"
	
}
返回参数:
{
	"code":0,
	"message":"消息内容""data":{}
}

应用删除
path:/app/resource/{appId}
method:delete
请求参数:
无
返回参数:
{
	"code":0,
	"message":"消息内容""data":{}
}

应用列表
path:/server/app/list
method:get
请求参数:
{
	"appId":"uuid",//新增不填,修改必填
	"enAppName":"英文应用名称",
	"chAppName":"中文应用名称",
	"keyword":"查询词",
	"pageNo":1,//第几页,从1开始。
	"pageSize":10//页面大小
}
返回参数:
{
	"code":0,
	"message":"消息内容""data":{
		"list":[
			{
				"appId":"uuid",
				"enAppName":"英文应用名称",
				"chAppName":"中文应用名称",
				"dataSourceType":"1",
				"operator":"操作人",
				
				"createTime":毫秒长整型,
				"updateTime":毫秒长整型
			},
			{
				"appId":"uuid",
				"enAppName":"英文应用名称",
				"chAppName":"中文应用名称",
				"dataSourceType":"1",
				"operator":"操作人",
				
				"createTime":毫秒长整型,
				"updateTime":毫秒长整型				
			}
		],
		"pageNo":1,//第几页,从1开始。
		"pageSize":10,//页面大小	
		"totalRecord":100,//总数
	}
}

应用详情
path:/app/resource/{appId}
method:get

返回参数:
{
	"code":0,
	"message":"消息内容""data":{
				"appId":"uuid",
				"enAppName":"英文应用名称",
				"chAppName":"中文应用名称",
				"dataSourceType":"1",
				"operator":"操作人",
				"createTime":毫秒长整型,
				"upateTime":毫秒长整型
		}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

# 分布式锁之红锁

千万级流量以上的项目,基本上都会用redis。

RedLock,redis创始人 比较提出的方案。

# 我们真的需要锁么?

需要锁的条件:

  1. 多任务环境下。(进程,线程)
  2. 任务都对同一共享资源进行写操作。
  3. 对资源的访问是互斥的。

操作周期:

  1. 竞争锁。获取锁后才能对资源进行操作。
  2. 占有锁。操作中。
  3. 其他竞争者,任务阻塞。
  4. 占有锁者,释放锁。继续从1开始。

JVM 锁 解决不了分布式环境中的加锁问题。

分布式锁应用场景:服务集群,比如N个订单服务,接受到大量司机的发送的对一个订单的抢单请求。如果是单个服务,可以用jvm锁控制,但是服务集群,jvm 就不行了。因为不在一个jvm中。

# 分布式锁解决方案

api-driver, eureka 7900 service-order 8004,8005

# 无锁情况

@Qualifier("grabNoLockService")

tb_order表中 status设置0

执行jmeter。司机抢单。
结果:
司机:1 执行抢单逻辑
司机:2 执行抢单逻辑
司机:1 抢单成功
司机:3 执行抢单逻辑
司机:2 抢单成功
司机:4 执行抢单逻辑
司机:3 抢单失败
司机:5 执行抢单逻辑
司机:4 抢单失败
司机:6 执行抢单逻辑
司机:5 抢单失败
司机:7 执行抢单逻辑
司机:6 抢单失败
司机:8 执行抢单逻辑
司机:7 抢单失败
司机:8 抢单失败
司机:9 执行抢单逻辑
司机:10 执行抢单逻辑
司机:9 抢单失败
司机:10 抢单失败

1和2 都抢单成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# JVM 锁

@Qualifier("grabJvmLockService")

司机:1 执行抢单逻辑
2020-03-07 12:20:46.931  INFO 20484 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-9} inited
司机:1 抢单成功
司机:10 执行抢单逻辑
司机:10 抢单失败
司机:9 执行抢单逻辑
司机:9 抢单失败
司机:8 执行抢单逻辑
司机:8 抢单失败
司机:7 执行抢单逻辑
司机:7 抢单失败
司机:6 执行抢单逻辑
司机:6 抢单失败
司机:5 执行抢单逻辑
司机:5 抢单失败
司机:4 执行抢单逻辑
司机:4 抢单失败
司机:3 执行抢单逻辑
司机:3 抢单失败
司机:2 执行抢单逻辑
司机:2 抢单失败

只有一个抢单成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

但是:启动两个service-order8004,8005,则有下面情况

8005:
司机:1 执行抢单逻辑
2020-03-07 12:43:49.821  INFO 9292 --- [nio-8005-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
司机:1 抢单成功
司机:9 执行抢单逻辑
司机:9 抢单失败
司机:7 执行抢单逻辑
司机:7 抢单失败
司机:5 执行抢单逻辑
司机:5 抢单失败
司机:3 执行抢单逻辑
司机:3 抢单失败


8004:
司机:2 执行抢单逻辑
2020-03-07 12:43:49.977  INFO 8880 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
司机:2 抢单成功
司机:10 执行抢单逻辑
司机:10 抢单失败
司机:8 执行抢单逻辑
司机:8 抢单失败
司机:6 执行抢单逻辑
司机:6 抢单失败
司机:4 执行抢单逻辑
司机:4 抢单失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

问题:无法解决分布式,集群环境的问题。所以要用分布锁

# 基于mysql

测试时要恢复数据。tbl_order 中status 为0,tbl_order_lock清空

@Qualifier("grabMysqlLockService") 实际用 事件实现。

8005:
司机6加锁成功
司机:6 执行抢单逻辑
司机:6 抢单成功
司机4加锁成功
司机:4 执行抢单逻辑
司机:4 抢单失败
司机8加锁成功
司机:8 执行抢单逻辑
司机:8 抢单失败
司机10加锁成功
司机:10 执行抢单逻辑
司机:10 抢单失败
司机2加锁成功
司机:2 执行抢单逻辑
司机:2 抢单失败

8004:
2020-03-07 12:50:04.938  INFO 7356 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
司机7加锁成功
司机:7 执行抢单逻辑
司机:7 抢单失败
司机1加锁成功
司机:1 执行抢单逻辑
司机:1 抢单失败
司机5加锁成功
司机:5 执行抢单逻辑
司机:5 抢单失败
司机9加锁成功
司机:9 执行抢单逻辑
司机:9 抢单失败
司机3加锁成功
司机:3 执行抢单逻辑
司机:3 抢单失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

问题:

1、如果中间出异常了,如何释放锁,用存储过程,还是可以解决。

2、mysql 并发是由限制的。不适合高并发场景。

压测结果:https://help.aliyun.com/document_detail/150351.html?spm=a2c4g.11186623.6.1463.1e732d02nCMBBa

牛逼点的:https://help.aliyun.com/document_detail/101100.html?spm=5176.11065259.1996646101.searchclickresult.5a6316bcjenDJn

# 基于Redis

stringRedisTemplate 用法
https://blog.csdn.net/zzz127333092/article/details/88742088
1
2

redis:内存存储的数据结构服务器,内存数据库。可用于:数据库,高速缓存,消息队列。采用单线程模型,并发能力强大。10万并发没问题。

分布锁知识:

redis的单进程单线程。

缓存有效期。有效期到,删除数据。

setnx。当key存在,不做任何操作,key不存在,才设置。

《Redis 分布锁》

# 单节点

加锁

SET orderId driverId NX PX 30000

上面的命令如果执行成功,则客户端成功获取到了锁,接下来就可以访问共享资源了;而如果上面的命令执行失败,则说明获取锁失败。

释放锁

关键,判断是不是自己加的锁。

关注点

  1. orderId,是我们的key,要锁的目标。

  2. driverId是由我们的司机ID,它要保证在足够长的一段时间内在所有客户端的所有获取锁的请求中都是唯一的。即一个订单被一个司机抢。

  3. NX表示只有当orderId不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。

  4. PX 30000表示这个锁有一个30秒的自动过期时间。当然,这里30秒只是一个例子,客户端可以选择合适的过期时间。

  5. **这个锁必须要设置一个过期时间。**否则的话,当一个客户端获取锁成功之后,假如它崩溃了,或者由于发生了网络分区,导致它再也无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了。antirez在后面的分析中也特别强调了这一点,而且把这个过期时间称为锁的有效时间(lock validity time)。获得锁的客户端必须在这个时间之内完成对共享资源的访问。

  6. 此操作不能分割。

    SETNX orderId driverId
    EXPIRE orderId 30
    虽然这两个命令和前面算法描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。造成死锁。
    
    
    
    1
    2
    3
    4
    5
  7. 必须给key设置一个value。value保证每个线程不一样。如果value在每个线程间一样。会发生 误解锁的问题。

    1.客户端1获取锁成功。
    2.客户端1在某个操作上阻塞了很长时间。
    3.过期时间到了,锁自动释放了。
    4.客户端2获取到了对应同一个资源的锁。
    5.客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。
    之后,客户端2在访问共享资源的时候,就没有锁为它提供保护了。
    
    1
    2
    3
    4
    5
    6
  8. 释放锁的操作,得释放自己加的锁。

    1.客户端1获取锁成功。
    2.客户端1访问共享资源。
    3.客户端1为了释放锁,先执行'GET'操作获取随机字符串的值。
    4.客户端1判断随机字符串的值,与预期的值相等。
    5.客户端1由于某个原因阻塞住了很长时间。
    6.过期时间到了,锁自动释放了。
    7.客户端2获取到了对应同一个资源的锁。
    8.客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的锁。
    
    1
    2
    3
    4
    5
    6
    7
    8
  9. redis故障问题。

    如果redis故障了,所有客户端无法获取锁,服务变得不可用。为了提高可用性。我们给redis 配置主从。当master不可用时,系统切换到slave,由于Redis的主从复制(replication)是异步的,这可能导致丧失锁的安全性。

    1.客户端1从Master获取了锁。
    2.Master宕机了,存储锁的key还没有来得及同步到Slave上。
    3.Slave升级为Master。
    4.客户端2从新的Master获取到了对应同一个资源的锁。
    
    1
    2
    3
    4

    客户端1和客户端2同时持有了同一个资源的锁。锁的安全性被打破。

  10. 这个算法中出现的锁的有效时间(lock validity time),设置成多少合适呢?如果设置太短的话,锁就有可能在客户端完成对于共享资源的访问之前过期,从而失去保护;如果设置太长的话,一旦某个持有锁的客户端释放锁失败,那么就会导致所有其它客户端都无法获取锁,从而长时间内无法正常工作。应该设置稍微短一些,如果线程持有锁,开启线程自动延长有效期。

还有一点,如果在过期时间内,程序没有执行完,是不能让key过期的,所以要延时。

断点打在:rlock.lock();
执行完,之后,等着,去redis查看,看过期时间,是不是一直在变,答案:一直在变。到20时,自动加到30.
1
2

为了解决9.10问题。antirez设计了Redlock算法

Redis的作者antirez给出了一个更好的实现,称为Redlock,算是Redis官方对于实现分布式锁的指导规范。Redlock的算法描述就放在Redis的官网上:

https://redis.io/topics/distlock

# RedLock(多master)

debug

断点达到:rLock.lock()
执行完后,看结果,发现如果是3个redis节点,则有2个节点中 设置了值。
1
2

目的:对共享资源做互斥访问。

因此antirez提出了新的分布式锁的算法Redlock,它基于N个完全独立的Redis节点(通常情况下N可以设置成5)。

运行Redlock算法的客户端依次执行下面各个步骤,来完成 获取锁 的操作:

  1. 获取当前时间(毫秒数)。
  2. 按顺序依次向N个Redis节点执行 获取锁 的操作。这个获取操作跟前面基于单Redis节点的 获取锁 的过程相同,包含value driverId ,也包含过期时间(比如 PX 30000 ,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个 获取锁 的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。
  3. 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。
  4. 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。
  5. 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起 释放锁 的操作(即前面介绍的Redis Lua脚本)。

当然,上面描述的只是 获取锁 的过程,而 释放锁 的过程比较简单:客户端向所有Redis节点发起 释放锁 的操作,不管这些节点当时在获取锁的时候成功与否。

问题:

由于N个Redis节点中的大多数能正常工作就能保证Redlock正常工作,因此理论上它的可用性更高。我们前面讨论的单Redis节点的分布式锁在failover的时候锁失效的问题,在Redlock中不存在了,但如果有节点发生崩溃重启,还是会对锁的安全性有影响的。具体的影响程度跟Redis对数据的持久化程度有关。

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

  1. 客户端1成功锁住了A, B, C, 获取锁 成功(但D和E没有锁住)。
  2. 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
  3. 节点C重启后,客户端2锁住了C, D, E, 获取锁 成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

在默认情况下,Redis的AOF持久化方式是每秒写一次磁盘(即执行fsync),因此最坏情况下可能丢失1秒的数据。为了尽可能不丢数据,Redis允许设置成每次修改数据都进行fsync,但这会降低性能。当然,即使执行了fsync也仍然有可能丢失数据(这取决于系统而不是Redis的实现)。所以,上面分析的由于节点重启引发的锁失效问题,总是有可能出现的。为了应对这一问题,antirez又提出了 延迟重启 (delayed restarts)的概念。也就是说,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

关于Redlock还有一点细节值得拿出来分析一下:在最后 释放锁 的时候,antirez在算法描述中特别强调,客户端应该向所有Redis节点发起 释放锁 的操作。也就是说,即使当时向某个节点获取锁没有成功,在释放锁的时候也不应该漏掉这个节点。这是为什么呢?设想这样一种情况,客户端发给某个Redis节点的 获取锁 的请求成功到达了该Redis节点,这个节点也成功执行了 SET操作,但是它返回给客户端的响应包却丢失了。这在客户端看来,获取锁的请求由于超时而失败了,但在Redis这边看来,加锁已经成功了。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些Redis节点同样发起请求。实际上,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。

# 代码

jwt编写

事务

# 面试题

# 计算机基础

http三次握手,4次握手。

http和https区别。

post和get区别。

http协议:状态码,返回值,优先级

http基于?tcp?为什么?

git使用

计算机内存区域

负载均衡有哪些?软的,硬的,服务端的,客户端的

tcp握手为什么可靠

# mysql

事务隔离机制。

阐述对数据的理解。

数据库索引的实现。

数据库存储引擎的区别。

索引原理,聚集和非聚集区别。

分库分表

数据库有100万访问,有10个常用ip登录,怎样优化数据库?

hash索引和btree索引?

mysql 什么时候导致索引失败?

mysql性能优化。

mysql连接池作用

explain使用

# Java

java 泛型,擦除

反射机制

如何保证对象不被回收

hashcode和toString

hashmap原理

concurrenthashmap原理

hashmap 全面考察。

hashmap为什么初始是2的n次方。

java撞线拆箱

面向对象特性

如何看待java开发与数据开发

java内存模型

java如何保证高并发

类加载机制,都做了些什么

jvm垃圾回收。

实现多线程几种方式。

反射

GC

gc算法,cms收集器原理

字符串操作

事件监听机制

如何实现 多终端日志打印接口,让它动态支持不同终端的日志打印。

synchronized原理

java各类锁的区别

公平锁和非公平锁

rpc框架?

常用设计模式及其应用

io框架用什么设计模式

数组和链表

spring ioc aop

# 算法

7种经典算法

快排,堆排,选择排 的思想,时间复杂度

手写快排

散列表,折半查找

插入删除查找的时间复杂度

爬楼梯算法,

1-100乱序,如何从中取一个数,怎么确定拿了哪一个。

八佛像算法

一个无序数组,如何得到和为n的直对,比如 n=5,求:1,4, 2,3 要求优化到时间复杂度为n

几种查找算法

二叉树相关

双向链表二叉树,二叉树转双向链表

双链表求公共节点

写代码判断是二叉树还是平衡二叉树

如何判断链表好坏,如何判断链表是否交叉。

logN 是如何用数学推导出来的

zk leader选举算法。

面试官说 各种遍历树的顺序,面试者画出来。

网页中有上千个城市,如何让用户快速找到自己想要的城市。

农夫带狼和羊过河,如何用算法实现。

25匹马,5个跑道,最少比多少次比赛能必出前3名,前5名。

100匹马,4跑道,比赛多少次能选出前4名。

n个台阶,每次只能上一个或2个,计算有多少种走法。

100层楼一个杯子,判断恰好碎的层数。

1000个苹果,从里面拿n个,怎样最快。

一个绳子从一头开始烧,烧完1小时,如何测出45分钟。

给上千个文件,每个1k-100M,给n个词,设计算法对每个词找到所有包含的文件,你只有100K内存。

倒置英文句子

# 人才观

闻味官。

聪明:

​ 智商:硬的,足够的专业知识。

​ 情商:软的,开放与人交流,能互通有无。对别人感同身受。

皮实:

​ 经得起摔打折腾,经得起崇拜追捧。不管捧你还是打你,胜不骄败不馁。去玻璃心。

乐观:

​ 了解真实情况后,仍然积极向上。了解了阴暗面,还要走向阳光。

自省:

​ 自我总结。不做永远对先生。

闻味官,不看学历,就闻味道,是否符合上面4点,但是也不要看破红尘,与世无争。