copy一份redis出来并分别启动服务端:
redis2的启动方式要以指定端口启动,或者conf修改了端口后,指定conf文件启动(即使在conf中设置了端口,以默认方式启动也是启动的6379端口):
Jedis实现分布式客户端并添加数据:
在Redis在java客户端的使用--jedis中介绍了jedis的基本使用。分布式操作也差不多,不过这里使用的就不是JedisPool
链接池了,而是使用的ShardedJedisPool
。
具体代码实现:
public class RedisShardedPool {
private static ShardedJedisPool pool;//sharded jedis连接池
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20")); //最大连接数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));//在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");
private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));
private static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setBlockWhenExhausted(true);//连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true。
JedisShardInfo info1 = new JedisShardInfo(redis1Ip,redis1Port,1000*2);
JedisShardInfo info2 = new JedisShardInfo(redis2Ip,redis2Port,1000*2);
List jedisShardInfoList = new ArrayList(2);
jedisShardInfoList.add(info1);
jedisShardInfoList.add(info2);
pool = new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
}
static{
initPool();
}
public static ShardedJedis getJedis(){
return pool.getResource();
}
public static void returnBrokenResource(ShardedJedis jedis){
pool.returnBrokenResource(jedis);
}
public static void returnResource(ShardedJedis jedis){
pool.returnResource(jedis);
}
}
- 设置config参数
- 在JedisPool构造器中传入
config,jedisShardInfoList...
- 上述初始化完成过后就可以通过
getJedis()
获取到ShardedJedis
对象,进行存储、查看、设置等操作,具体命令查看Redis基础命令 - 对
jedis
操作后成功调用returnResource
失败调用returnBrokenResources
方法,这里真正的将之前的操作设置到了服务器。
注意:jedisShardInfoList分别添加了JedisShardInfo(info1,info2),连接两个redis服务器
添加filter
更新token过期时间:
- 在
web.xml
中添加filter:
sessionExpireFilter
com.mmall.controller.common.SessionExpireFilter
sessionExpireFilter
*.do
所有.do方法都会走这个过滤器。
- 创建SessionExpireFilter实现
Filter
接口,在doFilter
中执行更新token过期时间逻辑:
public class SessionExpireFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isNotEmpty(loginToken)){
//判断logintoken是否为空或者"";
//如果不为空的话,符合条件,继续拿user信息
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);
if(user != null){
//如果user不为空,则重置session的时间,即调用expire命令
RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
测试:
public static void main(String[] args) {
ShardedJedis jedis = RedisShardedPool.getResource();
for(int i =0;i<10;i++){
jedis.set("key"+i,"value"+i);
}
returnResource(jedis);
System.out.println("program is end");
}
查看服务端:
两个redis服务端都存入了数据
查看客户端:
看到6390中存入了key9、key7、key6、key4等数据,剩余的存入到6380中。
补充:上文完成了redis的分布实现,但在现实项目中如果需要项目中每个接口都能获取到存储的信息(比如:当前登录用户的信息),还需要对cookie进行设置:
public class CookieUtil {
private final static String COOKIE_DOMAIN = ".zou.work";
private final static String COOKIE_NAME = "login_token";
public static String readLoginToken(HttpServletRequest request){
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck : cks){
log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
return ck.getValue();
}
}
}
return null;
}
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
ck.setDomain(COOKIE_DOMAIN);
//代表设置在根目录
ck.setPath("/");
//防止脚本攻击
ck.setHttpOnly(true);
//单位是秒。
//如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。
ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
response.addCookie(ck);
}
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck : cks){
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");
ck.setMaxAge(0);//设置成0,代表删除此cookie。
log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
response.addCookie(ck);
return;
}
}
}
}
当我们在登录时调用writeLoginToken
,就将token
存入到了cookie
,通过浏览器可以看到存储成功,之后只要时在COOKIE_DOMAIN
路径下的接口都可以获得该COKIE_NAME
,这个时候在将COOKIE_NAME作为key
,用户信息作为value
存入到redis
中就实现了--每个接口都能获取到存储的信息的效果(当然用户信息要先转化成字符串在存入,取的时候在转化成对应的bean即可(Jackson 封装):