Redis分布式Java实现

Posted by Chenyawei on 2020-04-27
Words 1.9k and Reading Time 10 Minutes
Viewed Times

Redis分布式Java实现

一、单点登录Redis存储Session和Cookie问题说明与集群实战

1、解决Cookie问题读写sessionId问题:

封装CookieUtil处理对Cookie的读写操作,COOKIE_DOMAIN参数用限定读写范围,bytenote.cn的二级域名下如mmall.byte.cn都可以读取到Cookie。

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
package com.mmall.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by chenyawei on 2019/4/21.
*/
@Slf4j
public class CookieUtil {
private final static String COOKIE_DOMAIN = "bytenote.cn"; // "localhost"; //".happymmall.com";
private final static String COOKIE_NAME = "mmall_login_token";//客户端种到服务端的Cookie名字

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;
}

//X:domain=".happymmall.com"
//a:A.happymmall.com cookie:domain=A.happymmall.com;path="/"
//b:B.happymmall.com cookie:domain=B.happymmall.com;path="/"
//c:A.happymmall.com/test/cc cookie:domain=A.happymmall.com;path="/test/cc"
//d:A.happymmall.com/test/dd cookie:domain=A.happymmall.com;path="/test/dd"
//e:A.happymmall.com/test cookie:domain=A.happymmall.com;path="/test"
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;
}
}
}
}

}

写操作:CookieUtil.writeLoginToken(httpServletResponse, session.getId());//保存redis的key值

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping(value = "login.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest) {
ServerResponse<User> response = iUserService.login(username, password);
if (response.isSuccess()) {

//session.setAttribute(Const.CURRENT_USER,response.getData());
CookieUtil.writeLoginToken(httpServletResponse, session.getId());//保存redis的key值
RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
return response;
}

读操作:String loginToken = CookieUtil.readLoginToken(httpServletRequest);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(value = "get_user_info.do",method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> getUserInfo(HttpServletRequest httpServletRequest){

String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);

if(user != null){
return ServerResponse.createBySuccess(user);
}
return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
}

2、单点登录之SessionExpireFilter重置session有效期:

//如果user不为空,则重置session的时间,即调用expire命令
RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);

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
package com.mmall.controller.common;

import com.mmall.common.Const;
import com.mmall.pojo.User;
import com.mmall.util.CookieUtil;
import com.mmall.util.JsonUtil;
import com.mmall.util.RedisShardedPoolUtil;
import org.apache.commons.lang.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Author: chenyawei
* @Date: 2020-04-20 11:56
* @Description:
*/
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() {

}
}

在web.xml中添加配置:凡是以*.do结尾的就重置session结束时间(即重置redis时间)

1
2
3
4
5
6
7
8
9
<!-- 二期新增重置session时间的filter -->
<filter>
<filter-name>sessionExpireFilter</filter-name>
<filter-class>com.mmall.controller.common.SessionExpireFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionExpireFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

2、Redis分布式缓存环境搭建

封装分布式连接池RedisShardedPool

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
package com.mmall.common;

import com.mmall.util.PropertiesUtil;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;
import java.util.ArrayList;
import java.util.List;

/**
* Created by chenyawei on 2019/3/10.
* redis分布式连接池
*/
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<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>(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);
}

public static void main(String[] args) {
ShardedJedis jedis = pool.getResource();
for (int i = 0; i < 10; i++) {
jedis.set("key" + i, "value" + i);
}
returnResource(jedis);
// pool.destroy();//临时调用,销毁连接池中的所有连接
System.out.println("program is end");

}
}

封装Redis分布式读写工具RedisShardedPoolUtil:

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.mmall.util;

import com.mmall.common.RedisPool;
import com.mmall.common.RedisShardedPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;

/**
* Created by chenyawei on 2019/3/10.
*/
@Slf4j
public class RedisShardedPoolUtil {

/**
* 设置key的有效期,单位是秒
* @param key
* @param exTime
* @return
*/
public static Long expire(String key,int exTime){
ShardedJedis jedis = null;
Long result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.expire(key,exTime);
} catch (Exception e) {
log.error("expire key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

//exTime的单位是秒
public static String setEx(String key,String value,int exTime){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.setex(key,exTime,value);
} catch (Exception e) {
log.error("setex key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static String set(String key,String value){
ShardedJedis jedis = null;
String result = null;

try {
jedis = RedisShardedPool.getJedis();
result = jedis.set(key,value);
} catch (Exception e) {
log.error("set key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static String getSet(String key,String value){
ShardedJedis jedis = null;
String result = null;

try {
jedis = RedisShardedPool.getJedis();
result = jedis.getSet(key,value);
} catch (Exception e) {
log.error("getset key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static String get(String key){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static Long del(String key){
ShardedJedis jedis = null;
Long result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static Long setnx(String key,String value){
ShardedJedis jedis = null;
Long result = null;

try {
jedis = RedisShardedPool.getJedis();
result = jedis.setnx(key,value);
} catch (Exception e) {
log.error("setnx key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}

public static void main(String[] args) {
ShardedJedis shardedJedis = RedisShardedPool.getJedis();

RedisShardedPoolUtil.set("keyTest","value");

String value = RedisShardedPoolUtil.get("keyTest");

RedisShardedPoolUtil.setEx("keyex","valueex",60*10);

RedisShardedPoolUtil.expire("keyTest",60*20);

RedisShardedPoolUtil.del("keyTest");


String aaa = RedisShardedPoolUtil.get(null);
System.out.println(aaa);

System.out.println("end");


}
}

notice

欢迎访问 chenyawei 的博客, 若有问题或者有好的建议欢迎留言,笔者看到之后会及时回复。 评论点赞需要github账号登录,如果没有账号的话请点击 github 注册, 谢谢 !

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !