用spring-data-redis实现类似twitter的网站

1. [url=http://projects.spring.io/spring-data-redis/]spring-data-redis[/url]简介
封装了一下redis的客户端,使得使用起来更方便。
优点是把客户端连接放到一个连接池里,从而提高性能。还有就是可以不同的客户端之间实现切换,而不用改一行代码(Spring惯用的一个手法)。
本文写作时最新版是1.3,
目前支持下面4种java客户端,可以自由切换而不用改代码。
[list]
[*][url=https://github.com/xetorthio/jedis]Jedis[/url]
[*][url=https://github.com/alphazero/jredis]JRedis[/url]
[*][url=https://github.com/spullara/redis-protocol]SRP[/url]
[*][url=https://github.com/wg/lettuce]Lettuce[/url]
[/list]

2. twitter简介
twitter如果没用过的话,可以理解成类似于国内的新浪微博。因为微博的访问量和使用人数极大,用传统的关系型数据库支撑不了,所以有了用redis这种非关系型数据库的架构设计。
本文我们将用spring-data-redis来实现一个类似twitter的网站。

3. 环境
本人亲测通过环境
jdk 1.6.0_45 64bit
gradle 1.7
tomcat 7.0.35
redis 2.8.11 win64
jedis 2.5.1
spring 3.2.9
spring-data-redis 1.3.0

4. 先动手操作
4.1 首先git clone以下项目
https://github.com/spring-projects/spring-data-keyvalue-examples/tree/master/retwisj

4.2 本人做了一些修改,一些3pp改成本文写作时最新的版本
build.gradle

compile "redis.clients:jedis:2.5.1"


gradle.properties

springVersion = 3.2.9.RELEASE
springRedisVersion = 1.3.0.RELEASE


4.3 编译

gradle build


4.4 将retwisj.war放入tomcat的webapp目录下,启动tomcat

4.5 访问http://localhost:8080/retwisj
花几分钟上去玩一下,熟悉一下功能。
好了,我们已经做好了一个微博网站了。玩好了我们简单分析一下代码实现。

5. 实现分析

5.1 表结构设计

5.1.1 用户(User)

用以前的关系型数据库设计是这样的
[table]
|Key|Username|Password|
|1|springrod|interface21|
|2|costinl|this is fun|
[/table]

而用redis的则变成了这样
[table]
|Key|Type|Value
|uid:1|hash|{name: springrod, pass: interface21}
|uid:2|hash|{name: costinl, pass: secret}
[/table]

将用户的名字和密码作为一个hash存入
对应的redis原生命令是

HMSET user:1 name springrod pass interface21


uid是自增长的,可以用redis的INCR来实现,这是一个原子操作

INCR global:uid


[table]
|Key|Type|Value
|global:uid|string|2
[/table]

我们要保存username和uid的对应关系,比如一个user登录时,我们要得到他的uid。可以创建一个倒转的key来实现此功能。user:[name]:uid
还创建了一个users用来保存所有uid的list
[table]
|Key|Type|Value
|user:springrod:uid|string|1
|user:costinl:uid|string|2
|users|list|{1, 2}
[/table]

5.1.2 微博(Post)

同样的,用global:pid来记录自增长的post id
[table]
|Key|Type|Value
|global:pid|string|2
[/table]

将微博的内容、时间和作者作为一个hash存入
[table]
|Key|Type|Value
|pid:1|hash|{content: Hello World, time: 1301931414757, uid: 1}
|pid:2|hash|{content: Working on some cool stuff, time: 1301931414897, uid: 1}
|pid:3|hash|{content: Checking out RetwisJ, time: 1301931454897, uid: 2}
[/table]

某个用户的所有微博
[table]
|Key|Type|Value
|uid:1:posts|list|{1, 2}
|uid:2:posts|list|{3}
[/table]

所有微博列表(Timeline)
[table]
|Key|Type|Value
|timeline|list|{1, 2, 3}
[/table]

5.1.3 关系(粉丝(Follower),关注(Following))

比如user2关注了user1,则user2是user1的粉丝,
user3也关注了user1,
则user1被user2,user3所关注

[table]
|Key|Type|Value
|uid:1:followers|set|{2,3}
|uid:2:following|set|{1}
|uid:3:following|set|{1}
[/table]

新浪微博上姚晨的粉丝有7000万,光记录她一个人的粉丝就要7000万条记录。
如果用传统的关系型数据库实现,压力很大。

5.2 安全验证
我们不用session来跟踪用户,而用cookie。
每当用户登录后,为他生成一个随机数(令牌),发给该用户,让他作为cookie保存,这个cookie用来验证用户的身份。
同样的存储一个倒转的key,可以根据cookie来得到uid

[table]
|Key|Type|Value
|uid:2:auth|string|{fea5e81ac8ca77622bed1c2132a021f9}
|auth:fea5e81ac8ca77622bed1c2132a021f9|string|{2}
[/table]

5.3 包的结构
[table]
|org.springframework.data.redis.sample.retwisj.web|web层
|org.springframework.data.redis.sample.retwisj.redis|持久层
|org.springframework.data.redis.sample.retwisj|领域层
[/table]

5.4 使用redisTemplate
applicationContext-redis.xml

<beans>
<context:property-placeholder location="classpath:redis.properties"/>

<!-- Redis client 这里就是不改代码实现不同客户端切换的地方了,我们用jedis-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"/>

<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"
p:connection-factory-ref="connectionFactory"/>

<context:annotation-config />
<context:component-scan base-package="org.springframework.data.redis.samples"/>

</beans>


5.5 web层
采用spring MVC和JSP

5.5.1 Controller层
RetwisController

5.5.2 CookieInterceptor
对于用户验证,采用cookie的方式,写了一个Spring MVC的拦截器

public class CookieInterceptor extends HandlerInterceptorAdapter


并在retwisj-servlet.xml里配置

<mvc:interceptors>
<bean class="org.springframework.data.redis.samples.retwisj.web.CookieInterceptor" />
</mvc:interceptors>

执行效果就是在客户端保存了一个叫"retwisauth"的cookie,保存了用户的令牌。

5.5.3 i18

CookieLocaleResolver把语言作为cookie存在客户端

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>


执行效果就是在客户端保存了一个叫"org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE"的cookie,保存了语言。

LocaleChangeInterceptor可以用来变更语言

<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
</mvc:interceptors>


这样就可以通过http://localhost:8080/retwisj/?lang=cn来变更语言

5.6 持久层
RetwisRepository

如下代码示例了如何用intersect来找出2个用户相同的粉丝


private RedisSet<String> following(String uid) {
return new DefaultRedisSet<String>(KeyUtils.following(uid), template);
}

public List<String> commonFollowers(String uid, String targetUid) {
RedisSet<String> tempSet = following(uid).intersectAndStore(following(targetUid),
KeyUtils.commonFollowers(uid, targetUid));

tempSet.expire(5, TimeUnit.SECONDS);

return covertUidsToNames(tempSet.getKey());
}


如下代码示例了用RedisAtomicLong实现自增长的id,BoundHashOperations来存储用户的用户名和密码


public String addUser(String name, String password) {
String uid = String.valueOf(userIdCounter.incrementAndGet());

// save user as hash
// uid -> user
BoundHashOperations<String, String, String> userOps = template.boundHashOps(KeyUtils.uid(uid));
userOps.put("name", name);
userOps.put("pass", password);
valueOps.set(KeyUtils.user(name), uid);

users.addFirst(name);
return addAuth(name);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值