Redis的性能优于Memcached,且数据结构更多样化,支持多种过期淘汰策略;更重要的是支持持久化,持久化策略丰富。
Redis的命令都是原子性的,可以轻松地利用INCR,DECR命令来构建计数器系统。同理,可以用INCR命令,为游戏玩家生成唯一的ID。
示例:
import java.util.Scanner;
import redis.clients.jedis.Jedis;
public class Incr {
// 访问一次web,计数一次
public static void accessWeb(Jedis jedis, String url) {
jedis.incr(url);
}
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 10011;
Jedis jedis = new Jedis(host, port);
String url = "www.ucloud.cn";
//获取原始的值
long origin_cnt = Long.parseLong(jedis.get(url));
//接收终端输⼊入
Scanner sc = new Scanner(System.in);
while(true) {
System.out.println("访问 " + url +" ? [y/n]");
String ac = sc.nextLine();
if (ac.equals("y")) {
accessWeb(jedis,url);
}else {
break;
}
}
sc.close();
//获取现在的值
long now_cnt = Long.parseLong(jedis.get(url));
//计算访问www.ucloud.cn的次数。
System.out.println("你总共访问了 "+ url+ " " + Long.toString(now_cnt - o
rigin_cnt)+"次.");
jedis.close();
}
}
输出
访问 www.ucloud.cn ? [y/n]
y
访问 www.ucloud.cn ? [y/n]
y
访问 www.ucloud.cn ? [y/n]
y
访问 www.ucloud.cn ? [y/n]
y
访问 www.ucloud.cn ? [y/n]
n
你总共访问了 www.ucloud.cn 4次.
游戏服务器中涉及到很多排行信息,取TOP N操作,比如玩家等级排名、金钱排名、战斗力排名等,虽然通过关系数据库也可以实现,但是随着数据量增多,数据库的排序压力就会变大。
这些操作对于Redis来说都很简单,即使你有几百万个用户,每分钟都会有几百万个新的得分。
只需要jedis.zrevrank(key,member);
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
public class HelloWorldApp {
static int TOTAL_SIZE = 10000;
//获取长度为8,由小写字母组成的随机名称
public static String randomName(int length) {
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
builder.append((char) ThreadLocalRandom.current().nextInt(97,122));//a~z
}
return builder.toString();
}
public static void main(String[] args)
{
//连接信息,从控制台可以获得
String host = "127.0.0.1";
int port = 10011;
Jedis jedis = new Jedis(host, port);
//排行榜应用,取TOP N操作
try {
//Key(键)
String key = "游戏排行榜!";
//清除可能的已有数据
jedis.del(key);
//模拟生成若干个游戏选手
List<String> playerList = new ArrayList<String>();
for (int i = 0; i < TOTAL_SIZE; ++i)
{
//随机生成每个选手的名称
playerList.add(randomName(8));
}
System.out.println("输入全部" + TOTAL_SIZE +" 选手 ");
//记录每个选手的得分
for (int i = 0; i < playerList.size(); i++)
{
//随机生成数字,模拟选手的游戏得分
int score = (int)(Math.random()*5000);
String member = playerList.get(i);
if (i < 10) {
System.out.println("选手名称:" + member + ", 选手得分: " + score
);
}
//将选手的名称和得分,都加到对应key的SortedSet中去
jedis.zadd(key, score, member);
}
System.out.println("更多选手得分......");
//从对应key的SortedSet中获取已经排好序的选手列表
Set<Tuple> scoreList = jedis.zrevrangeWithScores(key, 0, -1);
//输出打印Top100选手排⾏行榜
System.out.println();
System.out.println(" "+key);
System.out.println(" Top 100 选手");
scoreList = jedis.zrevrangeWithScores(key, 0, 99);
for (Tuple item : scoreList) {
System.out.println("选手名称:"+item.getElement()+", 选手得分:"+Doubl
e.valueOf(item.getScore()).intValue());
}
//输出某个选手的排名
String player = playerList.get(0);
System.out.println();
System.out.println(" "+key);
System.out.println(" 选手"+player+"的排名: "+ jedis.zrevrank(key,player));
} catch (Exception e) {
e.printStackTrace();
}finally{
jedis.quit();
jedis.close();
}
}
}
输出:
输入全部10000 选手 选手名称:hegmsrcs, 选手得分: 1191 选手名称:ocvhhxke, 选手得分: 653 选手名称:ekgdllwj, 选手得分: 694 选手名称:kxwsnnjj, 选手得分: 2051 选手名称:vjflcktn, 选手得分: 2100 选手名称:jtrlmnlk, 选手得分: 4257 选手名称:aatbchgk, 选手得分: 2912 选手名称:phukvvxy, 选手得分: 2044 选手名称:aqqdqnel, 选手得分: 1859 选手名称:hyndvsen, 选手得分: 2381 更多选手得分...... 游戏排行榜! Top 100 选手 选手名称:kqyhxehe, 选手得分:4999 选手名称:datnveli, 选手得分:4998 选手名称:ovxfislm, 选手得分:4997 选手名称:llqnigun, 选手得分:4997 选手名称:ckikmasa, 选手得分:4997 选手名称:wlmdrpnx, 选手得分:4996 选手名称:trslhgga, 选手得分:4996 选手名称:bkbfnutg, 选手得分:4996 选手名称:yqesafda, 选手得分:4995 选手名称:grtjbjrq, 选手得分:4995 更多选手排名...... 游戏排行榜! 选手hegmsrcs的排名: 7618
通过使用list的lpop及lpush命令进行队列的写入和消费,可以轻松实现消息队列;由于它是独立于游戏服务器的,所以多个游戏服务器可以通过它来交换数据、发送事件,通过使用sorted set,甚至可以构建有优先级的队列系统。
import redis.clients.jedis.Jedis;
class ProducerThread extends Thread {
private Jedis jedis;
private int port;
private String host,key;
ProducerThread(String host, int port, String key) {
super(host);
this.key = key;
this.port = port;
jedis = new Jedis(host, port);
}
protected void finalize( ){
jedis.close();
}
public void run() {
//System.out.println("ProducerThread is running...");
for(int i = 0; i < 10; i++) {
jedis.lpush(key, "job"+ Integer.toString(i));
System.out.println("ProducerThread push job"+ Integer.toString(i));
}
}
}
class ConsumerThread extends Thread {
private Jedis jedis;
private int port;
private String host,key;
ConsumerThread(String host, int port, String key) {
super(host);
this.key = key;
this.port = port;
jedis = new Jedis(host, port);
}
protected void finalize( ) {
jedis.close();
}
public void run() {
//构建队列系统
//System.out.println("ConsumerThread is running...");
for(int i = 0; i < 10; i++) {
System.out.println("ConsumerThread pop "+ jedis.lpop(key));
}
}
}
public class QueueSystem {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 10011;
String key = "jobs";
ProducerThread pT = new ProducerThread(host,port,key);
ConsumerThread cT = new ConsumerThread(host,port,key);
pT.start();
cT.start();
}
}
输出:
ConsumerThread pop null ProducerThread push job0 ConsumerThread pop job0 ProducerThread push job1 ConsumerThread pop job1 ProducerThread push job2 ConsumerThread pop job2 ProducerThread push job3 ConsumerThread pop job3 ProducerThread push job4 ConsumerThread pop job4 ProducerThread push job5 ConsumerThread pop job5 ProducerThread push job6 ConsumerThread pop job6 ProducerThread push job7 ConsumerThread pop job7 ProducerThread push job8 ConsumerThread pop job8 ProducerThread push job9
如果要取网站的最新评论
import java.util.List;
import java.util.UUID;
import redis.clients.jedis.Jedis;
public class LastestN {
static int TOTAL_NUM = 5000;
static String host = "127.0.0.1";
static int port = 10011;
static String key = "comments";
static Jedis jedis;
public static void getLastestComments(int start, int num) {
System.out.println("最新20条评论:");
List<String> lastest_list = jedis.lrange(key, start, start + num - 1);
if (lastest_list.size() < num) {
//get remaining comments from db
}
for (int i = 0; i < lastest_list.size(); i ++) {
System.out.println("CommentID " + Integer.toString(i) + " "+lastest_
list.get(i));
}
}
public static void setComments() {
System.out.println(key);
for (int i = 0; i < TOTAL_NUM; i++) {
String commentID = UUID.randomUUID().toString();
jedis.lpush(key, commentID);
jedis.ltrim(key, 0, 99);//永远最多保存100条评论在redis中。
if (i < 10) {
System.out.println("CommentID: " + commentID);
}
}
System.out.println("更多CommentID......");
System.out.println("");
}
public static void main(String[] args) {
jedis = new Jedis(host, port);
//添加若干条评论;
setComments();
//取最新20条评论
getLastestComments(0,20);
jedis.close();
}
}
输出:
comments CommentID: b93db3f4-ff88-422a-ad49-bcbf7de5a396 CommentID: 3bf56f8a-5d90-4fa1-a068-7dd7c3993917 CommentID: 83a32ebd-89c4-40a5-bbd8-ace6bf723c57 CommentID: 6003d965-f6cd-4e60-8b12-f9494fcb9bc0 CommentID: a932c934-5dfd-4a5f-90da-5a40da468e78 CommentID: 08ce995b-2ee1-4db9-8e3c-ca5069f87cce CommentID: ce5b57d5-fc02-4c91-90d5-bbe211073e0b CommentID: 5314a796-5574-4282-aab5-d8a8fc6e7ade CommentID: 3018252d-4f9b-40e7-bbbd-e0f3ab8b753d CommentID: 730bc8f5-d5fc-4d29-adf8-23c2fb632c0b 更多CommentID...... 最新20条评论: CommentID 0 d194a83c-005a-411e-ba36-2b513c3565c1 CommentID 1 c8104907-8912-463a-9d05-7fe0385d13d9 CommentID 2 88b918ac-bf35-4687-a06c-af5e6159a376 CommentID 3 324ff8c1-dfa5-46b3-9463-8a2d6aba3d26 CommentID 4 6b3b76b0-3ce6-4dd4-9ed9-b40618330a44 CommentID 5 7561efe9-96e0-46df-8f7c-e5f6812246c1 CommentID 6 937122ca-a203-4ae9-89d6-454dbf616e1e CommentID 7 8e0f24fe-152a-4297-afbd-703c51fee50e CommentID 8 328f4858-6adc-41c7-88c1-a896c5b122a5 CommentID 9 50151b7e-1225-4093-a30b-9d370b44ea25 CommentID 10 dd1bd655-760e-41af-8929-9986dce9a41b CommentID 11 358fa7d3-4291-4c1b-8b2e-dbee55c60084 CommentID 12 7367b111-9144-428b-af32-685004318c97 CommentID 13 5e06f20a-a5b0-4e85-98c4-9be4ec613d70 CommentID 14 924ac607-8af2-47b7-a08a-37ee851a1693 CommentID 15 add23360-f2d9-4049-b935-97a8823176e4 CommentID 16 15aca269-e4dd-4f2f-9eaa-cf8dc19cc8f3 CommentID 17 45c6f96b-4a8a-4b57-a1f4-f10621911d67 CommentID 18 c0a4fcd1-8df5-4f44-9a04-ba23ce8c8168 CommentID 19 3e06fc46-766a-4e78-a67b-276840f72f62
譬如将用戶的好友/粉丝/关注,可以存在一个set中,这样求两个人的共同好友的操作,只需要用sinter交集命令即可;也可以进行并集,差集操作。
import java.util.Set;
import redis.clients.jedis.Jedis;
public class CommonFriends {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 10011;
Jedis jedis = new Jedis(host, port);
//my friends
jedis.sadd("myfriends", "John");
jedis.sadd("myfriends", "Emliy");
jedis.sadd("myfriends", "Ben");
jedis.sadd("myfriends", "Steven");
System.out.println("my friends are: ");
Set<String> myList = jedis.smembers("myfriends");
for (String item:myList) {
System.out.print(item+" ");
}
//your friends
jedis.sadd("yourfriends", "Mark");
jedis.sadd("yourfriends", "Tim");
jedis.sadd("yourfriends", "Willim");
jedis.sadd("yourfriends", "Ben");
jedis.sadd("yourfriends", "Steven");
System.out.println("\n");
System.out.println("your friends are: ");
Set<String> yourList = jedis.smembers("yourfriends");
for (String item:yourList) {
System.out.print(item+" ");
}
//our common friends
System.out.println("\n");
System.out.println("our common friends are: ");
Set<String> commonList = jedis.sinter("myfriends","yourfriends");
for (String item:commonList) {
System.out.print(item+" ");
}
jedis.close();
}
}
输出:
my friends are: Steven Emliy John Ben your friends are: Steven Mark Tim Ben Willim our common friends are: Steven Ben
Redis的Pub/Sub系统可以构建实时的消息系统,比如很多开发人员用Pub/Sub构建实时聊天系统。
import redis.clients.jedis.*;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.RandomStringUtils;
class PrintListener extends JedisPubSub{
public void onMessage(String channel, String message) {
String time = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
System.out.println("message receive:" + message + ",channel:" + channel +
"..." + time);
//此处我们可以取消订阅
if(message.equalsIgnoreCase("quit")){
this.unsubscribe(channel);
}
}
}
class PubClient {
private Jedis jedis;
public PubClient(String host,int port){
jedis = new Jedis(host,port);
}
public void pub(String channel,String message){
jedis.publish(channel, message);
}
public void close(String channel){
jedis.publish(channel, "quit");
jedis.del(channel);//实时消息系统
}
}
class SubClient {
private Jedis jedis;//
public SubClient(String host,int port){
jedis = new Jedis(host,port);
}
public void sub(JedisPubSub listener,String channel){
jedis.subscribe(listener, channel);
//此处将会阻塞,在client代码级别为JedisPubSub在处理消息时,将会“独占”链接
//并且采取了while循环的⽅方式,侦听订阅的消息
}
}
public class PubSubTest {
/**
* @param args
*/
static String host = "127.0.0.1";
static int port = 10011;
public static void main(String[] args) throws Exception{
PubClient pubClient = new PubClient(host,port);
final String channel = "pubsub-channel";
pubClient.pub(channel, "before1");
pubClient.pub(channel, "before2");
Thread.sleep(2000);
//消息订阅者⾮非常特殊,需要独占链接,因此我们需要为它创建新的链接;
//此外,jedis客户端的实现也保证了“链接独占”的特性,sub⽅方法将⼀一直阻塞,
//直到调⽤用listener.unsubscribe⽅方法
Thread subThread = new Thread(new Runnable() {
public void run() {
try{
SubClient subClient = new SubClient(host,port);
System.out.println("----------subscribe operation begin-------");
JedisPubSub listener = new PrintListener();
//在API级别,此处为轮询操作,直到unsubscribe调⽤用,才会返回
subClient.sub(listener, channel);
System.out.println("----------subscribe operation end-------")
;
}catch(Exception e){
e.printStackTrace();
}
}
});
subThread.start();
int i=0;
while(i < 10){
String message = RandomStringUtils.random(6, true, true);//apache-commons
pubClient.pub(channel, message);
i++;
Thread.sleep(1000);
}
//被动关闭指示,如果通道中,消息发布者确定通道需要关闭,那么就发送一个“quit”
//那么在listener.onMessage()中接收到“quit”时,其他订阅client将执行“unsubscribe”操作。
pubClient.close(channel);
//此外,你还可以这样取消订阅
//listener.unsubscribe(channel);
}
}
输出:
----------subscribe operation begin------- message receive:erRIEe,channel:pubsub-channel...2016-03-15 15:53:52 message receive:Ovcwiw,channel:pubsub-channel...2016-03-15 15:53:53 message receive:STPWfV,channel:pubsub-channel...2016-03-15 15:53:54 message receive:SR4iIk,channel:pubsub-channel...2016-03-15 15:53:55 message receive:GI3Ege,channel:pubsub-channel...2016-03-15 15:53:56 message receive:0V1JUt,channel:pubsub-channel...2016-03-15 15:53:57 message receive:3iU8BV,channel:pubsub-channel...2016-03-15 15:53:58 message receive:BqeI2x,channel:pubsub-channel...2016-03-15 15:53:59 message receive:D53cHF,channel:pubsub-channel...2016-03-15 15:54:00 message receive:quit,channel:pubsub-channel...2016-03-15 15:54:01 ----------subscribe operation end-------
用户可以使用MULTI,EXEC,DISCARD,WATCH,UNWATCH指令用来执行原子性的事务操作。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
public class JTransaction {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 10011;
Jedis jedis1 = new Jedis(host, port);
Jedis jedis2 = new Jedis(host, port);
String key = "transaction-key";
jedis1.set(key, "20");
//jedis1 watch key
jedis1.watch(key);//如果在执⾏行事务之前,其他的客户端改变了key,则事务执⾏行失败。
Transaction tx = jedis1.multi();//开始事务
tx.get(key);
tx.get(key);
tx.get(key);
//jedis2.incr(key);//如果jedis2改变key,那么jedis1的事务就会失败
List<Object> result = tx.exec();//执⾏行事务
if(result == null || result.isEmpty()){
System.out.println("Transaction error...");
return;
}
for(Object rt : result){
System.out.println(rt.toString());
}
}
}
输出:
20 20 20
Redis HyperLogLog 实现了基数统计功能,方便统计一组不同元素且数量很大的数据集,且只耗费很小的空间。如统计网站每天访问的独立IP数量;使用PFADD和PFCOUNT,可以轻松实现。
import redis.clients.jedis.Jedis;
import java.util.Random;
public class HyperLog {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 10011;
Jedis jedis = new Jedis(host, port);
String key = "src_ip";
jedis.del(key);
//随机生成10000个 ip,代表访问"www.ucloud.cn"的源ip.
for (int i = 0; i < 10000; i++) {
int section1 = new Random().nextInt(255);
int section2 = new Random().nextInt(253);
String ip = "10.10." + Integer.toString(section1) + "." + Integer.toSt
ring(section2);
//System.out.println(ip);
jedis.pfadd(key, ip);127.0.0.1
}
System.out.println("今天访问www.ucloud.cn的独立ip数为:"+jedis.pfcount(key));
jedis.close();
}
}
输出:
今天访问www.ucloud.cn的独立ip数为:9218