LostKing - Software Engineering
type: channel
link: http://lostking.cn/category/SE
description: 计算机技术以及编程相关
items: C++复习（一）
items: Leetcode 字符串压缩
items: Leetcode.300 最长上升子序列(LIS问题)
items: Bilibili视频插入代码
items: Shiro框架实现登录验证和授权，以及客户端持久化登录
items: 实现用户文件和图片上传
items: 登录系统的表设计和Shiro框架
items: 实现通过POST请求进行注册
items: SSM框架入门
items: Kafka消息队列和用户登录方案设计

C++复习（一）
type: item
date: 2020-03-16T17:53:00+08:00
description: C++常见考点：三大特性static，const关键字C++和java以及python有什么不同，不同在哪里？C++类型转化是否安全？堆和栈哪个更快虚函数知道吗？析构函数为什么要用虚函数？内存的划分，请堆区内存时，所需空间不足抛出异常后怎么办，怎样才能成功申请到内存三大特性封装：把客观事物封装成抽象的类，并且类可以把自己的数据和方法只让可信的类或者对象操作，对不可信的进行信息隐藏。使得代码模块化，隐藏细节。多态：一个类实例的相同方法在不同情形有不同表现形式。继承：可以使用现有类的所有功能，并在无需重新编写原来的类的情况下对这些功能进行扩展。重写：当一个子类继承一父类，而子类中的方法与父类中的方法的名称，参数个数、类型都完全一致时，就称子类中的这个方法重写了父类中的方法。重载：一个类中的方法与另一个方法同名，但是参数表不同，这种方法称之为重载方法。Java和C++的区别：Java是解释型语言，所谓的解释型语言，就是源码会先经过一次编译，成为中间码，中间码再被解释器解释成机器码。对于Java而言，中间码就是字节码(.class)，而解释器在JVM中内置了。C++是编译型语言，所谓编译型语言，就是源码一次编译，直接在编译的过程中链接了，形成了机器码。C++比Java执行速度快，但是Java可以利用JVM跨平台。Java是纯面向对象的语言，所有代码（包括函数、变量）都必须在类中定义。而C++中还有面向过程的东西，比如是全局变量和全局函数。C++中有指针，Java中没有，但是有引用。C++支持多继承，Java中类都是单继承的。但是继承都有传递性，同时Java中的接口是多继承，类对接口的实现也是多实现。C++中，开发需要自己去管理内存，但是Java中JVM有自己的GC机制，虽然有自己的GC机制，但是也会出现OOM和内存泄漏的问题。C++中有析构函数，Java中Object的finalize方法。C++运算符可以重载，但是Java中不可以。同时C++中支持强制自动转型，Java中不行，会出现ClassCastException（类型不匹配）。static和constC++为了管理静态成员，提供了静态函数，静态函数对外提供接口。并且静态函数只能访问静态成员（原因：非静态成员函数在调用时 this指针被当作参数传进。而静态成员函数是属于类，不属于对象，没有 this 指针）。static特点1、 共享 ： static 成员变量实现了同族类对象间信息共享;2、 初始化：static 成员使用时必须初始化，且只能类外初始化。声明与实现分离时;只能初始化在实现部分(cpp 部分);3 、类大小： static 成员类外存储，求类大小，并不包含在内;4、 存储 ： static 成员是命名空间属于类的全局变量，存储在 data 区 rw 段;5、 访问 ：可以通过类名访问(无对象生成时亦可)，也可以通过对象访问。static静态成员可以被类内的普通函数静态函数都可访问。const 修饰数据成员，称为常数据成员。常数据成员可被普通成员函数和常成员函数来使用，不可以更改。const 常数据成员在使用前必须被初始化。也就是定义的同时就要初始化，之后不能再去赋值，只能使用。1、const 修饰函数放在声明之后，实现体之前。2、const 修饰函数不能修改类内的数据成员变量。3、const 修饰函数只能调用 const 函数。非 const 函数可以调用 const 函数。4、const 修饰的全局函数在定义和声明处都需要 const 修饰符。const char ptr, char const ptr和char* const ptr前两者相同，定义一个指向字符常量的指针，第三种，定义一个指向字符的指针常数，不能修改ptr指针，但是可以修改该指针指向的内容。const char *ptr==char const *ptr; 可以直接改变指针指向，但不能直接改变指针指向的值；*ptr=*ss;char *const ptr; 可以直接改变指针指向的值，但不能直接改变指针指向;ptr[0]='s';但两者都可以通过改变所指向的指针的内容，来改变它的值。include头文件时，""和<>的区别“”先在当前目录下查找头文件，若无便去第三方库文件里查找，最后去库文件查找，<>直接到库文件里查找源文件到可执行文件的过程预处理，生成.ii文件；编译，生成汇编文件.s文件；汇编，生成目标文件.o或.obj文件；链接，产生可执行文件.out或.exe文件其中预处理主要：对#define宏展开，处理条件编译指令，处理#include指令。。。C++与C的差别C只能在函数开始处声明变量，C没有bool类型；C++有引用变量，c++强制类型转换多了int（num）这种C和C++的函数…

Leetcode 字符串压缩
type: item
date: 2020-03-16T17:36:00+08:00
description: 做道简单的题放松一下。。。题目字符串压缩。利用字符重复出现的次数，编写一种方法，实现基本的字符串压缩功能。比如，字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短，则返回原先的字符串。你可以假设字符串中只包含大小写英文字母（a至z）。示例1:输入："aabcccccaaa"输出："a2b1c5a3"示例2:输入：："abbccd"输出：："abbccd"解释："abbccd"压缩后为"a1b2c2d1"，比原字符串长度更长。提示：字符串长度在[0, 50000]范围内。思路设置两个标记，和一个临时存放结果的字符串temp，分别为pre和last，pre指向当前的字母并将其附在temp尾部，last往后搜索相同的字母，直到遇到不同字符或者字符串尾部才停止，然后另last-pre得到相同连续字符的个数，并附在temp尾部，并重复前面的步骤。代码class Solution { public: string compressString(string S) { if (S.length()==0||S.length()==1) { return S; } string temp = ""; int pre = 0, last = 0; while (last<S.length()) { temp += S[pre]; while (last<S.length() && S[last]==S[pre]) { last++; } temp += to_string(last - pre); pre = last; } return temp.length() >= S.length() ? S : temp; } }; 额外：中间一开始使用了temp = temp + to_string(last - pre)，提示超出内存限制，是在字符串很大的情况下，改用temp += to_string(last - pre)后便没有了问题，了解之后，前者会另开辟空间，存储temp + to_string(last - pre)的值，再赋值给temp，而后者相当于直接append到temp字符串上。

Leetcode.300 最长上升子序列(LIS问题)
type: item
date: 2020-03-16T16:09:00+08:00
description: 题目描述给定一个无序的整数数组，找到其中最长上升子序列的长度。示例输入: [10,9,2,5,3,7,101,18]输出: 4 解释: 最长的上升子序列是 [2,3,7,101]，它的长度是 4。说明:可能会有多种最长上升子序列的组合，你只需要输出对应的长度即可。你算法的时间复杂度应该为 O(n2) 。思路使用动态规划来解题，定义$d(i), (i \in [1,n])$ 表示前$i$个数以$A[i]$结尾的最长上升子序列的长度，而$d(i)=max\{d(1),d(2),……,d(i)\}$于是从i=0开始，往后迭代序列，求得以i结尾的最大上升子序列，代码如下：class Solution { public: int lengthOfLIS(vector<int>& nums) { if (nums.size()==0) { return 0; } int n = nums.size(); //定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度 int dp[n] = {0}; int maxSeq = 0; for (int i = 0; i < n; i++) { maxSeq = 0; for (int j = 0; j <= i;j++) { if (nums[j] < nums[i]) { maxSeq = max(dp[j], maxSeq); } } dp[i] = maxSeq + 1; } for (int i = 0; i < n; i++) { maxSeq = maxSeq > dp[i] ? maxSeq : dp[i]; } return maxSeq; } };

Bilibili视频插入代码
type: item
date: 2020-03-15T16:19:00+08:00
description: 代码如下 如何通过aid获取cid？https://www.bilibili.com/widget/getPageList?aid=对应aid

Shiro框架实现登录验证和授权，以及客户端持久化登录
type: item
date: 2019-06-23T23:58:00+08:00
description: 二、大致内容实现用户登录和登录状态持久化，以及权限验证三、效果首现，通过运用shiro框架并设置好Fliter过滤器，并且设置好过滤规则，可以将所有请求都拦截，并且在配置之后，shiro框架会接管Tomcat的会话管理。因为HTTP请求是没有状态的，因此Tomcat自身会产生一个sessionID来对每个会话进行标记，而shiro框架对这一过程进行了接管，并能按自己的配置设置这个sessionID在cookie中的字段名。然后使用缓存，例如ehcache，redis等，进行这个sessionID的存储。我这里将其命名为sid。shiro框架中，请求经过过滤器后，会交给SecurityManager进行处理，而SecurityManager一般又从Realm中获取验证数据，所以我要结合我的数据接口的情况来自定义一个Realm，而它一般要实现两个功能，身份验证和权限授予，分别在doGetAuthorizationInfo()和doGetAuthenticationInfo()中进行，返回的是AuthorizationInfo对象和AuthenticationInfo对象。这两个分别表示着授权信息和验证信息。在我的代码中，对于前者，我通过setRoles()函数向其中添加了和当前用户有关的角色信息，并且通过setStringPermissions()函数向其中添加了和当前用户有关的权限信息，然后返回。而对于后者，在构建authenticationInfo的时候，使用了四个参数，分别是登录者发送的用户名和密码，还有随机生成的盐值，以及这个realm的类名。再到用户验证，那么要匹配登录者发送的信息和数据库中对应的信息，就会有个验证和匹配的地方，这个匹配的借口我在RetryLimitHashedCredentialsMatcher类中实现，它继承了HashedCredentialsMatcher这个类，这里的匹配和验证本质上是交给父类去做的，而我增加的内容，则是从缓存中取出现在登录的这个用户名，它的连续错误登录次数，若是过多，则会抛出ExcessiveAttemptsException。这些以上的内容，都会在配置文件spring-shiro.xml中用到大概的顺序级和关系大概如下：未命名文件-2然后再到我的登录入口，我经常会用到Subject对象，这个对象存有用户相关的各种信息，例如用户名，sessionID等，登录和登出操作也是以它为入口进行的。而Subject对象为什么能存储呢，通过观看源码可以发现，它从SecurityUtils中返回，而这又是从ThreadContext.getSubject()中获取的，再接着看，创建Subject的时候，是将其和ThreadContext绑定的，相当于与线程绑定了。然后演示一下首先进行登录，试试输错的情况：屏幕快照 2019-06-23 23.47.36.png然后成功登录，并在返回信息中加入用户相关的信息，如昵称，性别，头像url等：屏幕快照 2019-06-23 23.54.47.png然后可以看看cookie中的sid：屏幕快照 2019-06-23 23.48.44.png然后进行信息的请求，这个API是需要用户登录的，因此登录了的用户可以成功通过：屏幕快照 2019-06-23 23.52.57.png然后删除sid，此刻已经相当于未登录状态，即一个新的会话，然后再次请求刚刚的那个API：屏幕快照 2019-06-23 23.53.18.png出现错误了，即表明shiro框架的验证和匹配是成功的。再试试看持久登录，rememberMe的选项：屏幕快照 2019-06-24 00.17.33.png可以看到，cookie中多了rememberMe的key-value，这个就是用来存储在客户端，能够保持持久登录的字段和密文。试试重启服务器，并且删去sid：屏幕快照 2019-06-24 00.20.17.png依然是成功获取了，这表明持久化登录的功能是成功了的四、代码详情和解析我选择的是shiro框架来实现用户登录和验证的功能，正好附带了权限验证的功能，能够在后续实现用户的锁定，设置超级管理员等等，可以方便App的功能升级。而权限验证也方便了Api的开发。首先需要改变表的结构，当然，可以结合之前的表，将之前的表改为userinfo，对应的mapper，service，dao和controller也改为userinfoxxx，然后在数据库中添加如下表格：create table sys_users ( id bigint auto_increment comment '编号', username varchar(100) comment '用户名', password varchar(100…

实现用户文件和图片上传
type: item
date: 2019-06-21T03:36:00+08:00
description: 一、目录实现用户文件和图片上传二、实验结果和学习内容首先，文件上传的功能主要作用是为了实现用户能够上传相关图片和自定义的头像。而为了方便后续功能的实现，接收文件的时候就默认全部按照多文件上传的方式处理，虽然可能效率会比单文件处理要低一些。首先，在SpringMVC中实现文件上传的功能需要用到几个包，添加进pom.xml： <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.9</version> </dependency>然后需要进行相应的配置，在spring-web.xml文件中： <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"/> <property name="maxInMemorySize" value="10240"/> <property name="maxUploadSize" value="10485760"/> <property name="uploadTempDir" value="/uploadTemp"></property> </bean>然后就可以开始开发图片上传功能了。不过我在这之前，做了点别的事情，因为询问过字节跳动的老师，他们存储图片的时候是以文件的形式存储在服务器上的，而不是base64或是其他编码方式存在数据库中。那么为了方便，需要创建一个新的目录，而为了方便管理以及后续的迁移，使得图片目录和项目目录分开，我创建了一个虚拟目录。屏幕快照 2019-06-07 03.39.01然后就能直接通过localhost:8080/mySSMphoto/xxx.jpg来直接获取到图片了。然后开始实现文件上传的代码首先是一个工具类FileUploadUtil.javapublic class FileUploadUtil { // 使用日志工厂获取日志对象 private static Log log = LogFactory.getLog(FileUploadUtil.class); private static String imgPath = "/Users/wangjing/Desktop/mySSMphoto"; /** * 批量上传文件 * * @param request * 用户名; 用于区分用户上传的图片 * 模块名称; 用于区分该图片是位于那个模块进行上传 * @return * @throws FileNotFoundException */ public static List<String> uploadFile(HttpServletRequest request) throws FileNotFoundException { // 创建list集合，用于接收上传文件的路径 List<String> filePathList = new ArrayList<String>(); // 拼接文件上传位置，这里使用Tomcat服务器，将文件上传到webapps中，和项目同目录，files将用于保存上传的文件，将上传的文件于项目分开 //String strPath = ",webapps,files," + moduleName + "," + username; // 解析出文件存放路径位置 //String filepath = System.getProperty("catalina.base") + strPath.replace(',', File.separatorChar); String filepath = imgPath; log.debug("文件上传路劲位置-------->>>>>>>>>>>>" + filepath); // 转换request，解析出request中的…

登录系统的表设计和Shiro框架
type: item
date: 2019-06-21T03:26:00+08:00
description: 一、目录用户表的设计重定向和转发shiro框架初探二、实验结果和学习内容用户表的设计一开始，用于测试的时候，用户表就是简单的username加password两个字段，而为了后续的功能实现，我需要增加一个userID，并且尽量使得不能存在溢出的可能（当作亿级用户量来处理）。并且如果应用支持第三方登录，或者只使用第三方登录，那么第三方的 User ID 有可能是 64 位的。虽然自己的应用不一定能增长到超过 32 位，但是第三方很可能是 64 位的。然后还要增加用户状态 status 判断用户是否被封禁，注册时间和注册IP地址、上次登录时间和IP地址备查（并衍生出登录记录表，用来判断是否异地登录等，在此不表），用户角色/权限 role （又衍生出用户角色权限关系），业务也需要个人的个人信息如真实姓名、地址等也要往上添加，现在形成了一个很完整的用户关系表。在这之后，微博开放了第三方网站登录，用微博帐号就能登录。那就要增加个微博用户登录表，并且和本来的用户表关联，则这个微博用户信息表如下：id 自增ID user_id 关联本站用户ID uid 微博唯一ID access_token access_expire不仅如此，算上QQ和微信，一下子要接入好多家第三方登录了，只能就着“微博用户信息表”继续加类型加判断，如果是每个第三方登录都新建一个表，肯定会很冗杂。而作为iOS应用，必须要支持手机号登录。所以表结构如下：用户表 id username email phone … 用户第三方登录表 id user_id app_type app_user_id access_token进行改进无论username+password，还是phone+password，都是一种用户信息+密码的验证形式；再来理解第三方登录，其实它也是用户信息+密码的形式，用户信息即第三方系统中的ID（第三方登录一定会给一个在他们系统中的唯一标识），密码即access_token，只不过是一种有使用时效定期修改的密码。所以我们把它抽象出了用户基础信息表加上用户授权信息表的形式。用户基础信息表 users id nickname avatar 用户授权信息表 user_auths id user_id identity_type 登录类型（手机号 邮箱 用户名）或第三方应用名称（微信 微博等） identifier 标识（手机号 邮箱 用户名或第三方应用的唯一标识） credential 密码凭证（站内的保存密码，站外的不保存或保存token）这个系统最大的特色就是，用户信息表不保存任何密码，不保存任何登录信息（如用户名、手机号、邮箱），只留有昵称、头像等基础信息。所有和授权相关（且基本前端展示无关的），都放在用户信息授权表，用户信息表和用户授权表是一对多的关系。例如：usersidnicknameavatar1慕容雪村http://…/avatar.jpg2魔力鸟http://…/avatar2.jpg3科比http://…/avatar3.jpguser_authsiduser_ididentity_typeidentifiercredential11email123@example.compassword_hash(密码)21phone13888888888password_hash(密码)31weibo微博UID微博access_token42usernamemoliniaopassword_hash(密码)53weixin微信UserName微信token具体处理如下，当用户发来邮箱/用户名/手机号和密码请求登录的时候，依然是先判断类型，以某用户使用了手机号登录为例，使用 SELECT * FROM user_auths WHERE type=’phone’ and identifier=’手机号’ 查找条目，如有，取出并判断password_hash(密码)是否和该条目的credential相符，相符则通过验证，随后通过user_id获取用户信息。如果使用第三方登录，则只要判断 SELECT * FROM user_auths WHERE type=’weixin’ and identifier=’微信UserName’ ，如果有记录，则直接登录成功，使用新的token更新原token。假设与微信服务器通信不被劫持的情况下无需判断凭证问题。--重定向和转发然后由于功能实现需要用到转发或者重定向的功能，因此了解了一下：1.使用HttpServletResponse 重定向到另一个视图 @RequestMapping("/resp") public void handleRequest(HttpServletRequest req, HttpServletRe…

实现通过POST请求进行注册
type: item
date: 2019-06-21T01:58:00+08:00
description: 一、目录实现通过POST请求进行账号注册通过POST发送账号返回密码了解各种注解作用和POST详情二、实验结果和学习内容首先因为要解析JSON数据以及返回JSON数据，因此我在选择依赖包的时候，在JSON-lib，Jackson和fastjson中选择了Jackson，那么添加依赖，这时候发现之前用的阿里云的maven仓库好像没有最新的版本，于是在下面的网站中查找，换了一个库。AliRepo添加依赖： <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> 然后因为我要实现注册用户，以及返回用户密码，那么就要在Dao层和Service层进行修改，然后发现Dao层的两个方法分别是通过用户获取整个用户对象，以及直接向数据库添加用户。然后在Service层增加函数 public String findUser(String username) { User user = userDao.findByUsername(username); if(user != null){ return user.getPassword(); } return "Not Found!"; } 这样就能返回用户密码了。然后在Controller中实现我所要实现的功能。首先是用户注册，这里我返回的一组测试数据。 @ResponseBody @RequestMapping(value = "/trypost2", method = RequestMethod.POST) public List<User> trypost2(@RequestBody User user){ System.out.println(user.getId()+" "+user.getPassword()+" "+user.getPassword()); List<User> list = new ArrayList<User>(); User user1 = new User(); user1.setUsername("00001111"); user1.setPassword("tttttttt"); User user2 = new User(); user2.setUsername("22221111"); user2.setPassword("pppppppp"); list.add(user1); list.add(user2); userServivce.Regist(user); return list; }其中注解@ResponseBody的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后，写入到response对象的body区，通常用来返回JSON数据或者是XML数据，而需要注意的是，在使用此注解之后不会再走试图处理器，而是直接将数据写入到输入流中，他的效果等同于通过response对象输出指定格式的数据。 @RequestMapping("/login") @ResponseBody public User login(User user){ return user; } //User字段：userName pwd //那么在前台接收到的数据为：'{"userName":"xxx","pwd":"xxx"}' //效果等同于如下代码： @RequestMapping("/login") public void login(User user, HttpServletResponse response){ response.getWriter.write(JSONObject.fromObject(user).toString()); } 然后可以进行测试了，因为直接使用curl比较麻烦，因此我下载了一个Postman，用于测试API。效果如下：这是请求：然后得到的结果：说明返回成功了，然后看看服务器的输出：可以看的出成果连接数据库并成功插入了数据，再看看数据库的表的变化：pic7.png产生了变化了说明这个API是成功了的然后是第二个API： @ResponseBody @RequestMapping(value = "/trypost", method = RequestMethod.POST) public List<String> trypost(@RequestParam(value = "username") String username){ List<String> list = new Arr…

SSM框架入门
type: item
date: 2019-06-21T01:54:00+08:00
description: 一、目录通过SSM框架实现用户登录注册的功能部署到tomcat服务器上二、实验结果和学习内容首先前期准备当然是下载并安装好MySQL，Tomcat8，还有Maven了首先配置好maven之后，通过maven来构建项目：然后构建好项目目录：首先是依赖文件pom.xml，即包含了要用到的包，会下载到maven的本地仓库中：<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>sysu.wangjing</groupId> <artifactId>mySSM</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>mySSM Maven Webapp</name> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> <scope>runtime</scope> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artif…

Kafka消息队列和用户登录方案设计
type: item
date: 2019-06-21T01:50:00+08:00
description: 一、目录kafka详解用户登录方案二、实验结果和学习内容Kafka 详解Kafka是一种分布式的，基于发布/订阅的消息系统。主要设计目标如下：以时间复杂度为O(1)的方式提供消息持久化能力，即使对TB级以上数据也能保证常数时间的访问性能高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输支持Kafka Server间的消息分区，及分布式消费，同时保证每个partition内的消息顺序传输同时支持离线数据处理和实时数据处理而我们的项目目标是完成一款Feed流App，显然消息队列系统是非常重要的。送达保证 消息队列提供的冗余机制保证了消息能被实际的处理，只要一个进程读取了该队列即可。在此基础上，部分消息系统提供了一个”只送达一次”保证。无论有多少进程在从队列中领取数据，每一个消息只能被处理一次。这之所以成为可能，是因为获取一个消息只是”预定”了这个消息，暂时把它移出了队列。除非客户端明确的表示已经处理完了这个消息，否则这个消息会被放回队列中去，在一段可配置的时间之后可再次被处理。顺序保证 在大多使用场景下，数据处理的顺序都很重要。消息队列本来就是排序的，并且能保证数据会按照特定的顺序来处理。部分消息系统保证消息通过FIFO（先进先出）的顺序来处理，因此消息在队列中的位置就是从队列中检索他们的位置。缓冲 在任何重要的系统中，都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行–写入队列的处理会尽可能的快速，而不受从队列读的预备处理的约束。该缓冲有助于控制和优化数据流经过系统的速度。结构Broker Kafka集群包含一个或多个服务器，这种服务器被称为brokerTopic 每条发布到Kafka集群的消息都有一个类别，这个类别被称为topic。（物理上不同topic的消息分开存储，逻辑上一个topic的消息虽然保存于一个或多个broker上但用户只需指定消息的topic即可生产或消费数据而不必关心数据存于何处）Partition parition是物理上的概念，每个topic包含一个或多个partition，创建topic时可指定parition数量。每个partition对应于一个文件夹，该文件夹下存储该partition的数据和索引文件Producer 负责发布消息到Kafka brokerConsumer 消费消息。每个consumer属于一个特定的consumer group（可为每个consumer指定group name，若不指定group name则属于默认的group）。使用consumer high level API时，同一topic的一条消息只能被同一个consumer group内的一个consumer消费，但多个consumer group可同时消费这一消息。kafka的四个核心Api应用程序使用producer API发布消息到1个或多个topic中。应用程序使用consumer API来订阅一个或多个topic,并处理产生的消息。应用程序使用streams API充当一个流处理器,从1个或多个topic消费输入流,并产生一个输出流到1个或多个topic,有效地将输入流转换到输出流。connector API允许构建或运行可重复使用的生产者或消费者,将topic链接到现有的应用程序或数据系统。kafka配置说明ProducerProperties props = new Properties(); props.put("bootstrap.servers", "master:9092,slave1:9092,slave2:9092"); props.put("acks", "all"); props.put("retries", 0); props.put("batch.size", 16384); props.put("key.serializer", StringSerializer.class.getName()); props.put("value.serializer", StringSerializer.class.getName()); KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);其中：bootstrap.servers： kafka的地址。acks:消息的确认机制，默认值是0；acks=0：如果设置为0，生产者不会等待kafka的响应。acks=1：这个配置意味着kafka会把这条消息写到本地日志文件中，但是不会等待集群中其他机器的成功响应。acks=all：这个配置意味着lea…