redis概述
- 开源、免费、高性能的key-value数据库,与Memcached类似
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value非关系型数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
- Windows下安装运行(由MSOpenTech开发维护)
- 下载:https://github.com/MSOpenTech/redis/releases
redis-server.exe redis.windows.conf
redis-cli.exe -h 127.0.0.1 -p 6379
- Linux下安装运行
- 下载:http://download.redis.io/releases/redis-3.2.8.tar.gz
$ tar xzf redis-3.2.8.tar.gz
$ cd redis-3.2.8
$ make
$ src/redis-server
$ src/redis-cli
- 后台启动
- nano /usr/local/software/redis-3.2.8/redis.conf
- 将daemonize修改为yes
- daemonize yes
- 启动命令-表示使用redis的配置文件进行启动
- ./src/redis-server redis.conf
- 外部程序访问,需要开发6379端口
- nano /etc/sysconfig/iptables
- -A INPUT -m state --state NEW -m tcp -p tcp --dport 6379 -j ACCEPT
- service iptables restart
- 配置命令
- 语法
- config get CONFIGSETTINGNAME
- config set CONFIGSETTINGNAME NEWCONFIGVALUE
- 示例
- config get requirepass
- config set requirepass "123456789"
- 设置完密码后,查看密码,需要权限,使用auth命令登陆后再去执行
- auth "123"
- 语法
- config set requirepass 123 这种方式只能够在当前会话中设置密码,redis重启之后失效
- 实际开发中我们在使用redis的时候,需要进行密码验证,那么可以修改redis.conf配置文件,设置对应的redis密码
- 将#requirepass foobared去掉注释,然后将密码改为自己对应的密码
- 键命令:用于管理 Redis 的键
- 字符串命令:用于管理 Redis 字符串类型值
- 语法
- command KEY_NAME
- 示例
- set name xiaoming
- 设置一个键值对 key为name,value为小明
- get name
- 根据key去查询数据
- keys *
- 列举出redis数据库中所有的key
- expire name 60
- 设置key为name的数据,有效时间为60秒
- ttl name
- 查看key为name的数据剩余的有效时间,-1表示永久有效,-2表示已经不存在
- exists name
- 判断key为name的数据是否存在
- del name
- 删除一个key为name的数据
- set name xiaoming
- 语法
java中使用redis
- Jedis是Redis官方首选的Java客户端开发包
- pom.xml中加入Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.1.0</version>
</dependency>
- 连接jedis
@Test
public void test3() {
//连接本地的 Redis 服务
Jedis jedis = new Jedis("192.168.1.6");
//查看服务是否运行
System.out.println("Server is running: "+jedis.ping());
jedis.set("foo", "bar");
String value = jedis.get("foo");
}
- 注意:直接运行会报错
- 需要注释掉redis.conf中bind 127.0.0.1这一行
- 将保护模式改为no
- protected-mode no
- 重启redis服务,kill -9 pid将之前进程直接杀死后再次启动
- 将redis整合到项目中
- 在itrip-utils中编写RedisAPI工具类
public class RedisAPI {
public JedisPool jedisPool;
public JedisPool getJedisPool() {
return jedisPool;
}
public void setJedisPool(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* set key and value to redis
* @param key
* @param value
* @return
*/
public boolean set(String key,String value){
try{
Jedis jedis = jedisPool.getResource();
jedis.set(key, value);
return true;
}catch(Exception e){
e.printStackTrace();
}
return false;
}
/**
* set key and value to redis
* @param key
* @param seconds 有效期
* @param value
* @return
*/
public boolean set(String key,int seconds,String value){
try{
Jedis jedis = jedisPool.getResource();
jedis.setex(key, seconds, value);
return true;
}catch(Exception e){
e.printStackTrace();
}
return false;
}
/**
* 判断某个key是否存在
* @param key
* @return
*/
public boolean exist(String key){
try{
Jedis jedis = jedisPool.getResource();
return jedis.exists(key);
}catch(Exception e){
e.printStackTrace();
}
return false;
}
/**
* 返还到连接池
* @param pool
* @param redis
*/
public static void returnResource(JedisPool pool,Jedis redis){
if(redis != null){
pool.returnResource(redis);
}
}
/**
* 获取数据
* @param key
* @return
*/
public String get(String key){
String value = null;
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
value = jedis.get(key);
}catch(Exception e){
e.printStackTrace();
}finally{
//返还到连接池
returnResource(jedisPool, jedis);
}
return value;
}
/**
* 查询key的有效期,当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
* 注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
* @param key
* @return 剩余多少秒
*/
public Long ttl(String key){
try{
Jedis jedis = jedisPool.getResource();
return jedis.ttl(key);
}catch(Exception e){
e.printStackTrace();
}
return (long) -2;
}
/**
* 删除
* @param key
*/
public void delete(String key){
try{
Jedis jedis = jedisPool.getResource();
jedis.del(key);
}catch(Exception e){
e.printStackTrace();
}
}
}
-
编写测试方法
@Test
public void test2() {
boolean b = redisAPI.set("age", "18");
assertEquals(true, b);
boolean b1 = redisAPI.set("sex", "男", 60);
assertEquals(true, b);
Long sex = redisAPI.ttl("age");
assertEquals(-1, sex.longValue());
String age = redisAPI.get("age");
assertEquals("18", age);
boolean exists = redisAPI.exists("age");
assertEquals(true, exists);
Long name = redisAPI.del("name");
assertEquals(1, name.longValue());
}
Redis的5种类型
- String: 字符串
- Hash: 散列
- List: 列表
- Set: 集合
- Sorted Set: 有序集合
-
各个数据类型应用场景:
类型 简介 特性 场景 String(字符串) 二进制安全 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M --- Hash(字典) 键值对集合,即编程语言中的Map类型 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) 存储、读取、修改用户属性 List(列表) 链表(双向链表) 增删快,提供了操作某一段元素的API 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 Set(集合) 哈希表实现,元素不重复 1,添加、删除,查找的复杂度都是O(1) 2,为集合提供了求交集、并集、差集等操作 1,共同好友 2,利用唯一性,统计访问网站的所有独立ip 3,好用推荐时,根据tag求交集,大于某个阈值就可以推荐 Sorted Set(有序集合) 将Set中的元素增加一个权重参数score,元素按score有序排列 数据插入集合时,已经进行天然排序 1,排行榜 2,带权重的消息队列
Redis分布式锁
分布式锁特点
系统中各个节点共享:系统中的每个节点(应用)都可以去持有该分布式锁,
以及判断锁是否被其它节点持有
单节点操作:当其中一个节点持有锁的时候,其它节点处于等待状态
常见的分布式锁
Memcached的add、cas命令
Zookeeper
*Redis的setnx命令:最常用
1.4.1Redis的set与setnx命令
Set命令针对相同的key的设置会覆盖
Setnx只允许针对相同的key设置一次值,如果再次设置会返回0代表设置失败
setnx key成功代表持有锁
del key 或者 expire key second 都可以代表释放锁
1.4.2编写RedisUtils类
在RedisUtils中主要是添加lock方法和unlock方法
@Component
public class RedisUtils {
private Logger logger = LoggerFactory.getLogger(RedisUtils.class);
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* set key and value to redis
*
* @param key
* @param value
* @return
*/
public boolean set(String key, String value) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
return true;
}
/**
* set key and value to redis
*
* @param key
* @param seconds 有效期
* @param value
* @return
*/
public boolean set(String key, long seconds, String value) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
expire(key, seconds);
return true;
}
/**
* 判断某个key是否存在
*
* @param key
* @return
*/
public boolean exist(String key) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
Object value = vo.get(key);
return EmptyUtils.isEmpty(value) ? false : true;
}
public Object get(String key) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
return vo.get(key);
}
public void delete(String key) {
try {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.delete(key);
} catch (Exception e) {
e.printStackTrace();
}
}
public Boolean expire(final String key, final Long expireTime) {
return redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
boolean flag = false;
try {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
byte keys[] = stringRedisSerializer.serialize(key);
flag = redisConnection.expire(keys, expireTime);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
});
}
public boolean validate(String token) {
return exist(token);
}
/**
* 利用redis的setnx命令为某一件的商品库存加锁
*
* @return 如果返回true, 代表某一个线程持有锁成功, 如果返回false代表某一个线程持有锁失败
*/
public boolean lock(String key) {
//redisTemplate.getConnectionFactory().getConnection().setNX()
return redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
byte[] keyBytes = stringRedisSerializer.serialize(key);
byte[] valueBytes = stringRedisSerializer.serialize("lock");
Boolean flag = connection.setNX(keyBytes, valueBytes);
if (flag) {//加了一个保险,防止redis中的key清除不掉
connection.expire(keyBytes, Constants.Redis_Expire.DEFAULT_EXPIRE);
}
return flag;
}
});
}
/**
* 删掉这个key
* @param key
*/
public void unlock(String key) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.delete(key);
}
}
1.4.3编写shop-goods-consumber的QgGoodsServiceImpl
public Dto qgGoods(String token, String goodsId) {
//1.根据token判断用户是否是登录状态,只有登录状态才能抢购
try {
QgUser currentUser = rpcQgUserService.getCurrentUser(token);
if (EmptyUtils.isEmpty(currentUser)) {
return DtoUtil.returnFail("用户未登录", Constants.User_AUTH.AUTH_TOKEN_INVALID);
}
//**********添加Redis锁***************
//有可能刚开始其它线程持有锁,所以当前线程不能只获取一次锁,可能获取不到就继续向下执行
//需要控制不断获取锁,直到获取到锁在继续向下执行
//这里说明设置超时时间的目的
while(!redisUtils.lock("lock:" + goodsId)) {//获取成功lock()返回true结束while循环
Thread.sleep(3000);//每隔三秒尝试获取一次锁
}
//2.用户已经登录判断库存是否充足
//如果库存不足直接返回
//如果库存充足锁定库存
int stockFlag = rpcQgGoodsService.checkGoodsStock(goodsId);
if (stockFlag <= 0) {
return DtoUtil.returnFail("商品库存不足,抢购失败", Constants.MessageStatus.FAIL+"");
}
int lockFlag = rpcQgGoodsService.lockGoodsStock(goodsId,currentUser.getId());
if (lockFlag<=0) {
return DtoUtil.returnFail("抢购失败", Constants.MessageStatus.FAIL+"");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//**********释放Redis锁***************
//无论上面代码是否抛出异常都要释放掉锁
redisUtils.unlock("lock:" + goodsId);
}
return DtoUtil.returnSuccess("抢购成功");
}