用户服务主要开放了会员,乘车人相关的接口,主要涉及到了责任链,数据脱敏,布隆过滤器(优化)。
会员接口
判断用户名是否存在
采用布隆过滤器以及redis中的set数据结构(用于解决无法删除的问题),快速的判断用户名是否存在。
其中为了防止该set成为大key,热key,对其进行分片处理。
整体流程如下:
public Boolean hasUsername(String username) {
boolean hasUsername = bl.contains(username);
// 布隆过滤器存在哈希冲突的问题,如果判断存在是不一定存在的,需要调整碰撞率
if (hasUsername) {
// 采用set来存储已经注销的用户名,解决布隆过滤器无法删除的问题
return StringRedisTemplate.opsForSet().isMember(USER_REGISTER_REUSE_SHARDING + hashShardingIdx(username), username);
// hashShardingIdx,就是对username的hashCode进行取模
}
// true 代表用户名可用,也就是不存在
return true;
}
注册接口
注册接口中遇到的重点就是缓存穿透,在注册流程中,第一步就需要判断用户名是否存在,对于大量用户的并发注册,很容易就对数据库造成过大压力。所以采用布隆过滤器+分布式锁减轻数据库压力,整体流程如下:
public UserRegisterRespDTO register(UserRegisterReqDTO requestParam) {
// 责任链:用户注册参数必填检验--->用户名是否存在检验--->用户是否被拉入黑名单检验
abstractChainContext.handler(UserChainMarkEnum.USER_REGISTER_FILTER.name(), requestParam);
RLock lock = redissonClient.getLock(LOCK_USER_REGISTER + requestParam.getUsername());
boolean tryLock = lock.tryLock();
if (!tryLock) {
throw new ServiceException(HAS_USERNAME_NOTNULL);
}
// 确保只有一个线程能够操作数据库
try {
// 新增user表
...
// 新增user_phone表
...
// 新增user_mail表
...
String username = requestParam.getUsername();
// 删除用户名可重复利用表中的记录
userReuseMapper.delete(Wrappers.update(new UserReuseDO(username)));
// 删除用户名可重复利用分片缓存中的记录
StringRedisTemplate.opsForSet().remove(USER_REGISTER_REUSE_SHARDING + hashShardingIdx(username), username);
// 将用户名加入缓存穿透布隆过滤器
userRegisterCachePenetrationBloomFilter.add(username);
} finally {
lock.unlock();
}
return BeanUtil.convert(requestParam, UserRegisterRespDTO.class);
}
登录和注销接口
登录和常规的接口相同,采用了redis共享session。
注销接口也是一样,不过有一个注意的业务点是,如果注销的用户和当前登录的用户不相同,需要上传风控中心。
乘车人接口
乘车人接口的重点主要体现在数据脱敏处理上,在向前端返回时,需要使用Jackson的
JsonSerializer
自定义序列化处理,采用hutool对身份证号和手机号之类的信息进行加密处理。对于后端的RPC调用,我们不需要进行加密处理,所以采用了一个新的实体类进行复制。对于乘车人的增删改使用了幂等组件库中的SpEL来保证幂等。
采用先操作数据库,后删缓存的做法保证数据的一致性