实现Redis缓存查询(出了点问题,已解决)
在昨天的服务C上添加一个新的服务
dao层
String findUsernameById(int id);
service接口层
String findUsernameById(int id);
service实现层
@Override
public String findUsernameById(int id) {
while(template.opsForList().size("user")>0){
template.opsForList().leftPop("user");
}
template.opsForList().rightPush("user",userRepository.findUsernameById(id));
return userRepository.findUsernameById(id);
}
controller层
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping(value = "/findUsernameById/{id}")
public String findAll(@PathVariable(value = "id") int id){
String result = null;
result=userService.findUsernameById(id);
List<User> list = redisTemplate.opsForList().range("user",0,-1);
result = list.toString();
return result;
}
mapper编写
<select id="findUsernameById" resultType="String">
select username
from user
where
id = #{id}
</select>
这里有一些小细节,resultType是需要修改的,之前的是UserMap,而我们需要加入到缓存中的数据只是一个string所以不是Map,如果使用resultMap就会报错,这里需要具体情况具体分析,这篇文章的后面会详细学习每种类型的不同用法
解决redis中文乱码问题
编写一个redisconfig
package com.xfgg.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
//key haspmap序列化
template.setHashKeySerializer(redisSerializer);
//
return template;
}
}
还需要修改服务B,昨天改的忘记贴出来了
Feign接口
package com.xfgg.demo.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(value = "turbine-c" , fallback = FeignApiFallBack.class)
public interface FeignApi {
@GetMapping(value = "/getList")
public List<Object> getList();
@RequestMapping(value = "/getUser/{id}")
public Object getUserById(@PathVariable(value = "id") int id);
@RequestMapping(value = "/deleteUser/{id}")
public void delete(@PathVariable(value = "id") int id);
@RequestMapping(value = "/updateUser/{id}/{username}/{password}")
public void update(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password);
@RequestMapping(value = "/newp/{id}/{username}/{password}")
public void newp(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password);
@RequestMapping(value = "/findUsernameById/{id}")
public String findAll(@PathVariable(value = "id") int id);
}
Feign回退方法(一定要有不然仪表盘那边显示错误)
package com.xfgg.demo.feign;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class FeignApiFallBack implements FeignApi{
@Overridepackage com.xfgg.demo.controller;
import com.xfgg.demo.feign.FeignApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class BUseController {
@Qualifier("com.xfgg.demo.feign.FeignApi")
@Autowired(required = false)
private FeignApi feignApi;
/*
* @ClassName FeignController
* @Desc TODO 通过 turbine-b 服务名调用 b() 方法
* @Date 2019/5/20 23:13
* @Version 1.0
*/
@RequestMapping("/getList")
public List<Object> getList() {
return feignApi.getList();
}
@RequestMapping(value = "/getUser/{id}")
public Object getUserById(@PathVariable(value = "id") int id){
return feignApi.getUserById(id);
}
@RequestMapping(value = "/deleteUser/{id}")
public void delete(@PathVariable(value = "id") int id){
feignApi.delete(id);
System.out.println("成功删除");
}
@RequestMapping(value = "/updateUser/{id}/{username}/{password}")
public void update(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password){
feignApi.update(id,username,password);
System.out.println("成功修改");
}
@RequestMapping(value = "/newp/{id}/{username}/{password}")
public void newp(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password){
feignApi.newp(id, username, password);
System.out.println("成功添加");
}
@RequestMapping(value = "/findUsernameById/{id}")
public String findAll(@PathVariable(value = "id") int id){
System.out.println("查找成功");
return feignApi.findAll(id);
}
}
public List<Object> getList() {
List<Object> list = new ArrayList<>();
list.add("获取用户列表失败");
return list;
}
@Override
public Object getUserById(int id) {
Object result = new Object();
result="获取"+id+"用户失败";
return result;
}
@Override
public void delete(int id) {
System.out.println("删除失败");
}
@Override
public void update(int id, String username, String password) {
System.out.println("修改失败");
}
@Override
public void newp(int id, String username, String password) {
System.out.println("添加失败");
}
@Override
public String findAll(int id) {
return "获取失败";
}
}
controller层编写
package com.xfgg.demo.controller;
import com.xfgg.demo.feign.FeignApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class BUseController {
@Qualifier("com.xfgg.demo.feign.FeignApi")
@Autowired(required = false)
private FeignApi feignApi;
/*
* @ClassName FeignController
* @Desc TODO 通过 turbine-b 服务名调用 b() 方法
* @Date 2019/5/20 23:13
* @Version 1.0
*/
@RequestMapping("/getList")
public List<Object> getList() {
return feignApi.getList();
}
@RequestMapping(value = "/getUser/{id}")
public Object getUserById(@PathVariable(value = "id") int id){
return feignApi.getUserById(id);
}
@RequestMapping(value = "/deleteUser/{id}")
public void delete(@PathVariable(value = "id") int id){
feignApi.delete(id);
System.out.println("成功删除");
}
@RequestMapping(value = "/updateUser/{id}/{username}/{password}")
public void update(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password){
feignApi.update(id,username,password);
System.out.println("成功修改");
}
@RequestMapping(value = "/newp/{id}/{username}/{password}")
public void newp(@PathVariable(value = "id") int id,
@PathVariable(value = "username") String username,
@PathVariable(value = "password") String password){
feignApi.newp(id, username, password);
System.out.println("成功添加");
}
@RequestMapping(value = "/findUsernameById/{id}")
public String findAll(@PathVariable(value = "id") int id){
System.out.println("查找成功");
return feignApi.findAll(id);
}
}
效果图
redis中也出现缓存
利用postman进行测试,观察仪表盘参数的变化
主要观察使用缓存和没使用缓存的差别
对比getuserbyId和findAll(只是名字命名错了)
发现最大的区别在于百分位延迟统计的不同
一个是6ms,11ms,11ms
另一个是11ms,21ms,21ms
发现使用redis缓存的延迟更大了,造孽呀!!!!!!!
研究了会代码就发现了问题所在
在service实现层中我使用了左出右进的遍历方式,想了想发现几乎每次查询都要对redis进行一次左出右进的操作,延迟自然而然就大了
解决方式是使用Map类型,缓存id和username,判断是否存在时只要判断key id是否有值,然后输出value就好了
这里打算先学习一下redisTemplate的详细用法,在对方法进行修改,以免出现问题
RedisTemplate用法详解
Redis命令参考
http://doc.redisfans.com/
String数据结构
- set void set(K key,V value)
redisTemplate.opsForValue().set("num","123");
redisTemplate.opsForValue().get("num") 输出结果为123
- set void set(K key,V value, long timeout,TimeUnit unit)
redisTemplate.opsForValue().set("num","123",10, TimeUnit.SECONDS);
redisTemplate.opsForValue().get("num")设置的是10秒失效,十秒之内查询有结果,十秒之后返回为null
- set void set(K key,V value,long offset)
覆写(overwrite)给定 key 所储存的字符串值,从偏移量 offset 开始
template.opsForValue().set("key","hello world");
template.opsForValue().set("key","redis", 6);
System.out.println("***************"+template.opsForValue().get("key"));
结果:***************hello redis
- get V get(Object key)
template.opsForValue().set("key","hello world");
System.out.println("***************"+template.opsForValue().get("key"));
结果:***************hello world
- getAndSet V getAndSet(K key, V value)
设置键的字符串值并返回其旧值
template.opsForValue().set("getSetTest","test");
System.out.println(template.opsForValue().getAndSet("getSetTest","test2"));
结果:test
- append Integer append(K key, String value)
如果key已经存在并且是一个字符串,则该命令将该值追加到字符串的末尾。如果键不存在,则它被创建并设置为空字符串,因此APPEND在这种特殊情况下将类似于SET。
template.opsForValue().append("test","Hello");
System.out.println(template.opsForValue().get("test"));
template.opsForValue().append("test","world");
System.out.println(template.opsForValue().get("test"));
Hello
Helloworld
- size Long size(K key)
返回key所对应的value值得长度
template.opsForValue().set("key","hello world");
System.out.println("***************"+template.opsForValue().size("key"));
***************11
List数据结构
- Long size(K key)
返回存储在键中的列表的长度。如果键不存在,则将其解释为空列表,并返回0。当key存储的值不是列表时返回错误。
System.out.println(template.opsForList().size("list"));
6
- Long leftPush(K key, V value)
将所有指定的值插入存储在键的列表的头部。如果键不存在,则在执行推送操作之前将其创建为空列表。(从左边插入)
template.opsForList().leftPush("list","java");
template.opsForList().leftPush("list","python");
template.opsForList().leftPush("list","c++");
返回的结果为推送操作后的列表的长度
1
2
3
- Long leftPushAll(K key,V values)
批量把一个数组插入到列表中
String[] strs = new String[]{"1","2","3"};
template.opsForList().leftPushAll("list",strs);
System.out.println(template.opsForList().range("list",0,-1));
[3, 2, 1]
- Long rightPush(K key, V value)
将所有指定的值插入存储在键的列表的头部。如果键不存在,则在执行推送操作之前将其创建为空列表 - Long rightPushAll(K key, V… values);
- void set(K key, long index, V value);
在列表中index的位置设置value值
System.out.println(template.opsForList().range("listRight",0,-1));
template.opsForList().set("listRight",1,"setValue");
System.out.println(template.opsForList().range("listRight",0,-1));
[java, python, oc, c++]
[java, setValue, oc, c++]
- Long remove(K key, long count, Object value);
从存储在键中的列表中删除等于值的元素的第一个计数事件。
计数参数以下列方式影响操作:
count> 0:删除等于从头到尾移动的值的元素。
count <0:删除等于从尾到头移动的值的元素。
count = 0:删除等于value的所有元素。
System.out.println(template.opsForList().range("listRight",0,-1));
template.opsForList().remove("listRight",1,"setValue");//将删除列表中存储的列表中第一次次出现的“setValue”。
System.out.println(template.opsForList().range("listRight",0,-1));
[java, setValue, oc, c++]
[java, oc, c++]
- V index(K key, long index);
根据下表获取列表中的值,下标是从0开始的
System.out.println(template.opsForList().range("listRight",0,-1));
System.out.println(template.opsForList().index("listRight",2));
[java, oc, c++]
c++
- V leftPop(K key);
弹出最左边的元素,弹出之后该值在列表中将不复存在
System.out.println(template.opsForList().range("list",0,-1));
System.out.println(template.opsForList().leftPop("list"));
System.out.println(template.opsForList().range("list",0,-1));
[c++, python, oc, java, c#, c#]
c++
[python, oc, java, c#, c#]
- V rightPop(K key);
弹出最右边的元素,弹出之后该值在列表中将不复存在
System.out.println(template.opsForList().range("list",0,-1));
System.out.println(template.opsForList().rightPop("list"));
System.out.println(template.opsForList().range("list",0,-1));
[python, oc, java, c#, c#]
c#
[python, oc, java, c#]
Hash数据结构
- Long delete(H key, Object… hashKeys);
删除给定的哈希hashKeys
System.out.println(template.opsForHash().delete("redisHash","name"));
System.out.println(template.opsForHash().entries("redisHash"));
1
{class=6, age=28.1}
- Boolean hasKey(H key, Object hashKey);
确定哈希hashKey是否存在
System.out.println(template.opsForHash().hasKey("redisHash","666"));
System.out.println(template.opsForHash().hasKey("redisHash","777"));
true
false
- HV get(H key, Object hashKey);
从键中的哈希获取给定hashKey的值
System.out.println(template.opsForHash().get("redisHash","age"));
26
- Set keys(H key);
获取key所对应的散列表的key
System.out.println(template.opsForHash().keys("redisHash"));
//redisHash所对应的散列表为{class=1, name=666, age=27}
[name, class, age]
- Long size(H key);
获取key所对应的散列表的大小个数
System.out.println(template.opsForHash().size("redisHash"));
//redisHash所对应的散列表为{class=1, name=666, age=27}
3
- void putAll(H key, Map<? extends HK, ? extends HV> m);
使用m中提供的多个散列字段设置到key对应的散列表中
Map<String,Object> testMap = new HashMap();
testMap.put("name","666");
testMap.put("age",27);
testMap.put("class","1");
template.opsForHash().putAll("redisHash1",testMap);
System.out.println(template.opsForHash().entries("redisHash1"));
{class=1, name=jack, age=27}
- void put(H key, HK hashKey, HV value);
设置散列hashKey的值
template.opsForHash().put("redisHash","name","666");
template.opsForHash().put("redisHash","age",26);
template.opsForHash().put("redisHash","class","6");
System.out.println(template.opsForHash().entries("redisHash"));
{age=26, class=6, name=666}
- List values(H key);
获取整个哈希存储的值根据密钥
System.out.println(template.opsForHash().values("redisHash"));
[tom, 26, 6]
- Map<HK, HV> entries(H key);
获取整个哈希存储根据密钥
System.out.println(template.opsForHash().entries("redisHash"));
{age=26, class=6, name=tom}
- Cursor<Map.Entry<HK, HV>> scan(H key, ScanOptions options);
使用Cursor在key的hash中迭代,相当于迭代器。
Cursor<Map.Entry<Object, Object>> curosr = template.opsForHash().scan("redisHash",
ScanOptions.ScanOptions.NONE);
while(curosr.hasNext()){
Map.Entry<Object, Object> entry = curosr.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
age:27
class:6
name:666
Set数据结构
- Long add(K key, V… values);
无序集合中添加元素,返回添加个数
也可以直接在add里面添加多个值 如:template.opsForSet().add(“setTest”,“aaa”,“bbb”)
String[] strs= new String[]{"str1","str2"};
System.out.println(template.opsForSet().add("setTest", strs));
2
- Long remove(K key, Object… values);
移除集合中一个或多个成员
String[] strs = new String[]{"str1","str2"};
System.out.println(template.opsForSet().remove("setTest",strs));
2
- V pop(K key);
移除并返回集合中的一个随机元素
System.out.println(template.opsForSet().pop("setTest"));
System.out.println(template.opsForSet().members("setTest"));
bbb
[aaa, ccc]
- Boolean move(K key, V value, K destKey);
将 member 元素从 source 集合移动到 destination 集合
template.opsForSet().move("setTest","aaa","setTest2");
System.out.println(template.opsForSet().members("setTest"));
System.out.println(template.opsForSet().members("setTest2"));
[ccc]
[aaa]
- Long size(K key);
无序集合的大小长度
System.out.println(template.opsForSet().size("setTest"));
1
- Set members(K key);
返回集合中的所有成员
System.out.println(template.opsForSet().members("setTest"));
[ddd, bbb, aaa, ccc]
- Cursor scan(K key, ScanOptions options);
遍历set
Cursor<Object> curosr = template.opsForSet().scan("setTest", ScanOptions.NONE);
while(curosr.hasNext()){
System.out.println(curosr.next());
}
ddd
bbb
aaa
ccc
ZSet数据结构
- Boolean add(K key, V value, double score);
新增一个有序集合,存在的话为false,不存在的话为true
System.out.println(template.opsForZSet().add("zset1","zset-1",1.0));
true
- Long add(K key, Set<TypedTuple> tuples);
新增一个有序集合
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-5",9.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-6",9.9);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<ZSetOperations.TypedTuple<Object>>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
System.out.println(template.opsForZSet().add("zset1",tuples));
System.out.println(template.opsForZSet().range("zset1",0,-1));
[zset-1, zset-2, zset-3, zset-4, zset-5, zset-6]
- Long remove(K key, Object… values);
从有序集合中移除一个或者多个元素
System.out.println(template.opsForZSet().range("zset1",0,-1));
System.out.println(template.opsForZSet().remove("zset1","zset-6"));
System.out.println(template.opsForZSet().range("zset1",0,-1));
[zset-1, zset-2, zset-3, zset-4, zset-5, zset-6]
1
[zset-1, zset-2, zset-3, zset-4, zset-5]
- Long rank(K key, Object o);
返回有序集中指定成员的排名,其中有序集成员按分数值递增(从小到大)顺序排列
System.out.println(template.opsForZSet().range("zset1",0,-1));
System.out.println(template.opsForZSet().rank("zset1","zset-2"));
[zset-2, zset-1, zset-3, zset-4, zset-5]
0
- Set range(K key, long start, long end);
通过索引区间返回有序集合成指定区间内的成员,其中有序集成员按分数值递增(从小到大)顺序排列
System.out.println(template.opsForZSet().range("zset1",0,-1));
[zset-2, zset-1, zset-3, zset-4, zset-5]
- Long count(K key, double min, double max);
通过分数返回有序集合指定区间内的成员个数
System.out.println(template.opsForZSet().rangeByScore("zset1",0,5));
System.out.println(template.opsForZSet().count("zset1",0,5));
[zset-2, zset-1, zset-3]
3
- Long size(K key);
获取有序集合的成员数,内部调用的就是zCard方法
System.out.println(template.opsForZSet().size("zset1"));
6
- Double score(K key, Object o);
获取指定成员的score值
System.out.println(template.opsForZSet().score("zset1","zset-1"));
2.2
- Long removeRange(K key, long start, long end);
移除指定索引位置的成员,其中有序集成员按分数值递增(从小到大)顺序排列
System.out.println(template.opsForZSet().range("zset2",0,-1));
System.out.println(template.opsForZSet().removeRange("zset2",1,2));
System.out.println(template.opsForZSet().range("zset2",0,-1));
[zset-1, zset-2, zset-3, zset-4]
2
[zset-1, zset-4]
- Cursor<TypedTuple> scan(K key, ScanOptions options);
遍历zset
Cursor<ZSetOperations.TypedTuple<Object>> cursor = template.opsForZSet().scan("zzset1", ScanOptions.NONE);
while (cursor.hasNext()){
ZSetOperations.TypedTuple<Object> item = cursor.next();
System.out.println(item.getValue() + ":" + item.getScore());
}
zset-1:1.0
zset-2:2.0
zset-3:3.0
zset-4:6.0
重新编写redistemplate
先进行小改
尝试将检查去掉,发现会多出来很多重复的值
/**while(template.opsForList().size("user")>0){
template.opsForList().leftPop("user");
}**/
这一段去掉下面不修改会出现重复username
所以选择使用set来去重,成功是成功了,但是达不到理想的服务
service层
@Override
public String findUsernameById(int id) {
/**while(template.opsForList().size("user")>0){
template.opsForList().leftPop("user");
}**/
template.opsForSet().add("user",userRepository.findUsernameById(id));
return userRepository.findUsernameById(id);
}
controller层
@RequestMapping(value = "/findUsernameById/{id}")
public String findAll(@PathVariable(value = "id") int id){
String result = null;
result=userService.findUsernameById(id);
Set<User> list = redisTemplate.opsForSet().members("user");
result = list.toString();
return result;
}
可以看到百分位延迟统计低于没有使用缓存的getUserById
说明使用redis缓存成功
但是输出是把redis中所有数据输出了
进行大改,达到自己想要的效果
dao层
User findUserById(int id);
service接口层
User findUserById(int id);
service实现层
@Override
public User findUserById(int id) {
String key ="password_"+id;
ValueOperations<String,User> operations = template.opsForValue();
//缓存存在
boolean hashkey = template.hasKey(key);
if (hashkey){
User user=operations.get(key);
System.out.println("从缓存中获取了密码");
return user;
}
//从数据库中
User user = userRepository.findUserById(id);
user.toString();
operations.set(key,user);
return user;
}
controller层
@RequestMapping(value = "/findUserById/{id}")
public User findPasswordById(@PathVariable(value = "id") int id){
return userService.findUserById(id);
}
mapper编写
<select id="findUserById" resultMap="UserMap" parameterType="java.lang.Integer">
select
<include refid="Base_List"></include>
from user
where
id = #{id}
</select>
效果图
kafka入门
kafka学习了:https://www.orchome.com/5
kafka是一个分布式,分区的,多副本的,多订阅者,基于zookeeper协调的分布式日志系统,也可以当作MQ系统,常见可以用于web/nginx日志,访问日志,消息服务等,
主要应用场景是:日志收集系统和i消息系统
kafka的主要设计目标:
- 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能。
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输。
- 支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输。
- 同时支持离线数据处理和实时数据处理。
- Scale out:支持在线水平扩展
kafka作为一个集群运行在一个或多个服务器上
kafka集群存储的信息是以topic为类别记录的
每个消息(记录record)是由一个key,一个value和时间戳构成
kafka有四个核心API
- 应用程序使用Producer API发布消息到一个或多个topic主题中
- 应用程序使用Comsumer API 来订阅一个或多个topic,并处理产生的信息
- 应用程序使用Streams API充当一个流处理器,从1个或多个topic消费输入流,并生产一个输出流到一个或多个输出topic,有效地将输入流转换到输出流
- Connector API 可构建或运行可重用的生产者或消费者,将topic连接到现有的应用程序或数据系统。例如,连接到关系数据库的连接器可以捕获表的每个变更。
Client和Server之间的通讯,是通过一条简单、高性能并且和开发语言无关的TCP协议。并且该协议保持与老版本的兼容。Kafka提供了Java Client(客户端)
kafka使用的基本术语
- Topic
Kafka将消息分门别类,每一类的消息称之为一个主题 - Producer
发布消息的对象称之为主题生产者 - Consumer
订阅消息并处理发布的消息的对象称之为主题消费者1 - Broker
已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
Topic是发布的消息的别类名,一个topic可以有零个,一个或多个消费者订阅该主题的消息,对于每个topic,afka集群都会维护一个分区log
每一个分区都是一个顺序的,不可变的消息队列,并且可以用持续的添加,分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的
Kafka集群保持所有的消息,直到它们过期(无论消息是否被消费)。实际上消费者所持有的仅有的元数据就是这个offset(偏移量),也就是说offset由消费者来控制:正常情况当消费者消费消息的时候,偏移量也线性的的增加。但是实际偏移量由消费者控制,消费者可以将偏移量重置为更早的位置,重新读取消息。可以看到这种设计对消费者来说操作自如,一个消费者的操作不会影响其它消费者对此log的处理。
分区的目的:一是可以i处理更多的信息,不受单台服务器的限制,Topic拥有多个分区意味着它可以不受限的处理更多的数据,第二,分区可以作为并行处理的单元
分布式
Log的分区被分布到集群中的多个服务器上。每个服务器处理它分到的分区。 根据配置每个分区还可以复制到其它服务器作为备份容错。 每个分区有一个leader,零或多个follower。Leader处理此分区的所有的读写请求,而follower被动的复制数据。如果leader宕机,其它的一个follower会被推举为新的leader。 一台服务器可能同时是一个分区的leader,另一个分区的follower。 这样可以平衡负载,避免所有的请求都只让一台或者某几台服务器处理。
Geo-Replication(异地数据同步技术)
Kafka MirrorMaker为群集提供geo-replication支持。借助MirrorMaker,消息可以跨多个数据中心或云区域进行复制。 您可以在active/passive场景中用于备份和恢复; 或者在active/passive方案中将数据置于更接近用户的位置,或数据本地化。
生产者
生产者往某个Topic发布消息,生产者也负责选择发布到Topic的哪一个分区,最简单的方式从分区列表中轮流选择,也可以根据某种算法依照权重选择分区,开发者负责如何选择分区的算法
消费者
通常来讲,消息模型可以分为两种, 队列和发布-订阅式。 队列的处理方式是 一组消费者从服务器读取消息,一条消息只有其中的一个消费者来处理。在发布-订阅模型中,消息被广播给所有的消费者,接收到消息的消费者都可以处理此消息。Kafka为这两种模型提供了单一的消费者抽象模型: 消费者组 (consumer group)。 消费者用一个消费者组名标记自己。 一个发布在Topic上消息被分发给此消费者组中的一个消费者。 假如所有的消费者都在一个组中,那么这就变成了queue模型。 假如所有的消费者都在不同的组中,那么就完全变成了发布-订阅模型。 更通用的, 我们可以创建一些消费者组作为逻辑上的订阅者。每个组包含数目不等的消费者, 一个组内多个消费者可以用来扩展性能和容错。正如下图所示:
2个kafka集群托管4个分区(P0-P3),2个消费者组,消费组A有2个消费者实例,消费组B有4个。
正像传统的消息系统一样,Kafka保证消息的顺序不变。 再详细扯几句。传统的队列模型保持消息,并且保证它们的先后顺序不变。但是, 尽管服务器保证了消息的顺序,消息还是异步的发送给各个消费者,消费者收到消息的先后顺序不能保证了。这也意味着并行消费将不能保证消息的先后顺序。用过传统的消息系统的同学肯定清楚,消息的顺序处理很让人头痛。如果只让一个消费者处理消息,又违背了并行处理的初衷。 在这一点上Kafka做的更好,尽管并没有完全解决上述问题。 Kafka采用了一种分而治之的策略:分区。 因为Topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。 所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。
kafka的保证
生产者发送到一个特定的Topic的分区上,消息将会按照它们发送的顺序依次加入,也就是说,如果一个消息M1和M2使用相同的producer发送,M1先发送,那么M1将比M2的offset低,并且优先的出现在日志中。
消费者收到的消息也是此顺序。
如果一个Topic配置了复制因子(replication factor)为N, 那么可以允许N-1服务器宕机而不丢失任何已经提交(committed)的消息。