1.背景
- 本人在开发pingss-sys脚手架(项目地址)的时候,使用了jwt无状态权限认证。每个用户请求,都需要对比用户的token,是否有权限访问对应的资源,访问相当频繁,如果每次都访问provider,将耗费相当多的资源,本人觉得应该使用缓存。由于使用了微服务架构,缓存的用户数据可能会有多个客户端都访问,考虑使用redis分布式缓存。
- 环境springboot-2.1.1 + dubbo-2.6.5
- 这种访问非常频繁的数据,放到java堆缓存,效率更高。虽然多个微服务不能共享,但是每个服务向provider查一次,也不会使用太多资源。考虑更换成java堆缓存
2.步骤
2.1.本地service
- 刚开始只有一个权限项目,为了简单,使用了本地UserServiceImpl,所有请求先在访问本地缓存,没有在去provider获取。
- 问题:这种方式,基本没有问题,但是在项目多了以后,非常的不美观,每个项目都有一个相同的UserServiceImpl文件。
/**
*********************************************************
** @desc : 用户管理
** @author Pings
** @date 2019/1/25
** @version v1.0
* *******************************************************
*/
@Component
@CacheConfig(cacheNames="user")
public class IUserServiceImpl implements UserService {
public static final String USER_KEY_PREFIX = "user::";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Reference(version = "${sys.service.version}")
private UserService userService;
@Override
public User getById(int id) {
return this.userService.getById(id);
}
@Override
@Cacheable
public User getByUserName(String userName) {
return this.userService.getByUserName(userName);
}
@Override
public IPage<User> findPage(IPage<User> page, User entity) {
IPage<User> rst = this.userService.findPage(page, entity);
return rst;
}
@Override
public List<User> findAll() {
return this.userService.findAll();
}
@Override
public User save(User user) {
//**删除缓存
this.redisTemplate.delete(USER_KEY_PREFIX + user.getUserName());
user = this.userService.save(user);
return user;
}
@Override
public int deleteById(int id) {
//**删除缓存
User user = this.userService.getById(id);
this.redisTemplate.delete(USER_KEY_PREFIX + user.getUserName());
return this.userService.deleteById(id);
}
@Override
public int allotRole(int id, int[] roles, int currentUserId) {
//**删除缓存
User user = this.userService.getById(id);
this.redisTemplate.delete(USER_KEY_PREFIX + user.getUserName());
return this.userService.allotRole(id, roles, currentUserId);
}
}
2.2.dubbo本地存根stub
- dubbo提供了本地存根stub的,在接口旁边写一个stub,并在provider的接口中配置后,dubbo会调用它的构造方法,并传入相应接口的代理。用户发起请求,先执行stub的逻辑访问缓存,如果没有在去provider获取。
- 问题:
- 缓存使用spring提供的封装redisTemplate,redisTemplate由spring管理
/**
*********************************************************
** @desc : 用户管理本地存根,使用redis缓存用户信息
** @author Pings
** @date 2019/3/29
** @version v1.0
* *******************************************************
*/
public class UserServiceStub implements UserService {
private static final String USER_KEY_PREFIX = "user::";
private static RedisTemplate<String, Object> redisTemplate;
private final UserService userService;
//**构造函数传入真正的远程代理对象
public UserServiceStub(UserService userService) {
this.userService = userService;
redisTemplate = SpringContextUtil.getBean("redisTemplate");
}
@Override
public User getById(int id) {
return this.userService.getById(id);
}
@Override
public User getByUserName(String userName) {
String key = this.getKey(userName);
User user = (User) redisTemplate.opsForValue().get(key);
if(user == null) {
user = this.userService.getByUserName(userName);
redisTemplate.opsForValue().set(key, user);
}
return user;
}
@Override
public IPage<User> findPage(IPage<User> page, User entity) {
return this.userService.findPage(page, entity);
}
@Override
public List<User> findAll() {
return this.userService.findAll();
}
@Override
public User save(User user) {
//**删除缓存
redisTemplate.delete(this.getKey(user.getUserName()));
user = this.userService.save(user);
return user;
}
@Override
public int deleteById(int id) {
//**删除缓存
User user = this.userService.getById(id);
redisTemplate.delete(this.getKey(user.getUserName()));
return this.userService.deleteById(id);
}
@Override
public int allotRole(int id, int[] roles, int currentUserId) {
//**删除缓存
User user = this.userService.getById(id);
redisTemplate.delete(this.getKey(user.getUserName()));
return this.userService.allotRole(id, roles, currentUserId);
}
//**获取用户在缓存中的key
private String getKey(String userName) {
return USER_KEY_PREFIX + userName;
}
}
- stub由dubbo管理,无法使用spring注入,因此使用ApplcationContext.getBean(“redisTemplate”)
- 编写SpringContextUtil继承ApplicationContextAware,然后在stub中使用SpringContextUtil.getBean(“redisTemplate”)获取redisTemplate
- stub的初始化在SpringContextUtil之前,stub调用SpringContextUtil.getBean(“redisTemplate”)的时候,applicationContext为null
- 把dubbo ApplicationConfig由配置文件改为JavaConfig,调整bean初始化顺序,在springContextUtil之后初始化dubbo
@Bean
@DependsOn("springContextUtil")
public ApplicationConfig applicationConfig(){
return new ApplicationConfig("pings-web-admin");
- 启动报错:Duplicate application configs: <dubbo:application /> and <dubbo:application name=“pings-web-admin” id=“pings-web-admin” />
@SuppressWarnings({"unchecked"})
public void afterPropertiesSet() throws Exception {
if (getConsumer() == null) {
//**获取到两个ApplicationConfig的bean
Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);
......
- 默认启用了一个没有参数的ApplicationConfig,bean名:com.alibaba.dubbo.config.ApplicationConfig#0,看上去像是spring xml配置的bean,没有配置名称。导致无法使用JavaConfig配置ApplicationConfig(未找到配置的位置)
- 设置为非默认的ApplicationConfig,后面报错ApplicationConfig.application == null
@Bean("applicationConfig")
@DependsOn("springContextUtil")
public ApplicationConfig applicationConfig(){
ApplicationConfig config = new ApplicationConfig("pings-web-admin");
//**设置为false,不会发生上述问题,但是后面报错ApplicationConfig.application == null
config.setDefault(false);
return config;
}
- 暂时没有解决办法,如果你有方法,麻烦告知
- 替代方法,在yml中配置ApplicationConfig,在JavaConfig中配置RegistryConfig中配置
@Bean
@DependsOn("springContextUtil")
public RegistryConfig registryConfig(){
RegistryConfig config = new RegistryConfig();
config.setId("client-admin");
config.setAddress("zookeeper://192.168.1.111:2181");
return config;
}